8 changed files with 869 additions and 0 deletions
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
############################################################################ |
||||
# |
||||
# Copyright (c) 2020-2021 PX4 Development Team. All rights reserved. |
||||
# |
||||
# Redistribution and use in source and binary forms, with or without |
||||
# modification, are permitted provided that the following conditions |
||||
# are met: |
||||
# |
||||
# 1. Redistributions of source code must retain the above copyright |
||||
# notice, this list of conditions and the following disclaimer. |
||||
# 2. Redistributions in binary form must reproduce the above copyright |
||||
# notice, this list of conditions and the following disclaimer in |
||||
# the documentation and/or other materials provided with the |
||||
# distribution. |
||||
# 3. Neither the name PX4 nor the names of its contributors may be |
||||
# used to endorse or promote products derived from this software |
||||
# without specific prior written permission. |
||||
# |
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
# POSSIBILITY OF SUCH DAMAGE. |
||||
# |
||||
############################################################################ |
||||
|
||||
px4_add_library(SystemIdentification |
||||
system_identification.cpp |
||||
system_identification.hpp |
||||
arx_rls.hpp |
||||
) |
||||
|
||||
px4_add_unit_gtest(SRC arx_rls_test.cpp LINKLIBS SystemIdentification) |
||||
px4_add_unit_gtest(SRC system_identification_test.cpp LINKLIBS SystemIdentification mathlib) |
@ -0,0 +1,208 @@
@@ -0,0 +1,208 @@
|
||||
/****************************************************************************
|
||||
* |
||||
* Copyright (c) 2020-2021 PX4 Development Team. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in |
||||
* the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* 3. Neither the name PX4 nor the names of its contributors may be |
||||
* used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
****************************************************************************/ |
||||
|
||||
/**
|
||||
* @file arx_rls.hpp |
||||
* @brief Efficient recursive weighted least-squares algorithm without matrix inversion |
||||
* |
||||
* Assumes an ARX (autoregressive) model: |
||||
* A(q^-1)y(k) = q^-d * B(q^-1)u(k) + A(q^-1)e(k) |
||||
* |
||||
* with: |
||||
* q^-i backward shift operator |
||||
* A(q^-1) = 1 + a_1*q^-1 +...+ a_n*q^-n |
||||
* B(q^-1) = b_0 + b_1*q^-1...+ b_m*q^-m |
||||
* n order of A(q^-1) |
||||
* m order of B(q^-1) |
||||
* d delay |
||||
* u input of the system |
||||
* y output of the system |
||||
* e white noise input |
||||
* |
||||
* References: |
||||
* - Identification de systemes dynamiques, D.Bonvin and A.Karimi, epfl, 2011 |
||||
* |
||||
* @author Mathieu Bresciani <mathieu@auterion.com> |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <matrix/matrix/math.hpp> |
||||
|
||||
template<size_t N, size_t M, size_t D> |
||||
class ArxRls final |
||||
{ |
||||
public: |
||||
ArxRls() |
||||
{ |
||||
static_assert(N >= M, "The transfer function needs to be proper"); |
||||
|
||||
reset(); |
||||
} |
||||
|
||||
~ArxRls() = default; |
||||
|
||||
void setForgettingFactor(float time_constant, float dt) { _lambda = 1.f - dt / time_constant; } |
||||
void setForgettingFactor(float lambda) { _lambda = lambda; } |
||||
|
||||
/*
|
||||
* return the vector of estimated parameters |
||||
* [a_1 .. a_n b_0 .. b_m]' |
||||
*/ |
||||
const matrix::Vector < float, N + M + 1 > &getCoefficients() const { return _theta_hat; } |
||||
const matrix::Vector < float, N + M + 1 > getVariances() const { return _P.diag(); } |
||||
float getInnovation() const { return _innovation; } |
||||
const matrix::Vector < float, N + M + 1 > &getDiffEstimate() const { return _diff_theta_hat; } |
||||
|
||||
void reset(const matrix::Vector < float, N + M + 1 > &theta_init = {}) |
||||
{ |
||||
/* _P.uncorrelateCovarianceSetVariance<N + M + 1>(0, 10e3f); // does not work */ |
||||
_P.setZero(); |
||||
|
||||
for (size_t i = 0; i < (N + M + 1); i++) { |
||||
_P(i, i) = 10e3f; |
||||
} |
||||
|
||||
_diff_theta_hat.setZero(); |
||||
|
||||
_theta_hat = theta_init; |
||||
|
||||
for (size_t i = 0; i < M + D + 1; i++) { |
||||
_u[i] = 0.f; |
||||
} |
||||
|
||||
for (size_t i = 0; i < N + 1; i++) { |
||||
_y[i] = 0.f; |
||||
} |
||||
|
||||
_nb_samples = 0; |
||||
_innovation = 0.f; |
||||
} |
||||
|
||||
void update(float u, float y) |
||||
{ |
||||
const matrix::Vector < float, N + M + 1 > theta_prev = _theta_hat; |
||||
|
||||
addInputOutput(u, y); |
||||
|
||||
if (!isBufferFull()) { |
||||
// Do not start to update the RLS algorithm when the
|
||||
// buffer still contains zeros
|
||||
return; |
||||
} |
||||
|
||||
const matrix::Vector < float, N + M + 1 > phi = constructDesignVector(); |
||||
const matrix::Matrix < float, 1, N + M + 1 > phi_t = phi.transpose(); |
||||
|
||||
_P = (_P - _P * phi * phi_t * _P / (_lambda + (phi_t * _P * phi)(0, 0))) / _lambda; |
||||
_innovation = _y[N] - (phi_t * _theta_hat)(0, 0); |
||||
_theta_hat = _theta_hat + _P * phi * _innovation; |
||||
|
||||
for (size_t i = 0; i < N + M + 1; i++) { |
||||
_diff_theta_hat(i) = fabsf(_theta_hat(i) - theta_prev(i)); |
||||
} |
||||
|
||||
/* fixCovarianceErrors(); // TODO: this could help against ill-conditioned matrix but needs more testing*/ |
||||
} |
||||
|
||||
private: |
||||
void addInputOutput(float u, float y) |
||||
{ |
||||
shiftRegisters(); |
||||
_u[M + D] = u; |
||||
_y[N] = y; |
||||
|
||||
if (!isBufferFull()) { |
||||
_nb_samples++; |
||||
} |
||||
} |
||||
|
||||
void shiftRegisters() |
||||
{ |
||||
for (size_t i = 0; i < N; i++) { |
||||
_y[i] = _y[i + 1]; |
||||
} |
||||
|
||||
for (size_t i = 0; i < (M + D); i++) { |
||||
_u[i] = _u[i + 1]; |
||||
} |
||||
} |
||||
|
||||
bool isBufferFull() const { return _nb_samples > (M + N + D); } |
||||
|
||||
matrix::Vector < float, N + M + 1 > constructDesignVector() const |
||||
{ |
||||
matrix::Vector < float, N + M + 1 > phi; |
||||
|
||||
for (size_t i = 0; i < N; i++) { |
||||
phi(i) = -_y[N - i - 1]; |
||||
} |
||||
|
||||
int j = 0; |
||||
|
||||
for (size_t i = N; i < (N + M + 1); i++) { |
||||
phi(i) = _u[M - j]; |
||||
j++; |
||||
} |
||||
|
||||
return phi; |
||||
} |
||||
|
||||
void fixCovarianceErrors() |
||||
{ |
||||
float max_var = 0.f; |
||||
|
||||
for (size_t i = 0; i < (N + M + 1); i++) { |
||||
if (_P(i, i) > max_var) { |
||||
max_var = _P(i, i); |
||||
} |
||||
} |
||||
|
||||
const float min_var_allowed = max_var * 0.1f; |
||||
|
||||
for (size_t i = 0; i < (N + M + 1); i++) { |
||||
if (_P(i, i) < min_var_allowed) { |
||||
_P(i, i) = min_var_allowed; |
||||
} |
||||
} |
||||
} |
||||
|
||||
matrix::SquareMatrix < float, N + M + 1 > _P; |
||||
matrix::Vector < float, N + M + 1 > _theta_hat; |
||||
matrix::Vector < float, N + M + 1 > _diff_theta_hat; |
||||
float _innovation{}; |
||||
float _u[M + D + 1] {}; |
||||
float _y[N + 1] {}; |
||||
unsigned _nb_samples{0}; |
||||
float _lambda{1.f}; |
||||
}; |
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
/****************************************************************************
|
||||
* |
||||
* Copyright (C) 2020-2021 PX4 Development Team. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in |
||||
* the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* 3. Neither the name PX4 nor the names of its contributors may be |
||||
* used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
****************************************************************************/ |
||||
|
||||
/**
|
||||
* Test code for the ArxRls class
|
||||
* Run this test only using make tests TESTFILTER=arx_rls |
||||
*/ |
||||
|
||||
#include <gtest/gtest.h> |
||||
#include <matrix/matrix/math.hpp> |
||||
|
||||
#include "arx_rls.hpp" |
||||
|
||||
using namespace matrix; |
||||
|
||||
class ArxRlsTest : public ::testing::Test |
||||
{ |
||||
public: |
||||
ArxRlsTest() {}; |
||||
}; |
||||
|
||||
TEST_F(ArxRlsTest, test211) |
||||
{ |
||||
ArxRls<2, 1, 1> _rls; |
||||
|
||||
for (int i = 0; i < (2 + 1 + 1); i++) { |
||||
// Fill the buffers with zeros
|
||||
_rls.update(0.f, 0.f); |
||||
} |
||||
|
||||
_rls.update(1, 2); |
||||
_rls.update(3, 4); |
||||
_rls.update(5, 6); |
||||
const Vector<float, 4> coefficients = _rls.getCoefficients(); |
||||
float data_check[] = {-1.79f, 0.97f, 0.42f, -0.48f}; // generated from Python script
|
||||
const Vector<float, 4> coefficients_check(data_check); |
||||
float eps = 1e-2; |
||||
EXPECT_TRUE((coefficients - coefficients_check).abs().max() < eps); |
||||
} |
||||
|
||||
TEST_F(ArxRlsTest, test221) |
||||
{ |
||||
ArxRls<2, 2, 1> _rls; |
||||
|
||||
for (int i = 0; i < (2 + 2 + 1); i++) { |
||||
// Fill the buffers with zeros
|
||||
_rls.update(0.f, 0.f); |
||||
} |
||||
|
||||
_rls.update(1, 2); |
||||
_rls.update(3, 4); |
||||
_rls.update(5, 6); |
||||
_rls.update(7, 8); |
||||
const Vector<float, 5> coefficients = _rls.getCoefficients(); |
||||
float data_check[] = {-1.81, 1.06f, 0.38f, -0.27f, 0.26f}; |
||||
const Vector<float, 5> coefficients_check(data_check); |
||||
float eps = 1e-2; |
||||
EXPECT_TRUE((coefficients - coefficients_check).abs().max() < eps); |
||||
coefficients.print(); |
||||
coefficients_check.print(); |
||||
} |
||||
|
||||
TEST_F(ArxRlsTest, resetTest) |
||||
{ |
||||
ArxRls<2, 2, 1> _rls; |
||||
_rls.update(1, 2); |
||||
_rls.update(3, 4); |
||||
_rls.update(5, 6); |
||||
_rls.update(7, 8); |
||||
const Vector<float, 5> coefficients = _rls.getCoefficients(); |
||||
|
||||
// WHEN: resetting
|
||||
_rls.reset(); |
||||
|
||||
// THEN: the variances and coefficients should be properly reset
|
||||
EXPECT_TRUE(_rls.getVariances().min() > 5000.f); |
||||
EXPECT_TRUE(_rls.getCoefficients().abs().max() < 1e-8f); |
||||
|
||||
// AND WHEN: running the same sequence of inputs-outputs
|
||||
_rls.update(1, 2); |
||||
_rls.update(3, 4); |
||||
_rls.update(5, 6); |
||||
_rls.update(7, 8); |
||||
|
||||
// THEN: the result should be exactly the same
|
||||
EXPECT_TRUE((coefficients - _rls.getCoefficients()).abs().max() < 1e-8f); |
||||
} |
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/****************************************************************************
|
||||
* |
||||
* Copyright (c) 2020-2021 PX4 Development Team. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in |
||||
* the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* 3. Neither the name PX4 nor the names of its contributors may be |
||||
* used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
****************************************************************************/ |
||||
|
||||
/**
|
||||
* @file system_identification.cpp |
||||
* |
||||
* @author Mathieu Bresciani <mathieu@auterion.com> |
||||
*/ |
||||
|
||||
#include "system_identification.hpp" |
||||
|
||||
void SystemIdentification::reset(const matrix::Vector<float, 5> &id_state_init) |
||||
{ |
||||
_rls.reset(id_state_init); |
||||
_u_lpf.reset(0.f); |
||||
_u_lpf.reset(0.f); |
||||
_u_hpf = 0.f; |
||||
_y_hpf = 0.f; |
||||
_u_prev = 0.f; |
||||
_y_prev = 0.f; |
||||
_fitness_lpf.reset(10.f); |
||||
_are_filters_initialized = false; |
||||
} |
||||
|
||||
void SystemIdentification::update(float u, float y) |
||||
{ |
||||
updateFilters(u, y); |
||||
update(); |
||||
} |
||||
|
||||
void SystemIdentification::update() |
||||
{ |
||||
_rls.update(_u_hpf, _y_hpf); |
||||
updateFitness(); |
||||
} |
||||
|
||||
void SystemIdentification::updateFilters(float u, float y) |
||||
{ |
||||
if (!_are_filters_initialized) { |
||||
_u_lpf.reset(u); |
||||
_y_lpf.reset(y); |
||||
_u_hpf = 0.f; |
||||
_y_hpf = 0.f; |
||||
_u_prev = u; |
||||
_y_prev = y; |
||||
_are_filters_initialized = true; |
||||
return; |
||||
} |
||||
|
||||
const float u_lpf = _u_lpf.apply(u); |
||||
const float y_lpf = _y_lpf.apply(y); |
||||
_u_hpf = _alpha_hpf * _u_hpf + _alpha_hpf * (u_lpf - _u_prev); |
||||
_y_hpf = _alpha_hpf * _y_hpf + _alpha_hpf * (y_lpf - _y_prev); |
||||
|
||||
_u_prev = u_lpf; |
||||
_y_prev = y_lpf; |
||||
} |
||||
|
||||
void SystemIdentification::updateFitness() |
||||
{ |
||||
const matrix::Vector<float, 5> &diff = _rls.getDiffEstimate(); |
||||
float sum = 0.f; |
||||
|
||||
for (size_t i = 0; i < 5; i++) { |
||||
sum += diff(i); |
||||
} |
||||
|
||||
if (_dt > FLT_EPSILON) { |
||||
_fitness_lpf.update(sum / _dt); |
||||
} |
||||
} |
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
/****************************************************************************
|
||||
* |
||||
* Copyright (c) 2020-2021 PX4 Development Team. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in |
||||
* the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* 3. Neither the name PX4 nor the names of its contributors may be |
||||
* used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
****************************************************************************/ |
||||
|
||||
/**
|
||||
* @file system_identification.hpp |
||||
* |
||||
* @author Mathieu Bresciani <mathieu@auterion.com> |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
#include <lib/mathlib/math/filter/AlphaFilter.hpp> |
||||
#include <matrix/matrix/math.hpp> |
||||
#include <mathlib/mathlib.h> |
||||
#include <mathlib/math/filter/LowPassFilter2p.hpp> |
||||
#include <px4_platform_common/defines.h> |
||||
|
||||
#include "arx_rls.hpp" |
||||
|
||||
class SystemIdentification final |
||||
{ |
||||
public: |
||||
SystemIdentification() = default; |
||||
~SystemIdentification() = default; |
||||
|
||||
void reset(const matrix::Vector<float, 5> &id_state_init = {}); |
||||
void update(float u, float y); // update filters and model
|
||||
void update(); // update model only (to be called after updateFilters)
|
||||
void updateFilters(float u, float y); |
||||
bool areFiltersInitialized() const { return _are_filters_initialized; } |
||||
void updateFitness(); |
||||
const matrix::Vector<float, 5> &getCoefficients() const { return _rls.getCoefficients(); } |
||||
const matrix::Vector<float, 5> getVariances() const { return _rls.getVariances(); } |
||||
const matrix::Vector<float, 5> &getDiffEstimate() const { return _rls.getDiffEstimate(); } |
||||
float getFitness() const { return _fitness_lpf.getState(); } |
||||
float getInnovation() const { return _rls.getInnovation(); } |
||||
|
||||
void setLpfCutoffFrequency(float sample_freq, float cutoff) |
||||
{ |
||||
_u_lpf.set_cutoff_frequency(sample_freq, cutoff); |
||||
_y_lpf.set_cutoff_frequency(sample_freq, cutoff); |
||||
} |
||||
void setHpfCutoffFrequency(float sample_freq, float cutoff) { _alpha_hpf = sample_freq / (sample_freq + 2.f * M_PI_F * cutoff); } |
||||
|
||||
void setForgettingFactor(float time_constant, float dt) { _rls.setForgettingFactor(time_constant, dt); } |
||||
void setFitnessLpfTimeConstant(float time_constant, float dt) |
||||
{ |
||||
_fitness_lpf.setParameters(dt, time_constant); |
||||
_dt = dt; |
||||
} |
||||
|
||||
float getFilteredInputData() const { return _u_hpf; } |
||||
float getFilteredOutputData() const { return _y_hpf; } |
||||
|
||||
private: |
||||
ArxRls<2, 2, 1> _rls; |
||||
math::LowPassFilter2p<float> _u_lpf{400.f, 30.f}; |
||||
math::LowPassFilter2p<float> _y_lpf{400.f, 30.f}; |
||||
|
||||
//TODO: replace by HighPassFilter class
|
||||
float _alpha_hpf{0.f}; |
||||
float _u_hpf{0.f}; |
||||
float _y_hpf{0.f}; |
||||
|
||||
float _u_prev{0.f}; |
||||
float _y_prev{0.f}; |
||||
|
||||
bool _are_filters_initialized{false}; |
||||
|
||||
AlphaFilter<float> _fitness_lpf; |
||||
float _dt{0.1f}; |
||||
}; |
@ -0,0 +1,243 @@
@@ -0,0 +1,243 @@
|
||||
/****************************************************************************
|
||||
* |
||||
* Copyright (C) 2020-2021 PX4 Development Team. All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions |
||||
* are met: |
||||
* |
||||
* 1. Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* 2. Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in |
||||
* the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* 3. Neither the name PX4 nor the names of its contributors may be |
||||
* used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
****************************************************************************/ |
||||
|
||||
/**
|
||||
* Test code for the SystemIdentification class
|
||||
* Run this test only using make tests TESTFILTER=system_identification |
||||
*/ |
||||
|
||||
#include <gtest/gtest.h> |
||||
#include <matrix/matrix/math.hpp> |
||||
|
||||
#include "system_identification.hpp" |
||||
#include "test_data.h" |
||||
|
||||
using namespace matrix; |
||||
|
||||
class SystemIdentificationTest : public ::testing::Test |
||||
{ |
||||
public: |
||||
SystemIdentificationTest() {}; |
||||
float apply(float sample); |
||||
void setCoefficients(float a1, float a2, float b0, float b1, float b2) |
||||
{ |
||||
_a1 = a1; |
||||
_a2 = a2; |
||||
_b0 = b0; |
||||
_b1 = b1; |
||||
_b2 = b2; |
||||
} |
||||
|
||||
private: |
||||
float _a1{}; |
||||
float _a2{}; |
||||
float _b0{}; |
||||
float _b1{}; |
||||
float _b2{}; |
||||
float _delay_element_1{}; |
||||
float _delay_element_2{}; |
||||
}; |
||||
|
||||
float SystemIdentificationTest::apply(float sample) |
||||
{ |
||||
// Direct Form II implementation
|
||||
const float delay_element_0{sample - _delay_element_1 *_a1 - _delay_element_2 * _a2}; |
||||
const float output{delay_element_0 *_b0 + _delay_element_1 *_b1 + _delay_element_2 * _b2}; |
||||
|
||||
_delay_element_2 = _delay_element_1; |
||||
_delay_element_1 = delay_element_0; |
||||
|
||||
return output; |
||||
} |
||||
|
||||
TEST_F(SystemIdentificationTest, basicTest) |
||||
{ |
||||
constexpr float fs = 800.f; |
||||
|
||||
SystemIdentification _sys_id; |
||||
_sys_id.setHpfCutoffFrequency(fs, 0.05f); |
||||
_sys_id.setLpfCutoffFrequency(fs, 30.f); |
||||
_sys_id.setForgettingFactor(80.f, 1.f / fs); |
||||
|
||||
for (int i = 0; i < 5; i++) { |
||||
// Fill the buffers with zeros
|
||||
_sys_id.update(0.f, 0.f); |
||||
} |
||||
|
||||
for (int i = 0; i < 10; i += 2) { |
||||
_sys_id.update(float(i), float(i + 1)); |
||||
} |
||||
|
||||
const Vector<float, 5> coefficients = _sys_id.getCoefficients(); |
||||
float data_check[] = {-3.96f, 1.30f, -2.28f, -0.33f, 0.369f}; |
||||
const Vector<float, 5> coefficients_check(data_check); |
||||
float eps = 1e-2; |
||||
EXPECT_TRUE((coefficients - coefficients_check).abs().max() < eps); |
||||
coefficients.print(); |
||||
_sys_id.getVariances().print(); |
||||
} |
||||
|
||||
TEST_F(SystemIdentificationTest, resetTest) |
||||
{ |
||||
constexpr float fs = 800.f; |
||||
|
||||
SystemIdentification _sys_id; |
||||
_sys_id.setHpfCutoffFrequency(fs, 0.05f); |
||||
_sys_id.setLpfCutoffFrequency(fs, 30.f); |
||||
_sys_id.setForgettingFactor(80.f, 1.f / fs); |
||||
|
||||
for (int i = 0; i < 10; i += 2) { |
||||
_sys_id.update(float(i), float(i + 1)); |
||||
} |
||||
|
||||
const Vector<float, 5> coefficients = _sys_id.getCoefficients(); |
||||
|
||||
// WHEN: resetting
|
||||
_sys_id.reset(); |
||||
|
||||
// THEN: the variances and coefficients should be properly reset
|
||||
EXPECT_TRUE(_sys_id.getCoefficients().abs().max() < 1e-8f); |
||||
EXPECT_TRUE(_sys_id.getVariances().min() > 9e3f); |
||||
|
||||
// AND WHEN: running the same sequence of inputs-outputs
|
||||
|
||||
for (int i = 0; i < 10; i += 2) { |
||||
_sys_id.update(float(i), float(i + 1)); |
||||
} |
||||
|
||||
// THEN: the result should be exactly the same
|
||||
EXPECT_TRUE((coefficients - _sys_id.getCoefficients()).abs().max() < 1e-8f); |
||||
coefficients.print(); |
||||
_sys_id.getVariances().print(); |
||||
} |
||||
|
||||
TEST_F(SystemIdentificationTest, simulatedModelTest) |
||||
{ |
||||
constexpr float fs = 200.f; |
||||
const float gyro_lpf_cutoff = 30.f; |
||||
|
||||
SystemIdentification _sys_id; |
||||
_sys_id.setHpfCutoffFrequency(fs, 0.05f); |
||||
_sys_id.setLpfCutoffFrequency(fs, gyro_lpf_cutoff); |
||||
_sys_id.setForgettingFactor(60.f, 1.f / fs); |
||||
|
||||
// Simulated model with integrator
|
||||
const float a1 = -1.77f; |
||||
const float a2 = 0.77f; |
||||
const float b0 = 0.3812f; |
||||
const float b1 = -0.25f; |
||||
const float b2 = 0.2f; |
||||
setCoefficients(a1, a2, b0, b1, b2); |
||||
const float u_bias = -0.1f; // constant control offset
|
||||
const float y_bias = 0.2f; // measurement bias
|
||||
|
||||
const float dt = 1.f / fs; |
||||
const float duration = 2.f; |
||||
float u = 0.f; |
||||
float y = 0.f; |
||||
|
||||
for (int i = 0; i < static_cast<int>(duration / dt); i++) { |
||||
|
||||
// Generate square input signal
|
||||
if (_sys_id.areFiltersInitialized() |
||||
&& (i % 30 == 0)) { |
||||
if (u > 0.f) { |
||||
u = -1.f; |
||||
|
||||
} else { |
||||
u = 1.f; |
||||
} |
||||
} |
||||
|
||||
_sys_id.update(u + u_bias, y + y_bias); // apply new input and previous output
|
||||
|
||||
y = apply(u); |
||||
#if 0 |
||||
const float t = i * dt; |
||||
printf("%.6f, %.6f, %.6f\n", (double)t, (double)u, (double)y); |
||||
#endif |
||||
} |
||||
|
||||
const Vector<float, 5> coefficients = _sys_id.getCoefficients(); |
||||
float data_check[] = {a1, a2, b0, b1, b2}; |
||||
const Vector<float, 5> coefficients_check(data_check); |
||||
float eps = 1e-3; |
||||
EXPECT_TRUE((coefficients - coefficients_check).abs().max() < eps); |
||||
coefficients.print(); |
||||
_sys_id.getVariances().print(); |
||||
} |
||||
|
||||
TEST_F(SystemIdentificationTest, realDataTest) |
||||
{ |
||||
static constexpr int n_samples_dt_avg = 100; |
||||
static constexpr int n_samples_test_data = sizeof(u_data) / sizeof(u_data[0]); |
||||
float dt_sum = 0.f; |
||||
|
||||
for (int i = 1; i <= n_samples_dt_avg; i++) { |
||||
dt_sum += t_data[i] - t_data[i - 1]; |
||||
} |
||||
|
||||
const float dt = dt_sum / n_samples_dt_avg; |
||||
const float fs = 1.f / dt; |
||||
|
||||
printf("dt = %.6f fs = %.3f\n", (double)dt, (double)(1.f / dt)); |
||||
|
||||
const float gyro_lpf_cutoff = 30.f; |
||||
|
||||
SystemIdentification _sys_id; |
||||
_sys_id.setHpfCutoffFrequency(fs, 0.05f); |
||||
_sys_id.setLpfCutoffFrequency(fs, gyro_lpf_cutoff); |
||||
_sys_id.setForgettingFactor(60.f, dt); |
||||
|
||||
float u = 0.f; |
||||
float y_prev = 0.f; |
||||
|
||||
for (int i = 0; i < n_samples_test_data; i++) { |
||||
u = u_data[i]; |
||||
_sys_id.update(u, y_prev); // apply new input and previous output
|
||||
|
||||
y_prev = y_data[i]; |
||||
|
||||
#if 0 |
||||
printf("%.6f, %.6f, %.6f\n", (double)t_data[i], (double)u, (double)y); |
||||
#endif |
||||
} |
||||
|
||||
const Vector<float, 5> coefficients = _sys_id.getCoefficients(); |
||||
float data_check[] = {-1.916f, 0.916, -0.017f, -0.001f, 0.036f}; |
||||
const Vector<float, 5> coefficients_check(data_check); |
||||
float eps = 1e-3; |
||||
EXPECT_TRUE((coefficients - coefficients_check).abs().max() < eps); |
||||
coefficients.print(); |
||||
_sys_id.getVariances().print(); |
||||
} |
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue