diff --git a/CMakeLists.txt b/CMakeLists.txt index fdc80ea..1bb6ba7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,9 @@ if (BUILD_SCALAPACK) add_custom_target(RUN_SLPK_BUILD ALL DEPENDS SLPK_BUILD) endif(BUILD_SCALAPACK) +# HDF5 +find_package(HDF5 1.8.0 REQUIRED) + #=================== libROM ================================ #It is tedious to build libROM. option to not build @@ -108,17 +111,19 @@ include_directories( ${LIBROM_INCLUDE_DIR} ${MPI_INCLUDE_PATH} ${MPI4PY} + ${HDF5_C_INCLUDE_DIRS} # ${MFEM_INCLUDES} # ${HYPRE_INCLUDES} # ${PARMETIS_INCLUDES} # ${MFEM_C_INCLUDE_DIRS} ) -# link_libraries( +link_libraries( + ${HDF5_LIBRARIES} # ${MFEM} # ${HYPRE} # ${PARMETIS} # ${METIS} -# ) +) add_subdirectory("extern/pybind11") @@ -135,9 +140,15 @@ pybind11_add_module(_pylibROM bindings/pylibROM/linalg/svd/pySVD.cpp bindings/pylibROM/linalg/svd/pyStaticSVD.cpp bindings/pylibROM/linalg/svd/pyIncrementalSVD.cpp - bindings/pylibROM/algo/pyDMD.cpp + + bindings/pylibROM/algo/pyDMD.cpp + bindings/pylibROM/algo/pyParametricDMD.cpp + bindings/pylibROM/utils/mpi_utils.cpp + bindings/pylibROM/utils/pyDatabase.hpp bindings/pylibROM/utils/pyDatabase.cpp + bindings/pylibROM/utils/pyHDFDatabase.cpp + bindings/pylibROM/utils/pyCSVDatabase.cpp # 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. diff --git a/bindings/pylibROM/algo/pyDMD.cpp b/bindings/pylibROM/algo/pyDMD.cpp index 8fd35a8..0331833 100644 --- a/bindings/pylibROM/algo/pyDMD.cpp +++ b/bindings/pylibROM/algo/pyDMD.cpp @@ -7,40 +7,13 @@ #include #include "algo/DMD.h" #include "linalg/Vector.h" +#include "linalg/Matrix.h" #include "python_utils/cpp_utils.hpp" namespace py = pybind11; using namespace CAROM; using namespace std; -/* -//wrapper class needed for setOffset and takeSample methods. -class PyDMD : public DMD -{ -public: - using DMD::DMD; - - void setOffset(py::object offset_vector, int order) - { - // Convert the Python object to the appropriate type - Vector* offset = py::cast(offset_vector); - DMD::setOffset(offset, order); - } - - void takeSample(py::array_t u_in, double t) - { - // Access the underlying data of the NumPy array - auto buf = u_in.request(); - double* u_in_ptr = static_cast(buf.ptr); - - // Call the original method - DMD::takeSample(u_in_ptr, t); - } - - // Add wrappers for other methods as needed -}; -*/ - void init_DMD(pybind11::module_ &m) { py::class_(m, "DMD") @@ -58,9 +31,9 @@ void init_DMD(pybind11::module_ &m) { self.takeSample(getVectorPointer(u_in), t); }) .def("train", py::overload_cast(&DMD::train), - py::arg("energy_fraction"), py::arg("W0") = nullptr, py::arg("linearity_tol") = 0.0) + py::arg("energy_fraction").noconvert(), py::arg("W0") = nullptr, py::arg("linearity_tol") = 0.0) .def("train", py::overload_cast(&DMD::train), - py::arg("k"), py::arg("W0") = nullptr, py::arg("linearity_tol") = 0.0) + py::arg("k").noconvert(), py::arg("W0") = nullptr, py::arg("linearity_tol") = 0.0) .def("projectInitialCondition", &DMD::projectInitialCondition, py::arg("init"), py::arg("t_offset") = -1.0) .def("predict", &DMD::predict, py::arg("t"), py::arg("deg") = 0) diff --git a/bindings/pylibROM/algo/pyParametricDMD.cpp b/bindings/pylibROM/algo/pyParametricDMD.cpp new file mode 100644 index 0000000..1da4c00 --- /dev/null +++ b/bindings/pylibROM/algo/pyParametricDMD.cpp @@ -0,0 +1,123 @@ +// +// Created by barrow9 on 6/4/23. +// +#include +#include +#include +#include +#include "algo/DMD.h" +#include "algo/ParametricDMD.h" +#include "linalg/Vector.h" +#include "python_utils/cpp_utils.hpp" + +namespace py = pybind11; +using namespace CAROM; +using namespace std; + +template +T* getParametricDMD_Type( + std::vector& parameter_points, + std::vector& dmds, + Vector* desired_point, + std::string rbf, + std::string interp_method, + double closest_rbf_val, + bool reorthogonalize_W) +{ + T *parametric_dmd = NULL; + getParametricDMD(parametric_dmd, parameter_points, dmds, desired_point, + rbf, interp_method, closest_rbf_val, reorthogonalize_W); + return parametric_dmd; +} + +template +T* getParametricDMD_Type( + std::vector& parameter_points, + std::vector& dmd_paths, + Vector* desired_point, + std::string rbf = "G", + std::string interp_method = "LS", + double closest_rbf_val = 0.9, + bool reorthogonalize_W = false) +{ + T *parametric_dmd = NULL; + getParametricDMD(parametric_dmd, parameter_points, dmd_paths, desired_point, + rbf, interp_method, closest_rbf_val, reorthogonalize_W); + return parametric_dmd; +} + +void init_ParametricDMD(pybind11::module_ &m) { + + // original getParametricDMD pass a template pointer reference T* ¶metric_dmd. + // While it is impossible to bind a template function as itself, + // this first argument T* ¶metric_dmd is mainly for determining the type T. + // Here we introduce a dummy argument in place of parametric_dmd. + // This will let python decide which function to use, based on the first argument type. + // We will need variants of this as we bind more DMD classes, + // where dmd_type is the corresponding type. + m.def("getParametricDMD", []( + py::object &dmd_type, + std::vector& parameter_points, + std::vector& dmds, + Vector* desired_point, + std::string rbf = "G", + std::string interp_method = "LS", + double closest_rbf_val = 0.9, + bool reorthogonalize_W = false) + { + std::string name = dmd_type.attr("__name__").cast(); + if (name == "DMD") + return getParametricDMD_Type(parameter_points, dmds, desired_point, + rbf, interp_method, closest_rbf_val, reorthogonalize_W); + else + { + std::string msg = name + " is not a proper libROM DMD class!\n"; + throw std::runtime_error(msg.c_str()); + } + }, + py::arg("dmd_type"), + py::arg("parameter_points"), + py::arg("dmds"), + py::arg("desired_point"), + py::arg("rbf") = "G", + py::arg("interp_method") = "LS", + py::arg("closest_rbf_val") = 0.9, + py::arg("reorthogonalize_W") = false); + + // original getParametricDMD pass a template pointer reference T* ¶metric_dmd. + // While it is impossible to bind a template function as itself, + // this first argument T* ¶metric_dmd is mainly for determining the type T. + // Here we introduce a dummy argument in place of parametric_dmd. + // This will let python decide which function to use, based on the first argument type. + // We will need variants of this as we bind more DMD classes, + // where dmd_type is the corresponding type. + m.def("getParametricDMD", []( + py::object &dmd_type, + std::vector& parameter_points, + std::vector& dmd_paths, + Vector* desired_point, + std::string rbf = "G", + std::string interp_method = "LS", + double closest_rbf_val = 0.9, + bool reorthogonalize_W = false) + { + std::string name = dmd_type.attr("__name__").cast(); + if (name == "DMD") + return getParametricDMD_Type(parameter_points, dmd_paths, desired_point, + rbf, interp_method, closest_rbf_val, reorthogonalize_W); + else + { + std::string msg = name + " is not a proper libROM DMD class!\n"; + throw std::runtime_error(msg.c_str()); + } + }, + py::arg("dmd_type"), + py::arg("parameter_points"), + py::arg("dmd_paths"), + py::arg("desired_point"), + py::arg("rbf") = "G", + py::arg("interp_method") = "LS", + py::arg("closest_rbf_val") = 0.9, + py::arg("reorthogonalize_W") = false); + +} diff --git a/bindings/pylibROM/mfem/PointwiseSnapshot.py b/bindings/pylibROM/mfem/PointwiseSnapshot.py new file mode 100644 index 0000000..ae306bc --- /dev/null +++ b/bindings/pylibROM/mfem/PointwiseSnapshot.py @@ -0,0 +1,94 @@ +import numpy as np +import mfem.par as mfem +from mpi4py import MPI + +class PointwiseSnapshot: + finder = None + npoints = -1 + dims = [-1] * 3 + spaceDim = -1 + + domainMin = mfem.Vector() + domainMax = mfem.Vector() + xyz = mfem.Vector() + + def __init__(self, sdim, dims_): + self.finder = None + self.spaceDim = sdim + assert((1 < sdim) and (sdim < 4)) + + self.npoints = np.prod(dims_[:self.spaceDim]) + self.dims = np.ones((3,), dtype=int) + self.dims[:self.spaceDim] = dims_[:self.spaceDim] + + self.xyz.SetSize(self.npoints * self.spaceDim) + self.xyz.Assign(0.) + return + + def SetMesh(self, pmesh): + if (self.finder is not None): + self.finder.FreeData() # Free the internal gslib data. + del self.finder + + assert(pmesh.Dimension() == self.spaceDim) + assert(pmesh.SpaceDimension() == self.spaceDim) + + self.domainMin, self.domainMax = pmesh.GetBoundingBox(0) + + h = [0.] * 3 + for i in range(self.spaceDim): + h[i] = (self.domainMax[i] - self.domainMin[i]) / float(self.dims[i] - 1) + + rank = pmesh.GetMyRank() + if (rank == 0): + print("PointwiseSnapshot on bounding box from (", + self.domainMin[:self.spaceDim], + ") to (", self.domainMax[:self.spaceDim], ")") + + # TODO(kevin): we might want to re-write this loop in python manner. + xyzData = self.xyz.GetDataArray() + for k in range(self.dims[2]): + pz = self.domainMin[2] + k * h[2] if (self.spaceDim > 2) else 0.0 + osk = k * self.dims[0] * self.dims[1] + + for j in range(self.dims[1]): + py = self.domainMin[1] + j * h[1] + osj = (j * self.dims[0]) + osk + + for i in range(self.dims[0]): + px = self.domainMin[0] + i * h[0] + xyzData[i + osj] = px + if (self.spaceDim > 1): xyzData[self.npoints + i + osj] = py + if (self.spaceDim > 2): xyzData[2 * self.npoints + i + osj] = pz + + self.finder = mfem.FindPointsGSLIB(MPI.COMM_WORLD) + # mfem.FindPointsGSLIB() + self.finder.Setup(pmesh) + self.finder.SetL2AvgType(mfem.FindPointsGSLIB.NONE) + return + + def GetSnapshot(self, f, s): + vdim = f.FESpace().GetVDim() + s.SetSize(self.npoints * vdim) + + self.finder.Interpolate(self.xyz, f, s) + + code_out = self.finder.GetCode() + print(type(code_out)) + print(code_out.__dir__()) + + assert(code_out.Size() == self.npoints) + + # Note that Min() and Max() are not defined for Array + #MFEM_VERIFY(code_out.Min() >= 0 && code_out.Max() < 2, ""); + cmin = code_out[0] + cmax = code_out[0] + # TODO(kevin): does this work for mfem array? + for c in code_out: + if (c < cmin): + cmin = c + if (c > cmax): + cmax = c + + assert((cmin >= 0) and (cmax < 2)) + return \ No newline at end of file diff --git a/bindings/pylibROM/mfem/__init__.py b/bindings/pylibROM/mfem/__init__.py index b772315..bf20e2c 100644 --- a/bindings/pylibROM/mfem/__init__.py +++ b/bindings/pylibROM/mfem/__init__.py @@ -1,4 +1,5 @@ # To add pure python routines to this module, # either define/import the python routine in this file. # This will combine both c++ bindings/pure python routines into this module. -from .Utilities import ComputeCtAB \ No newline at end of file +from .Utilities import ComputeCtAB +from .PointwiseSnapshot import PointwiseSnapshot \ No newline at end of file diff --git a/bindings/pylibROM/pylibROM.cpp b/bindings/pylibROM/pylibROM.cpp index bbced7c..dd5d779 100644 --- a/bindings/pylibROM/pylibROM.cpp +++ b/bindings/pylibROM/pylibROM.cpp @@ -19,10 +19,13 @@ void init_IncrementalSVD(pybind11::module_ &m); //algo void init_DMD(pybind11::module_ &); +void init_ParametricDMD(pybind11::module_ &m); //utils void init_mpi_utils(pybind11::module_ &m); void init_Database(pybind11::module_ &m); +void init_HDFDatabase(pybind11::module_ &m); +void init_CSVDatabase(pybind11::module_ &m); //mfem // TODO(kevin): We do not bind mfem-related functions until we figure out how to type-cast SWIG Object. @@ -33,6 +36,8 @@ PYBIND11_MODULE(_pylibROM, m) { py::module utils = m.def_submodule("utils"); init_mpi_utils(utils); init_Database(utils); + init_HDFDatabase(utils); + init_CSVDatabase(utils); py::module linalg = m.def_submodule("linalg"); init_vector(linalg); @@ -42,6 +47,7 @@ PYBIND11_MODULE(_pylibROM, m) { init_BasisReader(linalg); init_Options(linalg); init_NNLSSolver(linalg); + py::module svd = linalg.def_submodule("svd"); init_SVD(svd); init_StaticSVD(svd); @@ -49,6 +55,7 @@ PYBIND11_MODULE(_pylibROM, m) { py::module algo = m.def_submodule("algo"); init_DMD(algo); + init_ParametricDMD(algo); // py::module mfem = m.def_submodule("mfem"); // init_mfem_Utilities(mfem); diff --git a/bindings/pylibROM/python_utils/StopWatch.py b/bindings/pylibROM/python_utils/StopWatch.py new file mode 100644 index 0000000..9eb36e2 --- /dev/null +++ b/bindings/pylibROM/python_utils/StopWatch.py @@ -0,0 +1,34 @@ +# pyMFEM does not provide mfem::StopWatch. +# there is not really a similar stopwatch package in python.. (seriously?) +import time + +class StopWatch: + duration = 0.0 + start_time = 0.0 + stop_time = 0.0 + running = False + + def __init__(self): + self.Reset() + return + + def Start(self): + assert(not self.running) + self.start_time = time.time() + self.running = True + return + + def Stop(self): + assert(self.running) + self.stop_time = time.time() + self.duration += self.stop_time - self.start_time + self.running = False + return + + def Reset(self): + self.duration = 0.0 + self.start_time = 0.0 + self.stop_time = 0.0 + self.running = False + return + \ No newline at end of file diff --git a/bindings/pylibROM/python_utils/__init__.py b/bindings/pylibROM/python_utils/__init__.py index 907a981..12eda07 100644 --- a/bindings/pylibROM/python_utils/__init__.py +++ b/bindings/pylibROM/python_utils/__init__.py @@ -1,9 +1,9 @@ # To add pure python routines to this module, # either define/import the python routine in this file. # This will combine both c++ bindings/pure python routines into this module. +from .StopWatch import StopWatch def swigdouble2numpyarray(u_swig, u_size): from ctypes import c_double from numpy import array - return array((c_double * u_size).from_address(int(u_swig)), copy=False) - \ No newline at end of file + return array((c_double * u_size).from_address(int(u_swig)), copy=False) \ No newline at end of file diff --git a/bindings/pylibROM/python_utils/cpp_utils.hpp b/bindings/pylibROM/python_utils/cpp_utils.hpp index 0edda15..3c191f7 100644 --- a/bindings/pylibROM/python_utils/cpp_utils.hpp +++ b/bindings/pylibROM/python_utils/cpp_utils.hpp @@ -11,26 +11,29 @@ namespace py = pybind11; -inline double* getPointer(py::array_t &u_in) +template +T* getPointer(py::array_t &u_in) { py::buffer_info buf_info = u_in.request(); // If it is 1D array, we should ensure that the memory is contiguous. - if ((buf_info.ndim == 1) && (buf_info.strides[0] != sizeof(double))) + if ((buf_info.ndim == 1) && (buf_info.strides[0] != sizeof(T))) { std::string msg = "Input numpy array must have a contiguous memory! - "; msg += std::to_string(buf_info.strides[0]) + "\n"; throw std::runtime_error(msg.c_str()); } - return static_cast(buf_info.ptr); + return static_cast(buf_info.ptr); } -inline ssize_t getDim(py::array_t &u_in) +template +ssize_t getDim(py::array_t &u_in) { return u_in.request().ndim; } -inline double* getVectorPointer(py::array_t &u_in) +template +T* getVectorPointer(py::array_t &u_in) { py::buffer_info buf_info = u_in.request(); // If it is 1D array, we should ensure that the memory is contiguous. @@ -38,14 +41,14 @@ inline double* getVectorPointer(py::array_t &u_in) { throw std::runtime_error("Input array must be 1-dimensional!\n"); } - else if (buf_info.strides[0] != sizeof(double)) + else if (buf_info.strides[0] != sizeof(T)) { std::string msg = "Input numpy array must have a contiguous memory! - "; msg += std::to_string(buf_info.strides[0]) + "\n"; throw std::runtime_error(msg.c_str()); } - return static_cast(buf_info.ptr); + return static_cast(buf_info.ptr); } #endif diff --git a/bindings/pylibROM/utils/pyCSVDatabase.cpp b/bindings/pylibROM/utils/pyCSVDatabase.cpp new file mode 100644 index 0000000..f475eb1 --- /dev/null +++ b/bindings/pylibROM/utils/pyCSVDatabase.cpp @@ -0,0 +1,103 @@ +// +// Created by sullan2 on 4/20/23. +// +#include +#include +#include +#include +#include "utils/CSVDatabase.h" +#include "utils/pyDatabase.hpp" +#include "python_utils/cpp_utils.hpp" + +namespace py = pybind11; +using namespace CAROM; + +class PyCSVDatabase : public PyDerivedDatabase { +public: + using PyDerivedDatabase::PyDerivedDatabase; + + void + putComplexVector( + const std::string& file_name, + const std::vector>& data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + CSVDatabase, /* Child class */ + putComplexVector, /* Name of function in C++ (must match Python name) */ + file_name, data, nelements /* Argument(s) */ + ); + } + + void + putStringVector( + const std::string& file_name, + const std::vector& data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + CSVDatabase, /* Child class */ + putStringVector, /* Name of function in C++ (must match Python name) */ + file_name, data, nelements /* Argument(s) */ + ); + } + + // somehow this function is not virtual on c++ side. technically does not need this trampoline? + void + getStringVector( + const std::string& file_name, + std::vector &data, + bool append = false) + { + PYBIND11_OVERRIDE( + void, /* Return type */ + CSVDatabase, /* Child class */ + getStringVector, /* Name of function in C++ (must match Python name) */ + file_name, data, append /* Argument(s) */ + ); + } + + // somehow this function is not virtual on c++ side. technically does not need this trampoline? + int + getLineCount( + const std::string& file_name) + { + PYBIND11_OVERRIDE( + int, /* Return type */ + CSVDatabase, /* Child class */ + getLineCount, /* Name of function in C++ (must match Python name) */ + file_name /* Argument(s) */ + ); + } + +}; + +void init_CSVDatabase(pybind11::module_ &m) { + + py::class_ csvdb(m, "CSVDatabase"); + + // Constructor + csvdb.def(py::init<>()); + + csvdb.def("create", &CSVDatabase::create); + csvdb.def("open", &CSVDatabase::open); + csvdb.def("close", &CSVDatabase::close); + + // TODO(kevin): finish binding of member functions. + csvdb.def("putDoubleArray", []( + CSVDatabase &self, const std::string& key, py::array_t &data, int nelements) + { + self.putDoubleArray(key, getVectorPointer(data), nelements); + }); + csvdb.def("putDoubleVector", &CSVDatabase::putDoubleVector); + csvdb.def("putInteger", &CSVDatabase::putInteger); + csvdb.def("putIntegerArray", []( + CSVDatabase &self, const std::string& key, py::array_t &data, int nelements) + { + self.putIntegerArray(key, getVectorPointer(data), nelements); + }); + +} + diff --git a/bindings/pylibROM/utils/pyDatabase.cpp b/bindings/pylibROM/utils/pyDatabase.cpp index 77ad280..6115382 100644 --- a/bindings/pylibROM/utils/pyDatabase.cpp +++ b/bindings/pylibROM/utils/pyDatabase.cpp @@ -6,168 +6,14 @@ // #include #include #include "utils/Database.h" +#include "utils/pyDatabase.hpp" namespace py = pybind11; using namespace CAROM; -class PyDatabase : public Database -{ -public: - using Database::Database; // Inherit constructors from the base class - - bool - create(const std::string& file_name) override - { - PYBIND11_OVERRIDE_PURE( - bool, /* Return type */ - Database, /* Parent class */ - create, /* Name of function in C++ (must match Python name) */ - file_name /* Argument(s) */ - ); - } - - bool - open( - const std::string& file_name, - const std::string& type) override - { - PYBIND11_OVERRIDE_PURE( - bool, /* Return type */ - Database, /* Parent class */ - open, /* Name of function in C++ (must match Python name) */ - file_name, type /* Argument(s) */ - ); - } - - bool - close() override - { - PYBIND11_OVERRIDE_PURE( - bool, /* Return type */ - Database, /* Parent class */ - close /* Name of function in C++ (must match Python name) */ - ); - } - - void - putIntegerArray( - const std::string& key, - const int* const data, - int nelements) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - putIntegerArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements /* Argument(s) */ - ); - } - - void - putDoubleArray( - const std::string& key, - const double* const data, - int nelements) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - putDoubleArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements /* Argument(s) */ - ); - } - - void - putDoubleVector( - const std::string& key, - const std::vector& data, - int nelements) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - putDoubleVector, /* Name of function in C++ (must match Python name) */ - key, data, nelements /* Argument(s) */ - ); - } - - void - getIntegerArray( - const std::string& key, - int* data, - int nelements) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - getIntegerArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements /* Argument(s) */ - ); - } - - int - getDoubleArraySize(const std::string& key) override - { - PYBIND11_OVERRIDE_PURE( - int, /* Return type */ - Database, /* Parent class */ - getDoubleArraySize, /* Name of function in C++ (must match Python name) */ - key /* Argument(s) */ - ); - } - - void - getDoubleArray( - const std::string& key, - double* data, - int nelements) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - getDoubleArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements /* Argument(s) */ - ); - } - - void - getDoubleArray( - const std::string& key, - double* data, - int nelements, - const std::vector& idx) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - getDoubleArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements, idx /* Argument(s) */ - ); - } - - void - getDoubleArray( - const std::string& key, - double* data, - int nelements, - int offset, - int block_size, - int stride) override - { - PYBIND11_OVERRIDE_PURE( - void, /* Return type */ - Database, /* Parent class */ - getDoubleArray, /* Name of function in C++ (must match Python name) */ - key, data, nelements, /* Argument(s) */ - offset, block_size, stride - ); - } - -}; - void init_Database(pybind11::module_ &m) { - py::class_ db(m, "Database"); + py::class_> db(m, "Database"); // Constructor db.def(py::init<>()); @@ -177,4 +23,6 @@ void init_Database(pybind11::module_ &m) { .value("CSV", Database::formats::CSV) .export_values(); + // TODO(kevin): finish binding of member functions. + } diff --git a/bindings/pylibROM/utils/pyDatabase.hpp b/bindings/pylibROM/utils/pyDatabase.hpp new file mode 100644 index 0000000..48897e7 --- /dev/null +++ b/bindings/pylibROM/utils/pyDatabase.hpp @@ -0,0 +1,327 @@ +// +// Created by sullan2 on 4/20/23. +// +#ifndef PYDATABASE_HPP +#define PYDATABASE_HPP + +#include +#include +// #include +#include +#include "utils/Database.h" + +namespace py = pybind11; +using namespace CAROM; + +template +class PyDatabase : public DatabaseType +{ +public: + using DatabaseType::DatabaseType; // Inherit constructors from the base class + + bool + create(const std::string& file_name) override + { + PYBIND11_OVERRIDE_PURE( + bool, /* Return type */ + DatabaseType, /* Parent class */ + create, /* Name of function in C++ (must match Python name) */ + file_name /* Argument(s) */ + ); + } + + bool + open( + const std::string& file_name, + const std::string& type) override + { + PYBIND11_OVERRIDE_PURE( + bool, /* Return type */ + DatabaseType, /* Parent class */ + open, /* Name of function in C++ (must match Python name) */ + file_name, type /* Argument(s) */ + ); + } + + bool + close() override + { + PYBIND11_OVERRIDE_PURE( + bool, /* Return type */ + DatabaseType, /* Parent class */ + close /* Name of function in C++ (must match Python name) */ + ); + } + + void + putIntegerArray( + const std::string& key, + const int* const data, + int nelements) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + putIntegerArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + putDoubleArray( + const std::string& key, + const double* const data, + int nelements) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + putDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + putDoubleVector( + const std::string& key, + const std::vector& data, + int nelements) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + putDoubleVector, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + getIntegerArray( + const std::string& key, + int* data, + int nelements) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + getIntegerArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + int + getDoubleArraySize(const std::string& key) override + { + PYBIND11_OVERRIDE_PURE( + int, /* Return type */ + DatabaseType, /* Parent class */ + getDoubleArraySize, /* Name of function in C++ (must match Python name) */ + key /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements, + const std::vector& idx) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements, idx /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements, + int offset, + int block_size, + int stride) override + { + PYBIND11_OVERRIDE_PURE( + void, /* Return type */ + DatabaseType, /* Parent class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements, /* Argument(s) */ + offset, block_size, stride + ); + } + +}; + +template +class PyDerivedDatabase : public PyDatabase { +public: + using PyDatabase::PyDatabase; + + bool + create(const std::string& file_name) override + { + PYBIND11_OVERRIDE( + bool, /* Return type */ + DerivedDatabaseType, /* Child class */ + create, /* Name of function in C++ (must match Python name) */ + file_name /* Argument(s) */ + ); + } + + bool + open( + const std::string& file_name, + const std::string& type) override + { + PYBIND11_OVERRIDE( + bool, /* Return type */ + DerivedDatabaseType, /* Child class */ + open, /* Name of function in C++ (must match Python name) */ + file_name, type /* Argument(s) */ + ); + } + + bool + close() override + { + PYBIND11_OVERRIDE( + bool, /* Return type */ + DerivedDatabaseType, /* Child class */ + close /* Name of function in C++ (must match Python name) */ + ); + } + + void + putIntegerArray( + const std::string& key, + const int* const data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + putIntegerArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + putDoubleArray( + const std::string& key, + const double* const data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + putDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + putDoubleVector( + const std::string& key, + const std::vector& data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + putDoubleVector, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + getIntegerArray( + const std::string& key, + int* data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + getIntegerArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + int + getDoubleArraySize(const std::string& key) override + { + PYBIND11_OVERRIDE( + int, /* Return type */ + DerivedDatabaseType, /* Child class */ + getDoubleArraySize, /* Name of function in C++ (must match Python name) */ + key /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements, + const std::vector& idx) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements, idx /* Argument(s) */ + ); + } + + void + getDoubleArray( + const std::string& key, + double* data, + int nelements, + int offset, + int block_size, + int stride) override + { + PYBIND11_OVERRIDE( + void, /* Return type */ + DerivedDatabaseType, /* Child class */ + getDoubleArray, /* Name of function in C++ (must match Python name) */ + key, data, nelements, /* Argument(s) */ + offset, block_size, stride + ); + } + +}; + +#endif \ No newline at end of file diff --git a/bindings/pylibROM/utils/pyHDFDatabase.cpp b/bindings/pylibROM/utils/pyHDFDatabase.cpp new file mode 100644 index 0000000..0d2f16c --- /dev/null +++ b/bindings/pylibROM/utils/pyHDFDatabase.cpp @@ -0,0 +1,44 @@ +// +// Created by sullan2 on 4/20/23. +// +#include +#include +#include +#include +#include "utils/HDFDatabase.h" +#include "utils/pyDatabase.hpp" +#include "python_utils/cpp_utils.hpp" + +namespace py = pybind11; +using namespace CAROM; + +void init_HDFDatabase(pybind11::module_ &m) { + + py::class_> hdfdb(m, "HDFDatabase"); + + // Constructor + hdfdb.def(py::init<>()); + + hdfdb.def("create", &HDFDatabase::create); + hdfdb.def("open", &HDFDatabase::open); + hdfdb.def("close", &HDFDatabase::close); + + // TODO(kevin): finish binding of member functions. + hdfdb.def("putDoubleArray", []( + HDFDatabase &self, const std::string& key, py::array_t &data, int nelements) + { + self.putDoubleArray(key, getVectorPointer(data), nelements); + }); + hdfdb.def("putDoubleVector", &HDFDatabase::putDoubleVector); + + hdfdb.def("putInteger", &HDFDatabase::putInteger); + hdfdb.def("putIntegerArray", []( + HDFDatabase &self, const std::string& key, py::array_t &data, int nelements) + { + self.putIntegerArray(key, getVectorPointer(data), nelements); + }); + + // hdfdb.def("__del__", [](HDFDatabase& self) { self.~HDFDatabase(); }); // Destructor + +} + diff --git a/examples/dmd/parametric_heat_conduction.py b/examples/dmd/parametric_heat_conduction.py new file mode 100644 index 0000000..6ad9027 --- /dev/null +++ b/examples/dmd/parametric_heat_conduction.py @@ -0,0 +1,731 @@ +''' +/****************************************************************************** + * + * Copyright (c) 2013-2023, Lawrence Livermore National Security, LLC + * and other libROM project developers. See the top-level COPYRIGHT + * file for details. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + *****************************************************************************/ + +// libROM MFEM Example: Parametric_Heat_Conduction (adapted from ex16p.cpp) +// +// Compile with: make parametric_heat_conduction +// +// ================================================================================= +// +// In these examples, the radius of the interface between different initial temperatures, the +// alpha coefficient, and two center location variables are modified. +// +// For Parametric DMD (ex. 1) (radius & cx & cy, interpolation): +// rm -rf parameters.txt +// python3 parametric_heat_conduction.py -r 0.1 -cx 0.1 -cy 0.1 -o 4 -visit -offline -rdim 16 +// python3 parametric_heat_conduction.py -r 0.1 -cx 0.1 -cy 0.5 -o 4 -visit -offline -rdim 16 +// python3 parametric_heat_conduction.py -r 0.1 -cx 0.5 -cy 0.5 -o 4 -visit -offline -rdim 16 +// python3 parametric_heat_conduction.py -r 0.5 -cx 0.1 -cy 0.1 -o 4 -visit -offline -rdim 16 +// python3 parametric_heat_conduction.py -r 0.25 -cx 0.2 -cy 0.4 -o 4 -online -predict +// python3 parametric_heat_conduction.py -r 0.4 -cx 0.2 -cy 0.3 -o 4 -online -predict +// +// ================================================================================= +// +// Description: This example solves a time dependent nonlinear heat equation +// problem of the form du/dt = C(u), with a non-linear diffusion +// operator C(u) = \nabla \cdot (\kappa + \alpha u) \nabla u. +// +// The example demonstrates the use of nonlinear operators (the +// class ConductionOperator defining C(u)), as well as their +// implicit time integration. Note that implementing the method +// ConductionOperator::ImplicitSolve is the only requirement for +// high-order implicit (SDIRK) time integration. Optional saving +// with ADIOS2 (adios2.readthedocs.io) is also illustrated. +''' +import os +import io +import pathlib +import sys +try: + import mfem.par as mfem +except ModuleNotFoundError: + msg = "PyMFEM is not installed yet. Install PyMFEM:\n" + msg += "\tgit clone https://github.com/mfem/PyMFEM.git\n" + msg += "\tcd PyMFEM\n" + msg += "\tpython3 setup.py install --with-parallel\n" + raise ModuleNotFoundError(msg) + +from mfem.par import intArray +from os.path import expanduser, join, dirname +import numpy as np +from numpy import sin, cos, exp, sqrt, pi, abs, array, floor, log, sum + +sys.path.append("../../build") +import pylibROM.algo as algo +import pylibROM.linalg as linalg +import pylibROM.utils as utils +from pylibROM.python_utils import StopWatch +from pylibROM.mfem import PointwiseSnapshot + +class ConductionOperator(mfem.PyTimeDependentOperator): + def __init__(self, fespace, alpha, kappa, u): + mfem.PyTimeDependentOperator.__init__( + self, fespace.GetTrueVSize(), 0.0) + rel_tol = 1e-8 + self.alpha = alpha + self.kappa = kappa + self.T = None + self.K = None + self.M = None + self.fespace = fespace + + self.ess_tdof_list = intArray() + self.Mmat = mfem.HypreParMatrix() + self.Kmat = mfem.HypreParMatrix() + self.M_solver = mfem.CGSolver(fespace.GetComm()) + self.M_prec = mfem.HypreSmoother() + self.T_solver = mfem.CGSolver(fespace.GetComm()) + self.T_prec = mfem.HypreSmoother() + self.z = mfem.Vector(self.Height()) + + self.M = mfem.ParBilinearForm(fespace) + self.M.AddDomainIntegrator(mfem.MassIntegrator()) + self.M.Assemble() + self.M.FormSystemMatrix(self.ess_tdof_list, self.Mmat) + + self.M_solver.iterative_mode = False + self.M_solver.SetRelTol(rel_tol) + self.M_solver.SetAbsTol(0.0) + self.M_solver.SetMaxIter(100) + self.M_solver.SetPrintLevel(0) + self.M_prec.SetType(mfem.HypreSmoother.Jacobi) + self.M_solver.SetPreconditioner(self.M_prec) + self.M_solver.SetOperator(self.Mmat) + + self.T_solver.iterative_mode = False + self.T_solver.SetRelTol(rel_tol) + self.T_solver.SetAbsTol(0.0) + self.T_solver.SetMaxIter(100) + self.T_solver.SetPrintLevel(0) + self.T_solver.SetPreconditioner(self.T_prec) + + self.SetParameters(u) + + def Mult(self, u, u_dt): + # Compute: + # du_dt = M^{-1}*-K(u) for du_dt + self.Kmat.Mult(u, self.z) + self.z.Neg() # z = -z + self.M_solver.Mult(self.z, du_dt) + + def ImplicitSolve(self, dt, u, du_dt): + # Solve the equation: + # du_dt = M^{-1}*[-K(u + dt*du_dt)] + # for du_dt + if self.T is None: + self.T = mfem.Add(1.0, self.Mmat, dt, self.Kmat) + current_dt = dt + self.T_solver.SetOperator(self.T) + self.Kmat.Mult(u, self.z) + self.z.Neg() + self.T_solver.Mult(self.z, du_dt) + + def SetParameters(self, u): + u_alpha_gf = mfem.ParGridFunction(self.fespace) + u_alpha_gf.SetFromTrueDofs(u) + for i in range(u_alpha_gf.Size()): + u_alpha_gf[i] = self.kappa + self.alpha * u_alpha_gf[i] + + self.K = mfem.ParBilinearForm(self.fespace) + u_coeff = mfem.GridFunctionCoefficient(u_alpha_gf) + self.K.AddDomainIntegrator(mfem.DiffusionIntegrator(u_coeff)) + self.K.Assemble(0) + self.K.FormSystemMatrix(self.ess_tdof_list, self.Kmat) + self.T = None + +class InitialTemperature(mfem.PyCoefficient): + def __init__(self, radius_, cx_, cy_): + self.radius = radius_ + self.cx = cx_ + self.cy = cy_ + + mfem.PyCoefficient.__init__(self) + return + + def EvalValue(self, x): + xx = np.array(x) - np.array([self.cx, self.cy]) + norm2 = np.sqrt(float(np.sum(xx**2))) + if norm2 < self.radius: + return 2.0 + return 1.0 + +if __name__ == "__main__": + from mpi4py import MPI + comm = MPI.COMM_WORLD + myid = comm.Get_rank() + num_procs = comm.Get_size() + + from mfem.common.arg_parser import ArgParser + parser = ArgParser(description="DMD - MFEM wave equation (ex23) example.") + parser.add_argument('-m', '--mesh', + default='../data/star.mesh', + action='store', type=str, + help='Mesh file to use.') + parser.add_argument('-rs', '--refine-serial', + action='store', default=2, type=int, + help="Number of times to refine the mesh uniformly in serial") + parser.add_argument('-rp', '--refine-parallel', + action='store', default=1, type=int, + help="Number of times to refine the mesh uniformly in parallel") + parser.add_argument('-o', '--order', + action='store', default=2, type=int, + help="Finite element order (polynomial degree)") + parser.add_argument('-s', '--ode-solver', + action='store', default=3, type=int, + help='\n'.join(["ODE solver: 1 - Backward Euler, 2 - SDIRK2, 3 - SDIRK3", + "\t\t 11 - Forward Euler, 12 - RK2, 13 - RK3 SSP, 14 - RK4."])) + parser.add_argument('-t', '--t-final', + action='store', default=0.5, type=float, + help="Final time; start time is 0.") + parser.add_argument("-dt", "--time-step", + action='store', default=0.01, type=float, + help="Time step.") + parser.add_argument('-a', '--alpha', + action='store', default=0.01, type=float, + help='Alpha coefficient') + parser.add_argument('-k', '--kappa', + action='store', default=0.5, type=float, + help='Kappa coefficient') + parser.add_argument("-r", "--radius", + action='store', default=0.5, type=float, + help="Radius of the interface of initial temperature.") + parser.add_argument("-cx", "--center_x", + action='store', default=0.0, type=float, + help="Center offset in the x direction.") + parser.add_argument("-cy", "--center_y", + action='store', default=0.0, type=float, + help="Center offset in the y direction.") + parser.add_argument("-crv", "--crv", + action='store', default=0.9, type=float, + help="DMD Closest RBF Value.") + parser.add_argument('-vis', '--visualization', + action='store_true', default=True, + help='Enable GLVis visualization') + parser.add_argument('-visit', '--visit-datafiles', + action='store_true', default=False, + help="Save data files for VisIt (visit.llnl.gov) visualization.") + parser.add_argument("-vs", "--visualization-steps", + action='store', default=5, type=int, + help="Visualize every n-th timestep.") + parser.add_argument("-rdim", "--rdim", + action='store', default=-1, type=int, + help="Reduced dimension for DMD.") + parser.add_argument("-offline", "--offline", + action='store_true', default=False, + help="Enable or disable the offline phase.") + parser.add_argument("-online", "--online", + action='store_true', default=False, + help="Enable or disable the online phase.") + parser.add_argument("-predict", "--predict", + action='store_true', default=False, + help="Enable or disable DMD prediction.") + parser.add_argument("-adios2", "--adios2-streams", + action='store_true', default=False, + help="Save data using adios2 streams.") + parser.add_argument("-save", "--save", + action='store_true', default=False, + help="Enable or disable MFEM DOF solution snapshot files).") + parser.add_argument("-csv", "--csv", + action='store_true', default=False, + help="Use CSV or HDF format for files output by -save option.") + parser.add_argument("-out", "--outputfile-name", + action='store', default="", type=str, + help="Name of the sub-folder to dump files within the run directory.") + parser.add_argument("-pwsnap", "--pw-snap", + action='store_true', default=False, + help="Enable or disable writing pointwise snapshots.") + parser.add_argument("-pwx", "--pwx", + action='store', default=0, type=int, + help="Number of snapshot points in x") + parser.add_argument("-pwy", "--pwy", + action='store', default=0, type=int, + help="Number of snapshot points in y") + parser.add_argument("-pwz", "--pwz", + action='store', default=0, type=int, + help="Number of snapshot points in z") + + args = parser.parse_args() + if (myid == 0): + parser.print_options(args) + + precision = 8 + + save_dofs = args.save + basename = args.outputfile_name + offline = args.offline + online = args.online + pointwiseSnapshots = args.pw_snap + rdim = args.rdim + mesh_file = expanduser(join(os.path.dirname(__file__), + '..', 'data', args.mesh)) + ode_solver_type = args.ode_solver + ser_ref_levels = args.refine_serial + par_ref_levels = args.refine_parallel + pwx, pwy, pwz = args.pwx, args.pwy, args.pwz + order = args.order + alpha = args.alpha + kappa = args.kappa + radius = args.radius + cx, cy = args.center_x, args.center_y + visit = args.visit_datafiles + adios2 = args.adios2_streams + visualization = args.visualization + csvFormat = args.csv + dt = args.time_step + t_final = args.t_final + vis_steps = args.visualization_steps + closest_rbf_val = args.crv + predict = args.predict + + outputPath = "." + if (save_dofs): + outputPath = "run" + if (basename != ""): + outputPath += "/" + basename + + if (myid == 0): + pathlib.Path(outputPath).mkdir(parents=True, exist_ok=True) + + check = pointwiseSnapshots ^ offline ^ online ^ save_dofs + if not check: + raise RuntimeError("Only one of offline, online, save, or pwsnap must be true!") + + if (offline and (rdim <= 0)): + raise RuntimeError("rdim must be set.") + + # 3. Read the serial mesh from the given mesh file on all processors. We can + # handle triangular, quadrilateral, tetrahedral and hexahedral meshes + # with the same code. + mesh = mfem.Mesh(mesh_file, 1, 1) + dim = mesh.Dimension() + + # 4. Define the ODE solver used for time integration. Several implicit + # singly diagonal implicit Runge-Kutta (SDIRK) methods, as well as + # explicit Runge-Kutta methods are available. + if ode_solver_type == 1: + ode_solver = mfem.BackwardEulerSolver() + elif ode_solver_type == 2: + ode_solver = mfem.SDIRK23Solver(2) + elif ode_solver_type == 3: + ode_solver = mfem.SDIRK33Solver() + elif ode_solver_type == 11: + ode_solver = mfem.ForwardEulerSolver() + elif ode_solver_type == 12: + ode_solver = mfem.RK2Solver(0.5) + elif ode_solver_type == 13: + ode_solver = mfem.RK3SSPSolver() + elif ode_solver_type == 14: + ode_solver = mfem.RK4Solver() + elif ode_solver_type == 22: + ode_solver = mfem.ImplicitMidpointSolver() + elif ode_solver_type == 23: + ode_solver = mfem.SDIRK23Solver() + elif ode_solver_type == 24: + ode_solver = mfem.SDIRK34Solver() + else: + print("Unknown ODE solver type: " + str(ode_solver_type)) + exit + + # 5. Refine the mesh in serial to increase the resolution. In this example + # we do 'ser_ref_levels' of uniform refinement, where 'ser_ref_levels' is + # a command-line parameter. + for lev in range(ser_ref_levels): + mesh.UniformRefinement() + + # 6. Define a parallel mesh by a partitioning of the serial mesh. Refine + # this mesh further in parallel to increase the resolution. Once the + # parallel mesh is defined, the serial mesh can be deleted. + pmesh = mfem.ParMesh(MPI.COMM_WORLD, mesh) + del mesh + for x in range(par_ref_levels): + pmesh.UniformRefinement() + + # TODO(kevin): enforce user to install pymfem with gslib? +# #ifndef MFEM_USE_GSLIB +# if (pointwiseSnapshots) { +# cout << "To use pointwise snapshots, compile with -mg option" << endl; +# MFEM_ABORT("Pointwise snapshots aren't available, since the " +# "compilation is done without the -mg option"); +# } + pws = None + pwsnap = mfem.Vector() + pwsnap_CAROM = None + + if (pointwiseSnapshots): + pmesh.EnsureNodes() + dmdDim = [pwx, pwy, pwz] + pws = PointwiseSnapshot(dim, dmdDim) + pws.SetMesh(pmesh) + + snapshotSize = np.prod(dmdDim[:dim]) + + pwsnap.SetSize(snapshotSize) + if (myid == 0): + pwsnap_CAROM = linalg.Vector(pwsnap.GetDataArray(), True, False) + + # 7. Define the vector finite element space representing the current and the + # initial temperature, u_ref. + fe_coll = mfem.H1_FECollection(order, dim) + fespace = mfem.ParFiniteElementSpace(pmesh, fe_coll) + + fe_size = fespace.GlobalTrueVSize() + if (myid == 0): + print("Number of temperature unknowns: %d" % fe_size) + + u_gf = mfem.ParGridFunction(fespace) + + # 8. Set the initial conditions for u. All boundaries are considered + # natural. + u_0 = InitialTemperature(radius, cx, cy) + u_gf.ProjectCoefficient(u_0) + u = mfem.Vector() + u_gf.GetTrueDofs(u) + + # 9. Initialize the conduction operator and the VisIt visualization. + oper = ConductionOperator(fespace, alpha, kappa, u) + u_gf.SetFromTrueDofs(u) + + mesh_name = "%s/parametric_heat_conduction_%f_%f_%f_%f-mesh.%06d" % (outputPath, radius, alpha, cx, cy, myid) + sol_name = "%s/parametric_heat_conduction_%f_%f_%f_%f-init.%06d" % (outputPath, radius, alpha, cx, cy, myid) + + pmesh.Print(mesh_name, precision) + + output = io.StringIO() + output.precision = precision + u_gf.Save(output) + fid = open(sol_name, 'w') + fid.write(output.getvalue()) + fid.close() + + visit_name = "%s/Parametric_Heat_Conduction_%f_%f_%f_%f" % (outputPath, radius, alpha, cx, cy) + visit_dc = mfem.VisItDataCollection(visit_name, pmesh) + visit_dc.RegisterField("temperature", u_gf) + if (visit): + visit_dc.SetCycle(0) + visit_dc.SetTime(0.0) + visit_dc.Save() + + # Optionally output a BP (binary pack) file using ADIOS2. This can be + # visualized with the ParaView VTX reader. + # TODO(kevin): enforce user to install pymfem with adios2? +#ifdef MFEM_USE_ADIOS2 + if (adios2): + postfix = mesh_file[len("../data/"):] + postfix += "_o%d" % order + postfix += "_solver%d" % ode_solver_type + collection_name = "%s/parametric_heat_conduction-p-%s.bp" % (outputPath, postfix) + + adios2_dc = mfem.ADIOS2DataCollection(MPI.COMM_WORLD, collection_name, pmesh) + adios2_dc.SetParameter("SubStreams", "%d" % (num_procs/2) ) + adios2_dc.RegisterField("temperature", u_gf) + adios2_dc.SetCycle(0) + adios2_dc.SetTime(0.0) + adios2_dc.Save() +#endif + + if visualization: + sol_sock = mfem.socketstream("localhost", 19916) + if not sol_sock.good(): + print("Unable to connect to GLVis server at localhost:19916") + visualization = False + print("GLVis visualization disabled.") + else: + sol_sock << "parallel " << num_procs << " " << myid << "\n" + sol_sock.precision(precision) + sol_sock << "solution\n" << pmesh << u_gf + sol_sock << "pause\n" + sol_sock.flush() + print( + "GLVis visualization paused. Press space (in the GLVis window) to resume it.") + +#ifdef MFEM_USE_GSLIB + # TODO(kevin): enforce user to install pymfem with gslib? + if (pointwiseSnapshots): + pws.GetSnapshot(u_gf, pwsnap) + + dmd_filename = "snap_%f_%f_%f_%f_0" % (radius, alpha, cx, cy) + if (myid == 0): + print("Writing DMD snapshot at step 0, time 0.0") + pwsnap_CAROM.write(dmd_filename) +#endif + + fom_timer, dmd_training_timer, dmd_prediction_timer = StopWatch(), StopWatch(), StopWatch() + + fom_timer.Start() + + # 10. Perform time-integration (looping over the time iterations, ti, with a + # time-step dt). + ode_solver.Init(oper) + t = 0.0 + ts = [] + # CAROM::Vector* init = NULL; + + # CAROM::Database *db = NULL; + if (csvFormat): + db = utils.CSVDatabase() + else: + db = utils.HDFDatabase() + + snap_list = [] + + fom_timer.Stop() + + # CAROM::DMD* dmd_u = NULL; + + if (offline): + dmd_training_timer.Start() + + # 11. Create DMD object and take initial sample. + u_gf.SetFromTrueDofs(u) + dmd_u = algo.DMD(u.Size(), dt) + dmd_u.takeSample(u.GetDataArray(), t) + + if (myid == 0): + print("Taking snapshot at: %f" % t) + + dmd_training_timer.Stop() + + if (online): + u_gf.SetFromTrueDofs(u) + init = linalg.Vector(u.GetDataArray(), u.Size(), True) + + if (save_dofs and (myid == 0)): + if (csvFormat): + pathlib.Path("%s/step0" % outputPath).mkdir(parents=True, exist_ok=True) + db.putDoubleArray(outputPath + "/step0/sol.csv", u.GetDataArray(), u.Size()) + else: + db.create(outputPath + "/dmd_0.hdf") + db.putDoubleArray("step0sol", u.GetDataArray(), u.Size()) + + ts += [t] + snap_list += [0] + + last_step = False + ti = 1 + while (not last_step): + fom_timer.Start() + + if (t + dt >= t_final - dt / 2.): + last_step = True + + t, dt = ode_solver.Step(u, t, dt) + + fom_timer.Stop() + + if (offline): + dmd_training_timer.Start() + + u_gf.SetFromTrueDofs(u) + dmd_u.takeSample(u.GetDataArray(), t) + + if (myid == 0): + print("Taking snapshot at: %f" % t) + + dmd_training_timer.Stop() + + if (save_dofs and (myid == 0)): + if (csvFormat): + pathlib.Path("%s/step%d" % (outputPath, ti)).mkdir(parents=True, exist_ok=True) + db.putDoubleArray("%s/step%d/sol.csv" % (outputPath, ti), u.GetDataArray(), u.Size()) + else: + db.putDoubleArray("step%dsol" % ti, u.GetData(), u.Size()) + + ts += [t] + snap_list += [ti] + + if (last_step or ((ti % vis_steps) == 0)): + if (myid == 0): + print("step %d, t = %f" % (ti, t)) + + u_gf.SetFromTrueDofs(u) + if (visualization): + if sol_sock.good(): + sol_sock << "parallel " << num_procs << " " << myid << "\n" + sol_sock << "solution\n" << pmesh << u_gf + # sol_sock << "pause\n" + sol_sock.flush() + + if (visit): + visit_dc.SetCycle(ti) + visit_dc.SetTime(t) + visit_dc.Save() + +#ifdef MFEM_USE_ADIOS2 + if (adios2): + adios2_dc.SetCycle(ti) + adios2_dc.SetTime(t) + adios2_dc.Save() +#endif + +#ifdef MFEM_USE_GSLIB + if (pointwiseSnapshots): + pws.GetSnapshot(u_gf, pwsnap) + + dmd_filename = "snap_%f_%f_%f_%f_%d" % (radius, alpha, cx, cy, ti) + if (myid == 0): + print("Writing DMD snapshot at step %d, time %f" % (ti, t)) + pwsnap_CAROM.write(dmd_filename) +#endif + + oper.SetParameters(u) + + ti += 1 + + if (save_dofs and (myid == 0)): + if (csvFormat): + db.putDoubleVector(outputPath + "/tval.csv", ts, len(ts)) + db.putInteger(outputPath + "/numsnap", len(snap_list)) + db.putIntegerArray(outputPath + "/snap_list.csv", snap_list, len(snap_list)) + else: + db.putDoubleVector("tval", ts, len(ts)) + db.putInteger("numsnap", len(snap_list)) + db.putInteger("snap_bound_size", 0) + db.putIntegerArray("snap_list", snap_list, len(snap_list)) + +#ifdef MFEM_USE_ADIOS2 + if (adios2): + del adios2_dc +#endif + + # 12. Save the final solution in parallel. This output can be viewed later + # using GLVis: "glvis -np -m parametric_heat_conduction-mesh -g parametric_heat_conduction-final". + sol_name = "%s/parametric_heat_conduction_%f_%f_%f_%f-final.%06d" % (outputPath, radius, alpha, cx, cy, myid) + output = io.StringIO() + output.precision = precision + u_gf.Save(output) + fid = open(sol_name, 'w') + fid.write(output.getvalue()) + fid.close() + + # 13. Calculate the DMD modes. + if (offline or online): + if (offline): + if (myid == 0): + print("Creating DMD with rdim: %d" % rdim) + + dmd_training_timer.Start() + + dmd_u.train(rdim) + + dmd_training_timer.Stop() + + dmd_u.save("%s/%f_%f_%f_%f" % (outputPath, radius, alpha, cx, cy)) + + if (myid == 0): + with open("parameters.txt", "ab") as f: + np.savetxt(f, [radius, alpha, cx, cy]) + + if (online): + if (myid == 0): + print("Creating DMD using the rdim of the offline phase") + + param_hist = np.loadtxt("parameters.txt") + param_hist = param_hist.reshape((int(param_hist.size / 4), 4)) + + dmd_paths = [] + param_vectors = [] + + for curr_param in param_hist: + curr_radius, curr_alpha, curr_cx, curr_cy = curr_param + + dmd_paths += ["%s/%f_%f_%f_%f" % (outputPath, curr_radius, curr_alpha, curr_cx, curr_cy)] + param_vectors += [linalg.Vector([curr_radius, curr_alpha, curr_cx, curr_cy], False)] + + desired_param = linalg.Vector([radius, alpha, cx, cy], False) + + dmd_training_timer.Start() + + dmd_u = algo.getParametricDMD(algo.DMD, param_vectors, dmd_paths, desired_param, + "G", "LS", closest_rbf_val) + + dmd_u.projectInitialCondition(init) + + dmd_training_timer.Stop() + del desired_param + + if (predict): + true_solution_u = mfem.Vector(u.GetDataArray(), u.Size()) + + dmd_prediction_timer.Start() + + # 14. Predict the state at t_final using DMD. + if (myid == 0): + print("Predicting temperature using DMD at: %f" % ts[0]) + + result_u = dmd_u.predict(ts[0]) + initial_dmd_solution_u = mfem.Vector(result_u.getData(), result_u.dim()) + u_gf.SetFromTrueDofs(initial_dmd_solution_u) + + visit_name = "%s/DMD_Parametric_Heat_Conduction_%f_%f_%f_%f" % (outputPath, radius, alpha, cx, cy) + dmd_visit_dc = mfem.VisItDataCollection(visit_name, pmesh) + dmd_visit_dc.RegisterField("temperature", u_gf) + if (visit): + dmd_visit_dc.SetCycle(0) + dmd_visit_dc.SetTime(0.0) + dmd_visit_dc.Save() + + del result_u + + if (visit): + for i, tsi in enumerate(ts): + if ((i == len(ts) - 1) or ((i % vis_steps) == 0)): + result_u = dmd_u.predict(tsi) + if (myid == 0): + print("Predicting temperature using DMD at: %f" % tsi) + + dmd_solution_u = mfem.Vector(result_u.getData(), result_u.dim()) + u_gf.SetFromTrueDofs(dmd_solution_u) + + dmd_visit_dc.SetCycle(i) + dmd_visit_dc.SetTime(tsi) + dmd_visit_dc.Save() + + del result_u + + dmd_prediction_timer.Stop() + + result_u = dmd_u.predict(t_final) + + # 15. Calculate the relative error between the DMD final solution and the true solution. + dmd_solution_u = mfem.Vector(result_u.getData(), result_u.dim()) + diff_u = mfem.Vector(true_solution_u.Size()) + mfem.subtract_vector(dmd_solution_u, true_solution_u, diff_u) + + tot_diff_norm_u = sqrt(mfem.InnerProduct(MPI.COMM_WORLD, diff_u, diff_u)) + tot_true_solution_u_norm = sqrt(mfem.InnerProduct(MPI.COMM_WORLD, + true_solution_u, true_solution_u)) + + if (myid == 0): + print("Relative error of DMD temperature (u) at t_final: %f is %f" % (t_final, + tot_diff_norm_u / tot_true_solution_u_norm)) + + print("Elapsed time for predicting DMD: %e second" % dmd_prediction_timer.duration) + + del result_u + + if (myid == 0): + print("Elapsed time for training DMD: %e second" % dmd_training_timer.duration) + + if (myid == 0): + print("Elapsed time for solving FOM: %e second" % fom_timer.duration) + + # 16. Free the used memory. + del ode_solver + del pmesh + if (offline): + del dmd_u + +#ifdef MFEM_USE_GSLIB + del pws + del pwsnap_CAROM +#endif + + MPI.Finalize() \ No newline at end of file