diff --git a/.ci/install_debian.sh b/.ci/install_debian.sh deleted file mode 100644 index f405be9..0000000 --- a/.ci/install_debian.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -set -e - -apt-get update - -# noninteractive tzdata ( https://stackoverflow.com/questions/44331836/apt-get-install-tzdata-noninteractive ) -export DEBIAN_FRONTEND=noninteractive - -# CI specific packages -apt-get install -y clang wget unzip build-essential cmake libeigen3-dev git diff --git a/.ci/install_debian_and_script.sh b/.ci/install_debian_and_script.sh deleted file mode 100644 index fc44203..0000000 --- a/.ci/install_debian_and_script.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -e - -DIR=$(dirname "$(readlink -f "$0")") - -sh $DIR/install_debian.sh -sh $DIR/script.sh diff --git a/.ci/script.sh b/.ci/script.sh deleted file mode 100644 index cbf69a0..0000000 --- a/.ci/script.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -set -e - -# google test -wget https://github.com/google/googletest/archive/release-1.8.0.zip -unzip release-1.8.0.zip -cd googletest-release-1.8.0 -mkdir build -cd build -cmake -G"${TRAVIS_CMAKE_GENERATOR}" -DBUILD_GTEST=ON -DBUILD_SHARED_LIBS=ON .. -cmake --build . --config ${TRAVIS_BUILD_TYPE} --target install -cd ../.. - -# osqp -git clone --recursive https://github.com/oxfordcontrol/osqp.git -cd osqp -mkdir build -cd build -cmake -G"${TRAVIS_CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=${TRAVIS_BUILD_TYPE} -DUNITTESTS=OFF .. -cmake --build . --config ${TRAVIS_BUILD_TYPE} --target install -cd ../.. - -# Build, test and install osqp-eigen -cd $TRAVIS_BUILD_DIR -mkdir build -cd build -cmake -G"${TRAVIS_CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=${TRAVIS_BUILD_TYPE} -DBUILD_TESTING=ON .. -cmake --build . --config ${TRAVIS_BUILD_TYPE} --target install -ctest --output-on-failure --build-config ${TRAVIS_BUILD_TYPE} - -# Build osqp-eigen example -cd ../example -mkdir build -cd build -cmake -G"${TRAVIS_CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=${TRAVIS_BUILD_TYPE} .. -cmake --build . --config ${TRAVIS_BUILD_TYPE} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4c174ec --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,176 @@ +name: C++ CI Workflow + +on: + push: + pull_request: + schedule: + # * is a special character in YAML so you have to quote this string + # Execute a "nightly" build at 2 AM UTC + - cron: '0 2 * * *' + +env: + osqp_TAG: v0.6.0 + vcpkg_robotology_TAG: v0.0.3 + Catch2_TAG: v2.12.1 + +# Test with different operating systems +jobs: + build: + name: '[${{ matrix.os }}@${{ matrix.build_type }}]' + runs-on: ${{ matrix.os }} + strategy: + matrix: + build_type: [Debug, Release] + os: [ubuntu-latest, macOS-latest, windows-latest] + fail-fast: false + + # operating system dependences + steps: + - uses: actions/checkout@master + + # Print environment variables to simplify development and debugging + - name: Environment Variables + shell: bash + run: env + + # ============ + # DEPENDENCIES + # ============ + + # Remove apt repos that are known to break from time to time + # See https://github.com/actions/virtual-environments/issues/323 + - name: Remove broken apt repos [Ubuntu] + if: matrix.os == 'ubuntu-latest' + run: | + for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done + + - name: Dependencies [Windows] + if: matrix.os == 'windows-latest' + run: | + # To avoid spending a huge time compiling vcpkg dependencies, we download a root that comes precompiled with all the ports that we need + choco install -y wget unzip + # To avoid problems with non-relocatable packages, we unzip the archive exactly in the same C:/robotology/vcpkg + # that has been used to create the pre-compiled archive + cd C:/ + md C:/robotology + md C:/robotology/vcpkg + wget https://github.com/robotology/robotology-superbuild-dependencies-vcpkg/releases/download/${env:vcpkg_robotology_TAG}/vcpkg-robotology.zip + unzip vcpkg-robotology.zip -d C:/robotology/vcpkg + # Overwrite the VCPKG_INSTALLATION_ROOT env variable defined by GitHub Actions to point to our vcpkg + echo "::set-env name=VCPKG_INSTALLATION_ROOT::C:/robotology/vcpkg" + + # Install Catch2 + cd C:/robotology/vcpkg + ./vcpkg.exe install --triplet x64-windows catch2 + + - name: Dependencies [macOS] + if: matrix.os == 'macOS-latest' + run: | + brew install eigen catch2 + + - name: Dependencies [Ubuntu] + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install git build-essential cmake libeigen3-dev valgrind + + - name: Cache Source-based Dependencies + id: cache-source-deps + uses: actions/cache@v1 + with: + path: ${{ github.workspace }}/install/deps + key: source-deps-${{ runner.os }}-vcpkg-robotology-${{ env.vcpkg_robotology_TAG }}-osqp-${{ env.osqp_TAG }}-catch2-${{ env.Catch2_TAG }} + + - name: Source-based Dependencies [Windows] + if: steps.cache-source-deps.outputs.cache-hit != 'true' && matrix.os == 'windows-latest' + shell: bash + run: | + # osqp + cd ${GITHUB_WORKSPACE} + git clone --recursive -b ${osqp_TAG} https://github.com/oxfordcontrol/osqp + cd osqp + mkdir -p build + cd build + cmake -A x64 -DCMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install/deps .. + + cmake --build . --config ${{ matrix.build_type }} --target INSTALL + + - name: Source-based Dependencies [Ubuntu/macOS] + if: steps.cache-source-deps.outputs.cache-hit != 'true' && (matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest') + shell: bash + run: | + # osqp + cd ${GITHUB_WORKSPACE} + git clone --recursive -b ${osqp_TAG} https://github.com/oxfordcontrol/osqp + cd osqp + mkdir -p build + cd build + cmake -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install/deps .. + cmake --build . --config ${{ matrix.build_type }} --target install + + + - name: Source-based Dependencies [Ubuntu] + if: steps.cache-source-deps.outputs.cache-hit != 'true' && matrix.os == 'ubuntu-latest' + shell: bash + run: | + git clone -b ${Catch2_TAG} https://github.com/catchorg/Catch2.git + cd Catch2 + mkdir -p build + cd build + cmake -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install/deps \ + -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install/deps \ + -DBUILD_TESTING=OFF .. + cmake --build . --config ${{ matrix.build_type }} --target install + + # =================== + # CMAKE-BASED PROJECT + # =================== + + - name: Configure [Windows] + if: matrix.os == 'windows-latest' + shell: bash + run: | + mkdir -p build + cd build + cmake -A x64 -DCMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install/deps \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install \ + -DBUILD_TESTING:BOOL=ON .. + + - name: Configure [Ubuntu] + if: matrix.os == 'ubuntu-latest' + shell: bash + run: | + mkdir -p build + cd build + cmake -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install/deps \ + -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DBUILD_TESTING:BOOL=ON \ + -DOSQPEIGEN_RUN_Valgrind_tests:BOOL=ON .. + + - name: Configure [macOS] + if: matrix.os == 'macOS-latest' + shell: bash + run: | + mkdir -p build + cd build + cmake -DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install/deps \ + -DCMAKE_INSTALL_PREFIX=${GITHUB_WORKSPACE}/install \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DBUILD_TESTING:BOOL=ON .. + + - name: Build + shell: bash + run: | + cd build + export PATH=$PATH:/d/a/osqp-eigen/osqp-eigen/install/bin:/d/a/osqp-eigen/osqp-eigen/install/deps/bin:/c/robotology/vcpkg/installed/x64-windows/bin:/c/robotology/vcpkg/installed/x64-windows/debug/bin + cmake --build . --config ${{ matrix.build_type }} + + - name: Test + shell: bash + run: | + cd build + export PATH=$PATH:/d/a/osqp-eigen/osqp-eigen/install/bin:/d/a/osqp-eigen/osqp-eigen/install/deps/bin:/c/robotology/vcpkg/installed/x64-windows/bin:/c/robotology/vcpkg/installed/x64-windows/debug/bin + ctest --output-on-failure -C ${{ matrix.build_type }} . diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..4e3a13e --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,50 @@ +name: GitHub Pages + +on: + push: + branches: + - 'master' + +jobs: + docs: + name: "Deploy" + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@master + - uses: webfactory/ssh-agent@v0.3.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_GH_PAGES }} + - name: Dependencies + run: | + sudo apt update + sudo apt install -y doxygen graphviz + + - name: Check remote + run: git ls-remote --heads --exit-code https://github.com/${{ github.repository }}.git gh-pages + + - name: Configure Git + run: | + git config --global push.default upstream + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + + - name: Clone and rebase + run: | + cd ${GITHUB_WORKSPACE} + git clone git@github.com:${{ github.repository }}.git gh-pages + cd gh-pages + git checkout gh-pages + git rebase master + + - name: Build Doxygen + run: | + cd ${GITHUB_WORKSPACE}/gh-pages/doxygen + doxygen ./generate.txt + + - name: Commit and push + run: | + cd ${GITHUB_WORKSPACE}/gh-pages + git add . + git commit --amend --no-edit + git push --force-with-lease diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 19a3c66..0000000 --- a/.travis.yml +++ /dev/null @@ -1,95 +0,0 @@ -dist: trusty -language: cpp -services: docker - -os: linux - -cache: - directories: - - $HOME/.ccache - - $HOME/Library/Caches/Homebrew - -stages: - - test # Default stage with job matrix - - osx - -compiler: - - gcc - -env: - global: - - TRAVIS_CMAKE_GENERATOR="Unix Makefiles" - matrix: - - TRAVIS_BUILD_TYPE="Release" UBUNTU="xenial" - - TRAVIS_BUILD_TYPE="Debug" UBUNTU="xenial" - - TRAVIS_BUILD_TYPE="Release" UBUNTU="bionic" - - TRAVIS_BUILD_TYPE="Debug" UBUNTU="bionic" - -# =================== -# STAGE: test (linux) -# =================== - -before_script: - - docker pull ubuntu:$UBUNTU - -script: - - >- - docker run -it \ - -v $TRAVIS_BUILD_DIR:$TRAVIS_BUILD_DIR \ - -v $HOME/.ccache:$HOME/.ccache \ - -w $TRAVIS_BUILD_DIR \ - --env CC \ - --env CXX \ - --env TRAVIS_BUILD_DIR \ - --env TRAVIS_BUILD_TYPE \ - --env TRAVIS_CMAKE_GENERATOR \ - ubuntu:$UBUNTU \ - sh .ci/install_debian_and_script.sh - -# ========== -# STAGE: osx -# ========== - -stage_osx: - install: &osx_install - # Setup ccache - - brew update - - brew install ccache - - export PATH="/usr/local/opt/ccache/libexec:$PATH" - # Install dependencies - - brew install eigen pkg-config - script: &osx_script - - cd $TRAVIS_BUILD_DIR/.ci - - sh ./script.sh - -# ====================== -# BUILD JOBS FROM STAGES -# ====================== - -jobs: - include: - # --------- - # STAGE OSX - # --------- - - &osx_template - stage: osx - os: osx - osx_image: xcode9.4 - before_install: skip - install: *osx_install - before_script: skip - script: *osx_script - after_failure: skip - after_success: skip - after_script: skip - env: - TRAVIS_CMAKE_GENERATOR="Xcode" - TRAVIS_BUILD_TYPE="Debug" - - <<: *osx_template - compiler: clang - env: - TRAVIS_CMAKE_GENERATOR="Unix Makefiles" - TRAVIS_BUILD_TYPE="Debug" - -notifications: - email: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 2494459..cb86134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,10 @@ # CopyPolicy: Released under the terms of the LGPLv2.1 or later # Set cmake mimimun version -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.5) + +project(OsqpEigen + VERSION 0.6.1) # ouptut paths set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") @@ -20,14 +23,14 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) option(BUILD_SHARED_LIBS "Build libraries as shared as opposed to static" ON) -# Enable C++14 +# Disable C and C++ compiler extensions. +# C/CXX_EXTENSIONS are ON by default to allow the compilers to use extended +# variants of the C/CXX language. +# However, this could expose cross-platform bugs in user code or in the headers +# of third-party dependencies and thus it is strongly suggested to turn +# extensions off. +set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED 11) - -project(OsqpEigen - LANGUAGES CXX - VERSION 0.6.0) # add GNU dirs include(GNUInstallDirs) @@ -53,12 +56,21 @@ if(NOT CMAKE_CONFIGURATION_TYPES) endif() endif() -# find Eigen -find_package(Eigen3 REQUIRED) -include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR}) +option(BUILD_TESTING "Create tests using CMake" OFF) +include(CTest) -# add OSQP library -find_package(osqp REQUIRED) +# Check OsqpEigen dependencies, find necessary libraries. +include(OsqpEigenDependencies) + +# Set default build type to "Release" in single-config generators +if(NOT CMAKE_CONFIGURATION_TYPES) + if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Choose the type of build, recommanded options are: Debug or Release" FORCE) + endif() + set(OSQPEIGEN_BUILD_TYPES "Debug" "Release" "MinSizeRel" "RelWithDebInfo") + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${OSQPEIGEN_BUILD_TYPES}) +endif() set(LIBRARY_TARGET_NAME OsqpEigen) @@ -83,8 +95,7 @@ add_library(${LIBRARY_TARGET_NAME} ${${LIBRARY_TARGET_NAME}_SRC} ${${LIBRARY_TAR target_include_directories(${LIBRARY_TARGET_NAME} PUBLIC "$" "$") -target_link_libraries(${LIBRARY_TARGET_NAME} PRIVATE Eigen3::Eigen) -target_link_libraries(${LIBRARY_TARGET_NAME} PUBLIC osqp::osqp) +target_link_libraries(${LIBRARY_TARGET_NAME} PUBLIC osqp::osqp Eigen3::Eigen) add_library(OsqpEigen::OsqpEigen ALIAS OsqpEigen) @@ -92,9 +103,11 @@ set_target_properties(${LIBRARY_TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} PUBLIC_HEADER "${${LIBRARY_TARGET_NAME}_HDR}") +target_compile_features(${LIBRARY_TARGET_NAME} PUBLIC cxx_std_14) + # List exported CMake package dependencies set(OSQP_EIGEN_EXPORTED_DEPENDENCIES "") -list(APPEND OSQP_EIGEN_EXPORTED_DEPENDENCIES osqp) +list(APPEND OSQP_EIGEN_EXPORTED_DEPENDENCIES osqp "Eigen3 CONFIG") install(TARGETS ${LIBRARY_TARGET_NAME} EXPORT ${PROJECT_NAME} @@ -106,6 +119,7 @@ install(TARGETS ${LIBRARY_TARGET_NAME} include(InstallBasicPackageFiles) install_basic_package_files(${PROJECT_NAME} + NAMESPACE OsqpEigen:: VERSION ${${PROJECT_NAME}_VERSION} COMPATIBILITY SameMajorVersion VARS_PREFIX ${PROJECT_NAME} @@ -113,12 +127,7 @@ install_basic_package_files(${PROJECT_NAME} DEPENDENCIES ${OSQP_EIGEN_EXPORTED_DEPENDENCIES}) ## Testing -option(BUILD_TESTING "Create tests using CMake" OFF) -option(RUN_VALGRIND_TESTS "Run tests with Valgrind" FALSE) -mark_as_advanced(RUN_VALGRIND_TESTS) -include(CTest) -if(BUILD_TESTING) - add_subdirectory(tests) -endif() +include(AddOsqpEigenUnitTest) +add_subdirectory(tests) include(AddUninstallTarget) diff --git a/README.md b/README.md index 5d4129d..f468394 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,22 @@ -# osqp-eigen -Simple C++ wrapper for [osqp](http://osqp.readthedocs.io/en/latest/index.html) library. +

