blob: 478843df35e9fcddc52e41f516dbff32fca688ef [file] [log] [blame]
Magnus Jedvert1927dfa2018-09-11 12:56:06 +02001/*
2 * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "rtc_tools/frame_analyzer/linear_least_squares.h"
12
Yves Gerey3e707812018-11-28 16:47:49 +010013#include <math.h>
14#include <cstdint>
15#include <cstdlib>
16#include <functional>
Magnus Jedvert1927dfa2018-09-11 12:56:06 +020017#include <numeric>
Yves Gerey3e707812018-11-28 16:47:49 +010018#include <type_traits>
Magnus Jedvert1927dfa2018-09-11 12:56:06 +020019#include <utility>
20
21#include "rtc_base/checks.h"
22#include "rtc_base/logging.h"
23
24namespace webrtc {
25namespace test {
26
27template <class T>
28using Matrix = std::valarray<std::valarray<T>>;
29
30namespace {
31
32template <typename R, typename T>
33R DotProduct(const std::valarray<T>& a, const std::valarray<T>& b) {
34 RTC_CHECK_EQ(a.size(), b.size());
35 return std::inner_product(std::begin(a), std::end(a), std::begin(b), R(0));
36}
37
38// Calculates a^T * b.
39template <typename R, typename T>
40Matrix<R> MatrixMultiply(const Matrix<T>& a, const Matrix<T>& b) {
41 Matrix<R> result(std::valarray<R>(a.size()), b.size());
42 for (size_t i = 0; i < a.size(); ++i) {
43 for (size_t j = 0; j < b.size(); ++j)
44 result[j][i] = DotProduct<R>(a[i], b[j]);
45 }
46
47 return result;
48}
49
50template <typename T>
51Matrix<T> Transpose(const Matrix<T>& matrix) {
52 if (matrix.size() == 0)
53 return Matrix<T>();
54 const size_t rows = matrix.size();
55 const size_t columns = matrix[0].size();
56 Matrix<T> result(std::valarray<T>(rows), columns);
57
58 for (size_t i = 0; i < rows; ++i) {
59 for (size_t j = 0; j < columns; ++j)
60 result[j][i] = matrix[i][j];
61 }
62
63 return result;
64}
65
66// Convert valarray from type T to type R.
67template <typename R, typename T>
68std::valarray<R> ConvertTo(const std::valarray<T>& v) {
69 std::valarray<R> result(v.size());
70 for (size_t i = 0; i < v.size(); ++i)
71 result[i] = static_cast<R>(v[i]);
72 return result;
73}
74
75// Convert valarray Matrix from type T to type R.
76template <typename R, typename T>
77Matrix<R> ConvertTo(const Matrix<T>& mat) {
78 Matrix<R> result(mat.size());
79 for (size_t i = 0; i < mat.size(); ++i)
80 result[i] = ConvertTo<R>(mat[i]);
81 return result;
82}
83
84// Convert from valarray Matrix back to the more conventional std::vector.
85template <typename T>
86std::vector<std::vector<T>> ToVectorMatrix(const Matrix<T>& m) {
87 std::vector<std::vector<T>> result;
88 for (const std::valarray<T>& v : m)
89 result.emplace_back(std::begin(v), std::end(v));
90 return result;
91}
92
93// Create a valarray Matrix from a conventional std::vector.
94template <typename T>
95Matrix<T> FromVectorMatrix(const std::vector<std::vector<T>>& mat) {
96 Matrix<T> result(mat.size());
97 for (size_t i = 0; i < mat.size(); ++i)
98 result[i] = std::valarray<T>(mat[i].data(), mat[i].size());
99 return result;
100}
101
102// Returns |matrix_to_invert|^-1 * |right_hand_matrix|. |matrix_to_invert| must
103// have square size.
104Matrix<double> GaussianElimination(Matrix<double> matrix_to_invert,
105 Matrix<double> right_hand_matrix) {
106 // |n| is the width/height of |matrix_to_invert|.
107 const size_t n = matrix_to_invert.size();
108 // Make sure |matrix_to_invert| has square size.
109 for (const std::valarray<double>& column : matrix_to_invert)
110 RTC_CHECK_EQ(n, column.size());
111 // Make sure |right_hand_matrix| has correct size.
112 for (const std::valarray<double>& column : right_hand_matrix)
113 RTC_CHECK_EQ(n, column.size());
114
115 // Transpose the matrices before and after so that we can perform Gaussian
116 // elimination on the columns instead of the rows, since that is easier with
117 // our representation.
118 matrix_to_invert = Transpose(matrix_to_invert);
119 right_hand_matrix = Transpose(right_hand_matrix);
120
121 // Loop over the diagonal of |matrix_to_invert| and perform column reduction.
122 // Column reduction is a sequence of elementary column operations that is
123 // performed on both |matrix_to_invert| and |right_hand_matrix| until
124 // |matrix_to_invert| has been transformed to the identity matrix.
125 for (size_t diagonal_index = 0; diagonal_index < n; ++diagonal_index) {
126 // Make sure the diagonal element has the highest absolute value by
127 // swapping columns if necessary.
128 for (size_t column = diagonal_index + 1; column < n; ++column) {
129 if (std::abs(matrix_to_invert[column][diagonal_index]) >
130 std::abs(matrix_to_invert[diagonal_index][diagonal_index])) {
131 std::swap(matrix_to_invert[column], matrix_to_invert[diagonal_index]);
132 std::swap(right_hand_matrix[column], right_hand_matrix[diagonal_index]);
133 }
134 }
135
136 // Reduce the diagonal element to be 1, by dividing the column with that
137 // value. If the diagonal element is 0, it means the system of equations has
138 // many solutions, and in that case we will return an arbitrary solution.
139 if (matrix_to_invert[diagonal_index][diagonal_index] == 0.0) {
140 RTC_LOG(LS_WARNING) << "Matrix is not invertible, ignoring.";
141 continue;
142 }
143 const double diagonal_element =
144 matrix_to_invert[diagonal_index][diagonal_index];
145 matrix_to_invert[diagonal_index] /= diagonal_element;
146 right_hand_matrix[diagonal_index] /= diagonal_element;
147
148 // Eliminate the other entries in row |diagonal_index| by making them zero.
149 for (size_t column = 0; column < n; ++column) {
150 if (column == diagonal_index)
151 continue;
152 const double row_element = matrix_to_invert[column][diagonal_index];
153 matrix_to_invert[column] -=
154 row_element * matrix_to_invert[diagonal_index];
155 right_hand_matrix[column] -=
156 row_element * right_hand_matrix[diagonal_index];
157 }
158 }
159
160 // Transpose the result before returning it, explained in comment above.
161 return Transpose(right_hand_matrix);
162}
163
164} // namespace
165
166IncrementalLinearLeastSquares::IncrementalLinearLeastSquares() = default;
167IncrementalLinearLeastSquares::~IncrementalLinearLeastSquares() = default;
168
169void IncrementalLinearLeastSquares::AddObservations(
170 const std::vector<std::vector<uint8_t>>& x,
171 const std::vector<std::vector<uint8_t>>& y) {
172 if (x.empty() || y.empty())
173 return;
174 // Make sure all columns are the same size.
175 const size_t n = x[0].size();
176 for (const std::vector<uint8_t>& column : x)
177 RTC_CHECK_EQ(n, column.size());
178 for (const std::vector<uint8_t>& column : y)
179 RTC_CHECK_EQ(n, column.size());
180
181 // We will multiply the uint8_t values together, so we need to expand to a
182 // type that can safely store those values, i.e. uint16_t.
183 const Matrix<uint16_t> unpacked_x = ConvertTo<uint16_t>(FromVectorMatrix(x));
184 const Matrix<uint16_t> unpacked_y = ConvertTo<uint16_t>(FromVectorMatrix(y));
185
186 const Matrix<uint64_t> xx = MatrixMultiply<uint64_t>(unpacked_x, unpacked_x);
187 const Matrix<uint64_t> xy = MatrixMultiply<uint64_t>(unpacked_x, unpacked_y);
188 if (sum_xx && sum_xy) {
189 *sum_xx += xx;
190 *sum_xy += xy;
191 } else {
192 sum_xx = xx;
193 sum_xy = xy;
194 }
195}
196
197std::vector<std::vector<double>>
198IncrementalLinearLeastSquares::GetBestSolution() const {
199 RTC_CHECK(sum_xx && sum_xy) << "No observations have been added";
200 return ToVectorMatrix(GaussianElimination(ConvertTo<double>(*sum_xx),
201 ConvertTo<double>(*sum_xy)));
202}
203
204} // namespace test
205} // namespace webrtc