Skip to content

Commit

Permalink
[benchmark] add Eigen 3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
FrancoisCarouge committed Sep 23, 2022
1 parent f1e04c4 commit f034cfa
Show file tree
Hide file tree
Showing 2,099 changed files with 389,448 additions and 1,344 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/verify_test_ubuntu-22-04_clang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ jobs:
export CC=clang
export CXX=clang++
cmake -B "build" .
cmake --build "build" --parallel
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel
cmake --build "build" --parallel 8
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel 8
9 changes: 6 additions & 3 deletions .github/workflows/verify_test_ubuntu-22-04_gcc-trunk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3.0.2
- name: Build
- name: Install
run: |
wget http://kayari.org/gcc-latest/gcc-latest.deb
sudo dpkg -i gcc-latest.deb
- name: Build
run: |
export CC=/opt/gcc-latest/bin/gcc
export CXX=/opt/gcc-latest/bin/g++
export LD_LIBRARY_PATH=/opt/gcc-latest/lib64:$LD_LIBRARY_PATH
cmake -B "build" .
cmake --build "build" --parallel
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel
cmake --build "build" --parallel 8
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel 8
4 changes: 2 additions & 2 deletions .github/workflows/verify_test_ubuntu-22-04_gcc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ jobs:
- name: Build
run: |
cmake -B "build" .
cmake --build "build" --parallel
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel
cmake --build "build" --parallel 8
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel 8
4 changes: 2 additions & 2 deletions .github/workflows/verify_test_windows-2019_msvc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ jobs:
- name: Build
run: |
cmake -B "build" .
cmake --build "build" --parallel
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel
cmake --build "build" --parallel 8
ctest --test-dir "build" --tests-regex "kalman_(test|sample)" --output-on-failure --parallel 8
108 changes: 56 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ Standard formatter specialization provided for representation of the filter stat
Example from the building height estimation sample. One estimated state and one observed output filter.

```cpp
kalman k;
kalman filter;

k.x(60.);
k.p(225.);
k.r(25.);
filter.x(60.);
filter.p(225.);
filter.r(25.);

k(48.54);
filter.update(48.54);
```

