diff --git a/.github/workflows/conan.yml b/.github/workflows/conan.yml new file mode 100644 index 00000000..bab9b80d --- /dev/null +++ b/.github/workflows/conan.yml @@ -0,0 +1,41 @@ +name: Run conan + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + conan: + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{matrix.os}} + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax + architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified + - name: Install conan + run: pip install conan>2 + - run: conan profile detect + - run: conan create . --build=missing --options shared=True + name: Build shared + - run: conan create . --build=missing --options shared=False + name: Build static diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml new file mode 100644 index 00000000..58969b77 --- /dev/null +++ b/.github/workflows/test-mac.yml @@ -0,0 +1,32 @@ +name: Mac unit tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + mac-tests: + runs-on: macos-latest + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false + - name : installPackages + run : brew install eigen boost boost-python3 libomp gflags + - name : install python packages + run: pip3 install numpy setuptools --break-system-packages + - name: Get number of CPU cores + uses: SimenB/github-actions-cpu-cores@v1 + id: cpu-cores + - name: cmake + run: mkdir build && cd build && cmake .. -DLIBCMAES_BUILD_PYTHON=On -DLIBCMAES_BUILD_TESTS=On -DLIBCMAES_USE_OPENMP=On + - name: compile + run: cd build && cmake --build . -j${{ steps.cpu-cores.outputs.count }} + - name: Run ctest + run: cd build && ctest -j${{ steps.cpu-cores.outputs.count }} --output-on-failure + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..57019d40 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: Ubuntu unit tests + +on: + push: + branches: [ master] + pull_request: + branches: [ master, develop ] + workflow_dispatch: + + +jobs: + ubuntu-tests-fullSetup: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false + - name : installPackages + run : sudo apt-get update && sudo apt-get install --no-install-recommends --yes libeigen3-dev libboost-all-dev libgflags-dev + - name : install python packages + run: pip3 install numpy + - name: Get number of CPU cores + uses: SimenB/github-actions-cpu-cores@v1 + id: cpu-cores + - name: cmake + run: mkdir build && cd build && cmake .. -DLIBCMAES_BUILD_PYTHON=On -DLIBCMAES_BUILD_TESTS=On -DLIBCMAES_USE_OPENMP=On + - name: compile + run: cd build && cmake --build . -j${{ steps.cpu-cores.outputs.count }} + - name: Run ctest + run: cd build && ctest -j${{ steps.cpu-cores.outputs.count }} --output-on-failure + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f8310a72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build/ +CMakeLists.txt.user +CMakeUserPresets.json +install/ +installFolder/ +conandummy/ +test_package/CMakeUserPresets.json +lib/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e5a4084..59b64884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ -cmake_minimum_required (VERSION 3.9) +cmake_minimum_required (VERSION 3.15) + +set(libcmaes_VERSION 0.10.1) project (libcmaes - VERSION 0.10 + VERSION ${libcmaes_VERSION} LANGUAGES C CXX DESCRIPTION "A C++11 library for stochastic optimization with CMA-ES") @@ -9,12 +11,11 @@ if (NOT DEFINED CMAKE_BUILD_TYPE) set (CMAKE_BUILD_TYPE Release CACHE STRING "Build type") endif () -set(CMAKE_CXX_FLAGS "-Wall -Wextra") -set(CMAKE_CXX_FLAGS_DEBUG "-g") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") - +list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) +include(CompilerOptions) + set (LIBCMAES_TOP_LEVEL NO) if (${PROJECT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR}) set (LIBCMAES_TOP_LEVEL YES) @@ -66,7 +67,19 @@ check_include_file (sys/types.h HAVE_SYS_TYPES_H) check_include_file (sys/stat.h HAVE_SYS_STAT_H) # ---------- dependencies ---------- -find_package (Eigen3 3.0.0 REQUIRED) +if(LIBCMAES_BUILD_PYTHON) + find_package (Python3 COMPONENTS Interpreter Development NumPy) + if (NOT TARGET Python3::Module AND Python3_Development_FOUND) + add_library (Python3::Module SHARED IMPORTED) + set_target_properties ( + Python3::Module + PROPERTIES IMPORTED_LOCATION ${Python3_LIBRARIES} + INTERFACE_INCLUDE_DIRECTORIES ${Python3_INCLUDE_DIRS}) + endif () +endif() + + +find_package (Eigen3 3.4.0 REQUIRED) if (LIBCMAES_USE_OPENMP) find_package (OpenMP QUIET) @@ -77,24 +90,21 @@ if (LIBCMAES_USE_OPENMP) endif () if (LIBCMAES_BUILD_PYTHON) - find_package (PythonInterp) - find_package (PythonLibs) - if (NOT TARGET Python::Module AND PYTHONLIBS_FOUND) - add_library (Python::Module SHARED IMPORTED) + find_package (Python3 COMPONENTS Interpreter Development NumPy) + if (NOT TARGET Python3::Module AND Python3_Development_FOUND) + add_library (Python3::Module SHARED IMPORTED) set_target_properties ( - Python::Module - PROPERTIES IMPORTED_LOCATION ${PYTHON_LIBRARIES} - INTERFACE_INCLUDE_DIRECTORIES ${PYTHON_INCLUDE_DIRS}) + Python3::Module + PROPERTIES IMPORTED_LOCATION ${Python3_LIBRARIES} + INTERFACE_INCLUDE_DIRECTORIES ${Python3_INCLUDE_DIRS}) endif () - find_package (NumPy) - # python site dir - if (PYTHON_EXECUTABLE AND NOT DEFINED PYTHON_SITE_PACKAGES) + if (Python3_EXECUTABLE AND NOT DEFINED PYTHON_SITE_PACKAGES) # $ENV{VIRTUAL_ENV} does not work because not exported by activate :/ execute_process ( COMMAND - ${PYTHON_EXECUTABLE} -c " + ${Python3_EXECUTABLE} -c " import sys; if 'real_prefix' in dir(sys): print(sys.prefix)" @@ -107,7 +117,7 @@ if 'real_prefix' in dir(sys): endif () execute_process ( COMMAND - ${PYTHON_EXECUTABLE} -c + ${Python3_EXECUTABLE} -c "from distutils import sysconfig; print(sysconfig.get_python_lib(plat_specific=True, prefix='${SITE_DIR_PREFIX}'))" OUTPUT_VARIABLE _ABS_PYTHON_SITE_PACKAGES RESULT_VARIABLE _exit_code @@ -120,7 +130,7 @@ if 'real_prefix' in dir(sys): ${_ABS_PYTHON_SITE_PACKAGES}) set (PYTHON_SITE_PACKAGES ${_REL_PYTHON_SITE_PACKAGES}) else () - message (SEND_ERROR "Could not run ${PYTHON_EXECUTABLE}") + message (SEND_ERROR "Could not run ${Python3_EXECUTABLE}") endif () endif () @@ -161,11 +171,11 @@ if (LIBCMAES_BUILD_EXAMPLES) add_subdirectory (examples) endif () if (LIBCMAES_BUILD_TESTS) - enable_testing () + include(CTest) add_subdirectory (tests) endif () -if (Boost_FOUND AND NumPy_FOUND) +if (Boost_FOUND AND Python3_NumPy_FOUND) add_subdirectory (python) endif () @@ -173,12 +183,21 @@ endif () add_library (libcmaes::cmaes ALIAS cmaes) include (CMakePackageConfigHelpers) -write_basic_package_version_file ( - libcmaesConfigVersion.cmake VERSION ${PACKAGE_VERSION} - COMPATIBILITY AnyNewerVersion) +write_basic_package_version_file( + libcmaes-config-version.cmake + VERSION ${PACKAGE_VERSION} + COMPATIBILITY AnyNewerVersion) + + + +configure_package_config_file( + libcmaes-config.cmake.in + libcmaes-config.cmake + INSTALL_DESTINATION ${RELATIVE_INSTALL_CMAKE_DIR}) # export target to build directory export (TARGETS cmaes NAMESPACE libcmaes:: FILE libcmaesTargets.cmake) + # export target on install install ( EXPORT libcmaesTargets @@ -186,17 +205,15 @@ install ( NAMESPACE libcmaes:: DESTINATION ${RELATIVE_INSTALL_CMAKE_DIR}) -configure_file (libcmaesConfig.cmake.in libcmaesConfig.cmake @ONLY) install ( - FILES "${CMAKE_CURRENT_BINARY_DIR}/libcmaesConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/libcmaesConfigVersion.cmake" - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindEigen3.cmake" + FILES "${CMAKE_CURRENT_BINARY_DIR}/libcmaes-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/libcmaes-config-version.cmake" DESTINATION ${RELATIVE_INSTALL_CMAKE_DIR}) # ------------------------ Doxygen -------------------------------------------- find_package(Doxygen) -if(DOXYGEN_FOUND) +if(DOXYGEN_FOUND AND LIBCMAES_TOP_LEVEL) set(DOXYGEN_USE_MATHJAX YES) set(DOXYGEN_STRIP_FROM_INC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/include") @@ -213,6 +230,6 @@ if(DOXYGEN_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/src/" "${CMAKE_CURRENT_SOURCE_DIR}/README.md") -else (DOXYGEN_FOUND) +else () message("Doxygen need to be installed to generate the doxygen documentation") -endif(DOXYGEN_FOUND) +endif() diff --git a/README.md b/README.md index bd71610f..8ae68159 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## libcmaes -[![Doxygen](https://img.shields.io/badge/Documentation-Doxygen-success)](https://beniz.github.io/libcmaes/doc/html/index.html) +[![Doxygen](https://img.shields.io/badge/Documentation-Doxygen-success)](https://cma-es.github.io/libcmaes/doc/html/index.html) libcmaes is a multithreaded C++11 implementation (with Python bindings) of algorithms of the CMA-ES family for optimization of nonlinear non-convex 'blackbox' functions. The implemented algorithms have a wide range of applications in various disciplines, ranging from pure function minimization, optimization in industrial and scientific applications, to the solving of reinforcement and machine learning problems. @@ -26,7 +26,7 @@ Current features include: Documentation: - Full documentation is available from https://github.com/beniz/libcmaes/wiki -- API documentation is available from http://beniz.github.io/libcmaes/doc/html/index.html +- API documentation is available from https://cma-es.github.io/libcmaes/doc/html/index.html Dependencies: diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake new file mode 100644 index 00000000..5ba9aaee --- /dev/null +++ b/cmake/CompilerOptions.cmake @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: 2021 Philipp Basler, Margarete Mühlleitner and Jonas Müller +# +# SPDX-License-Identifier: GPL-3.0-or-later + +set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -Wall" +) +set(CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE} -Wall ") + +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -Wextra") + set(CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE} -O3 -Wextra") +endif(CMAKE_COMPILER_IS_GNUCXX) + +if (MSVC) + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_USE_MATH_DEFINES" ) + + set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} /permissive- /bigobj /w44101") + + set(CMAKE_CXX_FLAGS_RELEASE + "${CMAKE_CXX_FLAGS_RELEASE} /permissive- /bigobj /Ox /w44101") + + set(MSVC_DISABLED_WARNINGS_LIST + "C4061" # enumerator 'identifier' in switch of enum 'enumeration' is not + # explicitly handled by a case label + # Disable this because it flags even when there is a default. + "C4068" + "C4100" # 'exarg' : unreferenced formal parameter + "C4127" # conditional expression is constant + "C4200" # nonstandard extension used : zero-sized array in + # struct/union. + "C4204" # nonstandard extension used: non-constant aggregate initializer + "C4221" # nonstandard extension used : 'identifier' : cannot be + # initialized using address of automatic variable + "C4242" # 'function' : conversion from 'int' to 'uint8_t', + # possible loss of data + "C4244" # 'function' : conversion from 'int' to 'uint8_t', + # possible loss of data + "C4245" # 'initializing' : conversion from 'long' to + # 'unsigned long', signed/unsigned mismatch + "C4251" + "C4267" # conversion from 'size_t' to 'int', possible loss of data + "C4275" + "C4355" + "C4371" # layout of class may have changed from a previous version of the + # compiler due to better packing of member '...' + "C4388" # signed/unsigned mismatch + "C4296" # '>=' : expression is always true + "C4350" # behavior change: 'std::_Wrap_alloc...' + "C4365" # '=' : conversion from 'size_t' to 'int', + # signed/unsigned mismatch + "C4389" # '!=' : signed/unsigned mismatch + "C4464" # relative include path contains '..' + "C4510" # 'argument' : default constructor could not be generated + "C4571" + "C4512" # 'argument' : assignment operator could not be generated + "C4514" # 'function': unreferenced inline function has been removed + "C4548" # expression before comma has no effect; expected expression with + # side-effect" caused by FD_* macros. + "C4610" # struct 'argument' can never be instantiated - user defined + # constructor required. + "C4619" + "C4623" # default constructor was implicitly defined as deleted + "C4625" # copy constructor could not be generated because a base class + # copy constructor is inaccessible or deleted + "C4626" # assignment operator could not be generated because a base class + # assignment operator is inaccessible or deleted + "C4643" + "C4668" # 'symbol' is not defined as a preprocessor macro, replacing with + # '0' for 'directives' + # Disable this because GTest uses it everywhere. + "C4706" # assignment within conditional expression + "C4710" # 'function': function not inlined + "C4711" # function 'function' selected for inline expansion + "C4800" # 'int' : forcing value to bool 'true' or 'false' + # (performance warning) + "C4820" # 'bytes' bytes padding added after construct 'member_name' + "C4868" + "C4996" + "C5026" # move constructor was implicitly defined as deleted + "C5027" # move assignment operator was implicitly defined as deleted + "C5031" + "C5039" + "C5045" + ) + string(REPLACE "C" " -wd" MSVC_DISABLED_WARNINGS_STR + ${MSVC_DISABLED_WARNINGS_LIST}) + + set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS} ${MSVC_DISABLED_WARNINGS_STR}") + endif() diff --git a/cmake/FindEigen3.cmake b/cmake/FindEigen3.cmake deleted file mode 100644 index 2dbed583..00000000 --- a/cmake/FindEigen3.cmake +++ /dev/null @@ -1,107 +0,0 @@ -# - Try to find Eigen3 lib -# -# This module supports requiring a minimum version, e.g. you can do -# find_package(Eigen3 3.1.2) -# to require version 3.1.2 or newer of Eigen3. -# -# Once done this will define -# -# EIGEN3_FOUND - system has eigen lib with correct version -# EIGEN3_INCLUDE_DIR - the eigen include directory -# EIGEN3_VERSION - eigen version -# -# and the following imported target: -# -# Eigen3::Eigen - The header-only Eigen library -# -# This module reads hints about search locations from -# the following environment variables: -# -# EIGEN3_ROOT -# EIGEN3_ROOT_DIR - -# Copyright (c) 2006, 2007 Montel Laurent, -# Copyright (c) 2008, 2009 Gael Guennebaud, -# Copyright (c) 2009 Benoit Jacob -# Redistribution and use is allowed according to the terms of the 2-clause BSD license. - -if(NOT Eigen3_FIND_VERSION) - if(NOT Eigen3_FIND_VERSION_MAJOR) - set(Eigen3_FIND_VERSION_MAJOR 2) - endif() - if(NOT Eigen3_FIND_VERSION_MINOR) - set(Eigen3_FIND_VERSION_MINOR 91) - endif() - if(NOT Eigen3_FIND_VERSION_PATCH) - set(Eigen3_FIND_VERSION_PATCH 0) - endif() - - set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") -endif() - -macro(_eigen3_check_version) - file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) - - string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") - set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") - string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") - set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") - string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") - set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") - - set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) - if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) - set(EIGEN3_VERSION_OK FALSE) - else() - set(EIGEN3_VERSION_OK TRUE) - endif() - - if(NOT EIGEN3_VERSION_OK) - - message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " - "but at least version ${Eigen3_FIND_VERSION} is required") - endif() -endmacro() - -if (EIGEN3_INCLUDE_DIR) - - # in cache already - _eigen3_check_version() - set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) - set(Eigen3_FOUND ${EIGEN3_VERSION_OK}) - -else () - - # search first if an Eigen3Config.cmake is available in the system, - # if successful this would set EIGEN3_INCLUDE_DIR and the rest of - # the script will work as usual - find_package(Eigen3 ${Eigen3_FIND_VERSION} NO_MODULE QUIET) - - if(NOT EIGEN3_INCLUDE_DIR) - find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library - HINTS - ENV EIGEN3_ROOT - ENV EIGEN3_ROOT_DIR - PATHS - ${CMAKE_INSTALL_PREFIX}/include - ${KDE4_INCLUDE_DIR} - PATH_SUFFIXES eigen3 eigen - ) - endif() - - if(EIGEN3_INCLUDE_DIR) - _eigen3_check_version() - endif() - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) - - mark_as_advanced(EIGEN3_INCLUDE_DIR) - -endif() - -if(EIGEN3_FOUND AND NOT TARGET Eigen3::Eigen) - add_library(Eigen3::Eigen INTERFACE IMPORTED) - set_target_properties(Eigen3::Eigen PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${EIGEN3_INCLUDE_DIR}") -endif() diff --git a/cmake/FindNumPy.cmake b/cmake/FindNumPy.cmake deleted file mode 100644 index d25d1f34..00000000 --- a/cmake/FindNumPy.cmake +++ /dev/null @@ -1,72 +0,0 @@ -# This module finds NumPy if it is installed, and sets the following variables -# indicating where it is. -# -# * NUMPY_FOUND - was NumPy found -# * NUMPY_VERSION - the version of NumPy found as a string -# * NUMPY_INCLUDE_DIRS - path to the NumPy include files -# -# Additionally the imported target -# -# * Python::NumPy -# -# is defined. - -# Finding NumPy involves calling the Python interpreter -if (NumPy_FIND_REQUIRED) - find_package (PythonInterp REQUIRED) -else () - find_package (PythonInterp ${NumPy_FIND_REQUIRED}) -endif () - -if (PYTHONINTERP_FOUND) - execute_process ( - COMMAND ${PYTHON_EXECUTABLE} -c "import numpy as n; print(n.__version__)" - RESULT_VARIABLE NumPy_VERSION_ERROR - OUTPUT_VARIABLE NumPy_VERSION_STRING - ERROR_VARIABLE NumPy_VERSION_ERROR_OUTPUT - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - execute_process ( - COMMAND ${PYTHON_EXECUTABLE} -c "import numpy as n; print(n.get_include())" - RESULT_VARIABLE NumPy_INCLUDE_ERROR - OUTPUT_VARIABLE NumPy_INCLUDE_DIRS - ERROR_VARIABLE NumPy_INCLUDE_ERROR_OUTPUT - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if (NOT NumPy_VERSION_ERROR) - string (REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" NumPy_VERSION - ${NumPy_VERSION_STRING} - ) - if ("${NumPy_VERSION}" STREQUAL "") - set (NumPy_VERSION_ERROR YES) - endif () - endif () - - if (NOT NumPy_INCLUDE_ERROR) # canonicalize paths - get_filename_component (NumPy_INCLUDE_DIRS ${NumPy_INCLUDE_DIRS} ABSOLUTE) - endif () - - if (NOT NumPy_VERSION_ERROR AND NOT NumPy_INCLUDE_ERROR) - set (NumPy_FIND_SUCCESS TRUE) - endif () -endif () - -include (FindPackageHandleStandardArgs) -find_package_handle_standard_args ( - NumPy - FOUND_VAR - NumPy_FOUND - REQUIRED_VARS - NumPy_INCLUDE_DIRS - NumPy_FIND_SUCCESS - VERSION_VAR - NumPy_VERSION -) - -if (NumPy_FOUND AND NOT TARGET Python::NumPy) - add_library (Python::NumPy INTERFACE IMPORTED) - set_target_properties (Python::NumPy PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - ${NumPy_INCLUDE_DIRS}) -endif () diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..9d12ffac --- /dev/null +++ b/conanfile.py @@ -0,0 +1,119 @@ +import re, os, functools + +from conan import ConanFile, tools +from conan import ConanFile +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout +from conan.tools.files import load +from conan.tools.env import Environment + +from conan.tools.scm import Git + + +class CmaesConan(ConanFile): + name = "libcmaes" + + generators = "CMakeDeps" + + # Optional metadata + license = "MIT" + author = " " + url = "https://github.com/CMA-ES/libcmaes" + description = "libcmaes is a multithreaded C++11 library with Python bindings for high performance blackbox stochastic optimization using the CMA-ES algorithm for Covariance Matrix Adaptation Evolution Strategy" + topics = ("", "", "") + + # Binary configuration + settings = "os", "compiler", "build_type", "arch" + options = { + "shared": [True, False], + "openmp": [True, False], + "surrog": [True, False], + "enable_tests": [True, False], + } + default_options = { + "shared": True, + "openmp": True, + "surrog": True, + "enable_tests": False, + "boost/*:without_python": False, + } + + # Sources are located in the same place as this recipe, copy them to the recipe + exports_sources = ( + "CMakeLists.txt", + "cmake/*", + "include/*", + "libcmaes-config.cmake.in", + "src/*", + "libcmaes.pc.in", + "tests/*", + "python/*", + ) + + def build_requirements(self): + if self.options.enable_tests: + self.test_requires("gflags/2.2.2") + self.test_requires("boost/1.85.0") + + def requirements(self): + self.requires("eigen/3.4.0", transitive_headers=True) + + if self.options.openmp and self.settings.os != "Windows": + self.requires("llvm-openmp/17.0.6", transitive_headers=True) + + def set_version(self): + content = load(self, os.path.join(self.recipe_folder, "CMakeLists.txt")) + value = re.search(r"set\(libcmaes_VERSION (.*)\)", content) + extracted_version = value.group(1).strip() + + is_git_tag = False + git = Git(self, folder=self.recipe_folder) + try: + git.run("describe --exact-match --tags") + is_git_tag = True + except Exception: + is_git_tag = False + + if is_git_tag: + self.version = extracted_version + else: + # if not tag -> pre-release version + commit_hash = git.get_commit()[:8] + self.version = f"{extracted_version}.{commit_hash}" + + def config_options(self): + pass + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.variables["LIBCMAES_BUILD_EXAMPLES"] = False + tc.variables["LIBCMAES_BUILD_SHARED_LIBS"] = self.options.shared + tc.variables["LIBCMAES_USE_OPENMP"] = self.options.openmp + tc.variables["LIBCMAES_ENABLE_SURROG"] = self.options.surrog + tc.variables["LIBCMAES_BUILD_PYTHON"] = self.options.enable_tests + tc.variables["LIBCMAES_BUILD_TESTS"] = self.options.enable_tests + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + environment = Environment() + environment.define("CTEST_OUTPUT_ON_FAILURE", "1") + envvars = environment.vars(self) + + if self.options.enable_tests: + with envvars.apply(): + cmake.test() + + def package(self): + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["cmaes"] + self.cpp_info.set_property("cmake_target_name", "libcmaes::cmaes") + # self.cpp_info.requires = ["eigen::eigen"] diff --git a/include/libcmaes/eigenmvn.h b/include/libcmaes/eigenmvn.h index fb8b11d4..c1a4eed9 100644 --- a/include/libcmaes/eigenmvn.h +++ b/include/libcmaes/eigenmvn.h @@ -58,7 +58,21 @@ namespace Eigen { static std::mt19937 rng; // The uniform pseudo-random algorithm mutable std::normal_distribution norm; // gaussian combinator - EIGEN_EMPTY_STRUCT_CTOR(scalar_normal_dist_op) + //EIGEN_EMPTY_STRUCT_CTOR(scalar_normal_dist_op) + scalar_normal_dist_op() = default; + scalar_normal_dist_op(const scalar_normal_dist_op& other) + : norm{other.norm} + { + rng = other.rng; + }; + scalar_normal_dist_op &operator=(const scalar_normal_dist_op &other) { + if(this != &other) + { + scalar_normal_dist_op temp(other); + swap(temp); + } + return *this; + }; scalar_normal_dist_op &operator=(scalar_normal_dist_op &&other) { diff --git a/libcmaes-config.cmake.in b/libcmaes-config.cmake.in new file mode 100644 index 00000000..8fa61830 --- /dev/null +++ b/libcmaes-config.cmake.in @@ -0,0 +1,14 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +list (APPEND CMAKE_MODULE_PATH @CMAKE_CURRENT_SOURCE_DIR@/lib/cmake/libcmaes ${CMAKE_CURRENT_LIST_DIR}) + +find_dependency(Eigen3 @EIGEN3_VERSION@ REQUIRED) + + +if(@LIBCMAES_USE_OPENMP@) + find_dependency(OpenMP @OpenMP_CXX_VERSION@) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/libcmaesTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/libcmaesConfig.cmake.in b/libcmaesConfig.cmake.in deleted file mode 100644 index a17175f5..00000000 --- a/libcmaesConfig.cmake.in +++ /dev/null @@ -1,9 +0,0 @@ -include (CMakeFindDependencyMacro) -list (APPEND CMAKE_MODULE_PATH @CMAKE_CURRENT_SOURCE_DIR@/cmake ${CMAKE_CURRENT_LIST_DIR}) - -find_dependency (Eigen3 REQUIRED) -if (@LIBCMAES_USE_OPENMP@ AND @OpenMP_FOUND@) - find_dependency (OpenMP REQUIRED) -endif () - -include ("${CMAKE_CURRENT_LIST_DIR}/libcmaesTargets.cmake") diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 79d1ae66..a32c3ae5 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,6 +1,11 @@ -python_add_module (lcmaes lcmaes.cc) +Python3_add_library (lcmaes MODULE lcmaes.cc) -target_link_libraries (lcmaes cmaes Python::Module Python::NumPy Boost::python) +if(MSVC) + add_custom_command(TARGET lcmaes POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ ) +endif() + +target_link_libraries (lcmaes PRIVATE cmaes Python3::Module Python3::NumPy Boost::python) if (APPLE) set_target_properties (lcmaes PROPERTIES LINK_FLAGS @@ -8,15 +13,17 @@ if (APPLE) endif () install (TARGETS lcmaes DESTINATION ${PYTHON_SITE_PACKAGES}) +if(MSVC) +install(TARGETS cmaes DESTINATION ${PYTHON_SITE_PACKAGES}) +endif() -set (PYINSTALLCHECK_ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}") if (LIBCMAES_BUILD_TESTS) macro (cmaes_add_pytest name) - add_test (NAME ${name} COMMAND ${PYTHON_EXECUTABLE} + add_test (NAME ${name} COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${name}.py) set_tests_properties (${name} PROPERTIES ENVIRONMENT - "${PYINSTALLCHECK_ENVIRONMENT}") + "PYTHONPATH=$") endmacro () cmaes_add_pytest (ptest) diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 00000000..65a1c3af --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.15) +project(PackageTest CXX) + +find_package(libcmaes CONFIG REQUIRED) + +add_executable(example src/example.cpp) +target_link_libraries(example libcmaes::cmaes) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 00000000..2037d777 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,27 @@ +import os + +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout +from conan.tools.build import can_run + + +class helloTestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeDeps", "CMakeToolchain" + + def requirements(self): + self.requires(self.tested_reference_str) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def layout(self): + cmake_layout(self) + + def test(self): + if can_run(self): + cmd = os.path.join(self.cpp.build.bindir, "example") + self.run(cmd, env="conanrun") + diff --git a/test_package/src/example.cpp b/test_package/src/example.cpp new file mode 100644 index 00000000..8d7db546 --- /dev/null +++ b/test_package/src/example.cpp @@ -0,0 +1,26 @@ +#include +#include + +using namespace libcmaes; + +FitFunc fsphere = [](const double *x, const int N) +{ + double val = 0.0; + for (int i=0;i x0(dim,10.0); + double sigma = 0.1; + //int lambda = 100; // offsprings at each generation. + CMAParameters<> cmaparams(x0,sigma); + //cmaparams._algo = BIPOP_CMAES; + CMASolutions cmasols = cmaes<>(fsphere,cmaparams); + std::cout << "best solution: " << cmasols << std::endl; + std::cout << "optimization took " << cmasols.elapsed_time() / 1000.0 << " seconds\n"; + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cf8620b6..87dafdab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,11 @@ +find_package(gflags REQUIRED) macro (cmaes_add_test name) add_executable (${name} ${name}.cc) target_link_libraries (${name} cmaes gflags) - add_test (NAME ${name} COMMAND t_${name}) + if(MSVC) + target_compile_definitions(${name} PUBLIC _USE_MATH_DEFINES) + endif() + add_test (NAME ${name} COMMAND ${name}) if (WIN32) set_tests_properties ( ${name}