├── .clang-format ├── .github └── workflows │ └── continuous.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── finitediff │ └── finitediff_warnings.cmake └── recipes │ ├── catch2.cmake │ ├── eigen.cmake │ └── spdlog.cmake ├── src ├── finitediff.cpp └── finitediff.hpp └── tests ├── CMakeLists.txt ├── test_flatten.cpp ├── test_gradient.cpp ├── test_hessian.cpp └── test_jacobian.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | BasedOnStyle: WebKit 3 | ColumnLimit: 80 4 | AlignTrailingComments: true 5 | FixNamespaceComments: true 6 | ReflowComments: true 7 | BinPackParameters: false 8 | AllowAllParametersOfDeclarationOnNextLine: true 9 | AlignAfterOpenBracket: AlwaysBreak 10 | BreakBeforeBinaryOperators: NonAssignment 11 | # BreakStringLiterals: false 12 | -------------------------------------------------------------------------------- /.github/workflows/continuous.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: ON 13 | CTEST_PARALLEL_LEVEL: 2 14 | 15 | jobs: 16 | #################### 17 | # Linux / macOS 18 | #################### 19 | 20 | Unix: 21 | name: ${{ matrix.name }} (${{ matrix.config }}) 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | os: [ubuntu-20.04, macos-latest] 27 | config: [Debug, Release] 28 | include: 29 | - os: macos-latest 30 | name: macOS 31 | - os: ubuntu-20.04 32 | name: Linux 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v3 36 | with: 37 | fetch-depth: 10 38 | 39 | - name: Dependencies (Linux) 40 | if: runner.os == 'Linux' 41 | run: | 42 | sudo apt-get update 43 | sudo apt-get install ccache 44 | 45 | - name: Dependencies (macOS) 46 | if: runner.os == 'macOS' 47 | run: brew install ccache 48 | 49 | - name: Cache Build 50 | id: cache-build 51 | uses: actions/cache@v1 52 | with: 53 | path: ~/.ccache 54 | key: ${{ runner.os }}-${{ matrix.config }}-cache 55 | 56 | - name: Prepare ccache 57 | run: | 58 | ccache --max-size=1.0G 59 | ccache -V && ccache --show-stats && ccache --zero-stats 60 | 61 | - name: Configure 62 | run: | 63 | mkdir -p build 64 | cd build 65 | cmake .. \ 66 | -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ 67 | -DFINITE_DIFF_BUILD_UNIT_TESTS=ON \ 68 | -DCMAKE_BUILD_TYPE=${{ matrix.config }} 69 | 70 | - name: Build 71 | run: cd build; make -j2; ccache --show-stats 72 | 73 | - name: Tests 74 | run: cd build; ctest --verbose 75 | 76 | #################### 77 | # Windows 78 | #################### 79 | 80 | Windows: 81 | name: ${{ matrix.name }} (${{ matrix.config }}) 82 | runs-on: windows-2019 83 | env: 84 | CC: cl.exe 85 | CXX: cl.exe 86 | SCCACHE_IDLE_TIMEOUT: "12000" 87 | strategy: 88 | fail-fast: false 89 | matrix: 90 | config: [Debug, Release] 91 | include: 92 | - os: windows-2019 93 | name: Windows 94 | steps: 95 | - name: Checkout repository 96 | uses: actions/checkout@v3 97 | with: 98 | fetch-depth: 10 99 | - uses: seanmiddleditch/gha-setup-ninja@master 100 | 101 | # https://github.com/actions/cache/issues/101 102 | - name: Set env 103 | run: | 104 | echo "appdata=$env:LOCALAPPDATA" >> ${env:GITHUB_ENV} 105 | 106 | - name: Cache build 107 | id: cache-build 108 | uses: actions/cache@v2 109 | with: 110 | path: ${{ env.appdata }}\Mozilla\sccache 111 | key: ${{ runner.os }}-${{ matrix.config }}-cache-${{ github.sha }} 112 | restore-keys: ${{ runner.os }}-${{ matrix.config }}-cache 113 | 114 | - name: Prepare sccache 115 | run: | 116 | iwr -useb 'https://raw.githubusercontent.com/scoopinstaller/install/master/install.ps1' -outfile 'install.ps1' 117 | .\install.ps1 -RunAsAdmin 118 | scoop install sccache --global 119 | # Scoop modifies the PATH so we make it available for the next steps of the job 120 | echo "${env:PATH}" >> ${env:GITHUB_PATH} 121 | 122 | # We run configure + build in the same step, since they both need to call VsDevCmd 123 | # Also, cmd uses ^ to break commands into multiple lines (in powershell this is `) 124 | - name: Configure and build 125 | shell: cmd 126 | run: | 127 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x64 128 | cmake -G Ninja ^ 129 | -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^ 130 | -DCMAKE_BUILD_TYPE=${{ matrix.config }} ^ 131 | -DFINITE_DIFF_BUILD_UNIT_TESTS=ON ^ 132 | -B build ^ 133 | -S . 134 | cd build 135 | ninja -j1 136 | 137 | - name: Tests 138 | run: | 139 | cd build 140 | ctest --verbose --output-on-failure 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## C++ 2 | # Prerequisites 3 | *.d 4 | 5 | # Compiled Object files 6 | *.slo 7 | *.lo 8 | *.o 9 | *.obj 10 | 11 | # Precompiled Headers 12 | *.gch 13 | *.pch 14 | 15 | # Compiled Dynamic libraries 16 | *.so 17 | *.dylib 18 | *.dll 19 | 20 | # Fortran module files 21 | *.mod 22 | *.smod 23 | 24 | # Compiled Static libraries 25 | *.lai 26 | *.la 27 | *.a 28 | *.lib 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | 35 | ## macOS 36 | # General 37 | .DS_Store 38 | .AppleDouble 39 | .LSOverride 40 | 41 | # Icon must end with two \r 42 | Icon 43 | 44 | # Thumbnails 45 | ._* 46 | 47 | # Files that might appear in the root of a volume 48 | .DocumentRevisions-V100 49 | .fseventsd 50 | .Spotlight-V100 51 | .TemporaryItems 52 | .Trashes 53 | .VolumeIcon.icns 54 | .com.apple.timemachine.donotpresent 55 | 56 | # Directories potentially created on remote AFP share 57 | .AppleDB 58 | .AppleDesktop 59 | Network Trash Folder 60 | Temporary Items 61 | .apdisk 62 | 63 | ## Custom 64 | build* 65 | external* 66 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Detects whether this is a top-level project 2 | get_directory_property(HAS_PARENT PARENT_DIRECTORY) 3 | if(HAS_PARENT) 4 | set(FINITE_DIFF_TOPLEVEL_PROJECT OFF) 5 | else() 6 | set(FINITE_DIFF_TOPLEVEL_PROJECT ON) 7 | endif() 8 | 9 | # Check required CMake version 10 | set(REQUIRED_CMAKE_VERSION "3.14.0") 11 | if(FINITE_DIFF_TOPLEVEL_PROJECT) 12 | cmake_minimum_required(VERSION ${REQUIRED_CMAKE_VERSION}) 13 | else() 14 | # Don't use cmake_minimum_required here to avoid implicitly overriding parent policies 15 | if(${CMAKE_VERSION} VERSION_LESS ${REQUIRED_CMAKE_VERSION}) 16 | message(FATAL_ERROR "CMake required version to build finite-diff is ${REQUIRED_CMAKE_VERSION}") 17 | endif() 18 | endif() 19 | 20 | ################################################################################ 21 | 22 | project(FiniteDiff 23 | DESCRIPTION "A simple finite difference library." 24 | LANGUAGES CXX) 25 | 26 | option(FINITE_DIFF_BUILD_UNIT_TESTS "Build unit-tests" ${FINITE_DIFF_TOPLEVEL_PROJECT}) 27 | 28 | # Set default minimum C++ standard 29 | if(FINITE_DIFF_TOPLEVEL_PROJECT) 30 | set(CMAKE_CXX_STANDARD 11) 31 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 32 | set(CMAKE_CXX_EXTENSIONS OFF) 33 | endif() 34 | 35 | list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/finitediff/") 36 | list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/recipes/") 37 | 38 | ################################################################################ 39 | # Finite Diff Library 40 | ################################################################################ 41 | 42 | add_library(finitediff_finitediff 43 | src/finitediff.cpp 44 | ) 45 | add_library(finitediff::finitediff ALIAS finitediff_finitediff) 46 | 47 | # Public include directory 48 | target_include_directories(finitediff_finitediff PUBLIC src) 49 | 50 | ################################################################################ 51 | # Optional Definitions 52 | ################################################################################ 53 | 54 | # For MSVC, do not use the min and max macros. 55 | target_compile_definitions(finitediff_finitediff PUBLIC NOMINMAX) 56 | 57 | ################################################################################ 58 | # Dependencies 59 | ################################################################################ 60 | 61 | # Extra warnings 62 | include(finitediff_warnings) 63 | target_link_libraries(finitediff_finitediff PRIVATE finitediff::warnings) 64 | 65 | # Eigen 66 | include(eigen) 67 | target_link_libraries(finitediff_finitediff PUBLIC Eigen3::Eigen) 68 | 69 | # Logger 70 | include(spdlog) 71 | target_link_libraries(finitediff_finitediff PUBLIC spdlog::spdlog) 72 | 73 | ################################################################################ 74 | # Compiler options 75 | ################################################################################ 76 | 77 | # Use C++11 78 | target_compile_features(finitediff_finitediff PUBLIC cxx_std_11) 79 | 80 | ################################################################################ 81 | # Tests 82 | ################################################################################ 83 | 84 | if(FINITE_DIFF_BUILD_UNIT_TESTS) 85 | include(CTest) 86 | enable_testing() 87 | 88 | add_subdirectory(tests) 89 | endif() 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zachary Ferguson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Finite Differences 2 | 3 | [![Build Status](https://github.com/zfergus/finite-diff/actions/workflows/continuous.yml/badge.svg)](https://github.com/zfergus/finite-diff/actions/workflows/continuous.yml) 4 | [![License](https://img.shields.io/github/license/zfergus/finite-diff.svg?color=blue)](https://opensource.org/licenses/MIT) 5 | 6 | **A simple finite-difference library using Eigen.** 7 | 8 | ## Usage 9 | 10 | ### Add it to CMake 11 | 12 | The easiest way to add the library to an existing CMake project is to download it through CMake. 13 | CMake provides functionality for doing this called [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) (requires CMake ≥ 3.14). 14 | We use this same process to download all external dependencies. 15 | For example, 16 | 17 | ```CMake 18 | include(FetchContent) 19 | FetchContent_Declare( 20 | finite-diff 21 | GIT_REPOSITORY https://github.com/zfergus/finite-diff.git 22 | GIT_TAG ${FINITE_DIFF_GIT_TAG} 23 | GIT_SHALLOW TRUE 24 | ) 25 | FetchContent_MakeAvailable(finite-diff) 26 | ``` 27 | 28 | where `FINITE_DIFF_GIT_TAG` is set to the version you want to use. This will download and add the library to CMake. The library can then be linked against using 29 | 30 | ```CMake 31 | target_link_libraries(${TARGET_NAME} PUBLIC finitediff::finitediff) 32 | ``` 33 | 34 | where `TARGET_NAME` is the name of your library/executable. 35 | 36 | ### API 37 | 38 | All functiononality can be included with `#include `. 39 | 40 | The library provides three main functions `finite_gradient`, `finite_jacobian`, and `finite_hessian`. 41 | 42 | #### `finite_gradient`: 43 | 44 | ```c++ 45 | void finite_gradient( 46 | const Eigen::VectorXd& x, 47 | const std::function& f, 48 | Eigen::VectorXd& grad, 49 | const AccuracyOrder accuracy = SECOND, 50 | const double eps = 1.0e-8); 51 | ``` 52 | 53 | The `finite_gradient` function computes the [gradient](https://en.wikipedia.org/wiki/Gradient) (first derivative) `grad` of a function `f: ℝⁿ ↦ ℝ` at a point `x`. This will result in a vector of size `n`. 54 | 55 | #### `finite_jacobian`: 56 | 57 | ```c++ 58 | void finite_jacobian( 59 | const Eigen::VectorXd& x, 60 | const std::function& f, 61 | Eigen::MatrixXd& jac, 62 | const AccuracyOrder accuracy = SECOND, 63 | const double eps = 1.0e-8); 64 | ``` 65 | 66 | The `finite_jacobian` function computes the [Jacobian](https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant) (first derivative) `jac` of a function `f: ℝⁿ ↦ ℝᵐ` at a point `x`. This will result in a matrix of size `m × n`. 67 | 68 | 69 | #### `finite_hessian`: 70 | 71 | ```c++ 72 | void finite_hessian( 73 | const Eigen::VectorXd& x, 74 | const std::function& f, 75 | Eigen::MatrixXd& hess, 76 | const AccuracyOrder accuracy = SECOND, 77 | const double eps = 1.0e-5); 78 | ``` 79 | 80 | The `finite_hessian` function computes the [Hessian](https://en.wikipedia.org/wiki/Hessian_matrix) (second derivative) `hess` of a function `f: ℝⁿ ↦ ℝ` at a point `x`. This will result in a matrix of size `n × n`. 81 | 82 | #### `AccuracyOrder`: 83 | 84 | Each finite difference function takes as input the accuracy order for the method. Possible options are: 85 | ```c++ 86 | enum AccuracyOrder { 87 | SECOND, // Second order accuracy. 88 | FOURTH, // Fourth order accuracy. 89 | SIXTH, // Sixth order accuracy. 90 | EIGHTH // Eighth order accuracy. 91 | }; 92 | ``` 93 | 94 | #### `eps`: 95 | 96 | The parameter `eps` is the finite difference step size. Smaller values result in a more accurate approximation, but too small of a value can result in a large numerical error because the difference will be divided by a small number. 97 | 98 | ## Dependencies 99 | 100 | **All dependencies are downloaded through CMake** depending on the build options. 101 | The following libraries are used in this project: 102 | 103 | * [Eigen](https://eigen.tuxfamily.org/): linear algebra 104 | * [spdlog](https://github.com/gabime/spdlog): logging information 105 | 106 | ### Optional 107 | 108 | * [Catch2](https://github.com/catchorg/Catch2.git): testing (see [Unit Tests](#unit_tests)) 109 | 110 | ## Unit Tests 111 | 112 | We provide unit tests for ensuring the correctness of our functions. 113 | To enable the unit tests, use the flag `-DFINITE_DIFF_BUILD_UNIT_TESTS=ON` with CMake. 114 | 115 | ## Contributing 116 | 117 | This project is open to contributors! Contributions can come in the form of feature requests, bug fixes, documentation, tutorials and the like. We highly recommend filing an Issue first before submitting a Pull Request. 118 | 119 | Simply fork this repository and make a Pull Request! We'd appreciate: 120 | 121 | * Implementation of new features 122 | * Bug Reports 123 | * Documentation 124 | * Testing 125 | 126 | ## License 127 | 128 | MIT License © 2019, Zachary Ferguson (See [`LICENSE.txt`](https://github.com/zfergus/finite-diff/blob/main/LICENSE) for details). 129 | 130 | ### Acknowledgements 131 | 132 | Based on the functions in [CppNumericalSolvers](https://github.com/PatWie/CppNumericalSolvers/blob/v2/include/cppoptlib/utils/derivatives.h) 133 | by Patrick Wieschollek and rewritten to use Eigen. 134 | -------------------------------------------------------------------------------- /cmake/finitediff/finitediff_warnings.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # See comments and discussions here: 3 | # http://stackoverflow.com/questions/5088460/flags-to-enable-thorough-and-verbose-g-warnings 4 | ################################################################################ 5 | 6 | if(TARGET finitediff::warnings) 7 | return() 8 | endif() 9 | 10 | set(FINITE_DIFF_FLAGS 11 | -Wall 12 | -Wextra 13 | -pedantic 14 | 15 | # -Wconversion 16 | #-Wunsafe-loop-optimizations # broken with C++11 loops 17 | -Wunused 18 | 19 | -Wno-long-long 20 | -Wpointer-arith 21 | -Wformat=2 22 | -Wuninitialized 23 | -Wcast-qual 24 | -Wmissing-noreturn 25 | -Wmissing-format-attribute 26 | -Wredundant-decls 27 | 28 | -Werror=implicit 29 | -Werror=nonnull 30 | -Werror=init-self 31 | -Werror=main 32 | -Werror=missing-braces 33 | -Werror=sequence-point 34 | -Werror=return-type 35 | -Werror=trigraphs 36 | -Werror=array-bounds 37 | -Werror=write-strings 38 | -Werror=address 39 | -Werror=int-to-pointer-cast 40 | -Werror=pointer-to-int-cast 41 | 42 | -Wno-unused-variable 43 | -Wunused-but-set-variable 44 | -Wno-unused-parameter 45 | 46 | #-Weffc++ 47 | -Wno-old-style-cast 48 | # -Wno-sign-conversion 49 | #-Wsign-conversion 50 | 51 | -Wshadow 52 | 53 | -Wstrict-null-sentinel 54 | -Woverloaded-virtual 55 | -Wsign-promo 56 | -Wstack-protector 57 | -Wstrict-aliasing 58 | -Wstrict-aliasing=2 59 | 60 | # Warn whenever a switch statement has an index of enumerated type and 61 | # lacks a case for one or more of the named codes of that enumeration. 62 | -Wswitch 63 | # This is annoying if all cases are already covered. 64 | # -Wswitch-default 65 | # This is annoying if there is a default that covers the rest. 66 | # -Wswitch-enum 67 | -Wswitch-unreachable 68 | # -Wcovered-switch-default # Annoying warnings from nlohmann::json 69 | 70 | -Wcast-align 71 | -Wdisabled-optimization 72 | #-Winline # produces warning on default implicit destructor 73 | -Winvalid-pch 74 | # -Wmissing-include-dirs 75 | -Wpacked 76 | -Wno-padded 77 | -Wstrict-overflow 78 | -Wstrict-overflow=2 79 | 80 | -Wctor-dtor-privacy 81 | -Wlogical-op 82 | -Wnoexcept 83 | -Woverloaded-virtual 84 | # -Wundef 85 | 86 | -Wnon-virtual-dtor 87 | -Wdelete-non-virtual-dtor 88 | -Werror=non-virtual-dtor 89 | -Werror=delete-non-virtual-dtor 90 | 91 | -Wno-sign-compare 92 | 93 | ########### 94 | # GCC 6.1 # 95 | ########### 96 | 97 | -Wnull-dereference 98 | -fdelete-null-pointer-checks 99 | -Wduplicated-cond 100 | -Wmisleading-indentation 101 | 102 | #-Weverything 103 | 104 | ########################### 105 | # Enabled by -Weverything # 106 | ########################### 107 | 108 | #-Wdocumentation 109 | #-Wdocumentation-unknown-command 110 | #-Wfloat-equal 111 | 112 | #-Wglobal-constructors 113 | #-Wexit-time-destructors 114 | #-Wmissing-variable-declarations 115 | #-Wextra-semi 116 | #-Wweak-vtables 117 | #-Wno-source-uses-openmp 118 | #-Wdeprecated 119 | #-Wnewline-eof 120 | #-Wmissing-prototypes 121 | 122 | #-Wno-c++98-compat 123 | #-Wno-c++98-compat-pedantic 124 | 125 | ########################### 126 | # Need to check if those are still valid today 127 | ########################### 128 | 129 | #-Wimplicit-atomic-properties 130 | #-Wmissing-declarations 131 | #-Wmissing-prototypes 132 | #-Wstrict-selector-match 133 | #-Wundeclared-selector 134 | #-Wunreachable-code 135 | 136 | # Not a warning, but enable link-time-optimization 137 | # TODO: Check out modern CMake version of setting this flag 138 | # https://cmake.org/cmake/help/latest/module/CheckIPOSupported.html 139 | #-flto 140 | 141 | # Gives meaningful stack traces 142 | -fno-omit-frame-pointer 143 | -fno-optimize-sibling-calls 144 | ) 145 | 146 | # Flags above don't make sense for MSVC 147 | if(MSVC) 148 | set(FINITE_DIFF_FLAGS) 149 | endif() 150 | 151 | include(CheckCXXCompilerFlag) 152 | 153 | add_library(finitediff_warnings INTERFACE) 154 | add_library(finitediff::warnings ALIAS finitediff_warnings) 155 | 156 | foreach(FLAG IN ITEMS ${FINITE_DIFF_FLAGS}) 157 | string(REPLACE "=" "-" FLAG_VAR "${FLAG}") 158 | if(NOT DEFINED IS_SUPPORTED_${FLAG_VAR}) 159 | check_cxx_compiler_flag("${FLAG}" IS_SUPPORTED_${FLAG_VAR}) 160 | endif() 161 | if(IS_SUPPORTED_${FLAG_VAR}) 162 | target_compile_options(finitediff_warnings INTERFACE ${FLAG}) 163 | endif() 164 | endforeach() 165 | -------------------------------------------------------------------------------- /cmake/recipes/catch2.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Adobe. All rights reserved. 3 | # This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. You may obtain a copy 5 | # of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software distributed under 8 | # the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | # OF ANY KIND, either express or implied. See the License for the specific language 10 | # governing permissions and limitations under the License. 11 | # 12 | if(TARGET Catch2::Catch2) 13 | return() 14 | endif() 15 | 16 | message(STATUS "Third-party: creating target 'Catch2::Catch2'") 17 | 18 | include(FetchContent) 19 | FetchContent_Declare( 20 | catch2 21 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 22 | GIT_TAG v3.0.1 23 | GIT_SHALLOW TRUE 24 | ) 25 | FetchContent_MakeAvailable(catch2) 26 | -------------------------------------------------------------------------------- /cmake/recipes/eigen.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Adobe. All rights reserved. 3 | # This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. You may obtain a copy 5 | # of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software distributed under 8 | # the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | # OF ANY KIND, either express or implied. See the License for the specific language 10 | # governing permissions and limitations under the License. 11 | # 12 | if(TARGET Eigen3::Eigen) 13 | return() 14 | endif() 15 | 16 | option(EIGEN_WITH_MKL "Use Eigen with MKL" OFF) 17 | 18 | if(EIGEN_ROOT) 19 | message(STATUS "Third-party: creating target 'Eigen3::Eigen' for external path: ${EIGEN_ROOT}") 20 | set(EIGEN_INCLUDE_DIRS ${EIGEN_ROOT}) 21 | else() 22 | message(STATUS "Third-party: creating target 'Eigen3::Eigen'") 23 | 24 | include(FetchContent) 25 | FetchContent_Declare( 26 | eigen 27 | GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git 28 | GIT_TAG tags/3.4.0 29 | GIT_SHALLOW TRUE 30 | ) 31 | FetchContent_GetProperties(eigen) 32 | if(NOT eigen_POPULATED) 33 | FetchContent_Populate(eigen) 34 | endif() 35 | set(EIGEN_INCLUDE_DIRS ${eigen_SOURCE_DIR}) 36 | 37 | install(DIRECTORY ${EIGEN_INCLUDE_DIRS}/Eigen 38 | DESTINATION include 39 | ) 40 | endif() 41 | 42 | add_library(Eigen3_Eigen INTERFACE) 43 | add_library(Eigen3::Eigen ALIAS Eigen3_Eigen) 44 | 45 | include(GNUInstallDirs) 46 | target_include_directories(Eigen3_Eigen SYSTEM INTERFACE 47 | $ 48 | $ 49 | ) 50 | # target_compile_definitions(Eigen3_Eigen INTERFACE EIGEN_MPL2_ONLY) 51 | 52 | if(EIGEN_WITH_MKL) 53 | # TODO: Checks that, on 64bits systems, `mkl::mkl` is using the LP64 interface 54 | # (by looking at the compile definition of the target) 55 | include(mkl) 56 | target_link_libraries(Eigen3_Eigen INTERFACE mkl::mkl) 57 | target_compile_definitions(Eigen3_Eigen INTERFACE 58 | EIGEN_USE_MKL_ALL 59 | EIGEN_USE_LAPACKE_STRICT 60 | ) 61 | endif() 62 | 63 | # On Windows, enable natvis files to improve debugging experience 64 | if(WIN32 AND eigen_SOURCE_DIR) 65 | target_sources(Eigen3_Eigen INTERFACE $) 66 | endif() 67 | 68 | # Install rules 69 | set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME eigen) 70 | set_target_properties(Eigen3_Eigen PROPERTIES EXPORT_NAME Eigen) 71 | install(DIRECTORY ${EIGEN_INCLUDE_DIRS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 72 | install(TARGETS Eigen3_Eigen EXPORT Eigen_Targets) 73 | install(EXPORT Eigen_Targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/eigen NAMESPACE Eigen3::) 74 | -------------------------------------------------------------------------------- /cmake/recipes/spdlog.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Adobe. All rights reserved. 3 | # This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. You may obtain a copy 5 | # of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software distributed under 8 | # the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | # OF ANY KIND, either express or implied. See the License for the specific language 10 | # governing permissions and limitations under the License. 11 | # 12 | if(TARGET spdlog::spdlog) 13 | return() 14 | endif() 15 | 16 | message(STATUS "Third-party: creating target 'spdlog::spdlog'") 17 | 18 | include(FetchContent) 19 | FetchContent_Declare( 20 | spdlog 21 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 22 | GIT_TAG v1.10.0 23 | GIT_SHALLOW TRUE 24 | ) 25 | 26 | option(SPDLOG_INSTALL "Generate the install target" ON) 27 | set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "spdlog") 28 | FetchContent_MakeAvailable(spdlog) 29 | 30 | set_target_properties(spdlog PROPERTIES POSITION_INDEPENDENT_CODE ON) 31 | 32 | set_target_properties(spdlog PROPERTIES FOLDER external) 33 | 34 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR 35 | "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 36 | target_compile_options(spdlog PRIVATE 37 | "-Wno-sign-conversion" 38 | ) 39 | endif() 40 | -------------------------------------------------------------------------------- /src/finitediff.cpp: -------------------------------------------------------------------------------- 1 | // Functions to compute gradients using finite difference. 2 | // Based on the functions in https://github.com/PatWie/CppNumericalSolvers 3 | // and rewritten to use Eigen 4 | #include "finitediff.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace fd { 12 | 13 | // The external coefficients, c1, in c1 * f(x + c2). 14 | // See: https://en.wikipedia.org/wiki/Finite_difference_coefficient 15 | std::vector get_external_coeffs(const AccuracyOrder accuracy) 16 | { 17 | switch (accuracy) { 18 | case SECOND: 19 | return { { 1, -1 } }; 20 | case FOURTH: 21 | return { { 1, -8, 8, -1 } }; 22 | case SIXTH: 23 | return { { -1, 9, -45, 45, -9, 1 } }; 24 | case EIGHTH: 25 | return { { 3, -32, 168, -672, 672, -168, 32, -3 } }; 26 | default: 27 | throw std::invalid_argument("invalid accuracy order"); 28 | } 29 | } 30 | 31 | // The internal coefficients, c2, in c1 * f(x + c2). 32 | // See: https://en.wikipedia.org/wiki/Finite_difference_coefficient 33 | std::vector get_interior_coeffs(const AccuracyOrder accuracy) 34 | { 35 | switch (accuracy) { 36 | case SECOND: 37 | return { { 1, -1 } }; 38 | case FOURTH: 39 | return { { -2, -1, 1, 2 } }; 40 | case SIXTH: 41 | return { { -3, -2, -1, 1, 2, 3 } }; 42 | case EIGHTH: 43 | return { { -4, -3, -2, -1, 1, 2, 3, 4 } }; 44 | default: 45 | throw std::invalid_argument("invalid accuracy order"); 46 | } 47 | } 48 | 49 | // The denominators of the finite difference. 50 | double get_denominator(const AccuracyOrder accuracy) 51 | { 52 | switch (accuracy) { 53 | case SECOND: 54 | return 2; 55 | case FOURTH: 56 | return 12; 57 | case SIXTH: 58 | return 60; 59 | case EIGHTH: 60 | return 840; 61 | default: 62 | throw std::invalid_argument("invalid accuracy order"); 63 | } 64 | } 65 | 66 | // Compute the gradient of a function at a point using finite differences. 67 | void finite_gradient( 68 | const Eigen::Ref& x, 69 | const std::function& f, 70 | Eigen::VectorXd& grad, 71 | const AccuracyOrder accuracy, 72 | const double eps) 73 | { 74 | const std::vector external_coeffs = get_external_coeffs(accuracy); 75 | const std::vector internal_coeffs = get_interior_coeffs(accuracy); 76 | 77 | assert(external_coeffs.size() == internal_coeffs.size()); 78 | const size_t inner_steps = internal_coeffs.size(); 79 | 80 | const double denom = get_denominator(accuracy) * eps; 81 | 82 | grad.setZero(x.rows()); 83 | 84 | Eigen::VectorXd x_mutable = x; 85 | for (size_t i = 0; i < x.rows(); i++) { 86 | for (size_t ci = 0; ci < inner_steps; ci++) { 87 | x_mutable[i] += internal_coeffs[ci] * eps; 88 | grad[i] += external_coeffs[ci] * f(x_mutable); 89 | x_mutable[i] = x[i]; 90 | } 91 | grad[i] /= denom; 92 | } 93 | } 94 | 95 | void finite_jacobian( 96 | const Eigen::Ref& x, 97 | const std::function& f, 98 | Eigen::MatrixXd& jac, 99 | const AccuracyOrder accuracy, 100 | const double eps) 101 | { 102 | const std::vector external_coeffs = get_external_coeffs(accuracy); 103 | const std::vector internal_coeffs = get_interior_coeffs(accuracy); 104 | 105 | assert(external_coeffs.size() == internal_coeffs.size()); 106 | const size_t inner_steps = internal_coeffs.size(); 107 | 108 | const double denom = get_denominator(accuracy) * eps; 109 | 110 | jac.setZero(f(x).rows(), x.rows()); 111 | 112 | Eigen::VectorXd x_mutable = x; 113 | for (size_t i = 0; i < x.rows(); i++) { 114 | for (size_t ci = 0; ci < inner_steps; ci++) { 115 | x_mutable[i] += internal_coeffs[ci] * eps; 116 | jac.col(i) += external_coeffs[ci] * f(x_mutable); 117 | x_mutable[i] = x[i]; 118 | } 119 | jac.col(i) /= denom; 120 | } 121 | } 122 | 123 | void finite_hessian( 124 | const Eigen::Ref& x, 125 | const std::function& f, 126 | Eigen::MatrixXd& hess, 127 | const AccuracyOrder accuracy, 128 | const double eps) 129 | { 130 | const std::vector external_coeffs = get_external_coeffs(accuracy); 131 | const std::vector internal_coeffs = get_interior_coeffs(accuracy); 132 | 133 | assert(external_coeffs.size() == internal_coeffs.size()); 134 | const size_t inner_steps = internal_coeffs.size(); 135 | 136 | double denom = get_denominator(accuracy) * eps; 137 | denom *= denom; 138 | 139 | hess.setZero(x.rows(), x.rows()); 140 | 141 | Eigen::VectorXd x_mutable = x; 142 | for (size_t i = 0; i < x.rows(); i++) { 143 | for (size_t j = i; j < x.rows(); j++) { 144 | for (size_t ci = 0; ci < inner_steps; ci++) { 145 | for (size_t cj = 0; cj < inner_steps; cj++) { 146 | x_mutable[i] += internal_coeffs[ci] * eps; 147 | x_mutable[j] += internal_coeffs[cj] * eps; 148 | hess(i, j) += external_coeffs[ci] * external_coeffs[cj] 149 | * f(x_mutable); 150 | x_mutable[j] = x[j]; 151 | x_mutable[i] = x[i]; 152 | } 153 | } 154 | hess(i, j) /= denom; 155 | hess(j, i) = hess(i, j); // The hessian is symmetric 156 | } 157 | } 158 | } 159 | 160 | // Compare if two gradients are close enough. 161 | bool compare_gradient( 162 | const Eigen::Ref& x, 163 | const Eigen::Ref& y, 164 | const double test_eps, 165 | const std::string& msg) 166 | { 167 | assert(x.rows() == y.rows()); 168 | 169 | bool same = true; 170 | for (long i = 0; i < x.rows(); i++) { 171 | double scale = 172 | std::max(std::max(std::abs(x[i]), std::abs(y[i])), double(1.0)); 173 | double abs_diff = std::abs(x[i] - y[i]); 174 | 175 | if (abs_diff > test_eps * scale) { 176 | spdlog::debug( 177 | "{} eps={:.3e} r={} x={:.3e} y={:.3e} |x-y|={:.3e} " 178 | "|x-y|/|x|={:.3e} |x-y|/|y|={:3e}", 179 | msg, test_eps, i, x(i), y(i), abs_diff, abs_diff / abs(x(i)), 180 | abs_diff / std::abs(y(i))); 181 | same = false; 182 | } 183 | } 184 | return same; 185 | } 186 | 187 | // Compare if two jacobians are close enough. 188 | bool compare_jacobian( 189 | const Eigen::Ref& x, 190 | const Eigen::Ref& y, 191 | const double test_eps, 192 | const std::string& msg) 193 | { 194 | assert(x.rows() == y.rows()); 195 | assert(x.cols() == y.cols()); 196 | 197 | bool same = true; 198 | for (long i = 0; i < x.rows(); i++) { 199 | for (long j = 0; j < x.cols(); j++) { 200 | double scale = std::max( 201 | std::max(std::abs(x(i, j)), std::abs(y(i, j))), double(1.0)); 202 | 203 | double abs_diff = std::abs(x(i, j) - y(i, j)); 204 | 205 | if (abs_diff > test_eps * scale) { 206 | spdlog::debug( 207 | "{} eps={:.3e} r={} c={} x={:.3e} y={:.3e} " 208 | "|x-y|={:.3e} |x-y|/|x|={:.3e} |x-y|/|y|={:3e}", 209 | msg, test_eps, i, j, x(i, j), y(i, j), abs_diff, 210 | abs_diff / std::abs(x(i, j)), abs_diff / std::abs(y(i, j))); 211 | same = false; 212 | } 213 | } 214 | } 215 | return same; 216 | } 217 | 218 | // Compare if two hessians are close enough. 219 | bool compare_hessian( 220 | const Eigen::Ref& x, 221 | const Eigen::Ref& y, 222 | const double test_eps, 223 | const std::string& msg) 224 | { 225 | return compare_jacobian(x, y, test_eps, msg); 226 | } 227 | 228 | // Flatten the matrix rowwise 229 | Eigen::VectorXd flatten(const Eigen::Ref& X) 230 | { 231 | Eigen::VectorXd x(X.size()); 232 | for (int i = 0; i < X.rows(); i++) { 233 | for (int j = 0; j < X.cols(); j++) { 234 | x(i * X.cols() + j) = X(i, j); 235 | } 236 | } 237 | return x; 238 | } 239 | 240 | // Unflatten rowwise 241 | Eigen::MatrixXd unflatten(const Eigen::Ref& x, int dim) 242 | { 243 | assert(x.size() % dim == 0); 244 | Eigen::MatrixXd X(x.size() / dim, dim); 245 | for (int i = 0; i < x.size(); i++) { 246 | X(i / dim, i % dim) = x(i); 247 | } 248 | return X; 249 | } 250 | 251 | } // namespace fd 252 | -------------------------------------------------------------------------------- /src/finitediff.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Functions to compute gradients using finite difference. 3 | * 4 | * Based on the functions in https://github.com/PatWie/CppNumericalSolvers 5 | * and rewritten to use Eigen 6 | */ 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace fd { 12 | 13 | /** 14 | * @brief Enumeration of available orders of accuracy for finite differences. 15 | * 16 | * The corresponding integer values are used internally and should be ignored. 17 | */ 18 | enum AccuracyOrder { 19 | SECOND, ///< @brief Second order accuracy. 20 | FOURTH, ///< @brief Fourth order accuracy. 21 | SIXTH, ///< @brief Sixth order accuracy. 22 | EIGHTH ///< @brief Eighth order accuracy. 23 | }; 24 | 25 | /** 26 | * @brief Compute the gradient of a function using finite differences. 27 | * 28 | * @param[in] x Point at which to compute the gradient. 29 | * @param[in] f Compute the gradient of this function. 30 | * @param[out] grad Computed gradient. 31 | * @param[in] accuracy Accuracy of the finite differences. 32 | * @param[in] eps Value of the finite difference step. 33 | */ 34 | void finite_gradient( 35 | const Eigen::Ref& x, 36 | const std::function& f, 37 | Eigen::VectorXd& grad, 38 | const AccuracyOrder accuracy = SECOND, 39 | const double eps = 1.0e-8); 40 | 41 | /** 42 | * @brief Compute the jacobian of a function using finite differences. 43 | * 44 | * @param[in] x Point at which to compute the jacobian. 45 | * @param[in] f Compute the jacobian of this function. 46 | * @param[out] jac Computed jacobian. 47 | * @param[in] accuracy Accuracy of the finite differences. 48 | * @param[in] eps Value of the finite difference step. 49 | */ 50 | void finite_jacobian( 51 | const Eigen::Ref& x, 52 | const std::function& f, 53 | Eigen::MatrixXd& jac, 54 | const AccuracyOrder accuracy = SECOND, 55 | const double eps = 1.0e-8); 56 | 57 | /** 58 | * @brief Compute the hessian of a function using finite differences. 59 | * 60 | * @param[in] x Point at which to compute the hessian. 61 | * @param[in] f Compute the hessian of this function. 62 | * @param[out] hess Computed hessian. 63 | * @param[in] accuracy Accuracy of the finite differences. 64 | * @param[in] eps Value of the finite difference step. 65 | */ 66 | void finite_hessian( 67 | const Eigen::Ref& x, 68 | const std::function& f, 69 | Eigen::MatrixXd& hess, 70 | const AccuracyOrder accuracy = SECOND, 71 | const double eps = 1.0e-5); 72 | 73 | /** 74 | * @brief Compare if two gradients are close enough. 75 | * 76 | * @param[in] x The first gradient to compare. 77 | * @param[in] y The second gradient to compare against. 78 | * @param[in] test_eps Tolerance of equality. 79 | * @param[in] msg Debug message header. 80 | * 81 | * @return A boolean for if x and y are close to the same value. 82 | */ 83 | bool compare_gradient( 84 | const Eigen::Ref& x, 85 | const Eigen::Ref& y, 86 | const double test_eps = 1e-4, 87 | const std::string& msg = "compare_gradient "); 88 | 89 | /** 90 | * @brief Compare if two jacobians are close enough. 91 | * 92 | * @param[in] x The first jacobian to compare. 93 | * @param[in] y The second jacobian to compare against. 94 | * @param[in] test_eps Tolerance of equality. 95 | * @param[in] msg Debug message header. 96 | * 97 | * @return A boolean for if x and y are close to the same value. 98 | */ 99 | bool compare_jacobian( 100 | const Eigen::Ref& x, 101 | const Eigen::Ref& y, 102 | const double test_eps = 1e-4, 103 | const std::string& msg = "compare_jacobian "); 104 | 105 | /** 106 | * @brief Compare if two hessians are close enough. 107 | * 108 | * @param[in] x The first hessian to compare. 109 | * @param[in] y The second hessian to compare against. 110 | * @param[in] test_eps Tolerance of equality. 111 | * @param[in] msg Debug message header. 112 | * 113 | * @return A boolean for if x and y are close to the same value. 114 | */ 115 | bool compare_hessian( 116 | const Eigen::Ref& x, 117 | const Eigen::Ref& y, 118 | const double test_eps = 1e-4, 119 | const std::string& msg = "compare_hessian "); 120 | 121 | /// @brief Flatten the matrix rowwise 122 | Eigen::VectorXd flatten(const Eigen::Ref& X); 123 | 124 | /// @brief Unflatten rowwise 125 | Eigen::MatrixXd unflatten(const Eigen::Ref& x, int dim); 126 | 127 | } // namespace fd 128 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Tests 3 | ################################################################################ 4 | 5 | add_executable(finitediff_tests 6 | test_gradient.cpp 7 | test_jacobian.cpp 8 | test_hessian.cpp 9 | test_flatten.cpp 10 | ) 11 | 12 | ################################################################################ 13 | # Required Libraries 14 | ################################################################################ 15 | 16 | target_link_libraries(finitediff_tests PUBLIC finitediff::finitediff) 17 | 18 | include(finitediff_warnings) 19 | target_link_libraries(finitediff_tests PRIVATE finitediff::warnings) 20 | 21 | include(catch2) 22 | target_link_libraries(finitediff_tests PUBLIC Catch2::Catch2WithMain) 23 | 24 | ################################################################################ 25 | # Compiler options 26 | ################################################################################ 27 | 28 | # target_compile_definitions(${PROJECT_NAME}_tests PUBLIC CATCH_CONFIG_ENABLE_BENCHMARKING) 29 | 30 | ################################################################################ 31 | # Register tests 32 | ################################################################################ 33 | 34 | FetchContent_GetProperties(catch2) 35 | list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) 36 | include(Catch) 37 | 38 | # Register tests 39 | set(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS ON) 40 | catch_discover_tests(finitediff_tests) 41 | -------------------------------------------------------------------------------- /tests/test_flatten.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace fd; 6 | 7 | TEST_CASE("Flatten and unflatten", "[utils]") 8 | { 9 | Eigen::MatrixXd X = Eigen::MatrixXd::Random(1000, 3); 10 | Eigen::MatrixXd R = unflatten(flatten(X), X.cols()); 11 | CHECK(X == R); 12 | } 13 | -------------------------------------------------------------------------------- /tests/test_gradient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | using namespace fd; 11 | 12 | TEST_CASE("Test finite difference gradient of quadratic", "[gradient]") 13 | { 14 | int n = GENERATE(1, 2, 4, 10, 100); 15 | 16 | // f(x) = xᵀAx + bᵀx 17 | Eigen::MatrixXd A = Eigen::MatrixXd::Random(n, n); 18 | Eigen::VectorXd b = Eigen::VectorXd::Random(n); 19 | 20 | const auto f = [&](const Eigen::VectorXd& x) -> double { 21 | return (x.transpose() * A * x + b.transpose() * x)(0); 22 | }; 23 | 24 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 25 | 26 | Eigen::VectorXd grad = A * x + A.transpose() * x + b; 27 | 28 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 29 | 30 | Eigen::VectorXd fgrad; 31 | finite_gradient(x, f, fgrad, accuracy); 32 | 33 | CHECK(compare_gradient(grad, fgrad)); 34 | } 35 | 36 | TEST_CASE("Test finite difference gradient of Rosenbrock", "[gradient]") 37 | { 38 | const auto f = [](const Eigen::VectorXd& x) { 39 | double t1 = 1 - x[0]; 40 | double t2 = (x[1] - x[0] * x[0]); 41 | return t1 * t1 + 100 * t2 * t2; 42 | }; 43 | 44 | const auto fdiff = [](const Eigen::VectorXd& x) { 45 | return Eigen::Vector2d( 46 | -2 * (1 - x[0]) + 200 * (x[1] - x[0] * x[0]) * (-2 * x[0]), 47 | 200 * (x[1] - x[0] * x[0])); 48 | }; 49 | 50 | Eigen::VectorXd x = Eigen::Vector2d::Random(); 51 | 52 | Eigen::VectorXd grad = fdiff(x); 53 | 54 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 55 | 56 | Eigen::VectorXd fgrad; 57 | finite_gradient(x, f, fgrad, accuracy); 58 | 59 | CHECK(compare_gradient(grad, fgrad)); 60 | } 61 | 62 | TEST_CASE("Test finite difference gradient of trig", "[gradient]") 63 | { 64 | int n = GENERATE(1, 2, 4, 10, 100); 65 | 66 | const auto f = [&](const Eigen::VectorXd& x) -> double { 67 | return x.array().sin().matrix().squaredNorm(); 68 | }; 69 | 70 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 71 | 72 | Eigen::VectorXd grad = 2 * x.array().sin() * x.array().cos(); 73 | 74 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 75 | 76 | Eigen::VectorXd fgrad; 77 | finite_gradient(x, f, fgrad, accuracy); 78 | 79 | CHECK(compare_gradient(grad, fgrad)); 80 | } 81 | -------------------------------------------------------------------------------- /tests/test_hessian.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using namespace fd; 12 | 13 | TEST_CASE("Test finite difference hessian of quadratic", "[hessian]") 14 | { 15 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 16 | 17 | int n = GENERATE(1, 2, 4, 10, 25); 18 | 19 | // f(x) = xᵀAx + bᵀx 20 | Eigen::MatrixXd A = Eigen::MatrixXd::Random(n, n); 21 | Eigen::VectorXd b = Eigen::VectorXd::Random(n); 22 | 23 | const auto f = [&](const Eigen::VectorXd& x) -> double { 24 | return (x.transpose() * A * x + b.transpose() * x)(0); 25 | }; 26 | 27 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 28 | 29 | Eigen::MatrixXd hess = A + A.transpose(); 30 | 31 | Eigen::MatrixXd fhess; 32 | finite_hessian(x, f, fhess, accuracy); 33 | 34 | CAPTURE(n); 35 | CHECK(compare_hessian(hess, fhess)); 36 | } 37 | 38 | TEST_CASE("Test finite difference hessian of Rosenbrock", "[hessian]") 39 | { 40 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 41 | const auto f = [](const Eigen::VectorXd& x) { 42 | double t1 = 1 - x[0]; 43 | double t2 = (x[1] - x[0] * x[0]); 44 | return t1 * t1 + 100 * t2 * t2; 45 | }; 46 | 47 | Eigen::VectorXd x = Eigen::Vector2d::Random(); 48 | 49 | Eigen::MatrixXd hess(2, 2); 50 | hess(0, 0) = 1200 * x[0] * x[0] - 400 * x[1] + 2; 51 | hess(0, 1) = -400 * x[0]; 52 | hess(1, 0) = -400 * x[0]; 53 | hess(1, 1) = 200; 54 | 55 | Eigen::MatrixXd fhess; 56 | finite_hessian(x, f, fhess, accuracy); 57 | 58 | CHECK(compare_hessian(hess, fhess)); 59 | } 60 | 61 | TEST_CASE("Test finite difference hessian of trig", "[hessian]") 62 | { 63 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 64 | int n = GENERATE(1, 2, 4, 10, 25); 65 | 66 | const auto f = [&](const Eigen::VectorXd& x) -> double { 67 | return x.array().sin().matrix().squaredNorm(); 68 | }; 69 | 70 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 71 | 72 | Eigen::ArrayXd sin_x = x.array().sin(), cos_x = x.array().cos(); 73 | Eigen::MatrixXd hess = Eigen::MatrixXd::Zero(n, n); 74 | hess.diagonal() = 2 * (cos_x * cos_x) - 2 * (sin_x * sin_x); 75 | 76 | Eigen::MatrixXd fhess; 77 | finite_hessian(x, f, fhess, accuracy); 78 | 79 | CHECK(compare_hessian(hess, fhess)); 80 | } 81 | -------------------------------------------------------------------------------- /tests/test_jacobian.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | using namespace fd; 11 | 12 | TEST_CASE("Test finite difference jacobian of linear", "[jacobian]") 13 | { 14 | int n = GENERATE(1, 2, 4, 10, 100); 15 | 16 | // f(x) = Ax 17 | Eigen::MatrixXd A = Eigen::MatrixXd::Random(n, n); 18 | 19 | const auto f = [&](const Eigen::VectorXd& x) -> Eigen::VectorXd { 20 | return A * x; 21 | }; 22 | 23 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 24 | 25 | Eigen::MatrixXd jac = A; 26 | 27 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 28 | 29 | Eigen::MatrixXd fjac; 30 | finite_jacobian(x, f, fjac, accuracy); 31 | 32 | CHECK(compare_jacobian(jac, fjac)); 33 | } 34 | 35 | TEST_CASE("Test finite difference jacobian of trig", "[jacobian]") 36 | { 37 | int n = GENERATE(1, 2, 4, 10, 100); 38 | 39 | const auto f = [&](const Eigen::VectorXd& x) -> Eigen::VectorXd { 40 | return x.array().sin(); 41 | }; 42 | 43 | Eigen::VectorXd x = Eigen::VectorXd::Random(n); 44 | 45 | Eigen::MatrixXd jac = x.array().cos().matrix().asDiagonal(); 46 | 47 | AccuracyOrder accuracy = GENERATE(SECOND, FOURTH, SIXTH, EIGHTH); 48 | 49 | Eigen::MatrixXd fjac; 50 | finite_jacobian(x, f, fjac, accuracy); 51 | 52 | CHECK(compare_jacobian(jac, fjac)); 53 | } 54 | --------------------------------------------------------------------------------