+

osqp-eigen

+

+ +

+C++ Standard +Size + + +

-| System | Status | -| ------------- | :-------------: | -| Linux / OSX | [![Build Status](https://travis-ci.org/robotology/osqp-eigen.svg?branch=master)](https://travis-ci.org/robotology/osqp-eigen) | -| Windows | [![Build status](https://ci.appveyor.com/api/projects/status/1uecfmyvxb2dujt9/branch/master?svg=true)](https://ci.appveyor.com/project/robotology/osqp-eigen/branch/master) | +Simple C++ wrapper for [osqp](http://osqp.readthedocs.io/en/latest/index.html) library. ## Dependeces - [osqp](http://osqp.readthedocs.io/en/latest/index.html) of course :smile:; - [Eigen3](http://eigen.tuxfamily.org/index.php?title=Main_Page); - [cmake](https://cmake.org/); -- [googletest](https://github.com/google/googletest) (only for testing). +- [Catch2](https://github.com/catchorg/Catch2) (only for testing). ## Build the library and the application ### Linux / macOs diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ad07af6..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,43 +0,0 @@ -version: 1.0.{build} - -clone_folder: c:\projects\osqp-eigen - -environment: - Eigen3_DIR: C:/Program Files (x86)/Eigen/lib/cmake/eigen3/ - -os: - - Visual Studio 2015 - - Visual Studio 2017 - -install: - # Check env variables - - cmd: echo CMAKE_PREFIX_PATH %CMAKE_PREFIX_PATH% - - cmd: echo PATH %PATH% -build: - -build_script: - # download and build osqp - - cd c:\projects - - git clone --recursive --depth 1 https://github.com/oxfordcontrol/osqp.git - - cd osqp - - md build - - cd build - - cmake .. - - cmake --build . --config Release - - cmake --build . --config Release --target INSTALL - # download and install eigen3 - - cd c:\projects - - hg clone https://bitbucket.org/eigen/eigen - - cd eigen - - hg checkout 3.3-beta2 - - md build - - cd build - - cmake .. - - cmake --build . --config Release --target INSTALL - # compile osqp-eigen - - cd c:\projects\osqp-eigen - - md build - - cd build - - cmake .. -DEIGEN3_INCLUDE_DIR="C:/Program Files (x86)/Eigen/include/eigen3" - - cmake --build . --config Release - - cmake --build . --config Release --target INSTALL diff --git a/cmake/AddOsqpEigenUnitTest.cmake b/cmake/AddOsqpEigenUnitTest.cmake new file mode 100644 index 0000000..7cbae44 --- /dev/null +++ b/cmake/AddOsqpEigenUnitTest.cmake @@ -0,0 +1,73 @@ +# Copyright (C) 2020 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. +# +# This software may be modified and distributed under the terms of the +# BSD-3-Clause license. See the accompanying LICENSE file for details. + +osqpeigen_dependent_option(OSQPEIGEN_COMPILE_tests + "Compile tests?" ON + "OSQPEIGEN_HAS_Catch2;BUILD_TESTING" OFF) + +osqpeigen_dependent_option(OSQPEIGEN_RUN_Valgrind_tests + "Run Valgrind tests?" OFF + "OSQPEIGEN_COMPILE_tests;VALGRIND_FOUND" OFF) + +if (OSQPEIGEN_RUN_Valgrind_tests) + set(CTEST_MEMORYCHECK_COMMAND ${VALGRIND_PROGRAM}) + set(MEMORYCHECK_COMMAND ${VALGRIND_PROGRAM}) + if (APPLE) + set(MEMORYCHECK_SUPPRESSIONS "--suppressions=${PROJECT_SOURCE_DIR}/cmake/valgrind-macos.supp") + else () + set(MEMORYCHECK_SUPPRESSIONS "") + endif () + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1 ${MEMORYCHECK_SUPPRESSIONS}" CACHE STRING "Options to pass to the memory checker") + mark_as_advanced(MEMORYCHECK_COMMAND_OPTIONS) + set(MEMCHECK_COMMAND_COMPLETE "${MEMORYCHECK_COMMAND} ${MEMORYCHECK_COMMAND_OPTIONS}") + separate_arguments(MEMCHECK_COMMAND_COMPLETE) +endif() + +if (OSQPEIGEN_COMPILE_tests) + configure_file(cmake/Catch2Main.cpp.in ${CMAKE_BINARY_DIR}/Testing/Catch2Main.cpp) + add_library(CatchTestMain ${CMAKE_BINARY_DIR}/Testing/Catch2Main.cpp) + target_link_libraries(CatchTestMain PUBLIC Catch2::Catch2) +endif() + + +function(add_osqpeigen_test) + + if(OSQPEIGEN_COMPILE_tests) + + set(options) + set(oneValueArgs NAME) + set(multiValueArgs SOURCES LINKS COMPILE_DEFINITIONS) + + set(prefix "osqp_eigen") + + cmake_parse_arguments(${prefix} + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN}) + + set(name ${${prefix}_NAME}) + set(unit_test_files ${${prefix}_SOURCES}) + + set(targetname ${name}UnitTests) + add_executable(${targetname} + "${unit_test_files}") + + target_link_libraries(${targetname} PRIVATE CatchTestMain ${${prefix}_LINKS}) + target_compile_definitions(${targetname} PRIVATE CATCH_CONFIG_FAST_COMPILE CATCH_CONFIG_DISABLE_MATCHERS) + target_compile_features(${targetname} PUBLIC cxx_std_14) + + add_test(NAME ${targetname} COMMAND ${targetname}) + target_compile_definitions(${targetname} PRIVATE ${${prefix}_COMPILE_DEFINITIONS}) + + if(OSQPEIGEN_RUN_Valgrind_tests) + add_test(NAME memcheck_${targetname} COMMAND ${MEMCHECK_COMMAND_COMPLETE} $) + endif() + + endif() + +endfunction() diff --git a/cmake/Catch2Main.cpp.in b/cmake/Catch2Main.cpp.in new file mode 100644 index 0000000..dc9329e --- /dev/null +++ b/cmake/Catch2Main.cpp.in @@ -0,0 +1,9 @@ +/** + * @file Catch2Main.cpp(.in) + * @authors Stefano Dafarra + * @copyright 2020 Istituto Italiano di Tecnologia (IIT). This software may be modified and + * distributed under the terms of the GNU Lesser General Public License v2.1 or any later version. + */ + +#define CATCH_CONFIG_MAIN +#include diff --git a/cmake/FindEigen3.cmake b/cmake/FindEigen3.cmake deleted file mode 100644 index aa05f75..0000000 --- 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(NOT Eigen3_FIND_VERSION_MAJOR) - if(NOT Eigen3_FIND_VERSION_MINOR) - set(Eigen3_FIND_VERSION_MINOR 91) - endif(NOT Eigen3_FIND_VERSION_MINOR) - if(NOT Eigen3_FIND_VERSION_PATCH) - set(Eigen3_FIND_VERSION_PATCH 0) - endif(NOT Eigen3_FIND_VERSION_PATCH) - - set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") -endif(NOT Eigen3_FIND_VERSION) - -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(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) - set(EIGEN3_VERSION_OK TRUE) - endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) - - 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(NOT EIGEN3_VERSION_OK) -endmacro(_eigen3_check_version) - -if (EIGEN3_INCLUDE_DIR) - - # in cache already - _eigen3_check_version() - set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) - set(Eigen3_FOUND ${EIGEN3_VERSION_OK}) - -else (EIGEN3_INCLUDE_DIR) - - # 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(NOT EIGEN3_INCLUDE_DIR) - - if(EIGEN3_INCLUDE_DIR) - _eigen3_check_version() - endif(EIGEN3_INCLUDE_DIR) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) - - mark_as_advanced(EIGEN3_INCLUDE_DIR) - -endif(EIGEN3_INCLUDE_DIR) - -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() \ No newline at end of file diff --git a/cmake/OsqpEigenDependencies.cmake b/cmake/OsqpEigenDependencies.cmake new file mode 100644 index 0000000..53a5561 --- /dev/null +++ b/cmake/OsqpEigenDependencies.cmake @@ -0,0 +1,21 @@ +# Copyright (C) 2019 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. +# +# This software may be modified and distributed under the terms of the +# BSD-3-Clause license. See the accompanying LICENSE file for details. + +include(OsqpEigenFindOptionalDependencies) + +#--------------------------------------------- +## Required Dependencies +find_package(Eigen3 3.2.92 REQUIRED) +find_package(osqp REQUIRED) + +#--------------------------------------------- +## Optional Dependencies +find_package(Catch2 QUIET) +checkandset_optional_dependency(Catch2) + +find_package(VALGRIND QUIET) +checkandset_optional_dependency(VALGRIND) diff --git a/cmake/OsqpEigenFindOptionalDependencies.cmake b/cmake/OsqpEigenFindOptionalDependencies.cmake new file mode 100644 index 0000000..4bcb6e6 --- /dev/null +++ b/cmake/OsqpEigenFindOptionalDependencies.cmake @@ -0,0 +1,115 @@ +# Copyright (C) 2019 Istituto Italiano di Tecnologia (IIT). All rights reserved. +# This software may be modified and distributed under the terms of the +# GNU Lesser General Public License v2.1 or any later version. +# +# This software may be modified and distributed under the terms of the +# BSD-3-Clause license. See the accompanying LICENSE file for details. + +# This module checks if all the dependencies are installed and if the +# dependencies to build some parts are satisfied. +# For every dependency, it creates the following variables: +# +# OSQPEIGEN_USE_${Package}: Can be disabled by the user if he doesn't want to use that +# dependency. +# OSQPEIGEN_HAS_${Package}: Internal flag. It should be used to check if a part of +# OSQPEIGEN should be built. It is on if OSQPEIGEN_USE_${Package} is +# on and either the package was found or will be built. +# OSQPEIGEN_BUILD_${Package}: Internal flag. Used to check if OSQPEIGEN has to build an +# external package. +# OSQPEIGEN_BUILD_DEPS_${Package}: Internal flag. Used to check if dependencies +# required to build the package are available. +# OSQPEIGEN_HAS_SYSTEM_${Package}: Internal flag. Used to check if the package is +# available on the system. +# OSQPEIGEN_USE_SYSTEM_${Package}: This flag is shown only for packages in the +# extern folder that were also found on the system +# (TRUE by default). If this flag is enabled, the +# system installed library will be used instead of +# the version shipped within the framework. + + +include(CMakeDependentOption) + +# Check if a package is installed and set some cmake variables +macro(checkandset_optional_dependency package) + + set(PREFIX "OSQPEIGEN") + + string(TOUPPER ${package} PKG) + + # OSQPEIGEN_HAS_SYSTEM_${package} + if(${package}_FOUND OR ${PKG}_FOUND) + set(${PREFIX}_HAS_SYSTEM_${package} TRUE) + else() + set(${PREFIX}_HAS_SYSTEM_${package} FALSE) + endif() + + # OSQPEIGEN_USE_${package} + cmake_dependent_option(${PREFIX}_USE_${package} "Use package ${package}" TRUE + ${PREFIX}_HAS_SYSTEM_${package} FALSE) + mark_as_advanced(${PREFIX}_USE_${package}) + + # OSQPEIGEN_USE_SYSTEM_${package} + set(${PREFIX}_USE_SYSTEM_${package} ${${PREFIX}_USE_${package}} CACHE INTERNAL "Use system-installed ${package}, rather than a private copy (recommended)" FORCE) + if(NOT "${package}" STREQUAL "${PKG}") + unset(${PREFIX}_USE_SYSTEM_${PKG} CACHE) + endif() + + # OSQPEIGEN_HAS_${package} + if(${${PREFIX}_HAS_SYSTEM_${package}}) + set(${PREFIX}_HAS_${package} ${${PREFIX}_USE_${package}}) + else() + set(${PREFIX}_HAS_${package} FALSE) + endif() + +endmacro() + +macro(OSQPEIGEN_DEPENDENT_OPTION _option _doc _default _deps _force) + + if(DEFINED ${_option}) + get_property(_option_strings_set CACHE ${_option} PROPERTY STRINGS SET) + if(_option_strings_set) + # If the user thinks he is smarter than the machine, he deserves an error + get_property(_option_strings CACHE ${_option} PROPERTY STRINGS) + list(GET _option_strings 0 _option_strings_first) + string(REGEX REPLACE ".+\"(.+)\".+" "\\1" _option_strings_first "${_option_strings_first}") + list(LENGTH _option_strings _option_strings_length) + math(EXPR _option_strings_last_index "${_option_strings_length} - 1") + list(GET _option_strings ${_option_strings_last_index} _option_strings_last) + if("${${_option}}" STREQUAL "${_option_strings_last}") + message(SEND_ERROR "That was a trick, you cannot outsmart me! I will never let you win! ${_option} stays OFF until I say so! \"${_option_strings_first}\" is needed to enable ${_option}. Now stop bothering me, and install your dependencies, if you really want to enable this option.") + endif() + unset(${_option} CACHE) + endif() + endif() + + cmake_dependent_option(${_option} "${_doc}" ${_default} "${_deps}" ${_force}) + + unset(_missing_deps) + foreach(_dep ${_deps}) + string(REGEX REPLACE " +" ";" _depx "${_dep}") + if(NOT (${_depx})) + list(APPEND _missing_deps "${_dep}") + endif() + endforeach() + + if(DEFINED _missing_deps) + set(${_option}_disable_reason " (dependencies unsatisfied: \"${_missing_deps}\")") + # Set a value that can be visualized on ccmake and on cmake-gui, but + # still evaluates to false + set(${_option} "OFF - Dependencies unsatisfied: '${_missing_deps}' - ${_option}-NOTFOUND" CACHE STRING "${_option_doc}" FORCE) + string(REPLACE ";" "\;" _missing_deps "${_missing_deps}") + set_property(CACHE ${_option} + PROPERTY STRINGS "OFF - Dependencies unsatisfied: '${_missing_deps}' - ${_option}-NOTFOUND" + "OFF - You can try as much as you want, but '${_missing_deps}' is needed to enable ${_option} - ${_option}-NOTFOUND" + "OFF - Are you crazy or what? '${_missing_deps}' is needed to enable ${_option} - ${_option}-NOTFOUND" + "OFF - Didn't I already tell you that '${_missing_deps}' is needed to enable ${_option}? - ${_option}-NOTFOUND" + "OFF - Stop it! - ${_option}-NOTFOUND" + "OFF - This is insane! Leave me alone! - ${_option}-NOTFOUND" + "ON - All right, you win. The option is enabled. Are you happy now? You just broke the build.") + # Set non-cache variable that will override the value in current scope + # For parent scopes, the "-NOTFOUND ensures that the variable still + # evaluates to false + set(${_option} ${_force}) + endif() + +endmacro() diff --git a/include/OsqpEigen/Settings.hpp b/include/OsqpEigen/Settings.hpp index 9e33c93..a2dab09 100644 --- a/include/OsqpEigen/Settings.hpp +++ b/include/OsqpEigen/Settings.hpp @@ -169,6 +169,12 @@ namespace OsqpEigen */ void setWarmStart(const bool warmStart); + /** + * Set the maximum number of seconds allowed to solve the problem. + * @param timeLimit is the time limit in seconds. If 0, then disabled. + */ + void setTimeLimit(const double timeLimit); + /** * Get a pointer to Settings struct. * @return a const pointer to OSQPSettings struct. diff --git a/include/OsqpEigen/Solver.hpp b/include/OsqpEigen/Solver.hpp index 8875ba3..3f483af 100644 --- a/include/OsqpEigen/Solver.hpp +++ b/include/OsqpEigen/Solver.hpp @@ -37,6 +37,7 @@ namespace OsqpEigen Eigen::Matrix m_primalVariables; Eigen::Matrix m_dualVariables; Eigen::VectorXd m_solution; + Eigen::VectorXd m_dualSolution; std::vector m_hessianNewIndices; std::vector m_hessianNewValues; @@ -123,6 +124,12 @@ namespace OsqpEigen */ const Eigen::VectorXd &getSolution(); + /** + * Get the dual optimization problem solution. + * @return an Eigen::Vector contating the optimization result. + */ + const Eigen::VectorXd &getDualSolution(); + /** * Update the linear part of the cost function (Gradient). * @param gradient is the Gradient vector. @@ -224,6 +231,12 @@ namespace OsqpEigen * @return the pointer to Data object. */ const std::unique_ptr& data() const; + + /** + * Get the pointer to the OSQP workspace. + * @return the pointer to Workspace object. + */ + const std::unique_ptr>& workspace() const; }; #include diff --git a/include/OsqpEigen/Solver.tpp b/include/OsqpEigen/Solver.tpp index 26e1647..d0325eb 100644 --- a/include/OsqpEigen/Solver.tpp +++ b/include/OsqpEigen/Solver.tpp @@ -89,7 +89,11 @@ bool OsqpEigen::Solver::updateHessianMatrix(const Eigen::SparseCompressedBase colMajorCopy; //Copying into a new sparse matrix to be sure to use a CSC matrix + colMajorCopy = eigenSparseMatrix; //This may perform merory allocation, but this is already the case for allocating the osqpSparseMatrix // get number of row, columns and nonZeros from Eigen SparseMatrix - c_int rows = eigenSparseMatrix.rows(); - c_int cols = eigenSparseMatrix.cols(); - c_int numberOfNonZeroCoeff = eigenSparseMatrix.nonZeros(); + c_int rows = colMajorCopy.rows(); + c_int cols = colMajorCopy.cols(); + c_int numberOfNonZeroCoeff = colMajorCopy.nonZeros(); // get innerr and outer index - const int* innerIndexPtr = eigenSparseMatrix.innerIndexPtr(); - const int* outerIndexPtr = eigenSparseMatrix.outerIndexPtr(); - const int* innerNonZerosPtr = eigenSparseMatrix.innerNonZeroPtr(); - - // get nonzero values - auto valuePtr = eigenSparseMatrix.valuePtr(); + const int* outerIndexPtr = colMajorCopy.outerIndexPtr(); + const int* innerNonZerosPtr = colMajorCopy.innerNonZeroPtr(); // instantiate csc matrix // MEMORY ALLOCATION!! @@ -35,7 +33,7 @@ bool OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(const Eigen::SparseCo int innerOsqpPosition = 0; for(int k = 0; k < cols; k++) { - if (eigenSparseMatrix.isCompressed()) { + if (colMajorCopy.isCompressed()) { osqpSparseMatrix->p[k] = static_cast(outerIndexPtr[k]); } else { if (k == 0) { @@ -44,7 +42,7 @@ bool OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(const Eigen::SparseCo osqpSparseMatrix->p[k] = osqpSparseMatrix->p[k-1] + innerNonZerosPtr[k-1]; } } - for (typename Eigen::SparseCompressedBase::InnerIterator it(eigenSparseMatrix,k); it; ++it) { + for (typename Eigen::SparseMatrix::InnerIterator it(colMajorCopy,k); it; ++it) { osqpSparseMatrix->i[innerOsqpPosition] = static_cast(it.row()); osqpSparseMatrix->x[innerOsqpPosition] = static_cast(it.value()); innerOsqpPosition++; diff --git a/src/Settings.cpp b/src/Settings.cpp index 01daca0..8958801 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -6,6 +6,9 @@ */ #include +#include + +template inline void unused(Args&&...) {} OsqpEigen::Settings::Settings() { @@ -40,22 +43,47 @@ void OsqpEigen::Settings::setScaling(const int scaling) void OsqpEigen::Settings::setAdaptiveRho(const bool isRhoStepSizeAdactive) { +# if EMBEDDED != 1 m_settings->adaptive_rho = (c_int)isRhoStepSizeAdactive; +# else + std::cerr<< "[OsqpEigen::Settings::setAdaptiveRho] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(isRhoStepSizeAdactive); +# endif } void OsqpEigen::Settings::setAdaptiveRhoInterval(const int rhoInterval) { +# if EMBEDDED != 1 m_settings->adaptive_rho_interval = (c_int)rhoInterval; +# else + std::cerr<< "[OsqpEigen::Settings::setAdaptiveRhoInterval] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(rhoInterval); +# endif } void OsqpEigen::Settings::setAdaptiveRhoTolerance(const double adaptiveRhoTolerance) { +# if EMBEDDED != 1 m_settings->adaptive_rho_tolerance = (c_float)adaptiveRhoTolerance; +# else + std::cerr<< "[OsqpEigen::Settings::setAdaptiveRhoTolerance] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(adaptiveRhoTolerance); +# endif } void OsqpEigen::Settings::setAdaptiveRhoFraction(const double adaptiveRhoFraction) { +# if EMBEDDED != 1 +# ifdef PROFILING m_settings->adaptive_rho_fraction = (c_float)adaptiveRhoFraction; +# else + std::cerr<< "[OsqpEigen::Settings::setAdaptiveRhoFraction] OSPQ has been set without PROFILING, hence this setting is disabled." << std::endl; + unused(adaptiveRhoFraction); +# endif //ifdef PROFILING +# else //# if EMBEDDED != 1 + std::cerr<< "[OsqpEigen::Settings::setAdaptiveRhoFraction] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(adaptiveRhoFraction); +# endif //# if EMBEDDED != 1 } void OsqpEigen::Settings::setMaxIteraction(const int maxIteration) { @@ -94,22 +122,42 @@ void OsqpEigen::Settings::setLinearSystemSolver(const int linsysSolver) void OsqpEigen::Settings::setDelta(const double delta) { +# ifndef EMBEDDED m_settings->delta = (c_float)delta; +# else + std::cerr<< "[OsqpEigen::Settings::setDelta] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(delta); +# endif } void OsqpEigen::Settings::setPolish(const bool polish) { +# ifndef EMBEDDED m_settings->polish = (c_int)polish; +# else + std::cerr<< "[OsqpEigen::Settings::setPolish] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(polish); +# endif } void OsqpEigen::Settings::setPolishRefineIter(const int polishRefineIter) { +# ifndef EMBEDDED m_settings->polish_refine_iter = (c_int)polishRefineIter; +# else + std::cerr<< "[OsqpEigen::Settings::setPolishRefineIter] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(polishRefineIter); +# endif } void OsqpEigen::Settings::setVerbosity(const bool isVerbose) { +#ifndef EMBEDDED m_settings->verbose = (c_int)isVerbose; +#else + std::cerr<< "[OsqpEigen::Settings::setVerbosity] OSPQ has been set to EMBEDDED, hence this setting is disabled." << std::endl; + unused(isVerbose); +#endif } void OsqpEigen::Settings::setScaledTerimination(const bool scaledTermination) @@ -127,6 +175,16 @@ void OsqpEigen::Settings::setWarmStart(const bool warmStart) m_settings->warm_start = (c_int)warmStart; } +void OsqpEigen::Settings::setTimeLimit(const double timeLimit) +{ +# ifdef PROFILING + m_settings->time_limit = (c_float)timeLimit; +# else + std::cerr<< "[OsqpEigen::Settings::setTimeLimit] OSPQ has been set without PROFILING, hence this setting is disabled." << std::endl; + unused(timeLimit); +# endif +} + OSQPSettings* const & OsqpEigen::Settings::getSettings() const { return m_settings; diff --git a/src/Solver.cpp b/src/Solver.cpp index 5426b9f..e1961aa 100644 --- a/src/Solver.cpp +++ b/src/Solver.cpp @@ -140,6 +140,15 @@ const Eigen::VectorXd &OsqpEigen::Solver::getSolution() return m_solution; } +const Eigen::VectorXd &OsqpEigen::Solver::getDualSolution() +{ + // copy data from an array to Eigen vector + c_float* solution = m_workspace->solution->y; + m_dualSolution = Eigen::Map(solution, m_workspace->data->m, 1); + + return m_dualSolution; +} + const std::unique_ptr& OsqpEigen::Solver::settings() const { return m_settings; @@ -150,6 +159,11 @@ const std::unique_ptr& OsqpEigen::Solver::data() const return m_data; } +const std::unique_ptr>& OsqpEigen::Solver::workspace() const +{ + return m_workspace; +} + bool OsqpEigen::Solver::updateGradient(const Eigen::Ref>& gradient) { // check if the dimension of the gradient is correct diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68f0ab1..7665f2b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,64 +1,29 @@ # Authors: Giulio Romualdi # CopyPolicy: Released under the terms of the LGPLv2.1 or later -cmake_minimum_required(VERSION 3.1) - -set (CMAKE_CXX_STANDARD 11) - -project(OsqpEigen-Test) - -if(NOT TARGET OsqpEigen::OsqpEigen) - find_package(osqp REQUIRED) - find_package(OsqpEigen REQUIRED) - find_package(Eigen3 REQUIRED) - include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR}) -endif() - -find_package(GTest REQUIRED) -include_directories(${GTEST_INCLUDE_DIRS}) - -# adding support for checking the tests with valgrind -if(RUN_VALGRIND_TESTS) - find_package(VALGRIND REQUIRED) - if(VALGRIND_FOUND) - set(CTEST_MEMORYCHECK_COMMAND ${VALGRIND_PROGRAM}) - set(MEMORYCHECK_COMMAND ${VALGRIND_PROGRAM}) - if (APPLE) - set(MEMORYCHECK_SUPPRESSIONS "--suppressions=${PROJECT_SOURCE_DIR}/cmake/valgrind-macos.supp") - else () - set(MEMORYCHECK_SUPPRESSIONS "") - endif () - set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1 ${MEMORYCHECK_SUPPRESSIONS}" CACHE STRING "Options to pass to the memory checker") - mark_as_advanced(MEMORYCHECK_COMMAND_OPTIONS) - set(MEMCHECK_COMMAND_COMPLETE "${MEMORYCHECK_COMMAND} ${MEMORYCHECK_COMMAND_OPTIONS}") - separate_arguments(MEMCHECK_COMMAND_COMPLETE) - endif() -endif() - -macro(add_osqpeigen_test classname) - set(testsrc ${classname}Test.cpp) - set(testbinary ${classname}Test) - set(testname ${classname}Test) - add_executable(${testbinary} ${testsrc}) - target_link_libraries(${testbinary} PRIVATE OsqpEigen::OsqpEigen osqp::osqp Eigen3::Eigen ${GTEST_LIBRARIES} pthread) - add_test(NAME ${testname} COMMAND ${testbinary}) - - if(RUN_VALGRIND_TESTS) - add_test(NAME memcheck_${testname} COMMAND ${MEMCHECK_COMMAND_COMPLETE} $) - endif() -endmacro() - -# QPTest -add_osqpeigen_test(SparseMatrix) - -# QPTest -add_osqpeigen_test(QP) - -# Update matrix -add_osqpeigen_test(UpdateMatrices) - -# MPCTest -add_osqpeigen_test(MPC) - -# MPCTest update matrix -add_osqpeigen_test(MPCUpdateMatrices) +add_osqpeigen_test( + NAME SparseMatrix + SOURCES SparseMatrixTest.cpp + LINKS OsqpEigen::OsqpEigen) + +add_osqpeigen_test( + NAME QP + SOURCES QPTest.cpp + LINKS OsqpEigen::OsqpEigen) + +add_osqpeigen_test( + NAME UpdateMatrices + SOURCES UpdateMatricesTest.cpp + LINKS OsqpEigen::OsqpEigen) + +add_osqpeigen_test( + NAME MPC + SOURCES MPCTest.cpp + LINKS OsqpEigen::OsqpEigen + COMPILE_DEFINITIONS _USE_MATH_DEFINES) + +add_osqpeigen_test( + NAME MPCUpdateMatrices + SOURCES MPCUpdateMatricesTest.cpp + LINKS OsqpEigen::OsqpEigen + COMPILE_DEFINITIONS _USE_MATH_DEFINES) diff --git a/tests/MPCTest.cpp b/tests/MPCTest.cpp index 45cab42..db2a3b9 100644 --- a/tests/MPCTest.cpp +++ b/tests/MPCTest.cpp @@ -5,8 +5,8 @@ * @date 2018 */ -// gtest -#include +// Catch2 +#include // OsqpEigen #include @@ -14,8 +14,9 @@ // eigen #include -#include +#include #include +#include // colors #define ANSI_TXT_GRN "\033[0;32m" @@ -213,7 +214,7 @@ double getErrorNorm(const Eigen::Matrix &x, } -TEST(MPCTest,) +TEST_CASE("MPCTest") { // open the ofstream std::ofstream dataStream; @@ -272,14 +273,14 @@ TEST(MPCTest,) // set the initial data of the QP solver solver.data()->setNumberOfVariables(12 * (mpcWindow + 1) + 4 * mpcWindow); solver.data()->setNumberOfConstraints(2 * 12 * (mpcWindow + 1) + 4 * mpcWindow); - ASSERT_TRUE(solver.data()->setHessianMatrix(hessian)); - ASSERT_TRUE(solver.data()->setGradient(gradient)); - ASSERT_TRUE(solver.data()->setLinearConstraintsMatrix(linearMatrix)); - ASSERT_TRUE(solver.data()->setLowerBound(lowerBound)); - ASSERT_TRUE(solver.data()->setUpperBound(upperBound)); + REQUIRE(solver.data()->setHessianMatrix(hessian)); + REQUIRE(solver.data()->setGradient(gradient)); + REQUIRE(solver.data()->setLinearConstraintsMatrix(linearMatrix)); + REQUIRE(solver.data()->setLowerBound(lowerBound)); + REQUIRE(solver.data()->setUpperBound(upperBound)); // instantiate the solver - ASSERT_TRUE(solver.initSolver()); + REQUIRE(solver.initSolver()); // controller input and QPSolution vector Eigen::Vector4d ctr; @@ -296,7 +297,7 @@ TEST(MPCTest,) startTime = clock(); // solve the QP problem - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.solve()); // get the controller input QPSolution = solver.getSolution(); @@ -313,7 +314,7 @@ TEST(MPCTest,) // update the constraint bound updateConstraintVectors(x0, lowerBound, upperBound); - ASSERT_TRUE(solver.updateBounds(lowerBound, upperBound)); + REQUIRE(solver.updateBounds(lowerBound, upperBound)); endTime = clock(); @@ -326,12 +327,5 @@ TEST(MPCTest,) std::cout << COUT_GTEST_MGT << "Avarage time = " << avarageTime / numberOfSteps << " seconds." << ANSI_TXT_DFT << std::endl; - ASSERT_LE(getErrorNorm(x0, xRef), 0.001); -} - - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + REQUIRE(getErrorNorm(x0, xRef) <= 0.001); } diff --git a/tests/MPCUpdateMatricesTest.cpp b/tests/MPCUpdateMatricesTest.cpp index b249f93..248f19b 100644 --- a/tests/MPCUpdateMatricesTest.cpp +++ b/tests/MPCUpdateMatricesTest.cpp @@ -2,11 +2,11 @@ * @file UpdateMatricesTest.cpp * @author Giulio Romualdi * @copyright Released under the terms of the LGPLv2.1 or later, see LGPL.TXT - * @date 2018 + * @date 2020 */ -// gtest -#include +// Catch2 +#include // OsqpEigen #include @@ -14,6 +14,7 @@ // eigen #include +#include #include #include @@ -183,7 +184,7 @@ void updateConstraintVectors(const Eigen::Matrix &x0, upperBound.block(0,0,2,1) = -x0; } -TEST(MPCTest,) +TEST_CASE("MPCTest Update matrices") { // open the ofstream std::ofstream dataStream; @@ -236,14 +237,14 @@ TEST(MPCTest,) // set the initial data of the QP solver solver.data()->setNumberOfVariables(2 * (mpcWindow + 1) + 1 * mpcWindow); solver.data()->setNumberOfConstraints(2 * (mpcWindow + 1)); - ASSERT_TRUE(solver.data()->setHessianMatrix(hessian)); - ASSERT_TRUE(solver.data()->setGradient(gradient)); - ASSERT_TRUE(solver.data()->setLinearConstraintsMatrix(linearMatrix)); - ASSERT_TRUE(solver.data()->setLowerBound(lowerBound)); - ASSERT_TRUE(solver.data()->setUpperBound(upperBound)); + REQUIRE(solver.data()->setHessianMatrix(hessian)); + REQUIRE(solver.data()->setGradient(gradient)); + REQUIRE(solver.data()->setLinearConstraintsMatrix(linearMatrix)); + REQUIRE(solver.data()->setLowerBound(lowerBound)); + REQUIRE(solver.data()->setUpperBound(upperBound)); // instantiate the solver - ASSERT_TRUE(solver.initSolver()); + REQUIRE(solver.initSolver()); // controller input and QPSolution vector Eigen::VectorXd ctr; @@ -262,17 +263,17 @@ TEST(MPCTest,) setDynamicsMatrices(a, b, c, i * T); // update the constraint bound - ASSERT_TRUE(updateHessianMatrix(solver, Q, R, mpcWindow, i)); - ASSERT_TRUE(updateLinearConstraintsMatrix(solver, mpcWindow, i)); + REQUIRE(updateHessianMatrix(solver, Q, R, mpcWindow, i)); + REQUIRE(updateLinearConstraintsMatrix(solver, mpcWindow, i)); castMPCToQPGradient(Q, yRef, mpcWindow, i, gradient); - ASSERT_TRUE(solver.updateGradient(gradient)); + REQUIRE(solver.updateGradient(gradient)); updateConstraintVectors(x0, lowerBound, upperBound); - ASSERT_TRUE(solver.updateBounds(lowerBound, upperBound)); + REQUIRE(solver.updateBounds(lowerBound, upperBound)); // solve the QP problem - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.solve()); // get the controller input QPSolution = solver.getSolution(); @@ -299,10 +300,3 @@ TEST(MPCTest,) std::cout << COUT_GTEST_MGT << "Avarage time = " << avarageTime / numberOfSteps << " seconds." << ANSI_TXT_DFT << std::endl; } - - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/QPTest.cpp b/tests/QPTest.cpp index 6d385ec..ccb4703 100644 --- a/tests/QPTest.cpp +++ b/tests/QPTest.cpp @@ -2,15 +2,15 @@ * @file QPTest.cpp * @author Giulio Romualdi * @copyright Released under the terms of the LGPLv2.1 or later, see LGPL.TXT - * @date 2018 + * @date 2020 */ -// gtest -#include +// Catch2 +#include #include -TEST(QPProblem, ) +TEST_CASE("QPProblem") { Eigen::Matrix2d H; H << 4, 1, @@ -37,37 +37,22 @@ TEST(QPProblem, ) OsqpEigen::Solver solver; solver.settings()->setVerbosity(false); - ASSERT_FALSE(solver.data()->setHessianMatrix(H_s)); + REQUIRE_FALSE(solver.data()->setHessianMatrix(H_s)); solver.data()->setNumberOfVariables(2); solver.data()->setNumberOfConstraints(3); - ASSERT_TRUE(solver.data()->setHessianMatrix(H_s)); - ASSERT_TRUE(solver.data()->setGradient(gradient)); - ASSERT_TRUE(solver.data()->setLinearConstraintsMatrix(A_s)); - ASSERT_TRUE(solver.data()->setLowerBound(lowerBound)); - ASSERT_TRUE(solver.data()->setUpperBound(upperBound)); + REQUIRE(solver.data()->setHessianMatrix(H_s)); + REQUIRE(solver.data()->setGradient(gradient)); + REQUIRE(solver.data()->setLinearConstraintsMatrix(A_s)); + REQUIRE(solver.data()->setLowerBound(lowerBound)); + REQUIRE(solver.data()->setUpperBound(upperBound)); - ASSERT_TRUE(solver.initSolver()); + REQUIRE(solver.initSolver()); - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.solve()); std::cerr << "The solution of the QP problem is" << std::endl; std::cerr << "[ " << solver.getSolution() << " ]" << std::endl; -}; - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - clock_t startTime, endTime; - - startTime = clock(); - bool outcome = RUN_ALL_TESTS(); - endTime = clock(); - - std::cerr << "Total time " << (static_cast(endTime - startTime) / CLOCKS_PER_SEC) - << " seconds." << std::endl; - - return outcome; } diff --git a/tests/SparseMatrixTest.cpp b/tests/SparseMatrixTest.cpp index 9b847e4..e14bf87 100644 --- a/tests/SparseMatrixTest.cpp +++ b/tests/SparseMatrixTest.cpp @@ -2,11 +2,11 @@ * @file SparseMatrixTest.cpp * @author Giulio Romualdi * @copyright Released under the terms of the LGPLv2.1 or later, see LGPL.TXT - * @date 2018 + * @date 2020 */ -// gtest -#include +// Catch2 +#include #include #include @@ -14,7 +14,7 @@ template bool computeTest(const Eigen::Matrix &mEigen) { - Eigen::SparseMatrix matrix, newMatrix; + Eigen::SparseMatrix matrix, newMatrix, newMatrixFromCSR; matrix = mEigen.sparseView(); csc* osqpSparseMatrix = nullptr; @@ -22,72 +22,84 @@ bool computeTest(const Eigen::Matrix &mEigen) if(!OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(matrix, osqpSparseMatrix)) return false; + Eigen::SparseMatrix csrMatrix; + csrMatrix = matrix; + csc* otherOsqpSparseMatrix = nullptr; + if(!OsqpEigen::SparseMatrixHelper::createOsqpSparseMatrix(csrMatrix, otherOsqpSparseMatrix)) + return false; + if(!OsqpEigen::SparseMatrixHelper::osqpSparseMatrixToEigenSparseMatrix(osqpSparseMatrix, newMatrix)) return false; + + if(!OsqpEigen::SparseMatrixHelper::osqpSparseMatrixToEigenSparseMatrix(otherOsqpSparseMatrix, newMatrixFromCSR)) + return false; + + if (!newMatrixFromCSR.isApprox(newMatrix)) + return false; + std::vector> tripletListCsc; if(!OsqpEigen::SparseMatrixHelper::osqpSparseMatrixToTriplets(osqpSparseMatrix, tripletListCsc)) return false; - for(auto a: tripletListCsc) - std::cout<> tripletListEigen; OsqpEigen::SparseMatrixHelper::eigenSparseMatrixToTriplets(matrix, tripletListEigen); - std::cout<<"***********************************************\n"; - for(auto a: tripletListEigen) - std::cout< m; - m << 0, 0, 0, 4, - 0, 0, 0, 0; - - ASSERT_TRUE(computeTest(m)); + return outcome; } - -int main(int argc, char **argv) +TEST_CASE("SparseMatrix") { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + SECTION("Data type - double") + { + + Eigen::Matrix3d m; + m << 0, 1.002311, 0, + 0, 0, 0, + 0, 0.90835435,0; + + REQUIRE(computeTest(m)); + } + + SECTION("Data type - float") + { + Eigen::Matrix3f m; + m << 0, 1, 0, + 0, 0, 0, + 0, 1,0; + + REQUIRE(computeTest(m)); + } + + SECTION("Data type - int") + { + + Eigen::Matrix3i m; + m << 0, 1, 0, + 0, 0, 0, + 0, 1,0; + + REQUIRE(computeTest(m)); + } + + SECTION("Data type - double") + { + Eigen::Matrix m; + m << 0, 0, 0, 4, + 0, 0, 0, 0; + + REQUIRE(computeTest(m)); + } } diff --git a/tests/UpdateMatricesTest.cpp b/tests/UpdateMatricesTest.cpp index eaf5ceb..8fa219f 100644 --- a/tests/UpdateMatricesTest.cpp +++ b/tests/UpdateMatricesTest.cpp @@ -2,11 +2,11 @@ * @file UpdateMatricesTest.cpp * @author Giulio Romualdi * @copyright Released under the terms of the LGPLv2.1 or later, see LGPL.TXT - * @date 2018 + * @date 2020 */ -// gtest -#include +// Catch2 +#include // OsqpEigen #include @@ -29,7 +29,7 @@ Eigen::Vector3d upperBound; OsqpEigen::Solver solver; -TEST(QPProblem, FirstRun) +TEST_CASE("QPProblem - FirstRun") { // hessian matrix H << 4, 0, @@ -52,22 +52,22 @@ TEST(QPProblem, FirstRun) solver.data()->setNumberOfVariables(2); solver.data()->setNumberOfConstraints(3); solver.settings()->setScaling(0); - ASSERT_TRUE(solver.data()->setHessianMatrix(H_s)); - ASSERT_TRUE(solver.data()->setGradient(gradient)); - ASSERT_TRUE(solver.data()->setLinearConstraintsMatrix(A_s)); - ASSERT_TRUE(solver.data()->setLowerBound(lowerBound)); - ASSERT_TRUE(solver.data()->setUpperBound(upperBound)); + REQUIRE(solver.data()->setHessianMatrix(H_s)); + REQUIRE(solver.data()->setGradient(gradient)); + REQUIRE(solver.data()->setLinearConstraintsMatrix(A_s)); + REQUIRE(solver.data()->setLowerBound(lowerBound)); + REQUIRE(solver.data()->setUpperBound(upperBound)); - ASSERT_TRUE(solver.initSolver()); - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.initSolver()); + REQUIRE(solver.solve()); auto solution = solver.getSolution(); std::cout << COUT_GTEST_MGT << "Solution [" << solution(0) << " " << solution(1) << "]" << ANSI_TXT_DFT << std::endl; -}; +} -TEST(QPProblem, SparsityConstant) +TEST_CASE("QPProblem - SparsityConstant") { // update hessian matrix H << 4, 0, @@ -78,9 +78,9 @@ TEST(QPProblem, SparsityConstant) 0, 1; A_s = A.sparseView(); - ASSERT_TRUE(solver.updateHessianMatrix(H_s)); - ASSERT_TRUE(solver.updateLinearConstraintsMatrix(A_s)); - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.updateHessianMatrix(H_s)); + REQUIRE(solver.updateLinearConstraintsMatrix(A_s)); + REQUIRE(solver.solve()); auto solution = solver.getSolution(); std::cout << COUT_GTEST_MGT << "Solution [" << solution(0) << " " @@ -88,7 +88,7 @@ TEST(QPProblem, SparsityConstant) << ANSI_TXT_DFT << std::endl; }; -TEST(QPProblem, SparsityChange) +TEST_CASE("QPProblem - SparsityChange") { // update hessian matrix H << 1, 1, @@ -99,27 +99,12 @@ TEST(QPProblem, SparsityChange) 0, 1; A_s = A.sparseView(); - ASSERT_TRUE(solver.updateHessianMatrix(H_s)); - ASSERT_TRUE(solver.updateLinearConstraintsMatrix(A_s)); - ASSERT_TRUE(solver.solve()); + REQUIRE(solver.updateHessianMatrix(H_s)); + REQUIRE(solver.updateLinearConstraintsMatrix(A_s)); + REQUIRE(solver.solve()); auto solution = solver.getSolution(); std::cout << COUT_GTEST_MGT << "Solution [" << solution(0) << " " << solution(1) << "]" << ANSI_TXT_DFT << std::endl; }; - -int main(int argc, char **argv) -{ - testing::InitGoogleTest(&argc, argv); - clock_t startTime, endTime; - - startTime = clock(); - bool outcome = RUN_ALL_TESTS(); - endTime = clock(); - - std::cerr << "Total time " << (static_cast(endTime - startTime) / CLOCKS_PER_SEC) - << " seconds." << std::endl; - - return outcome; -}