From aa6efe421e08c2d4740aba71631bab58c09041d7 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 12:27:55 +0200 Subject: [PATCH 01/19] Add Python bindings and example script --- CMakeLists.txt | 21 ++++++++++++++ python/CMakeLists.txt | 11 ++++++++ python/bindings.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++ python/example.py | 42 ++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 python/CMakeLists.txt create mode 100644 python/bindings.cpp create mode 100644 python/example.py diff --git a/CMakeLists.txt b/CMakeLists.txt index bc16fe5..5b032f1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,9 @@ project(libgp DESCRIPTION "Gaussian Process Regression Library" LANGUAGES CXX) +# Option for Python bindings +option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) + # Validate version validate_version(VERSION ${PROJECT_VERSION}) @@ -160,3 +163,21 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgp.pc if(BUILD_EXAMPLES) add_subdirectory(examples) endif() + +# Add Python bindings if enabled +if(BUILD_PYTHON_BINDINGS) + # Find Python + find_package(Python COMPONENTS Interpreter Development REQUIRED) + + # Include pybind11 + include(FetchContent) + FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + GIT_TAG v2.11.1 + ) + FetchContent_MakeAvailable(pybind11) + + # Add bindings directory + add_subdirectory(python) +endif() diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 0000000..e0cfc4c --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,11 @@ +pybind11_add_module(pygp MODULE bindings.cpp) +target_link_libraries(pygp PRIVATE libgp::gp) + +# Add include directories +target_include_directories(pygp PRIVATE ${CMAKE_SOURCE_DIR}/include) + +# Install the Python module +install(TARGETS pygp + LIBRARY DESTINATION ${Python_SITEARCH} + RUNTIME DESTINATION ${Python_SITEARCH} +) \ No newline at end of file diff --git a/python/bindings.cpp b/python/bindings.cpp new file mode 100644 index 0000000..fbfc20f --- /dev/null +++ b/python/bindings.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include "gp.h" +#include "cov_factory.h" + +namespace py = pybind11; + +PYBIND11_MODULE(pygp, m) { + m.doc() = "Python bindings for libgp - Gaussian Process Regression Library"; + + py::class_(m, "GaussianProcess") + .def(py::init<>()) + .def(py::init()) + .def(py::init()) + .def("add_pattern", [](libgp::GaussianProcess& self, py::array_t x, double y) { + py::buffer_info buf = x.request(); + if (buf.ndim != 1) + throw std::runtime_error("Input array must be 1-dimensional"); + double* ptr = static_cast(buf.ptr); + self.add_pattern(ptr, y); + }) + .def("predict", [](libgp::GaussianProcess& self, py::array_t x) { + py::buffer_info buf = x.request(); + if (buf.ndim != 1) + throw std::runtime_error("Input array must be 1-dimensional"); + double* ptr = static_cast(buf.ptr); + return self.f(ptr); + }) + .def("get_variance", [](libgp::GaussianProcess& self, py::array_t x) { + py::buffer_info buf = x.request(); + if (buf.ndim != 1) + throw std::runtime_error("Input array must be 1-dimensional"); + double* ptr = static_cast(buf.ptr); + return self.var(ptr); + }) + .def("set_y", &libgp::GaussianProcess::set_y) + .def("get_sampleset_size", &libgp::GaussianProcess::get_sampleset_size) + .def("clear_sampleset", &libgp::GaussianProcess::clear_sampleset) + .def("get_log_likelihood", &libgp::GaussianProcess::log_likelihood) + .def("get_log_likelihood_gradient", &libgp::GaussianProcess::log_likelihood_gradient) + .def("get_input_dim", &libgp::GaussianProcess::get_input_dim) + .def("set_loghyper", [](libgp::GaussianProcess& self, py::array_t params) { + py::buffer_info buf = params.request(); + if (buf.ndim != 1) + throw std::runtime_error("Parameter array must be 1-dimensional"); + Eigen::Map eigen_params(static_cast(buf.ptr), buf.shape[0]); + self.covf().set_loghyper(eigen_params); + }) + .def("get_loghyper", [](libgp::GaussianProcess& self) { + return py::array_t( + {static_cast(self.covf().get_param_dim())}, + {sizeof(double)}, + self.covf().get_loghyper().data() + ); + }) + .def("get_param_dim", [](libgp::GaussianProcess& self) { + return self.covf().get_param_dim(); + }); + + py::class_(m, "CovFactory") + .def(py::init<>()) + .def("create", &libgp::CovFactory::create) + .def("list", &libgp::CovFactory::list); +} \ No newline at end of file diff --git a/python/example.py b/python/example.py new file mode 100644 index 0000000..1d18735 --- /dev/null +++ b/python/example.py @@ -0,0 +1,42 @@ +import math + +from pygp import GaussianProcess + + +def main(): + # Create a 2D Gaussian Process with SE kernel + input_dim = 2 + gp = GaussianProcess(input_dim, "CovSum(CovSEiso, CovNoise)") + + # Set hyperparameters (log-values for length-scale, signal variance, and noise) + params = [0.0, 0.0, -2.0] # [log(l), log(sf), log(sigma_n)] + gp.set_loghyper(params) + + # Generate some sample data + x = [ + [0.0, 0.0], + [0.0, 1.0], + [1.0, 0.0], + [1.0, 1.0] + ] + y = [0.0, 0.5, 0.5, 1.0] + + # Add training data + for xi, yi in zip(x, y): + gp.add_pattern(xi, yi) + + # Make predictions + test_point = [0.5, 0.5] + mean = gp.predict(test_point) + var = gp.get_variance(test_point) + + print(f"Number of training points: {gp.get_sampleset_size()}") + print(f"Prediction at {test_point}:") + print(f"Mean: {mean:.3f}") + print(f"Variance: {var:.3f}") + print(f"95% Confidence Interval: [{mean-2*math.sqrt(var):.3f}, {mean+2*math.sqrt(var):.3f}]") + print(f"Log likelihood: {gp.get_log_likelihood():.3f}") + + +if __name__ == "__main__": + main() From 3a33831794fe4f2ffe5f86cc107b272bba1a75ae Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 12:29:24 +0200 Subject: [PATCH 02/19] fix: restrict CI workflow branches to master only --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a36493..62dabcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,10 @@ name: CI on: push: - branches: [ master, dev ] + branches: [ master ] tags: [ 'v*' ] pull_request: - branches: [ master, dev ] + branches: [ master ] permissions: contents: write From 2d55fe40e3caf4cf27ca04b20a78288a69403187 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 16:51:18 +0200 Subject: [PATCH 03/19] refactor: reorganize CMake configuration --- CMakeLists.txt | 232 ++++++++---------- cmake/CommonSettings.cmake | 26 -- cmake/VersionValidation.cmake | 19 -- cmake/libgp-config-version.cmake.in | 10 - cmake/libgp-config.cmake.in | 21 -- examples/CMakeLists.txt | 26 -- python/CMakeLists.txt | 11 - tests/CMakeLists.txt | 57 ----- tests/Sources.cmake | 9 - tests/gp_sparse_regression_test.cc | 45 ---- ...gression_test.cc => test_gp_regression.cc} | 0 ...elihood_test.cc => test_log_likelihood.cc} | 0 12 files changed, 104 insertions(+), 352 deletions(-) mode change 100755 => 100644 CMakeLists.txt delete mode 100644 cmake/CommonSettings.cmake delete mode 100644 cmake/VersionValidation.cmake delete mode 100644 cmake/libgp-config-version.cmake.in delete mode 100644 cmake/libgp-config.cmake.in delete mode 100755 examples/CMakeLists.txt delete mode 100644 python/CMakeLists.txt delete mode 100755 tests/CMakeLists.txt delete mode 100755 tests/Sources.cmake delete mode 100644 tests/gp_sparse_regression_test.cc rename tests/{gp_regression_test.cc => test_gp_regression.cc} (100%) rename tests/{log_likelihood_test.cc => test_log_likelihood.cc} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100755 new mode 100644 index 5b032f1..54db06d --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,130 +1,107 @@ -# Created by Manuel Blum on 2011-05-25. -# Copyright 2013 University of Freiburg. - -cmake_minimum_required(VERSION 3.14) - -# Add cmake modules directory -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -# Include version validation -include(VersionValidation) +cmake_minimum_required(VERSION 3.14...3.22) project(libgp - VERSION 0.1.5 - DESCRIPTION "Gaussian Process Regression Library" + VERSION "0.1.5" + DESCRIPTION "C++ Library for Gaussian Process Regression" LANGUAGES CXX) -# Option for Python bindings -option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) - -# Validate version -validate_version(VERSION ${PROJECT_VERSION}) +include(FetchContent) -# Load common settings -include(CommonSettings) +# Options +option(LIBGP_BUILD_ALL "Build everything (tests, Python bindings, and examples)" OFF) +option(LIBGP_BUILD_TESTS "Build libgp tests" ${LIBGP_BUILD_ALL}) +option(LIBGP_BUILD_PYTHON "Build Python bindings" ${LIBGP_BUILD_ALL}) +option(LIBGP_BUILD_EXAMPLES "Build example applications" ${LIBGP_BUILD_ALL}) +option(BUILD_SHARED_LIBS "Build shared libraries" ON) -# Check for required C++ features -include(CheckCXXCompilerFlag) -include(CheckCXXSourceCompiles) - -# Force C++17 before feature test +# Set C++ standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -# Configure version header +# Generate version header configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/include/gp_version.h.in - ${CMAKE_CURRENT_BINARY_DIR}/include/gp_version.h + "${PROJECT_SOURCE_DIR}/include/gp_version.h.in" + "${PROJECT_BINARY_DIR}/include/gp_version.h" @ONLY ) -# Feature tests -check_cxx_source_compiles(" - #include - #include - #include - int main() { - static_assert(std::is_same_v); - std::string_view sv = \"test\"; - std::optional opt; - return 0; - }" - HAVE_CPP17_FEATURES +# Core library +add_library(gp + src/gp.cc + src/gp_utils.cc + src/sampleset.cc + src/rprop.cc + src/cg.cc + src/input_dim_filter.cc + src/cov.cc + src/cov_factory.cc + src/cov_linear_ard.cc + src/cov_linear_one.cc + src/cov_matern3_iso.cc + src/cov_matern5_iso.cc + src/cov_noise.cc + src/cov_periodic.cc + src/cov_periodic_matern3_iso.cc + src/cov_prod.cc + src/cov_rq_iso.cc + src/cov_se_ard.cc + src/cov_se_iso.cc + src/cov_sum.cc ) -if(NOT HAVE_CPP17_FEATURES) - message(FATAL_ERROR "Compiler does not support required C++17 features") -endif() - -# Set visibility settings -set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) - -# Find dependencies -set(CMAKE_PREFIX_PATH "/opt/homebrew/share/eigen3/cmake" ${CMAKE_PREFIX_PATH}) -find_package(Eigen3 3.0.1 NO_MODULE REQUIRED) -find_package(Threads REQUIRED) - -# Create library target -add_library(gp) -add_library(libgp::gp ALIAS gp) - -# Include sources -include("Sources.cmake") -target_sources(gp PRIVATE ${LIBGP_SRC}) - -# Apply common settings -libgp_set_common_properties(gp) - -# Modern way to handle include directories target_include_directories(gp PUBLIC $ + $ $ - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src ) -target_link_libraries(gp - PUBLIC - Eigen3::Eigen - PRIVATE - Threads::Threads -) +# Dependencies +find_package(Eigen3 REQUIRED) -target_compile_definitions(gp - PRIVATE - libgp_EXPORTS - $<$:ENABLE_TESTING> -) -# Enable testing with CTest -if(BUILD_TESTS) +target_link_libraries(gp PUBLIC Eigen3::Eigen) + +# Tests +if(LIBGP_BUILD_TESTS) enable_testing() - include(CTest) - add_subdirectory(tests ${CMAKE_CURRENT_BINARY_DIR}/tests_build) + find_package(GTest REQUIRED) + + # Helper function to add tests + function(add_gp_test name) + add_executable(${name} tests/${name}.cc) + target_link_libraries(${name} PRIVATE gp GTest::GTest GTest::Main) + add_test(NAME ${name} COMMAND ${name}) + endfunction() + + add_gp_test(test_gp_regression) + add_gp_test(test_log_likelihood) + add_gp_test(test_cov_factory) + add_gp_test(test_covariance_functions) + add_gp_test(test_gp_utils) + add_gp_test(test_optimizer) +endif() + +# Python bindings +if(LIBGP_BUILD_PYTHON) + find_package(Python COMPONENTS Interpreter Development REQUIRED) + find_package(pybind11 REQUIRED) + + pybind11_add_module(pygp src/bindings.cpp) + target_link_libraries(pygp PRIVATE gp) +endif() + +# Examples +if(LIBGP_BUILD_EXAMPLES) + add_executable(gp_example_dense examples/gp_example_dense.cc) + target_link_libraries(gp_example_dense PRIVATE gp) endif() # Installation include(GNUInstallDirs) -include(CMakePackageConfigHelpers) set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/libgp) -# Configure version file -write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/libgpConfigVersion.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) - -# Configure config files -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/libgp-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/libgpConfig.cmake" - INSTALL_DESTINATION ${INSTALL_CONFIGDIR} -) - install(TARGETS gp EXPORT libgp-targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -134,15 +111,13 @@ install(TARGETS gp ) install(DIRECTORY include/ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h" + PATTERN "*.h.in" EXCLUDE ) -# Install CMake config files -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/libgpConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/libgpConfigVersion.cmake" - DESTINATION ${INSTALL_CONFIGDIR} +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/gp_version.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) # Export targets @@ -152,32 +127,33 @@ install(EXPORT libgp-targets DESTINATION ${INSTALL_CONFIGDIR} ) -# Generate pkg-config file -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/contrib/libgp.pc.in - ${CMAKE_CURRENT_BINARY_DIR}/libgp.pc @ONLY) +# Create and install config files +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/libgpConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgp.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/contrib/libgp.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/libgpConfig.cmake" + INSTALL_DESTINATION ${INSTALL_CONFIGDIR} +) -# Add components -if(BUILD_EXAMPLES) - add_subdirectory(examples) -endif() +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/libgpConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/libgpConfigVersion.cmake" + DESTINATION ${INSTALL_CONFIGDIR} +) -# Add Python bindings if enabled -if(BUILD_PYTHON_BINDINGS) - # Find Python - find_package(Python COMPONENTS Interpreter Development REQUIRED) - - # Include pybind11 - include(FetchContent) - FetchContent_Declare( - pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11.git - GIT_TAG v2.11.1 - ) - FetchContent_MakeAvailable(pybind11) - - # Add bindings directory - add_subdirectory(python) -endif() +# Generate and install pkg-config file +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/contrib/libgp.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/libgp.pc" + @ONLY +) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libgp.pc" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig +) \ No newline at end of file diff --git a/cmake/CommonSettings.cmake b/cmake/CommonSettings.cmake deleted file mode 100644 index 52dc2f3..0000000 --- a/cmake/CommonSettings.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# Common settings for all targets in libgp project - -# Set C++ standard -macro(libgp_set_cpp_standard target) - set_target_properties(${target} - PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED ON - CXX_EXTENSIONS OFF - ) -endmacro() - -# Set common compiler warnings -macro(libgp_set_compiler_warnings target) - target_compile_options(${target} - PRIVATE - $<$:-Wall -Wextra -Wpedantic> - $<$:/W4> - ) -endmacro() - -# Set common build settings -macro(libgp_set_common_properties target) - libgp_set_cpp_standard(${target}) - libgp_set_compiler_warnings(${target}) -endmacro() \ No newline at end of file diff --git a/cmake/VersionValidation.cmake b/cmake/VersionValidation.cmake deleted file mode 100644 index 5fc99f3..0000000 --- a/cmake/VersionValidation.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# Validates that version numbers follow semantic versioning -include(CMakeParseArguments) - -function(validate_version) - cmake_parse_arguments(PARSE_ARGV 0 ARG "" "VERSION" "") - - if(NOT ARG_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$") - message(FATAL_ERROR "Version '${ARG_VERSION}' does not follow semantic versioning (major.minor.patch)") - endif() - - string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" VERSION_MATCH "${ARG_VERSION}") - set(MAJOR "${CMAKE_MATCH_1}") - set(MINOR "${CMAKE_MATCH_2}") - set(PATCH "${CMAKE_MATCH_3}") - - if(MAJOR EQUAL 0) - message(STATUS "Package is still in initial development (version ${ARG_VERSION})") - endif() -endfunction() \ No newline at end of file diff --git a/cmake/libgp-config-version.cmake.in b/cmake/libgp-config-version.cmake.in deleted file mode 100644 index ca5947f..0000000 --- a/cmake/libgp-config-version.cmake.in +++ /dev/null @@ -1,10 +0,0 @@ -set(PACKAGE_VERSION "@PROJECT_VERSION@") - -if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) - set(PACKAGE_VERSION_COMPATIBLE FALSE) -else() - set(PACKAGE_VERSION_COMPATIBLE TRUE) - if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) - set(PACKAGE_VERSION_EXACT TRUE) - endif() -endif() \ No newline at end of file diff --git a/cmake/libgp-config.cmake.in b/cmake/libgp-config.cmake.in deleted file mode 100644 index f9052a4..0000000 --- a/cmake/libgp-config.cmake.in +++ /dev/null @@ -1,21 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# Find dependencies -find_dependency(Eigen3 3.0.1) - -# Include targets -include("${CMAKE_CURRENT_LIST_DIR}/libgpTargets.cmake") - -# Provide component support -foreach(_comp ${libgp_FIND_COMPONENTS}) - if(_comp STREQUAL "examples") - set(libgp_examples_FOUND TRUE) - else() - set(libgp_${_comp}_FOUND FALSE) - if(libgp_FIND_REQUIRED_${_comp}) - message(FATAL_ERROR "Unsupported libgp component: ${_comp}") - endif() - endif() -endforeach() \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100755 index 8eb3a6b..0000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# libgp - Gaussian process library for Machine Learning -# Copyright (c) 2013, Manuel Blum -# All rights reserved. - -# Examples configuration -add_executable(gpdense gp_example_dense.cc) -target_link_libraries(gpdense - PRIVATE - libgp::gp -) - -# Apply common settings -libgp_set_common_properties(gpdense) - -# Install examples (optional) -install(TARGETS gpdense - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}/examples - OPTIONAL -) - -# Install example source files for reference -install(FILES - gp_example_dense.cc - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/libgp/examples - OPTIONAL -) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt deleted file mode 100644 index e0cfc4c..0000000 --- a/python/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -pybind11_add_module(pygp MODULE bindings.cpp) -target_link_libraries(pygp PRIVATE libgp::gp) - -# Add include directories -target_include_directories(pygp PRIVATE ${CMAKE_SOURCE_DIR}/include) - -# Install the Python module -install(TARGETS pygp - LIBRARY DESTINATION ${Python_SITEARCH} - RUNTIME DESTINATION ${Python_SITEARCH} -) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100755 index a440084..0000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,57 +0,0 @@ -include(FetchContent) - -# Set minimum CMake version for Google Test -set(CMAKE_MINIMUM_REQUIRED_VERSION 3.14) - -# Modern GTest integration using FetchContent -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 -) - -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - -# Enable testing -include(GoogleTest) -enable_testing() - -# Include test sources -include("Sources.cmake") - -# Create test executable -add_executable(gptest ${LIBGP_TESTS}) -target_link_libraries(gptest - PRIVATE - gp - GTest::gtest - GTest::gtest_main - ${CMAKE_THREAD_LIBS_INIT} -) - -# Apply common settings -libgp_set_common_properties(gptest) - -# Add test labels based on file names -foreach(TEST_FILE ${LIBGP_TESTS}) - get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) - if(${TEST_NAME} MATCHES "^test_") - string(SUBSTRING ${TEST_NAME} 5 -1 LABEL_NAME) - else() - set(LABEL_NAME ${TEST_NAME}) - endif() - set_source_files_properties(${TEST_FILE} PROPERTIES - LABELS "${LABEL_NAME}" - ) -endforeach() - -# Configure test discovery with increased timeout -gtest_discover_tests(gptest - PROPERTIES - LABELS "unit" - TIMEOUT 30 - DISCOVERY_TIMEOUT 60 - XML_OUTPUT_DIR "${CMAKE_BINARY_DIR}/test-results" -) diff --git a/tests/Sources.cmake b/tests/Sources.cmake deleted file mode 100755 index 4b89410..0000000 --- a/tests/Sources.cmake +++ /dev/null @@ -1,9 +0,0 @@ -SET(LIBGP_TESTS - gp_regression_test.cc - #gp_sparse_regression_test.cc - log_likelihood_test.cc - test_optimizer.cc - test_covariance_functions.cc - test_gp_utils.cc - test_cov_factory.cc -) diff --git a/tests/gp_sparse_regression_test.cc b/tests/gp_sparse_regression_test.cc deleted file mode 100644 index 75ce055..0000000 --- a/tests/gp_sparse_regression_test.cc +++ /dev/null @@ -1,45 +0,0 @@ -// libgp - Gaussian process library for Machine Learning -// Copyright (c) 2011, Manuel Blum -// All rights reserved. - -#include "gp.h" -#include "gp_sparse.h" -#include "gp_utils.h" - -#include -#include -#include - - -TEST(GPSparseRegressionTest, CompareToDense) { - - libgp::GaussianProcess gp(2, "CovSum(CovSEiso, CovNoise)"); - libgp::SparseGaussianProcess gp_sparse(2, "CovSum(CovRBFCS, CovNoise)"); - - double params[] = {0.0, 0.0, -2}; - gp.covf().set_loghyper(params); - gp_sparse.covf().set_loghyper(params); - gp_sparse.covf().set_threshold(1e12); - for (int i=0; i<500; ++i) { - double x[] = {drand48()*4-2, drand48()*4-2}; - double y = libgp::Utils::hill(x[0], x[1]); - gp.add_pattern(x, y); - gp_sparse.add_pattern(x, y); - } - gp.compute(); - gp_sparse.compute(); - double error, tss = 0.0; - for (int i=0; i<500; ++i) { - double x[] = {drand48()*4-2, drand48()*4-2}; - error = gp.f(x) - gp_sparse.f(x); - ASSERT_NEAR(0.0, error, 1e-9); - } - gp_sparse.covf().set_threshold(1); - gp_sparse.compute(); - for (int i=0; i<500; ++i) { - double x[] = {drand48()*4-2, drand48()*4-2}; - error = gp.f(x) - gp_sparse.f(x); - tss += error*error; - } - ASSERT_GT(0.1, tss/500); -} diff --git a/tests/gp_regression_test.cc b/tests/test_gp_regression.cc similarity index 100% rename from tests/gp_regression_test.cc rename to tests/test_gp_regression.cc diff --git a/tests/log_likelihood_test.cc b/tests/test_log_likelihood.cc similarity index 100% rename from tests/log_likelihood_test.cc rename to tests/test_log_likelihood.cc From 57edb7fc44cf3566127d57f500eaebd69d7a4a7a Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 16:51:38 +0200 Subject: [PATCH 04/19] refactor: remove Sources.cmake file --- Sources.cmake | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100755 Sources.cmake diff --git a/Sources.cmake b/Sources.cmake deleted file mode 100755 index 6eefdd6..0000000 --- a/Sources.cmake +++ /dev/null @@ -1,45 +0,0 @@ -SET(LIBGP_SRC - src/cov.cc - src/cov_factory.cc - src/cov_linear_ard.cc - src/cov_linear_one.cc - src/cov_matern3_iso.cc - src/cov_matern5_iso.cc - src/cov_noise.cc - src/cov_rq_iso.cc - src/cov_periodic_matern3_iso.cc - src/cov_periodic.cc - src/cov_se_ard.cc - src/cov_se_iso.cc - src/cov_sum.cc - src/cov_prod.cc - src/gp.cc - src/gp_utils.cc - src/sampleset.cc - src/rprop.cc - src/input_dim_filter.cc - src/cg.cc -) - -SET(LIBGP_INTERFACES - include/cov.h - include/cov_factory.h - include/cov_linear_ard.h - include/cov_linear_one.h - include/cov_matern3_iso.h - include/cov_matern5_iso.h - include/cov_noise.h - include/cov_rq_iso.h - include/cov_periodic_matern3_iso.h - include/cov_periodic.h - include/cov_se_ard.h - include/cov_se_iso.h - include/cov_sum.h - include/cov_prod.h - include/gp.h - include/gp_utils.h - include/sampleset.h - include/rprop.h - include/input_dim_filter.h - include/cg.h -) From 994023f2e09b35e4b544c28247cd47e8094762fd Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 16:53:43 +0200 Subject: [PATCH 05/19] refactor: remove unused gp_sparse.cc implementation file --- src/gp_sparse.cc | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/gp_sparse.cc diff --git a/src/gp_sparse.cc b/src/gp_sparse.cc deleted file mode 100644 index a796bf7..0000000 --- a/src/gp_sparse.cc +++ /dev/null @@ -1,39 +0,0 @@ -// libgp - Gaussian process library for Machine Learning -// Copyright (c) 2013, Manuel Blum -// All rights reserved. - -#include "gp_sparse.h" - -namespace libgp { - - SparseGaussianProcess::SparseGaussianProcess (size_t input_dim, std::string covf_def) : GaussianProcess(input_dim, covf_def) {} - - SparseGaussianProcess::SparseGaussianProcess (const char * filename) : GaussianProcess(filename) {} - - SparseGaussianProcess::~SparseGaussianProcess () {} - - void SparseGaussianProcess::compute() - { - if (cf->get_threshold() == INFINITY) { - std::cerr << "warning: no threshold defined, computation will be slow." << std::endl - << "Use full GP or define distance threshold!" << std::endl; - } - if (sampleset->empty()) return; - Eigen::SparseMatrix K(sampleset->size(), sampleset->size()); - alpha.resize(sampleset->size()); - // compute kernel matrix (lower triangle) - for(size_t i = 0; i < sampleset->size(); ++i) { - K.startVec(i); - for(size_t j = i; j < sampleset->size(); ++j) { - double cov = cf->get(sampleset->x(i), sampleset->x(j)); - if (cov != 0) K.insertBack(j,i) = cov; - } - alpha(i) = sampleset->y(i); - } - K.finalize(); - // perform cholesky factorization - solver.compute(K); - alpha = solver.solve(alpha); - } - -} From ea03d9940921045b5d7a719f5edafeefa1a2995c Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 18:15:29 +0200 Subject: [PATCH 06/19] refactor: update CMake configuration for Python bindings --- CMakeLists.txt | 42 ++++++++++++++++++++++-------------- python/example.py | 42 ------------------------------------ {python => src}/bindings.cpp | 0 3 files changed, 26 insertions(+), 58 deletions(-) delete mode 100644 python/example.py rename {python => src}/bindings.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54db06d..8c0db6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,9 @@ project(libgp include(FetchContent) # Options -option(LIBGP_BUILD_ALL "Build everything (tests, Python bindings, and examples)" OFF) +option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON) # Changed from LIBGP_BUILD_PYTHON +option(LIBGP_BUILD_ALL "Build everything (tests and examples)" OFF) option(LIBGP_BUILD_TESTS "Build libgp tests" ${LIBGP_BUILD_ALL}) -option(LIBGP_BUILD_PYTHON "Build Python bindings" ${LIBGP_BUILD_ALL}) option(LIBGP_BUILD_EXAMPLES "Build example applications" ${LIBGP_BUILD_ALL}) option(BUILD_SHARED_LIBS "Build shared libraries" ON) @@ -18,6 +18,15 @@ option(BUILD_SHARED_LIBS "Build shared libraries" ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Added for Python bindings + +# Dependencies +find_package(Eigen3 REQUIRED) + +if(BUILD_PYTHON_BINDINGS) + find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) + find_package(pybind11 CONFIG REQUIRED) +endif() # Generate version header configure_file( @@ -57,18 +66,22 @@ target_include_directories(gp $ ) -# Dependencies -find_package(Eigen3 REQUIRED) - - target_link_libraries(gp PUBLIC Eigen3::Eigen) +# Python bindings +if(BUILD_PYTHON_BINDINGS) + python_add_library(pygp MODULE src/bindings.cpp WITH_SOABI) + target_link_libraries(pygp PRIVATE pybind11::headers gp) + install(TARGETS pygp + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() + # Tests if(LIBGP_BUILD_TESTS) enable_testing() find_package(GTest REQUIRED) - # Helper function to add tests function(add_gp_test name) add_executable(${name} tests/${name}.cc) target_link_libraries(${name} PRIVATE gp GTest::GTest GTest::Main) @@ -83,15 +96,6 @@ if(LIBGP_BUILD_TESTS) add_gp_test(test_optimizer) endif() -# Python bindings -if(LIBGP_BUILD_PYTHON) - find_package(Python COMPONENTS Interpreter Development REQUIRED) - find_package(pybind11 REQUIRED) - - pybind11_add_module(pygp src/bindings.cpp) - target_link_libraries(pygp PRIVATE gp) -endif() - # Examples if(LIBGP_BUILD_EXAMPLES) add_executable(gp_example_dense examples/gp_example_dense.cc) @@ -110,6 +114,12 @@ install(TARGETS gp INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) +if(BUILD_PYTHON_BINDINGS) + install(TARGETS pygp + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} + ) +endif() + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h" diff --git a/python/example.py b/python/example.py deleted file mode 100644 index 1d18735..0000000 --- a/python/example.py +++ /dev/null @@ -1,42 +0,0 @@ -import math - -from pygp import GaussianProcess - - -def main(): - # Create a 2D Gaussian Process with SE kernel - input_dim = 2 - gp = GaussianProcess(input_dim, "CovSum(CovSEiso, CovNoise)") - - # Set hyperparameters (log-values for length-scale, signal variance, and noise) - params = [0.0, 0.0, -2.0] # [log(l), log(sf), log(sigma_n)] - gp.set_loghyper(params) - - # Generate some sample data - x = [ - [0.0, 0.0], - [0.0, 1.0], - [1.0, 0.0], - [1.0, 1.0] - ] - y = [0.0, 0.5, 0.5, 1.0] - - # Add training data - for xi, yi in zip(x, y): - gp.add_pattern(xi, yi) - - # Make predictions - test_point = [0.5, 0.5] - mean = gp.predict(test_point) - var = gp.get_variance(test_point) - - print(f"Number of training points: {gp.get_sampleset_size()}") - print(f"Prediction at {test_point}:") - print(f"Mean: {mean:.3f}") - print(f"Variance: {var:.3f}") - print(f"95% Confidence Interval: [{mean-2*math.sqrt(var):.3f}, {mean+2*math.sqrt(var):.3f}]") - print(f"Log likelihood: {gp.get_log_likelihood():.3f}") - - -if __name__ == "__main__": - main() diff --git a/python/bindings.cpp b/src/bindings.cpp similarity index 100% rename from python/bindings.cpp rename to src/bindings.cpp From 2980f87f006130ac35d3031b9b03a4c2ba462cf6 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:14:46 +0200 Subject: [PATCH 07/19] fix: name of Python module --- src/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings.cpp b/src/bindings.cpp index fbfc20f..135de12 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -6,7 +6,7 @@ namespace py = pybind11; -PYBIND11_MODULE(pygp, m) { +PYBIND11_MODULE(libgp, m) { m.doc() = "Python bindings for libgp - Gaussian Process Regression Library"; py::class_(m, "GaussianProcess") From bea660b996c45afe17dd66f856056a9056ba130f Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:15:03 +0200 Subject: [PATCH 08/19] refactor: update README.md for building and testing instructions --- README.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4110701..62c7ff0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A C++ library for Gaussian process regression. A Gaussian process defines a dist 1. Create a build directory and configure the project with tests enabled: ```bash -cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON +cmake -B build -DCMAKE_BUILD_TYPE=Release ``` 2. Build the library: @@ -27,22 +27,22 @@ cmake --build build For development and debugging, you can use Debug build type: ```bash -cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON +cmake -B build -DCMAKE_BUILD_TYPE=Debug -DLIBGP_BUILD_TESTS=ON ``` ### Running Tests The project uses Google Test for unit testing. Tests are automatically configured when building the project. -1. Build the tests (they are built automatically with the main build): +1. Build the tests: ```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_TESTS=ON cmake --build build ``` 2. Run all tests: ```bash -cd build -ctest --output-on-failure +cmake --build build --target test ``` ### Examples @@ -50,7 +50,13 @@ ctest --output-on-failure The library includes example code demonstrating how to use Gaussian Process regression. To build and run the examples: ```bash -./build/examples/gpdense +cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_EXAMPLES=ON +cmake --build build +``` + +Then run the example: +```bash +./build/gp_example_dense ``` The example demonstrates: @@ -62,18 +68,18 @@ The example demonstrates: For more details, see the source code in `examples/gp_example_dense.cc`. -## Installing +## Python Bindings + +This library provides Python bindings for Gaussian Process regression. The bindings are generated using pybind11, allowing you to use the C++ library directly in Python. + +### Building Python Bindings ```bash -cmake --install build +cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_PYTHON_BINDINGS=ON +cmake --build build ``` -After installation, you can use libgp in your CMake project: -```cmake -find_package(libgp REQUIRED) -target_link_libraries(your_target PRIVATE libgp::gp) -``` ## Implemented covariance functions From 84b02af1527a5afc52bcbd3c8f258fc1ca34aa47 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:15:14 +0200 Subject: [PATCH 09/19] refactor: update CMake configuration for Python bindings and static library --- CMakeLists.txt | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c0db6c..0ba3980 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,11 +8,11 @@ project(libgp include(FetchContent) # Options -option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON) # Changed from LIBGP_BUILD_PYTHON +option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON) option(LIBGP_BUILD_ALL "Build everything (tests and examples)" OFF) option(LIBGP_BUILD_TESTS "Build libgp tests" ${LIBGP_BUILD_ALL}) option(LIBGP_BUILD_EXAMPLES "Build example applications" ${LIBGP_BUILD_ALL}) -option(BUILD_SHARED_LIBS "Build shared libraries" ON) +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) # Changed to OFF for static linking # Set C++ standard set(CMAKE_CXX_STANDARD 17) @@ -23,11 +23,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Added for Python bindings # Dependencies find_package(Eigen3 REQUIRED) -if(BUILD_PYTHON_BINDINGS) - find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) - find_package(pybind11 CONFIG REQUIRED) -endif() - # Generate version header configure_file( "${PROJECT_SOURCE_DIR}/include/gp_version.h.in" @@ -36,7 +31,7 @@ configure_file( ) # Core library -add_library(gp +add_library(gp STATIC # Changed to STATIC src/gp.cc src/gp_utils.cc src/sampleset.cc @@ -70,11 +65,15 @@ target_link_libraries(gp PUBLIC Eigen3::Eigen) # Python bindings if(BUILD_PYTHON_BINDINGS) - python_add_library(pygp MODULE src/bindings.cpp WITH_SOABI) - target_link_libraries(pygp PRIVATE pybind11::headers gp) - install(TARGETS pygp - LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} - ) + set(PYBIND11_FINDPYTHON ON) + find_package(pybind11 CONFIG REQUIRED) + pybind11_add_module(libgp src/bindings.cpp) + target_link_libraries(libgp PRIVATE gp Eigen3::Eigen) + + # Install both the Python module and the shared library together + install(TARGETS libgp gp + LIBRARY DESTINATION . + RUNTIME DESTINATION .) endif() # Tests @@ -114,11 +113,6 @@ install(TARGETS gp INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) -if(BUILD_PYTHON_BINDINGS) - install(TARGETS pygp - LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} - ) -endif() install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} From 9a86924acfa2ae148aa5cd26fe440911b6bbcd02 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:15:19 +0200 Subject: [PATCH 10/19] feat: add pyproject.toml for build system and project metadata --- pyproject.toml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a23af92 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["scikit-build-core>=0.10", "pybind11"] +build-backend = "scikit_build_core.build" + +[project] +name = "libgp" +version = "0.1.5" +description = "C++ Library for Gaussian Process Regression" +readme = "README.md" +requires-python = ">=3.7" +authors = [ + {name = "libgp contributors"} +] +dependencies = ["numpy"] + +[tool.scikit-build] +cmake.minimum-version = "3.14" +cmake.args = [ + "-DBUILD_PYTHON_BINDINGS=ON", + "-DBUILD_SHARED_LIBS=ON", + "-DLIBGP_BUILD_TESTS=OFF", + "-DLIBGP_BUILD_EXAMPLES=OFF", + "-DCMAKE_POSITION_INDEPENDENT_CODE=ON", +] \ No newline at end of file From dc1807d357ea64d961d17cb60e5c03341a2bd2ac Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:15:25 +0200 Subject: [PATCH 11/19] refactor: streamline CI configuration by removing unnecessary build options --- .github/workflows/ci.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62dabcc..7934309 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: CI on: push: - branches: [ master ] tags: [ 'v*' ] pull_request: branches: [ master ] @@ -33,17 +32,14 @@ jobs: brew install cmake eigen - name: Configure CMake - run: | - cmake -B build -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_TESTS=ON \ - -DBUILD_EXAMPLES=ON + run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_TESTS=ON - name: Build - run: cmake --build build --config Release + run: cmake --build build - name: Test working-directory: build - run: ctest --output-on-failure -C Release + run: cmake --build build --target test - name: Create Package run: | From d0d791a0e78e759135a78a80fc42b515bd4bfa09 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:16:36 +0200 Subject: [PATCH 12/19] fix: ensure CI triggers on all branches for push events --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7934309..2fb3e0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: CI on: push: + branches: [ '*' ] tags: [ 'v*' ] pull_request: branches: [ master ] From 50945a05aaf956d53c066956631bd6a2644edee2 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:19:18 +0200 Subject: [PATCH 13/19] fix: update CI configuration to trigger on all branches for push events --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fb3e0d..a546d71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [ '*' ] + branches: [ '**'] tags: [ 'v*' ] pull_request: branches: [ master ] From 501589199be67939bde86a6ef6e04605208b429e Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:21:14 +0200 Subject: [PATCH 14/19] fix: set default option for BUILD_PYTHON_BINDINGS to OFF --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ba3980..6ade3e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ project(libgp include(FetchContent) # Options -option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON) +option(BUILD_PYTHON_BINDINGS "Build Python bindings" OFF) option(LIBGP_BUILD_ALL "Build everything (tests and examples)" OFF) option(LIBGP_BUILD_TESTS "Build libgp tests" ${LIBGP_BUILD_ALL}) option(LIBGP_BUILD_EXAMPLES "Build example applications" ${LIBGP_BUILD_ALL}) From c78d1e537a68f2fa9877bc1b4506e8a26d524900 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:23:18 +0200 Subject: [PATCH 15/19] fix: add googletest dependency for CI builds on Linux and macOS --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a546d71..a744c0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,12 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y cmake libeigen3-dev + sudo apt-get install -y cmake libeigen3-dev libgtest-dev - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install cmake eigen + brew install cmake eigen googletest - name: Configure CMake run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_TESTS=ON From 58d014b48f986b67a743e43f07ad58e021142d15 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:24:26 +0200 Subject: [PATCH 16/19] fix: remove unnecessary working-directory specification for test step --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a744c0d..8f6041a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,6 @@ jobs: run: cmake --build build - name: Test - working-directory: build run: cmake --build build --target test - name: Create Package From 7b8b544afba333648a36736399ab1d67de1d39f5 Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:34:49 +0200 Subject: [PATCH 17/19] fix: add flake8 configuration for code style enforcement --- .flake8 | 8 ++++++++ pyproject.toml | 3 +++ 2 files changed, 11 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..b165063 --- /dev/null +++ b/.flake8 @@ -0,0 +1,8 @@ +[flake8] +max-line-length = 120 +exclude = + .git, + __pycache__, + build, + dist, + *.egg-info \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a23af92..4af8da9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,9 @@ authors = [ ] dependencies = ["numpy"] +[project.optional-dependencies] +dev = ["flake8>=6.0.0"] + [tool.scikit-build] cmake.minimum-version = "3.14" cmake.args = [ From 50844d6bae2dd41f31dc15970424af1081a76aaf Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:34:58 +0200 Subject: [PATCH 18/19] feat: add example for 2D Gaussian Process with SE kernel --- examples/python_example.py | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/python_example.py diff --git a/examples/python_example.py b/examples/python_example.py new file mode 100644 index 0000000..afb8bb4 --- /dev/null +++ b/examples/python_example.py @@ -0,0 +1,42 @@ +import math + +from libgp import GaussianProcess + + +def main(): + # Create a 2D Gaussian Process with SE kernel + input_dim = 2 + gp = GaussianProcess(input_dim, "CovSum(CovSEiso, CovNoise)") + + # Set hyperparameters (log-values for length-scale, signal variance, and noise) + params = [0.0, 0.0, -2.0] # [log(l), log(sf), log(sigma_n)] + gp.set_loghyper(params) + + # Generate some sample data + x = [ + [0.0, 0.0], + [0.0, 1.0], + [1.0, 0.0], + [1.0, 1.0] + ] + y = [0.0, 0.5, 0.5, 1.0] + + # Add training data + for xi, yi in zip(x, y): + gp.add_pattern(xi, yi) + + # Make predictions + test_point = [0.5, 0.5] + mean = gp.predict(test_point) + var = gp.get_variance(test_point) + + print(f"Number of training points: {gp.get_sampleset_size()}") + print(f"Prediction at {test_point}:") + print(f"Mean: {mean:.3f}") + print(f"Variance: {var:.3f}") + print(f"95% Confidence Interval: [{mean-2*math.sqrt(var):.3f}, {mean+2*math.sqrt(var):.3f}]") + print(f"Log likelihood: {gp.get_log_likelihood():.3f}") + + +if __name__ == "__main__": + main() From 5565fa05a004226c02f5783c05813ce4586afc5e Mon Sep 17 00:00:00 2001 From: Manuel Blum Date: Sat, 26 Apr 2025 19:36:41 +0200 Subject: [PATCH 19/19] fix: update README for Python bindings --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 62c7ff0..437dfbd 100644 --- a/README.md +++ b/README.md @@ -72,15 +72,11 @@ For more details, see the source code in `examples/gp_example_dense.cc`. This library provides Python bindings for Gaussian Process regression. The bindings are generated using pybind11, allowing you to use the C++ library directly in Python. -### Building Python Bindings - ```bash -cmake -B build -DCMAKE_BUILD_TYPE=Release -DLIBGP_BUILD_PYTHON_BINDINGS=ON -cmake --build build +pip install . +python examples/python_example.py ``` - - ## Implemented covariance functions ### Simple covariance functions