blob: 0c80145255a706fa6f440f7000a6d0728f23eeca [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// The measurement update of a Kalman filter in information form. A field x on
// n grid points has the prior N(0, tau^2 I); k sensors record weighted sums
// y = H x + e over short footprints, with noise e ~ N(0, s^2 I). The posterior
// precision
// P^{-1} = tau^{-2} I + s^{-2} H^T H
// is diagonal plus rank k, and DiagonalPlusLowRank::inverse returns the
// posterior covariance P as another DiagonalPlusLowRank: the Woodbury identity,
// which is the Kalman update. The posterior mean P H^T y / s^2 is then one O(nk)
// product and the posterior variances are the diagonal of P, with no n x n
// matrix anywhere.
#include <contrib/Eigen/StructuredMatrices>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <random>
using namespace Eigen;
int main() {
const int n = 400, k = 40, width = 4;
const double tau = 1.0, s = 0.05;
// Sensor j averages the points [100 + 2 j, 100 + 2 j + width); neighboring footprints overlap.
MatrixXd H = MatrixXd::Zero(k, n);
for (int j = 0; j < k; ++j) H.row(j).segment(100 + 2 * j, width).setConstant(1.0 / width);
VectorXd x(n);
for (int i = 0; i < n; ++i) x[i] = std::sin(0.05 * i) + 0.5 * std::cos(0.2 * i);
std::minstd_rand generator(1);
VectorXd y = H * x;
for (int j = 0; j < k; ++j) y[j] += s * (2.0 * generator() / generator.max() - 1);
const auto information = makeDiagonalPlusLowRank(VectorXd::Constant(n, 1 / (tau * tau)), H.transpose() / (s * s),
H.transpose()); // P^{-1}
const auto P = information.inverse();
const VectorXd mean = P * VectorXd(H.transpose() * y / (s * s));
const MatrixXd dense = (MatrixXd::Identity(n, n) / (tau * tau) + H.transpose() * H / (s * s)).inverse();
const VectorXd denseMean = dense * H.transpose() * y / (s * s);
double varianceError = 0;
for (int i = 0; i < n; ++i) varianceError = std::max(varianceError, std::abs(P.coeff(i, i) - dense(i, i)));
std::cout << "point footprints truth posterior mean posterior std\n";
for (int i : {50, 100, 101, 140, 179, 250}) {
const int footprints = int((H.col(i).array() != 0).count());
std::cout << std::setw(5) << i << std::setw(12) << footprints << std::setw(7) << std::setprecision(3) << x[i]
<< std::setw(16) << mean[i] << std::setw(15) << std::sqrt(P.coeff(i, i)) << "\n";
}
std::cout << std::setprecision(6) << "max |mean - dense|: " << (mean - denseMean).cwiseAbs().maxCoeff()
<< ", max |variance - dense|: " << varianceError << "\n";
}