From 9c4d0d5b7c3b7e2bb6fa8e5e5f929976373b8a0c Mon Sep 17 00:00:00 2001 From: jacobdenobel Date: Sat, 4 Nov 2023 14:51:38 +0100 Subject: [PATCH] added threaded that dont work --- CMakeLists.txt | 11 +++- VERSION | 2 +- include/ioh/logger/triggers.hpp | 31 +++++++++++ include/ioh/problem/single.hpp | 81 +++++++++++++++++++++++------ ioh/src/logger.cpp | 78 +++++++++++++++------------ ioh/src/problem.cpp | 4 +- tests/cpp/problem/test_threaded.cpp | 17 ++++++ 7 files changed, 170 insertions(+), 54 deletions(-) create mode 100644 tests/cpp/problem/test_threaded.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6247290f9..e03940d87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ project(ioh ) add_compile_definitions(PROJECT_VER="${CMAKE_PROJECT_VERSION}") -add_compile_definitions(HAS_JSON) +add_compile_definitions(HAS_JSON) set(CMAKE_CXX_STANDARD 17) set(EXTERNAL_DIR "${PROJECT_SOURCE_DIR}/external") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) @@ -33,7 +33,14 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") link_libraries(stdc++fs) add_compile_definitions(FSEXPERIMENTAL) endif() -endif() +endif() + +# find_package(OpenMP) +# if (OPENMP_FOUND) +# set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") +# set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +# set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +# endif() diff --git a/VERSION b/VERSION index 0b9c01996..e4737652c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.12 +0.3.13 diff --git a/include/ioh/logger/triggers.hpp b/include/ioh/logger/triggers.hpp index 96f96702d..7bb2f202c 100644 --- a/include/ioh/logger/triggers.hpp +++ b/include/ioh/logger/triggers.hpp @@ -277,6 +277,37 @@ namespace ioh */ inline OnImprovement on_improvement; // Uncomment if one want a library. + + struct OnDeltaImprovement: logger::Trigger { + double delta; + double best_so_far; + + OnDeltaImprovement(const double delta = 1e-10): delta(delta) { + reset(); + } + + OnDeltaImprovement(const double delta, const double best_so_far): delta(delta), best_so_far(best_so_far) { + } + + /** @returns true if a log event is to be triggered given the passed state. */ + virtual bool operator()(const logger::Info &log_info, const problem::MetaData &pb_info){ + if (best_so_far == std::numeric_limits::signaling_NaN()){ + best_so_far = log_info.y; + return true; + } + + if (pb_info.optimization_type(best_so_far, log_info.y) && std::abs(best_so_far - log_info.y) > delta) { + best_so_far = log_info.y; + return true; + } + return false; + }; + + virtual void reset() { + best_so_far = std::numeric_limits::signaling_NaN(); + } + }; + //! Trigger when there is constraint violation struct OnViolation: logger::Trigger { //! Track the number of violations diff --git a/include/ioh/problem/single.hpp b/include/ioh/problem/single.hpp index ef6bdfd46..6419aa2f4 100644 --- a/include/ioh/problem/single.hpp +++ b/include/ioh/problem/single.hpp @@ -47,29 +47,27 @@ namespace ioh::problem { } - //! Main call interface - virtual double operator()(const std::vector &x) override + void evaluate_for_state(const std::vector &x, State &state) { - if (!this->check_input(x)) - return std::numeric_limits::signaling_NaN(); - - this->state_.current.x = x; + state.current.x = x; if (this->constraintset_.hard_violation(x)) { - this->state_.current_internal.x = x; - this->state_.current_internal.y = + state.current_internal.x = x; + state.current_internal.y = this->constraintset_.penalize(this->meta_data_.optimization_type.initial_value()); - this->state_.y_unconstrained = this->state_.current_internal.y; - this->state_.current.y = this->state_.current_internal.y; + state.y_unconstrained = state.current_internal.y; + state.current.y = state.current_internal.y; } else { - this->state_.current_internal.x = this->transform_variables(x); - this->state_.current_internal.y = this->evaluate(this->state_.current_internal.x); - this->state_.y_unconstrained = this->transform_objectives(this->state_.current_internal.y); - this->state_.current.y = this->constraintset_.penalize(this->state_.y_unconstrained); + state.current_internal.x = this->transform_variables(x); + state.current_internal.y = this->evaluate(state.current_internal.x); + state.y_unconstrained = this->transform_objectives(state.current_internal.y); + state.current.y = this->constraintset_.penalize(state.y_unconstrained); } + } + void update_state_and_log() { this->state_.update(this->meta_data_, this->optimum_); if (this->logger_ != nullptr) @@ -77,19 +75,70 @@ namespace ioh::problem this->log_info_.update(this->state_, this->constraintset_); this->logger_->log(this->log_info()); } + } + + + //! Main call interface + virtual double operator()(const std::vector &x) override + { + if (!this->check_input(x)) + return std::numeric_limits::signaling_NaN(); + + evaluate_for_state(x, this->state_); + update_state_and_log(); return this->state_.current.y; } + +#if defined(_OPENMP) + virtual std::vector operator()(const std::vector> &X) override + { + + const size_t n = X.size(); + std::vector checked(n, 0); + std::vector> states(n, this->state_); + + //------------------------------------ // + //-----------[threaded code]---------- // + //------------------------------------ // + + #pragma omp parallel for + for (size_t i = 0; i < n; i++) + { + if (this->check_input(X[i])) { + evaluate_for_state(X[i], states[i]); + checked[i] = 1; + } + } + //------------------------------------ // + + std::vector y(n); + for (size_t i = 0; i < n; i++) + { + if(checked[i]) { + this->state_.current.x = states[i].current.x; + this->state_.current_internal.x = states[i].current_internal.x; + this->state_.current_internal.y = states[i].current_internal.y; + this->state_.y_unconstrained = states[i].y_unconstrained; + this->state_.current.y = states[i].current.y; + update_state_and_log(); + y[i] = states[i].current.y; + } else{ + y[i] = std::numeric_limits::signaling_NaN(); + } + } + return y; + } +#else virtual std::vector operator()(const std::vector> &X) override { std::vector y(X.size()); for (size_t i = 0; i < y.size(); i++) - { y[i] = (*this)(X[i]); - } return y; } +#endif }; diff --git a/ioh/src/logger.cpp b/ioh/src/logger.cpp index 8cb4b0f69..12ef303db 100644 --- a/ioh/src/logger.cpp +++ b/ioh/src/logger.cpp @@ -9,17 +9,17 @@ namespace py = pybind11; using namespace ioh; -// Trampoline +// Trampoline struct AbstractProperty : logger::Property { - AbstractProperty(const std::string& name): logger::Property(name) {} + AbstractProperty(const std::string &name) : logger::Property(name) {} std::string call_to_string(const logger::Info &log_info, const std::string &nan = "") const override { PYBIND11_OVERRIDE(std::string, logger::Property, call_to_string, log_info, nan); } - std::optional operator()(const logger::Info & info) const override + std::optional operator()(const logger::Info &info) const override { PYBIND11_OVERRIDE_PURE_NAME(std::optional, logger::Property, "__call__", operator(), info); } @@ -52,18 +52,22 @@ class PyProperty : public logger::Property } }; -// Trampoline -struct AbstractWatcher: logger::Watcher { +// Trampoline +struct AbstractWatcher : logger::Watcher +{ using logger::Watcher::Watcher; - void attach_problem(const problem::MetaData& problem) override { + void attach_problem(const problem::MetaData &problem) override + { PYBIND11_OVERRIDE(void, logger::Watcher, attach_problem, problem); } - void attach_suite(const std::string& suite_name) override { + void attach_suite(const std::string &suite_name) override + { PYBIND11_OVERRIDE_PURE(void, logger::Watcher, attach_suite, suite_name); } - void call(const logger::Info& log_info) override { + void call(const logger::Info &log_info) override + { PYBIND11_OVERRIDE_PURE_NAME(void, logger::Watcher, "__call__", call, log_info); } }; @@ -82,7 +86,7 @@ class PyWatcher : public WatcherType void watch(const py::object &container, const std::string &attribute) { - auto p = std::make_unique(container, attribute); + auto p = std::make_unique(container, attribute); watch(*p); property_ptrs_.push_back(std::move(p)); } @@ -107,9 +111,8 @@ class PyWatcher : public WatcherType }; - // Python spec. implementation -template +template class PyAnalyzer : public PyWatcher { std::unordered_map> double_ptrs_; @@ -119,14 +122,9 @@ class PyAnalyzer : public PyWatcher using AnalyzerType = PyWatcher; using AnalyzerType::AnalyzerType; - virtual void close() override - { - AnalyzerType::close(); - } + virtual void close() override { AnalyzerType::close(); } - virtual ~PyAnalyzer() { - close(); - } + virtual ~PyAnalyzer() { close(); } void add_run_attribute_python(const py::object &container, const std::string &name) { @@ -148,9 +146,7 @@ class PyAnalyzer : public PyWatcher double_ptrs_[name] = std::move(ptr); } - void set_run_attribute_python(const std::string &name, double value) { - *(double_ptrs_.at(name)) = value; - } + void set_run_attribute_python(const std::string &name, double value) { *(double_ptrs_.at(name)) = value; } void set_run_attributes_python(const std::map &attributes) { @@ -159,14 +155,15 @@ class PyAnalyzer : public PyWatcher } - virtual void handle_last_eval() override { - for (auto& ptr : prop_ptrs_) + virtual void handle_last_eval() override + { + for (auto &ptr : prop_ptrs_) set_run_attribute_python(ptr->name(), (*ptr)(logger::Info{}).value()); AnalyzerType::handle_last_eval(); } }; -template +template void define_analyzer(py::module &m) { using namespace logger; @@ -202,17 +199,17 @@ void define_analyzer(py::module &m) .def("add_experiment_attribute", &PyAnalyzer::add_experiment_attribute) .def("set_experiment_attributes", &PyAnalyzer::set_experiment_attributes) - .def("add_run_attribute", - py::overload_cast(&PyAnalyzer::add_run_attribute_python)) + .def("add_run_attribute", py::overload_cast(&PyAnalyzer::add_run_attribute_python)) .def("add_run_attribute", py::overload_cast(&PyAnalyzer::add_run_attribute_python)) .def("add_run_attributes", py::overload_cast &>( &PyAnalyzer::add_run_attributes_python)) - + .def("set_run_attributes", &PyAnalyzer::set_run_attributes_python) // takes a map - .def("set_run_attribute", &PyAnalyzer::set_run_attribute_python) // takes str, double> - .def_property_readonly("output_directory", [](const PyAnalyzer& self) {return self.output_directory().generic_string();}) + .def("set_run_attribute", &PyAnalyzer::set_run_attribute_python) // takes str, double> + .def_property_readonly("output_directory", + [](const PyAnalyzer &self) { return self.output_directory().generic_string(); }) .def("close", &PyAnalyzer::close) .def("watch", py::overload_cast(&PyAnalyzer::watch)) .def("watch", py::overload_cast(&PyAnalyzer::watch)) @@ -247,6 +244,18 @@ void define_triggers(py::module &m) ; t.attr("ON_IMPROVEMENT") = py::cast(trigger::on_improvement); + py::class_>( + t, "OnDeltaImprovement", + "Trigger that evaluates to true when improvement of the objective function is observed of at least greater " + "than delta") + .def(py::init(), py::arg("delta") = 1e-10) + .def(py::pickle([](const trigger::OnDeltaImprovement &t) { return py::make_tuple(t.delta, t.best_so_far); }, + [](py::tuple t) { + return trigger::OnDeltaImprovement{t[0].cast(), t[1].cast()}; + })); + + ; + py::class_>( t, "OnViolation", "Trigger that evaluates to true when there is a contraint violation") .def(py::init<>()) @@ -307,8 +316,9 @@ void define_triggers(py::module &m) [](py::tuple t) { return trigger::During{t[0].cast>>()}; })); } -template -void define_property(py::module &m, std::string name, P predef){ +template +void define_property(py::module &m, std::string name, P predef) +{ py::class_>(m, name.c_str(), py::buffer_protocol()) .def(py::init(), py::arg("name"), py::arg("format"), @@ -317,7 +327,7 @@ void define_property(py::module &m, std::string name, P predef){ [](py::tuple t) { return P{t[0].cast(), t[1].cast()}; })); - + std::transform(name.begin(), name.end(), name.begin(), ::toupper); m.attr(name.c_str()) = py::cast(predef); } @@ -378,8 +388,8 @@ void define_bases(py::module &m) .def_property_readonly("problem", &Logger::problem, "Reference to the currently attached problem"); using namespace logger; - py::class_>(m, "AbstractLogger", - "Base class for loggers which track properties") + py::class_>( + m, "AbstractLogger", "Base class for loggers which track properties") .def(py::init(), py::arg("triggers") = Triggers{}, py::arg("properties") = Properties{}) .def("watch", &Watcher::watch); } diff --git a/ioh/src/problem.cpp b/ioh/src/problem.cpp index 769e081b3..72f13108d 100644 --- a/ioh/src/problem.cpp +++ b/ioh/src/problem.cpp @@ -461,7 +461,9 @@ void define_wrapper_functions(py::module &m, const std::string &class_name, cons std::optional ub, std::optional tx, std::optional ty, std::optional co, Constraints cs) { register_python_fn(f); - auto of = [f](const std::vector &x) { return PyFloat_AsDouble(f(py::array(x.size(), x.data())).ptr()); }; + auto of = [f](const std::vector &x) { + return PyFloat_AsDouble(f(py::array(x.size(), x.data())).ptr()); + }; auto ptx = [tx](std::vector x, const int iid) { if (tx) diff --git a/tests/cpp/problem/test_threaded.cpp b/tests/cpp/problem/test_threaded.cpp new file mode 100644 index 000000000..e21c4676d --- /dev/null +++ b/tests/cpp/problem/test_threaded.cpp @@ -0,0 +1,17 @@ +#include "../utils.hpp" + +#include "ioh/problem/bbob.hpp" + + +TEST_F(BaseTest, TestThreaded) +{ + using namespace ioh::problem; + using namespace ioh::problem::bbob; + + Sphere problem(1, 2); + + std::vector> x0 = {{1, 2}, {2, 1}}; + problem(x0); + EXPECT_EQ(problem.state().evaluations, 2); + EXPECT_NEAR(problem.state().current_best.y, 87.1845, 1e-3); +} \ No newline at end of file