## 6x2 Constant Acceleration Dynamic Model
Expand All @@ -66,32 +66,33 @@ Example from the 2-dimension vehicle location estimation sample. Six estimated s
```cpp
using kalman = kalman<vector<double, 6>, vector<double, 2>>;

kalman k;
k.x(0., 0., 0., 0., 0., 0.);
k.p(kalman::estimate_uncertainty{ { 500, 0, 0, 0, 0, 0 },
{ 0, 500, 0, 0, 0, 0 },
{ 0, 0, 500, 0, 0, 0 },
{ 0, 0, 0, 500, 0, 0 },
{ 0, 0, 0, 0, 500, 0 },
{ 0, 0, 0, 0, 0, 500 } });
k.q(0.2 * 0.2 * kalman::process_uncertainty{ { 0.25, 0.5, 0.5, 0, 0, 0 },
{ 0.5, 1, 1, 0, 0, 0 },
{ 0.5, 1, 1, 0, 0, 0 },
{ 0, 0, 0, 0.25, 0.5, 0.5 },
{ 0, 0, 0, 0.5, 1, 1 },
{ 0, 0, 0, 0.5, 1, 1 } });
k.f(kalman::state_transition{ { 1, 1, 0.5, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 1, 0.5 },
{ 0, 0, 0, 0, 1, 1 },
{ 0, 0, 0, 0, 0, 1 } });
k.h(kalman::output_model{ { 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0 } });
k.r(kalman::output_uncertainty{ { 9, 0 }, { 0, 9 } });
k(-375.93, 301.78);
kalman filter;

filter.x(0., 0., 0., 0., 0., 0.);
filter.p(kalman::estimate_uncertainty{ { 500, 0, 0, 0, 0, 0 },
{ 0, 500, 0, 0, 0, 0 },
{ 0, 0, 500, 0, 0, 0 },
{ 0, 0, 0, 500, 0, 0 },
{ 0, 0, 0, 0, 500, 0 },
{ 0, 0, 0, 0, 0, 500 } });
filter.q(0.2 * 0.2 * kalman::process_uncertainty{ { 0.25, 0.5, 0.5, 0, 0, 0 },
{ 0.5, 1, 1, 0, 0, 0 },
{ 0.5, 1, 1, 0, 0, 0 },
{ 0, 0, 0, 0.25, 0.5, 0.5 },
{ 0, 0, 0, 0.5, 1, 1 },
{ 0, 0, 0, 0.5, 1, 1 } });
filter.f(kalman::state_transition{ { 1, 1, 0.5, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 1, 0.5 },
{ 0, 0, 0, 0, 1, 1 },
{ 0, 0, 0, 0, 0, 1 } });
filter.h(kalman::output_model{ { 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0 } });
filter.r(kalman::output_uncertainty{ { 9, 0 }, { 0, 9 } });

filter.predict();
filter.update(-375.93, 301.78);
```
## 4x1 Non-Linear Dynamic Model
Expand All @@ -102,29 +103,29 @@ Example from the thermal, current of warm air, strength, radius, and location es
using kalman = kalman<vector<float, 4>, float, void, std::tuple<float, float>,
std::tuple<float, float>>;
kalman k;
kalman filter;
k.x(1 / 4.06, 80, 0, 0);
k.p(kalman::estimate_uncertainty{ { 0.0049, 0, 0, 0 },
{ 0, 400, 0, 0 },
{ 0, 0, 400, 0 },
{ 0, 0, 0, 400 } });
k.transition([](const kalman::state &x, const float &drift_x,
const float &drift_y) -> kalman::state {
filter.x(1 / 4.06, 80, 0, 0);
filter.p(kalman::estimate_uncertainty{ { 0.0049, 0, 0, 0 },
{ 0, 400, 0, 0 },
{ 0, 0, 400, 0 },
{ 0, 0, 0, 400 } });
filter.transition([](const kalman::state &x, const float &drift_x,
const float &drift_y) -> kalman::state {
return x + kalman::state{ 0, 0, -drift_x, -drift_y };
});
k.q(kalman::process_uncertainty{ { 0.000001, 0, 0, 0 },
{ 0, 0.0009, 0, 0 },
{ 0, 0, 0.0009, 0 },
{ 0, 0, 0, 0.0009 } });
k.r(0.2025);
k.observation([](const kalman::state &x, const float &position_x,
const float &position_y) -> kalman::output {
filter.q(kalman::process_uncertainty{ { 0.000001, 0, 0, 0 },
{ 0, 0.0009, 0, 0 },
{ 0, 0, 0.0009, 0 },
{ 0, 0, 0, 0.0009 } });
filter.r(0.2025);
filter.observation([](const kalman::state &x, const float &position_x,
const float &position_y) -> kalman::output {
return kalman::output{ x(0) *
std::exp(-((x(2) - position_x)*(x(2) - position_x) +
(x(3) - position_y) * (x(3) - position_y)) / x(1) * x(1)) };
k.h([](const kalman::state &x, const float &position_x,
const float &position_y) -> kalman::output_model {
filter.h([](const kalman::state &x, const float &position_x,
const float &position_y) -> kalman::output_model {
const auto exp{ std::exp(-((x(2) - position_x) * (x(2) - position_x) +
(x(3) - position_y) * (x(3) - position_y)) / (x(1) * x(1))) };
const kalman::output_model h{
Expand All @@ -137,7 +138,8 @@ k.h([](const kalman::state &x, const float &position_x,
return h;
});
k(drift_x, drift_y, position_x, position_y, variometer);
filter.predict(drift_x, drift_y);
filter.update(position_x, position_y, variometer);
```

# Continuous Integration & Deployment Actions
Expand Down Expand Up @@ -277,9 +279,9 @@ class kalman
A specialization of the standard formatter is provided for the filter. Use `std::format` to store a formatted representation of all of the characteristics of the filter in a new string. Standard format parameters to be supported.
```cpp
kalman k;
kalman filter;
std::print("{}", k);
std::print("{}", filter);
// {"f": 1, "h": 1, "k": 1, "p": 1, "q": 0, "r": 0, "s": 1, "x": 0, "y": 0, "z": 0}
```

Expand Down Expand Up @@ -307,16 +309,18 @@ This package explores what could be a Kalman filter implementation a la standard

## Selected Tradeoffs

In theory there is no difference between theory and practice, while in practice there is:
In theory there is no difference between theory and practice, while in practice there is. The following tradeoffs hav been selected for the implementation:

- Update and prediction additional arguments are stored in the filter at the costs of memory and performance for the benefits of consistent data access and records.
- The default floating point data type for the filter is `double` with about 16 significant digits to reduce loss of information compared to `float`.

## Lessons Learned

Design, development, and testing uncovered unexpected facets of the projects:

- The filter's state, output, and input column vectors should be type template parameters to allow the filter to participate in full-compile time verification of unit and index-type safeties.
- There exists Kalman filters with hundreds of state variables.
- The `float` data type has about 7 significant digits. Floating point error is a loss of information to account for in design.
## Performance
Expand Down
19 changes: 17 additions & 2 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,27 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org> ]]

set(SOURCES "baseline.cpp" "predict1x1x0.cpp" "predict1x1x1.cpp"
"update1x1x0.cpp" "update1x1x1.cpp")
set(SOURCES "baseline.cpp" "predict_1x1x0.cpp" "predict_1x1x1.cpp"
"update_1x1x0.cpp" "update_1x1x1.cpp")

foreach(STATE_SIZE RANGE 1 2)
foreach(OUTPUT_SIZE RANGE 1 2)
configure_file(eigen_update_xx0.cpp
eigen_update_${STATE_SIZE}x${OUTPUT_SIZE}x0.cpp)
list(APPEND SOURCES eigen_update_${STATE_SIZE}x${OUTPUT_SIZE}x0.cpp)
endforeach()
foreach(INPUT_SIZE RANGE 1 2)
configure_file(eigen_predict_x1x.cpp
eigen_predict_${STATE_SIZE}x1x${INPUT_SIZE}.cpp)
list(APPEND SOURCES eigen_predict_${STATE_SIZE}x1x${INPUT_SIZE}.cpp)
endforeach()
endforeach()

foreach(BENCHMARK ${SOURCES})
get_filename_component(NAME ${BENCHMARK} NAME_WE)
add_executable(kalman_benchmark_${NAME}_driver ${BENCHMARK})
target_include_directories(kalman_benchmark_${NAME}_driver PRIVATE "."
"include")
target_link_libraries(
kalman_benchmark_${NAME}_driver
PRIVATE eigen kalman benchmark::benchmark_main benchmark::benchmark)
Expand Down
12 changes: 6 additions & 6 deletions benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ Plot the results on Linux:
Run on Microsoft Windows 10 on native x64 with Visual Studio 2022 compiler 19.33 in release mode.

![Float](image/float.svg)
![Float 1x1x0](image/float1x1x0.svg)
![Float 1x1x1](image/float1x1x1.svg)
![Float 1x1x0](image/float_1x1x0.svg)
![Float 1x1x1](image/float_1x1x1.svg)
![Baseline](image/baseline.svg)
![Update Float 1x1x0](image/update1x1x0.svg)
![Update Float 1x1x1](image/update1x1x1.svg)
![Predict Float 1x1x0](image/predict1x1x0.svg)
![Predict Float 1x1x1](image/predict1x1x1.svg)
![Update Float 1x1x0](image/update_1x1x0.svg)
![Update Float 1x1x1](image/update_1x1x1.svg)
![Predict Float 1x1x0](image/predict_1x1x0.svg)
![Predict Float 1x1x1](image/predict_1x1x1.svg)
2 changes: 1 addition & 1 deletion benchmark/baseline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org> */

#include "fcarouge/benchmark/benchmark.hpp"
#include "benchmark.hpp"

#include <benchmark/benchmark.h>

Expand Down
107 changes: 107 additions & 0 deletions benchmark/eigen_predict_x1x.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* __ _ __ __ _ _
| |/ / /\ | | | \/ | /\ | \ | |
| ' / / \ | | | \ / | / \ | \| |
| < / /\ \ | | | |\/| | / /\ \ | . ` |
| . \ / ____ \| |____| | | |/ ____ \| |\ |
|_|\_\/_/ \_\______|_| |_/_/ \_\_| \_|
Kalman Filter for C++
Version 0.1.0
https://github.com/FrancoisCarouge/Kalman
SPDX-License-Identifier: Unlicense
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org> */

#include "benchmark.hpp"
#include "fcarouge/eigen/kalman.hpp"
#include "fcarouge/internal/utility.hpp"

#include <benchmark/benchmark.h>

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <random>

namespace fcarouge::eigen::benchmark {

//! @benchmark Measure the prediction of the filter for different dimensions of
//! states and inputs with the Eigen linear algebra backend.
template <std::size_t StateSize, std::size_t InputSize>
void eigen_predict(::benchmark::State &state) {

using kalman =
kalman<vector<float, StateSize>, float, vector<float, InputSize>>;

kalman filter;
std::random_device random_device;
std::mt19937 random_generator(random_device());
std::uniform_real_distribution uniformly_distributed(0.f, 1.f);

for (auto _ : state) {

typename kalman::input u;

fcarouge::internal::for_constexpr<std::size_t{0}, InputSize, 1>(
[&u, &uniformly_distributed, &random_generator](auto position) {
u[position] = uniformly_distributed(random_generator);
});

::benchmark::ClobberMemory();
const auto start{clock::now()};

filter.predict(u);

::benchmark::ClobberMemory();
const auto end{clock::now()};

state.SetIterationTime(
std::chrono::duration_cast<std::chrono::duration<double>>(end - start)
.count());
}
}

//! @todo Find a way to remove macros or find a different benchmark library that
//! doesn't use macros.
BENCHMARK(eigen_predict<${STATE_SIZE}, ${INPUT_SIZE}>)
->Name("eigen_predict_${STATE_SIZE}x1x${INPUT_SIZE}")
->Unit(::benchmark::kNanosecond)
->ComputeStatistics("min",
[](const auto &results) {
return std::ranges::min(results);
})
-> ComputeStatistics("max",
[](const auto &results) {
return std::ranges::max(results);
}) -> UseManualTime()
-> Complexity(::benchmark::oAuto) -> DisplayAggregatesOnly(true)
-> Repetitions(100);

} // namespace fcarouge::eigen::benchmark
Loading

0 comments on commit f034cfa

Please sign in to comment.