| /* |
| * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "rtc_tools/frame_analyzer/linear_least_squares.h" |
| |
| #include <math.h> |
| |
| #include <cstdint> |
| #include <cstdlib> |
| #include <functional> |
| #include <numeric> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| |
| namespace webrtc { |
| namespace test { |
| |
| template <class T> |
| using Matrix = std::valarray<std::valarray<T>>; |
| |
| namespace { |
| |
| template <typename R, typename T> |
| R DotProduct(const std::valarray<T>& a, const std::valarray<T>& b) { |
| RTC_CHECK_EQ(a.size(), b.size()); |
| return std::inner_product(std::begin(a), std::end(a), std::begin(b), R(0)); |
| } |
| |
| // Calculates a^T * b. |
| template <typename R, typename T> |
| Matrix<R> MatrixMultiply(const Matrix<T>& a, const Matrix<T>& b) { |
| Matrix<R> result(std::valarray<R>(a.size()), b.size()); |
| for (size_t i = 0; i < a.size(); ++i) { |
| for (size_t j = 0; j < b.size(); ++j) |
| result[j][i] = DotProduct<R>(a[i], b[j]); |
| } |
| |
| return result; |
| } |
| |
| template <typename T> |
| Matrix<T> Transpose(const Matrix<T>& matrix) { |
| if (matrix.size() == 0) |
| return Matrix<T>(); |
| const size_t rows = matrix.size(); |
| const size_t columns = matrix[0].size(); |
| Matrix<T> result(std::valarray<T>(rows), columns); |
| |
| for (size_t i = 0; i < rows; ++i) { |
| for (size_t j = 0; j < columns; ++j) |
| result[j][i] = matrix[i][j]; |
| } |
| |
| return result; |
| } |
| |
| // Convert valarray from type T to type R. |
| template <typename R, typename T> |
| std::valarray<R> ConvertTo(const std::valarray<T>& v) { |
| std::valarray<R> result(v.size()); |
| for (size_t i = 0; i < v.size(); ++i) |
| result[i] = static_cast<R>(v[i]); |
| return result; |
| } |
| |
| // Convert valarray Matrix from type T to type R. |
| template <typename R, typename T> |
| Matrix<R> ConvertTo(const Matrix<T>& mat) { |
| Matrix<R> result(mat.size()); |
| for (size_t i = 0; i < mat.size(); ++i) |
| result[i] = ConvertTo<R>(mat[i]); |
| return result; |
| } |
| |
| // Convert from valarray Matrix back to the more conventional std::vector. |
| template <typename T> |
| std::vector<std::vector<T>> ToVectorMatrix(const Matrix<T>& m) { |
| std::vector<std::vector<T>> result; |
| for (const std::valarray<T>& v : m) |
| result.emplace_back(std::begin(v), std::end(v)); |
| return result; |
| } |
| |
| // Create a valarray Matrix from a conventional std::vector. |
| template <typename T> |
| Matrix<T> FromVectorMatrix(const std::vector<std::vector<T>>& mat) { |
| Matrix<T> result(mat.size()); |
| for (size_t i = 0; i < mat.size(); ++i) |
| result[i] = std::valarray<T>(mat[i].data(), mat[i].size()); |
| return result; |
| } |
| |
| // Returns `matrix_to_invert`^-1 * `right_hand_matrix`. `matrix_to_invert` must |
| // have square size. |
| Matrix<double> GaussianElimination(Matrix<double> matrix_to_invert, |
| Matrix<double> right_hand_matrix) { |
| // `n` is the width/height of `matrix_to_invert`. |
| const size_t n = matrix_to_invert.size(); |
| // Make sure `matrix_to_invert` has square size. |
| for (const std::valarray<double>& column : matrix_to_invert) |
| RTC_CHECK_EQ(n, column.size()); |
| // Make sure `right_hand_matrix` has correct size. |
| for (const std::valarray<double>& column : right_hand_matrix) |
| RTC_CHECK_EQ(n, column.size()); |
| |
| // Transpose the matrices before and after so that we can perform Gaussian |
| // elimination on the columns instead of the rows, since that is easier with |
| // our representation. |
| matrix_to_invert = Transpose(matrix_to_invert); |
| right_hand_matrix = Transpose(right_hand_matrix); |
| |
| // Loop over the diagonal of `matrix_to_invert` and perform column reduction. |
| // Column reduction is a sequence of elementary column operations that is |
| // performed on both `matrix_to_invert` and `right_hand_matrix` until |
| // `matrix_to_invert` has been transformed to the identity matrix. |
| for (size_t diagonal_index = 0; diagonal_index < n; ++diagonal_index) { |
| // Make sure the diagonal element has the highest absolute value by |
| // swapping columns if necessary. |
| for (size_t column = diagonal_index + 1; column < n; ++column) { |
| if (std::abs(matrix_to_invert[column][diagonal_index]) > |
| std::abs(matrix_to_invert[diagonal_index][diagonal_index])) { |
| std::swap(matrix_to_invert[column], matrix_to_invert[diagonal_index]); |
| std::swap(right_hand_matrix[column], right_hand_matrix[diagonal_index]); |
| } |
| } |
| |
| // Reduce the diagonal element to be 1, by dividing the column with that |
| // value. If the diagonal element is 0, it means the system of equations has |
| // many solutions, and in that case we will return an arbitrary solution. |
| if (matrix_to_invert[diagonal_index][diagonal_index] == 0.0) { |
| RTC_LOG(LS_WARNING) << "Matrix is not invertible, ignoring."; |
| continue; |
| } |
| const double diagonal_element = |
| matrix_to_invert[diagonal_index][diagonal_index]; |
| matrix_to_invert[diagonal_index] /= diagonal_element; |
| right_hand_matrix[diagonal_index] /= diagonal_element; |
| |
| // Eliminate the other entries in row `diagonal_index` by making them zero. |
| for (size_t column = 0; column < n; ++column) { |
| if (column == diagonal_index) |
| continue; |
| const double row_element = matrix_to_invert[column][diagonal_index]; |
| matrix_to_invert[column] -= |
| row_element * matrix_to_invert[diagonal_index]; |
| right_hand_matrix[column] -= |
| row_element * right_hand_matrix[diagonal_index]; |
| } |
| } |
| |
| // Transpose the result before returning it, explained in comment above. |
| return Transpose(right_hand_matrix); |
| } |
| |
| } // namespace |
| |
| IncrementalLinearLeastSquares::IncrementalLinearLeastSquares() = default; |
| IncrementalLinearLeastSquares::~IncrementalLinearLeastSquares() = default; |
| |
| void IncrementalLinearLeastSquares::AddObservations( |
| const std::vector<std::vector<uint8_t>>& x, |
| const std::vector<std::vector<uint8_t>>& y) { |
| if (x.empty() || y.empty()) |
| return; |
| // Make sure all columns are the same size. |
| const size_t n = x[0].size(); |
| for (const std::vector<uint8_t>& column : x) |
| RTC_CHECK_EQ(n, column.size()); |
| for (const std::vector<uint8_t>& column : y) |
| RTC_CHECK_EQ(n, column.size()); |
| |
| // We will multiply the uint8_t values together, so we need to expand to a |
| // type that can safely store those values, i.e. uint16_t. |
| const Matrix<uint16_t> unpacked_x = ConvertTo<uint16_t>(FromVectorMatrix(x)); |
| const Matrix<uint16_t> unpacked_y = ConvertTo<uint16_t>(FromVectorMatrix(y)); |
| |
| const Matrix<uint64_t> xx = MatrixMultiply<uint64_t>(unpacked_x, unpacked_x); |
| const Matrix<uint64_t> xy = MatrixMultiply<uint64_t>(unpacked_x, unpacked_y); |
| if (sum_xx && sum_xy) { |
| *sum_xx += xx; |
| *sum_xy += xy; |
| } else { |
| sum_xx = xx; |
| sum_xy = xy; |
| } |
| } |
| |
| std::vector<std::vector<double>> |
| IncrementalLinearLeastSquares::GetBestSolution() const { |
| RTC_CHECK(sum_xx && sum_xy) << "No observations have been added"; |
| return ToVectorMatrix(GaussianElimination(ConvertTo<double>(*sum_xx), |
| ConvertTo<double>(*sum_xy))); |
| } |
| |
| } // namespace test |
| } // namespace webrtc |