diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a1dd9f..b8a0a5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,9 @@ pybind11_add_module(_pylibROM bindings/pylibROM/algo/pyParametricDMD.cpp bindings/pylibROM/algo/pyNonuniformDMD.cpp bindings/pylibROM/algo/pyAdaptiveDMD.cpp + bindings/pylibROM/algo/greedy/pyGreedySampler.cpp + bindings/pylibROM/algo/greedy/pyGreedyCustomSampler.cpp + bindings/pylibROM/algo/greedy/pyGreedyRandomSampler.cpp bindings/pylibROM/algo/manifold_interp/pyInterpolator.cpp bindings/pylibROM/algo/manifold_interp/pyMatrixInterpolator.cpp bindings/pylibROM/algo/manifold_interp/pyVectorInterpolator.cpp diff --git a/bindings/pylibROM/algo/greedy/__init__.py b/bindings/pylibROM/algo/greedy/__init__.py new file mode 100644 index 0000000..79d4a69 --- /dev/null +++ b/bindings/pylibROM/algo/greedy/__init__.py @@ -0,0 +1,6 @@ +# 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. + +# For other c++ binding modules, change the module name accordingly. +from _pylibROM.algo.greedy import * \ No newline at end of file diff --git a/bindings/pylibROM/algo/greedy/pyGreedyCustomSampler.cpp b/bindings/pylibROM/algo/greedy/pyGreedyCustomSampler.cpp new file mode 100644 index 0000000..0717f92 --- /dev/null +++ b/bindings/pylibROM/algo/greedy/pyGreedyCustomSampler.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include "algo/greedy/GreedyCustomSampler.h" +#include "linalg/Vector.h" + +namespace py = pybind11; +using namespace CAROM; +using namespace std; + +void init_GreedyCustomSampler(pybind11::module_ &m) { + py::class_(m, "GreedyCustomSampler") + .def(py::init, bool, double, double, double, int, int, std::string, std::string, bool, int, bool>(), + py::arg("parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false) + .def(py::init, bool, double, double, double, int, int, std::string, std::string, bool, int, bool>(), + py::arg("parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false) + .def(py::init(), + py::arg("base_file_name"), + py::arg("output_log_path") = "") + .def("__del__", [](GreedyCustomSampler& self){ self.~GreedyCustomSampler(); }); +} diff --git a/bindings/pylibROM/algo/greedy/pyGreedyRandomSampler.cpp b/bindings/pylibROM/algo/greedy/pyGreedyRandomSampler.cpp new file mode 100644 index 0000000..751f61c --- /dev/null +++ b/bindings/pylibROM/algo/greedy/pyGreedyRandomSampler.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include "algo/greedy/GreedyRandomSampler.h" +#include "linalg/Vector.h" + +namespace py = pybind11; +using namespace CAROM; +using namespace std; + +void init_GreedyRandomSampler(py::module &m) { + py::class_(m, "GreedyRandomSampler") + .def(py::init(), + py::arg("param_space_min"), + py::arg("param_space_max"), + py::arg("num_parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("use_latin_hypercube"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false + ) + .def(py::init(), + py::arg("param_space_min"), + py::arg("param_space_max"), + py::arg("num_parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("use_latin_hypercube"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false + ) + .def(py::init(), + py::arg("base_file_name"), + py::arg("output_log_path") = "" + ) + .def("save", &GreedyRandomSampler::save, py::arg("base_file_name")) + .def("__del__", [](GreedyRandomSampler& self){ self.~GreedyRandomSampler(); }); +} diff --git a/bindings/pylibROM/algo/greedy/pyGreedySampler.cpp b/bindings/pylibROM/algo/greedy/pyGreedySampler.cpp new file mode 100644 index 0000000..5112765 --- /dev/null +++ b/bindings/pylibROM/algo/greedy/pyGreedySampler.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include "algo/greedy/GreedySampler.h" +#include "linalg/Vector.h" + +namespace py = pybind11; +using namespace CAROM; +using namespace std; + + +class PyGreedySampler : public GreedySampler { +public: + using GreedySampler::GreedySampler; + + void save(std::string base_file_name) override { + PYBIND11_OVERRIDE(void,GreedySampler,save,base_file_name ); + } +protected: + void constructParameterPoints() override { + PYBIND11_OVERRIDE_PURE(void, GreedySampler, constructParameterPoints,); + } + void getNextParameterPointAfterConvergenceFailure() override { + PYBIND11_OVERRIDE_PURE(void, GreedySampler, getNextParameterPointAfterConvergenceFailure,); + } +}; + +void init_GreedySampler(pybind11::module_ &m) { + py::class_(m, "GreedyErrorIndicatorPoint") + .def_property_readonly("point", [](GreedyErrorIndicatorPoint &self) { + return self.point.get(); + }) + .def_property_readonly("localROM", [](GreedyErrorIndicatorPoint &self) { + return self.localROM.get(); + }); + + py::class_(m, "GreedySampler") + .def(py::init, bool, double, double, double, int, int, std::string, std::string, bool, int, bool>(), + py::arg("parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false) + .def(py::init,bool, double, double, double, int, int, std::string, std::string, bool, int, bool>(), + py::arg("parameter_points"), + py::arg("check_local_rom"), + py::arg("relative_error_tolerance"), + py::arg("alpha"), + py::arg("max_clamp"), + py::arg("subset_size"), + py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", + py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, + py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false) + .def(py::init(), + py::arg("param_space_min"), py::arg("param_space_max"), py::arg("num_parameter_points"), + py::arg("check_local_rom"), py::arg("relative_error_tolerance"), py::arg("alpha"), + py::arg("max_clamp"), py::arg("subset_size"), py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false + ) + .def(py::init(), + py::arg("param_space_min"), py::arg("param_space_max"), py::arg("num_parameter_points"), + py::arg("check_local_rom"), py::arg("relative_error_tolerance"), py::arg("alpha"), + py::arg("max_clamp"), py::arg("subset_size"), py::arg("convergence_subset_size"), + py::arg("output_log_path") = "", py::arg("warm_start_file_name") = "", + py::arg("use_centroid") = true, py::arg("random_seed") = 1, + py::arg("debug_algorithm") = false + ) + .def(py::init(), py::arg("base_file_name"), py::arg("output_log_path") = "") + .def("getNextParameterPoint", [](GreedySampler& self) -> std::unique_ptr { + std::shared_ptr result = self.getNextParameterPoint(); + return std::make_unique(*(result.get())); + }) + .def("getNextPointRequiringRelativeError", [](GreedySampler& self) -> GreedyErrorIndicatorPoint { + // Create a deepcopy of the struct, otherwise it will get freed twice + GreedyErrorIndicatorPoint point = self.getNextPointRequiringRelativeError(); + Vector *t_pt = nullptr; + Vector *t_lROM = nullptr; + + if (point.point) + { + t_pt = new Vector(*(point.point)); + } + + if (point.localROM) + { + t_lROM = new Vector(*(point.localROM)); + } + + return createGreedyErrorIndicatorPoint(t_pt, t_lROM); + }, py::return_value_policy::reference) + .def("getNextPointRequiringErrorIndicator", [](GreedySampler& self) -> GreedyErrorIndicatorPoint { + // Create a deepcopy of the struct, otherwise it will get freed twice + GreedyErrorIndicatorPoint point = self.getNextPointRequiringErrorIndicator(); + + Vector *t_pt = nullptr; + Vector *t_lROM = nullptr; + + if (point.point) + { + t_pt = new Vector(*(point.point)); + } + + if (point.localROM) + { + t_lROM = new Vector(*(point.localROM)); + } + + return createGreedyErrorIndicatorPoint(t_pt, t_lROM); + }, py::return_value_policy::reference) + .def("setPointRelativeError", (void (GreedySampler::*) (double))&GreedySampler::setPointRelativeError) + .def("setPointErrorIndicator", (void (GreedySampler::*) (double,int)) &GreedySampler::setPointErrorIndicator) + .def("getNearestNonSampledPoint", (int (GreedySampler::*) (CAROM::Vector)) &GreedySampler::getNearestNonSampledPoint) + .def("getNearestROM", [](GreedySampler& self, Vector point) -> std::unique_ptr { + std::shared_ptr result = self.getNearestROM(point); + if (!result) + { + return nullptr; + } + return std::make_unique(*(result.get())); + }) + .def("getParameterPointDomain", &GreedySampler::getParameterPointDomain) + .def("getSampledParameterPoints", &GreedySampler::getSampledParameterPoints) + .def("save", &GreedySampler::save) + .def("__del__", [](GreedySampler& self){ self.~GreedySampler(); }) + .def("isComplete", &GreedySampler::isComplete); + + m.def("createGreedyErrorIndicatorPoint", [](Vector* point, Vector* localROM) { + return createGreedyErrorIndicatorPoint(point, localROM); + }); + m.def("createGreedyErrorIndicatorPoint", [](Vector* point, std::shared_ptr& localROM) { + return createGreedyErrorIndicatorPoint(point, localROM); + }); + m.def("getNearestPoint", [](std::vector& paramPoints,Vector point) { + return getNearestPoint(paramPoints, point); + }); + m.def("getNearestPoint", [](std::vector& paramPoints, double point) { + return getNearestPoint(paramPoints, point); + }); + m.def("getNearestPointIndex", [](std::vector paramPoints, Vector point) { + return getNearestPointIndex(paramPoints, point); + }); + m.def("getNearestPointIndex", [](std::vector paramPoints, double point) { + return getNearestPointIndex(paramPoints, point); + }); +} diff --git a/bindings/pylibROM/pylibROM.cpp b/bindings/pylibROM/pylibROM.cpp index c3a5ddd..6aa90ab 100644 --- a/bindings/pylibROM/pylibROM.cpp +++ b/bindings/pylibROM/pylibROM.cpp @@ -28,6 +28,11 @@ void init_Interpolator(pybind11::module_ &); void init_VectorInterpolator(pybind11::module_ &); void init_MatrixInterpolator(pybind11::module_ &); +//algo/greedy +void init_GreedySampler(pybind11::module_ &m); +void init_GreedyCustomSampler(pybind11::module_ &m); +void init_GreedyRandomSampler(pybind11::module_ &m); + //hyperreduction void init_DEIM(pybind11::module_ &m); void init_GNAT(pybind11::module_ &m); @@ -79,6 +84,11 @@ PYBIND11_MODULE(_pylibROM, m) { init_VectorInterpolator(manifold_interp); init_MatrixInterpolator(manifold_interp); + py::module greedy = algo.def_submodule("greedy"); + init_GreedySampler(greedy); + init_GreedyCustomSampler(greedy); + init_GreedyRandomSampler(greedy); + py::module hyperreduction = m.def_submodule("hyperreduction"); init_DEIM(hyperreduction); init_GNAT(hyperreduction); diff --git a/tests/test_pyGreedyCustomSampler.py b/tests/test_pyGreedyCustomSampler.py new file mode 100644 index 0000000..e8548d5 --- /dev/null +++ b/tests/test_pyGreedyCustomSampler.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +import pytest +import numpy as np +import sys +try: + # import pip-installed package + import pylibROM.linalg as linalg + import pylibROM.algo.greedy as greedy +except ModuleNotFoundError: + # If pip-installed package is not found, import cmake-built package + sys.path.append("../build") + import _pylibROM.linalg as linalg + import _pylibROM.algo.greedy as greedy + +from mpi4py import MPI + + +def test_greedy_custom_sampler_centroid(): + paramPoints = [1.0, 3.0, 6.0] + caromGreedySampler = greedy.GreedyCustomSampler(paramPoints, False, 0.1, 1, 1, 2, 3, "", "", True, 1, True) + nextPointToSample = caromGreedySampler.getNextParameterPoint() + + assert nextPointToSample.dim() == 1 + assert nextPointToSample.item(0) == 3.0 + + caromGreedySampler.getNextPointRequiringRelativeError() + caromGreedySampler.setPointRelativeError(100.0) + + localPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + assert localPoint.point is not None + assert localPoint.point.dim() == 1 + assert localPoint.point.item(0) == 3.0 + assert localPoint.localROM is not None + + caromGreedySampler.setPointErrorIndicator(1.0, 1) + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + assert firstPoint.point is not None + assert firstPoint.point.dim() == 1 + assert firstPoint.point.item(0) == 1.0 + + caromGreedySampler.setPointErrorIndicator(100.0, 1) + secondPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + assert secondPoint.point is not None + assert secondPoint.point.dim() == 1 + assert secondPoint.point.item(0) == 6.0 + + caromGreedySampler.setPointErrorIndicator(50.0, 1) + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 1 + assert nextPointToSample.item(0) == firstPoint.point.item(0) + + caromGreedySampler.getNextPointRequiringRelativeError() + caromGreedySampler.setPointRelativeError(100.0) + + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 1 + assert nextPointToSample.item(0) == paramPoints[2] + + caromGreedySampler.getNextPointRequiringRelativeError() + caromGreedySampler.setPointRelativeError(100.0) + + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + + +def test_greedy_custom_sampler_multi_dim_centroid(): + item0 = linalg.Vector(2, False) + item0[0] = 1.0 + item0[1] = 11.0 + + item1 = linalg.Vector(2, False) + item1[0] = 3.0 + item1[1] = 13.0 + + item2 = linalg.Vector(2, False) + item2[0] = 6.0 + item2[1] = 16.0 + + paramPoints = [item0, item1, item2] + + caromGreedySampler = greedy.GreedyCustomSampler(paramPoints, False, 0.1, 1, 1, 2, 3, "", "", True, 1, True) + + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 2 + assert nextPointToSample.item(0) == 3.0 + assert nextPointToSample.item(1) == 13.0 + + caromGreedySampler.getNextPointRequiringRelativeError() + caromGreedySampler.setPointRelativeError(100.0) + + localPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + + assert localPoint.point is not None + assert localPoint.point.dim() == 2 + assert localPoint.point.item(0) == 3.0 + assert localPoint.point.item(1) == 13.0 + assert localPoint.localROM is not None + + caromGreedySampler.setPointErrorIndicator(1.0, 1) + + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + assert firstPoint.point is not None + assert firstPoint.point.dim() == 2 + assert firstPoint.point.item(0) == 1.0 + assert firstPoint.point.item(1) == 11.0 + + caromGreedySampler.setPointErrorIndicator(100.0, 1) + secondPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + assert secondPoint.point is not None + assert secondPoint.point.dim() == 2 + assert secondPoint.point.item(0) == 6.0 + assert secondPoint.point.item(1) == 16.0 + + caromGreedySampler.setPointErrorIndicator(50.0, 1) + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 2 + assert nextPointToSample.item(0) == firstPoint.point.item(0) + assert nextPointToSample.item(1) == firstPoint.point.item(1) + + caromGreedySampler.getNextPointRequiringRelativeError() + caromGreedySampler.setPointRelativeError(100.0) + + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 2 + assert nextPointToSample.item(0) == item2.item(0) + assert nextPointToSample.item(1) == item2.item(1) + + tmp = caromGreedySampler.getNextPointRequiringRelativeError() + assert tmp.point is None + assert tmp.localROM is None + + caromGreedySampler.setPointRelativeError(100.0) + firstPoint = caromGreedySampler.getNextPointRequiringErrorIndicator() + + +def test_greedy_save_and_load(): + paramPoints = [1.0, 2.0, 3.0, 99.0, 100., 101.0] + + caromGreedySampler = greedy.GreedyCustomSampler(paramPoints, False, 0.1, 1, 1, 3, 4, "", "", False, 1, True) + caromGreedySampler.save("greedy_test") + + caromGreedySamplerLoad = greedy.GreedyCustomSampler("greedy_test") + caromGreedySamplerLoad.save("greedy_test_LOAD") + + pointToFindNearestROM = linalg.Vector(1, False) + pointToFindNearestROM[0] = 1.0 + + closestROM = caromGreedySampler.getNearestROM(pointToFindNearestROM) + closestROMLoad = caromGreedySamplerLoad.getNearestROM(pointToFindNearestROM) + + # there were no points sampled, so closestROM should be None + assert closestROM is None + assert closestROM == closestROMLoad + + nextPointToSample = caromGreedySampler.getNextParameterPoint() + nextPointToSampleLoad = caromGreedySamplerLoad.getNextParameterPoint() + + assert nextPointToSample.dim() == nextPointToSampleLoad.dim() + assert nextPointToSample.item(0) == nextPointToSampleLoad.item(0) + + +def test_greedy_save_and_load_with_sample(): + paramPoints = [1.0, 2.0, 3.0, 99.0, 100., 101.0] + + caromGreedySampler = greedy.GreedyCustomSampler(paramPoints, False, 0.1, 1, 1, 3, 4, "", "", False, 1, True) + + nextPointToSample = caromGreedySampler.getNextParameterPoint() + assert nextPointToSample.dim() == 1 + assert nextPointToSample.item(0) == 3.0 + + # save after sampling a point to test if sampled points are restored + caromGreedySampler.save("greedy_test") + + caromGreedySamplerLoad = greedy.GreedyCustomSampler("greedy_test") + caromGreedySamplerLoad.save("greedy_test_LOAD") + + pointToFindNearestROM = linalg.Vector(1, False) + pointToFindNearestROM[0] = 1.0 + + closestROM = caromGreedySampler.getNearestROM(pointToFindNearestROM) + closestROMLoad = caromGreedySamplerLoad.getNearestROM(pointToFindNearestROM) + + assert closestROM is not None + assert closestROM.dim() == 1 + assert closestROM.dim() == closestROMLoad.dim() + assert closestROM.item(0) == 3.0 + assert closestROM.item(0) == closestROMLoad.item(0) + + nextPointToSample = caromGreedySampler.getNextParameterPoint() + nextPointToSampleLoad = caromGreedySamplerLoad.getNextParameterPoint() + + assert nextPointToSample.dim() == nextPointToSampleLoad.dim() + assert nextPointToSample.item(0) == nextPointToSampleLoad.item(0) + +if __name__ == '__main__': + pytest.main()