diff --git a/CMakeLists.txt b/CMakeLists.txt index b8a0a5a..62c0ed3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ project(_pylibROM) set(CMAKE_BUILD_TYPE Debug) set(PYBIND11_FINDPYTHON ON) +option (USE_MFEM "Build pylibROM with MFEM" OFF) + #=================== ScaLAPACK (optional) ================== option(BUILD_SCALAPACK "Build static ScaLAPACK for libROM" OFF) @@ -50,14 +52,18 @@ if (BUILD_LIBROM) # ) # add_custom_target(RUN_LIBROM_BUILD ALL DEPENDS LIBROM_BUILD) - ExternalProject_Add( - libROM - SOURCE_DIR ${LIBROM_SCRIPTS_DIR} - CONFIGURE_COMMAND "" - BINARY_DIR ${LIBROM_DIR} - BUILD_COMMAND ${LIBROM_SCRIPTS_DIR}/compile.sh -m -g -t ${LIBROM_DIR}/cmake/toolchains/simple.cmake - INSTALL_COMMAND "" - ) + set(LIBROM_BUILD_CMD "${LIBROM_SCRIPTS_DIR}/compile.sh -t ${LIBROM_DIR}/cmake/toolchains/simple.cmake" CACHE STRING "Command used to build libROM and dependencies") + if (USE_MFEM) + set(LIBROM_BUILD_CMD "${LIBROM_BUILD_CMD} -m -g") + endif() + # ExternalProject_Add( + # libROM + # SOURCE_DIR ${LIBROM_SCRIPTS_DIR} + # CONFIGURE_COMMAND "" + # BINARY_DIR ${LIBROM_DIR} + # BUILD_COMMAND ${LIBROM_BUILD_CMD} + # INSTALL_COMMAND "" + # ) message("Building libROM dependency...") endif(BUILD_LIBROM) @@ -72,32 +78,34 @@ execute_process(COMMAND python3 -c "import mpi4py; print(mpi4py.get_include())" # # TODO(kevin): We do not bind mfem-related functions until we figure out how to type-cast SWIG Object. # # Until then, mfem-related functions need to be re-implemented on python-end, using PyMFEM. -find_library(MFEM mfem - "$ENV{MFEM_DIR}/lib" - "$ENV{MFEM_DIR}" - "${LIBROM_DIR}/dependencies/mfem") -find_library(HYPRE HYPRE - "$ENV{HYPRE_DIR}/lib" - "${LIBROM_DIR}/dependencies/hypre/src/hypre/lib") -find_library(PARMETIS parmetis - "$ENV{PARMETIS_DIR}/lib" - "$ENV{PARMETIS_DIR}/build/lib/libparmetis" - "${LIBROM_DIR}/dependencies/parmetis-4.0.3/build/lib/libparmetis") -find_library(METIS metis - "$ENV{METIS_DIR}/lib" - "$ENV{PARMETIS_DIR}/build/lib/libmetis" - "${LIBROM_DIR}/dependencies/parmetis-4.0.3/build/lib/libmetis") -find_path(MFEM_INCLUDES mfem.hpp - "$ENV{MFEM_DIR}/include" - "$ENV{MFEM_DIR}" - "${LIBROM_DIR}/dependencies/mfem") -find_path(HYPRE_INCLUDES HYPRE.h - "$ENV{HYPRE_DIR}/include" - "${LIBROM_DIR}/dependencies/hypre/src/hypre/include") -find_path(PARMETIS_INCLUDES metis.h - "$ENV{PARMETIS_DIR}/metis/include" - "${LIBROM_DIR}/dependencies/parmetis-4.0.3/metis/include") - +if (USE_MFEM) + find_library(MFEM mfem + "$ENV{MFEM_DIR}/lib" + "$ENV{MFEM_DIR}" + "${LIBROM_DIR}/dependencies/mfem") + find_library(HYPRE HYPRE + "$ENV{HYPRE_DIR}/lib" + "${LIBROM_DIR}/dependencies/hypre/src/hypre/lib") + find_library(PARMETIS parmetis + "$ENV{PARMETIS_DIR}/lib" + "$ENV{PARMETIS_DIR}/build/lib/libparmetis" + "${LIBROM_DIR}/dependencies/parmetis-4.0.3/build/lib/libparmetis") + find_library(METIS metis + "$ENV{METIS_DIR}/lib" + "$ENV{PARMETIS_DIR}/build/lib/libmetis" + "${LIBROM_DIR}/dependencies/parmetis-4.0.3/build/lib/libmetis") + find_path(MFEM_INCLUDES mfem.hpp + "$ENV{MFEM_DIR}/include" + "$ENV{MFEM_DIR}" + "${LIBROM_DIR}/dependencies/mfem") + find_path(HYPRE_INCLUDES HYPRE.h + "$ENV{HYPRE_DIR}/include" + "${LIBROM_DIR}/dependencies/hypre/src/hypre/include") + find_path(PARMETIS_INCLUDES metis.h + "$ENV{PARMETIS_DIR}/metis/include" + "${LIBROM_DIR}/dependencies/parmetis-4.0.3/metis/include") + set(PYLIBROM_HAS_MFEM 1) +endif() #===================== pylibROM ============================= @@ -105,30 +113,23 @@ set(CMAKE_CXX_STANDARD 14) find_package(MPI REQUIRED) -set(SOURCE_DIR "bindings/pylibROM") +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/bindings/pylibROM/pylibROM_config.h.in + ${CMAKE_CURRENT_SOURCE_DIR}/bindings/pylibROM/pylibROM_config.h) + +set(SOURCE_DIR "bindings/pylibROM") include_directories( ${SOURCE_DIR} ${LIBROM_INCLUDE_DIR} ${MPI_INCLUDE_PATH} ${MPI4PY} ${HDF5_C_INCLUDE_DIRS} - ${MFEM_INCLUDES} - ${HYPRE_INCLUDES} - ${PARMETIS_INCLUDES} - ${MFEM_C_INCLUDE_DIRS} -) -link_libraries( - ${HDF5_LIBRARIES} - ${MFEM} - ${HYPRE} - ${PARMETIS} - ${METIS} ) +link_libraries(${HDF5_LIBRARIES}) add_subdirectory("extern/pybind11") -pybind11_add_module(_pylibROM - bindings/pylibROM/pylibROM.cpp +set(PYLIBROM_SOURCES + bindings/pylibROM/pylibROM.cpp bindings/pylibROM/linalg/pyMatrix.cpp bindings/pylibROM/linalg/pyVector.cpp @@ -165,12 +166,35 @@ pybind11_add_module(_pylibROM bindings/pylibROM/utils/pyHDFDatabase.cpp bindings/pylibROM/utils/pyCSVDatabase.cpp - bindings/pylibROM/mfem/pyUtilities.cpp - bindings/pylibROM/mfem/pyPointwiseSnapshot.cpp - bindings/pylibROM/mfem/pySampleMesh.cpp - bindings/pylibROM/python_utils/cpp_utils.hpp ) + +if (USE_MFEM) + set(PYLIBROM_SOURCES ${PYLIBROM_SOURCES} + bindings/pylibROM/mfem/pyUtilities.cpp + bindings/pylibROM/mfem/pyPointwiseSnapshot.cpp + bindings/pylibROM/mfem/pySampleMesh.cpp) +endif() + +pybind11_add_module(_pylibROM ${PYLIBROM_SOURCES}) message("building pylibROM...") +if (USE_MFEM) + target_include_directories( + _pylibROM + PUBLIC + ${MFEM_INCLUDES} + ${HYPRE_INCLUDES} + ${PARMETIS_INCLUDES} + ${MFEM_C_INCLUDE_DIRS}) + + target_link_libraries( + _pylibROM + PUBLIC + ${MFEM} + ${HYPRE} + ${PARMETIS} + ${METIS}) +endif() + target_link_libraries(_pylibROM PRIVATE ROM) diff --git a/bindings/pylibROM/__init__.py b/bindings/pylibROM/__init__.py index 8353c0f..19641a6 100644 --- a/bindings/pylibROM/__init__.py +++ b/bindings/pylibROM/__init__.py @@ -6,4 +6,14 @@ # either define/import the python routine in this file. # This will combine both c++ bindings/pure python routines into this module. -from _pylibROM import * +from _pylibROM.algo import * +from _pylibROM.hyperreduction import * +from _pylibROM.linalg import * + +try: + import _pylibROM.mfem + from _pylibROM.mfem import * +except: + pass + +from _pylibROM.utils import * diff --git a/bindings/pylibROM/pylibROM.cpp b/bindings/pylibROM/pylibROM.cpp index 6aa90ab..bd5c523 100644 --- a/bindings/pylibROM/pylibROM.cpp +++ b/bindings/pylibROM/pylibROM.cpp @@ -1,5 +1,16 @@ #include +#include "CAROM_config.h" +#include "pylibROM_config.h" + +// check that libROM has MFEM if pylibROM is using MFEM +#ifdef PYLIBROM_HAS_MFEM +// temporarily disabled until libROM upstream adds this option +// #ifndef CAROM_HAS_MFEM +// #error "libROM was not compiled with MFEM support" +// #endif +#endif + namespace py = pybind11; //linalg @@ -47,10 +58,12 @@ void init_Database(pybind11::module_ &m); void init_HDFDatabase(pybind11::module_ &m); void init_CSVDatabase(pybind11::module_ &m); +#ifdef PYLIBROM_HAS_MFEM //mfem void init_mfem_Utilities(pybind11::module_ &m); void init_mfem_PointwiseSnapshot(pybind11::module_ &m); void init_mfem_SampleMesh(pybind11::module_ &m); +#endif PYBIND11_MODULE(_pylibROM, m) { py::module utils = m.def_submodule("utils"); @@ -97,10 +110,12 @@ PYBIND11_MODULE(_pylibROM, m) { init_STSampling(hyperreduction); init_Utilities(hyperreduction); +#ifdef PYLIBROM_HAS_MFEM py::module mfem = m.def_submodule("mfem"); init_mfem_Utilities(mfem); init_mfem_PointwiseSnapshot(mfem); init_mfem_SampleMesh(mfem); +#endif // py::module python_utils = m.def_submodule("python_utils"); } diff --git a/bindings/pylibROM/pylibROM_config.h.in b/bindings/pylibROM/pylibROM_config.h.in new file mode 100644 index 0000000..cf1ad19 --- /dev/null +++ b/bindings/pylibROM/pylibROM_config.h.in @@ -0,0 +1,6 @@ +#ifndef PYLIBROM_CONFIG_H_ +#define PYLIBROM_CONFIG_H_ + +#cmakedefine PYLIBROM_HAS_MFEM + +#endif diff --git a/setup.py b/setup.py index 5d3cac1..e3f06b7 100644 --- a/setup.py +++ b/setup.py @@ -8,21 +8,30 @@ # It recommends using "--config-settings", though there is no clear documentation about this. # For now, we enforce to use the versions < 23.3.0. import pkg_resources -pkg_resources.require(['pip < 23.3.0']) +#pkg_resources.require(['pip < 23.3.0']) from setuptools import Extension, setup, find_packages from setuptools.command.build_ext import build_ext +from setuptools.command.install import install as _install # Take the global option for pre-installed librom directory. librom_dir = None install_scalapack = False +use_mfem = True +# TODO: fix this.. passing options through pip to setuptools with PEP517 is not working for arg in sys.argv: - if (arg[:13] == "--librom_dir="): + if (arg[:13] == "--librom_dir=" or arg[:13] == "--librom-dir="): librom_dir = arg[13:] sys.argv.remove(arg) -if "--install_scalapack" in sys.argv: - install_scalapack = True - sys.argv.remove("--install_scalapack") + if (arg[:19] == "--install_scalapack"): + install_scalapack = True + sys.argv.remove(arg) + if (arg[:9] == "--no-mfem"): + use_mfem = False + sys.argv.remove(arg) + if (arg[:10] == "--use-mfem"): + use_mfem = True + sys.argv.remove(arg) # Convert distutils Windows platform specifiers to CMake -A arguments PLAT_TO_CMAKE = { @@ -41,6 +50,38 @@ def __init__(self, name: str, sourcedir: str = "") -> None: self.sourcedir = os.fspath(Path(sourcedir).resolve()) +class InstallBuild(_install): + + user_options = _install.user_options + [ + ("librom-dir=", None, "Path to libROM root directory. If specified, pylibROM will use this instead of compiling its own libROM."), + ("install-scalapack", None, "Enables SCALAPACK when building libROM."), + ("use-mfem", None, "Compile pylibROM with MFEM enabled."), + ("no-mfem", None, "Compile pylibROM without MFEM.") + ] + + negative_opt = dict(_install.negative_opt) + negative_opt.update({"no-mfem" : "use-mfem"}) + + def run(self): + _install.run(self) + + + def initialize_options(self): + _install.initialize_options(self) + self.librom_dir = None + self.install_scalapack = False + self.use_mfem = True + + + def finalize_options(self): + global librom_dir, install_scalapack, use_mfem + librom_dir = self.librom_dir + install_scalapack = self.install_scalapack + use_mfem = self.use_mfem + + _install.finalize_options(self) + + class CMakeBuild(build_ext): def build_extension(self, ext: CMakeExtension) -> None: # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ @@ -57,15 +98,16 @@ def build_extension(self, ext: CMakeExtension) -> None: # Can be set with Conda-Build, for example. cmake_generator = os.environ.get("CMAKE_GENERATOR", "") - global librom_dir, install_scalapack + global librom_dir, install_scalapack, use_mfem cmake_args = [] if (librom_dir is None): librom_dir = os.path.dirname(os.path.realpath(__file__)) - librom_dir += "/extern/libROM" + librom_dir += "/extern/libROM/" print("Installing libROM library: %s" % librom_dir) - librom_cmd = "cd %s && ./scripts/compile.sh -m -g -t ./cmake/toolchains/simple.cmake" % librom_dir + librom_cmd = "cd %s && ./scripts/compile.sh -t ./cmake/toolchains/simple.cmake" % librom_dir if (install_scalapack): librom_cmd += " -s" + if (use_mfem): librom_cmd += " -m -g" print("libROM installation command: %s" % librom_cmd) subprocess.run( librom_cmd, shell=True, check=True @@ -74,6 +116,8 @@ def build_extension(self, ext: CMakeExtension) -> None: print("Using pre-installed libROM library: %s" % librom_dir) cmake_args += [f"-DLIBROM_DIR=%s" % librom_dir] + cmake_args += ["-DUSE_MFEM={:s}".format('ON' if use_mfem else 'OFF')] + # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code # from Python. @@ -152,10 +196,18 @@ def build_extension(self, ext: CMakeExtension) -> None: ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True ) subprocess.run( - ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True + ["cmake", "--build", ".", "-j 8", "-v", *build_args], cwd=build_temp, check=True ) + def initialize_options(self): + build_ext.initialize_options(self) + + + def finalize_options(self): + build_ext.finalize_options(self) + + # The information here can also be placed in setup.cfg - better separation of # logic and declaration, and simpler if you include description/version in a file. setup( @@ -165,12 +217,13 @@ def build_extension(self, ext: CMakeExtension) -> None: # author_email="dean0x7d@gmail.com", description="Python Interface for LLNL libROM", long_description="", - packages=find_packages(where='bindings'), + packages=find_packages(where='bindings', exclude=['pylibROM.mfem'] if use_mfem == False else ['']), package_dir={"":"bindings"}, # packages=['bindings/pylibROM'], ext_modules=[CMakeExtension("_pylibROM")], cmdclass={ "build_ext": CMakeBuild, + "install": InstallBuild }, zip_safe=False, extras_require={"test": ["pytest>=6.0"]},