diff --git a/SimoxUtility/CMakeLists.txt b/SimoxUtility/CMakeLists.txt index b39459da876abe4caebf4d4c65af2b4b22101683..dd2a7c433bd57f01791724dba4458e9fb94f9e03 100644 --- a/SimoxUtility/CMakeLists.txt +++ b/SimoxUtility/CMakeLists.txt @@ -40,6 +40,8 @@ SET(SOURCES math/pose/orthogonalize.cpp math/pose/interpolate.cpp + math/regression/linear3d.cpp + math/statistics/Histogram1D.cpp meta/type_name.cpp @@ -187,6 +189,8 @@ SET(INCLUDES math/pose/transform.h math/pose/interpolate.h + math/regression/linear3d.h + math/similarity/cosine_similarity.h math/similarity/angular_similarity.h diff --git a/SimoxUtility/math.h b/SimoxUtility/math.h index 831dc4ca47cab6ec3d30ed744ca82479c25e5b6a..b57cef0caa0dce7c2474a110f47e0d200eec07a6 100644 --- a/SimoxUtility/math.h +++ b/SimoxUtility/math.h @@ -14,6 +14,7 @@ #include "math/periodic_clamp.h" #include "math/pose.h" #include "math/project_to_plane.h" +#include "math/regression.h" #include "math/rescale.h" #include "math/scale_value.h" #include "math/similarity.h" diff --git a/SimoxUtility/math/regression.h b/SimoxUtility/math/regression.h new file mode 100644 index 0000000000000000000000000000000000000000..1e6131d854a53b1695f052cb1de44b7f91765313 --- /dev/null +++ b/SimoxUtility/math/regression.h @@ -0,0 +1,5 @@ +#pragma once + +// This file is generated! + +#include "regression/linear3d.h" diff --git a/SimoxUtility/math/regression/linear3d.cpp b/SimoxUtility/math/regression/linear3d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f1a37111185b3752a549b1f9f29c770f8c0e1e46 --- /dev/null +++ b/SimoxUtility/math/regression/linear3d.cpp @@ -0,0 +1,69 @@ +#include "linear3d.h" + +#include <Eigen/Dense> + + +namespace simox::math +{ + + LinearRegression3D + LinearRegression3D::Fit(const std::vector<double>& xs, const std::vector<Eigen::Vector3d>& ys) + { + Eigen::Matrix3Xd ysMatrix(3, ys.size()); + for (long col = 0; col < ysMatrix.cols(); ++col) + { + ysMatrix.col(col) = ys.at(col); + } + + // The matrix of the predictor functions evaluated at the corresponding xs. + // Since this is a linear regression, the constant function a(t) = 1 and identity + // b(t) = t are used. + Eigen::MatrixX2d linFuncMatrix(xs.size(), 2); + linFuncMatrix.col(0) = Eigen::RowVectorXd::Ones(xs.size()); + linFuncMatrix.col(1) = Eigen::Map<const Eigen::VectorXd>(xs.data(), xs.size()); + + // `linFuncMatrix` is poorly conditioned for xs that are close together + // (e.g. time stamps), so the normal equation would loose a lot of precision. + auto qrDecomp = linFuncMatrix.colPivHouseholderQr(); + + // Each coordinate can be treated individually (general multivariate regression). + // `coeffs` contains a_i and b_i in a_0 + b_0 * t = x, a_1 + b_1 * t = y, etc. + Eigen::Matrix<double, 3, 2> coeffs; + for (int dim = 0; dim < 3; ++dim) + { + Eigen::VectorXd coords = ysMatrix.row(dim).transpose(); + coeffs.row(dim) = qrDecomp.solve(coords); + } + + return LinearRegression3D { .coefficients = coeffs }; + } + + + Eigen::Vector3d + LinearRegression3D::predict(double x) const + { + Eigen::Vector2d input; + input << 1.0, x; + return coefficients * input; + } + +} + + +std::ostream& +simox::math::operator<<(std::ostream& os, const LinearRegression3D& r) +{ + os << "<LinearRegression3D a + b * x = y [ "; + for (Eigen::Index row = 0; row < r.coefficients.rows(); ++row) + { + if (row != 0) + { + os << ", "; + } + os << r.coefficients(row, 0) + << " + " << r.coefficients(row, 1) << " * x_" << row + << " = y_" << row; + } + os << " ]"; + return os; +} diff --git a/SimoxUtility/math/regression/linear3d.h b/SimoxUtility/math/regression/linear3d.h new file mode 100644 index 0000000000000000000000000000000000000000..646b7ea56807c9809385b38a4ac354a048ff81ae --- /dev/null +++ b/SimoxUtility/math/regression/linear3d.h @@ -0,0 +1,58 @@ +#pragma once + +#include <Eigen/Core> + + +namespace simox::math +{ + + /** + * @brief A linear regression model of the form a + b * x = y, + * or per dimension, a_i + b_i * x_i = y_i. + * + * - x is the scalar input variable (e.g. time) + * - y is 3D vector output variable (e.g. position) + * - a is a 3D bias vector + * - b is a 3D coefficient vector + * + * In matrix notation, the underlying equation system is: + * + * [[ a_0 b_0 ] [ 1 1 1 ] [ y_0 ] + * [ a_1 b_1 ] * [ x_0 x_1 x_2 ] = [ y_1 ] + * [ a_2 b_2 ]] [ y_2 ] + */ + class LinearRegression3D + { + public: + + /** + * The coefficients of the bias term and input variable x + * [[ a_0 b_0 ] + * [ a_1 b_1 ] + * [ a_2 b_2 ]] + */ + Eigen::Matrix<double, 3, 2> coefficients; + + + /** + * @brief Fit a linear regression model to the given data. + * @param xs The input variables. + * @param ys The output variables. + * @return The regression model. + */ + static LinearRegression3D + Fit(const std::vector<double>& xs, const std::vector<Eigen::Vector3d>& ys); + + /** + * @brief Predict the output variable of the given input variable. + * @param x The input variable. + * @return The predicted output variable. + */ + Eigen::Vector3d predict(double x) const; + + }; + + + std::ostream& operator<<(std::ostream& os, const LinearRegression3D& r); + +}