Files
allwpilib/sysid/src/main/native/cpp/analysis/OLS.cpp
2024-01-23 21:26:49 -08:00

89 lines
2.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "sysid/analysis/OLS.h"
#include <cassert>
#include <cmath>
#include <Eigen/Cholesky>
namespace sysid {
OLSResult OLS(const Eigen::MatrixXd& X, const Eigen::VectorXd& y) {
assert(X.rows() == y.rows());
// The linear regression model can be written as follows:
//
// y = Xβ + ε
//
// where y is the dependent observed variable, X is the matrix of independent
// variables, β is a vector of coefficients, and ε is a vector of residuals.
//
// We want to find the value of β that minimizes εᵀε.
//
// ε = y
// εᵀε = (y Xβ)ᵀ(y Xβ)
//
// β̂ = argmin (y Xβ)ᵀ(y Xβ)
// β
//
// Take the partial derivative of the cost function with respect to β and set
// it equal to zero, then solve for β̂ .
//
// 0 = 2Xᵀ(y Xβ̂)
// 0 = Xᵀ(y Xβ̂)
// 0 = Xᵀy XᵀXβ̂
// XᵀXβ̂ = Xᵀy
// β̂ = (XᵀX)⁻¹Xᵀy
// β = (XᵀX)⁻¹Xᵀy
//
// XᵀX is guaranteed to be symmetric positive definite, so an LLT
// decomposition can be used.
Eigen::MatrixXd β = (X.transpose() * X).llt().solve(X.transpose() * y);
// Error sum of squares
double SSE = (y - X * β).squaredNorm();
// Sample size
int n = X.rows();
// Number of explanatory variables
int p = β.rows();
// Total sum of squares (total variation in y)
//
// From slide 24 of
// http://www.stat.columbia.edu/~fwood/Teaching/w4315/Fall2009/lecture_11:
//
// SSTO = yᵀy - 1/n yᵀJy
//
// where J is a matrix of ones.
double SSTO =
(y.transpose() * y - 1.0 / y.rows() * y.transpose() *
Eigen::MatrixXd::Ones(y.rows(), y.rows()) * y)
.value();
// R² or the coefficient of determination, which represents how much of the
// total variation (variation in y) can be explained by the regression model
double rSquared = 1.0 - SSE / SSTO;
// Adjusted R²
//
// n 1
// R̅² = 1 (1 R²) ---------
// n p 1
//
// See https://en.wikipedia.org/wiki/Coefficient_of_determination#Adjusted_R2
double adjRSquared = 1.0 - (1.0 - rSquared) * ((n - 1.0) / (n - p - 1.0));
// Root-mean-square error
double RMSE = std::sqrt(SSE / n);
return {{β.data(), β.data() + β.size()}, adjRSquared, RMSE};
}
} // namespace sysid