Program Listing for File math_utils.cc¶

↰ Return to documentation for file (numerics/math_utils.cc)

#include "lupnt/numerics/math_utils.h"

#include <Eigen/Cholesky>
#include <Eigen/Eigenvalues>
#include <Eigen/SVD>
#include <autodiff/forward/real.hpp>
#include <cmath>
#include <random>

#include "lupnt/core/constants.h"
#include "lupnt/core/random_engine.h"

namespace lupnt {
  template <typename T> VectorX<T> Arange(T start, T stop, T step) {
    std::vector<T> values;
    for (T value = start; value < stop; value += step) {
      values.push_back(value);
    }
    // Create a proper VectorX that owns its memory (not a Map to temporary data)
    return VectorX<T>(Eigen::Map<VectorX<T>>(values.data(), values.size()));
  }
  template VectorX<int> Arange<int>(int start, int stop, int step);
  template VectorX<double> Arange<double>(double start, double stop, double step);
  template VectorX<Real> Arange<Real>(Real start, Real stop, Real step);

  VecXd Subsample(const VecX& x, int step) {
    int n = x.size();
    VecX out(n / step);
    for (int i = 0; i < n; i += step) out(i / step) = x(i);
    return out;
  }

  MatXd Subsample(const MatX& x, int step_row, int step_col) {
    int n_row = x.rows();
    int n_col = x.cols();
    MatX out(n_row / step_row, n_col / step_col);
    for (int i = 0; i < n_row; i += step_row) {
      for (int j = 0; j < n_col; j += step_col) {
        out(i / step_row, j / step_col) = x(i, j);
      }
    }
    return out;
  }

  Real AngleBetweenVecs(const VecX& x, const VecX& y) {
    return 2.0
           * atan2((x.normalized() - y.normalized()).norm(),
                   (x.normalized() + y.normalized()).norm());
  }

  VecX AngleBetweenVecs(const MatX& x, const MatX& y) {
    return (x.rowwise().normalized().array() * y.rowwise().normalized().array())
        .rowwise()
        .sum()
        .cwiseMax(-1.0)
        .cwiseMin(1.0)
        .acos();
  }

  double DegMinSec2DeciDeg(double degrees, double minutes, double seconds) {
    double decimalDegrees = degrees + minutes / 60.0 + seconds / 3600.0;
    return decimalDegrees;
  }

  Real WrapToPi(Real angle) { return atan2(sin(angle), cos(angle)); }
  VecX WrapToPi(const VecX& angle) {
    int n = angle.size();
    VecX out(n);
    for (int i = 0; i < n; i++) out(i) = WrapToPi(angle(i));
    return out;
  }

  Real WrapToTwoPi(Real angle) { return angle - TWO_PI * floor(angle / TWO_PI); }
  VecX WrapToTwoPi(const VecX& angle) {
    int n = angle.size();
    VecX out(n);
    for (int i = 0; i < n; i++) out(i) = WrapToTwoPi(angle(i));
    return out;
  }

  Real round(Real x, int n) {
    Real y = x;
    y[0] = std::round(x.val() * std::pow(10, n)) / std::pow(10, n);
    return y;
  }

  Real frac(Real x) {
    Real y = x;
    y[0] = x.val() - std::floor(x.val());
    return y;
  }

  Real ceil(Real x) {
    Real y = x;
    y[0] = std::ceil(x.val());
    return y;
  }

  Real floor(Real x) {
    Real y = x;
    y[0] = std::floor(x.val());
    return y;
  }

  Real mod(Real x, Real y) {
    Real z = x;
    z[0] = std::fmod(x.val(), y.val());
    return z;
  }

  Real DecimalToDecibel(Real x) { return 10 * log10(x); }
  ArrX DecimalToDecibel(const ArrX& x) { return 10 * log10(x); }

  Real Max(Real x, Real y) { return x.val() > y.val() ? x : y; }
  Real Min(Real x, Real y) { return x.val() < y.val() ? x : y; }
  double MaxD(double x, double y) { return x > y ? x : y; }
  double MinD(double x, double y) { return x < y ? x : y; }

  Real DecibelToDecimal(Real x) { return pow(10, x / 10); }
  ArrX DecibelToDecimal(const ArrX& x) { return pow(10, x / 10); }

  Vec3 DegToDegMinSec(Real deg) {
    Real d = floor(deg);
    Real m = floor((deg - d) * 60);
    Real s = (deg - d - m / 60) * 3600;
    return Vec3(d, m, s);
  }

  Real DegMinSecToDeg(const Vec3& dms) {
    Real decdeg = dms(0) + dms(1) / 60.0 + dms(2) / 3600.0;
    return decdeg;
  }

  Real sind(Real x) { return sin(x * RAD); }

  Real cosd(Real x) { return cos(x * RAD); }

  Real tand(Real x) { return tan(x * RAD); }

  MatX SampleMvNormal(const VecX& mean, const MatX& cov, int nn, std::mt19937* rng) {
    int n = mean.size();
    Eigen::LLT<MatXd> llt(cov.cast<double>());
    MatXd L = llt.matrixL();
    std::normal_distribution<double> dist(0.0, 1.0);
    std::mt19937& gen = rng ? *rng : lupnt::RandomEngine::Get();
    MatX samples(nn, n);
    for (int i = 0; i < nn; i++) {
      VecXd z(n);
      for (int j = 0; j < n; j++) z(j) = dist(gen);
      samples.row(i) = mean + (L * z).cast<Real>();
    }
    return samples;
  }

  Real SampleNormal(Real mean, Real stddev, std::mt19937* rng) {
    std::normal_distribution<double> dist(0.0, 1.0);
    std::mt19937& gen = rng ? *rng : lupnt::RandomEngine::Get();
    return dist(gen) * stddev + mean;
  }

  Real safe_acos(Real x) {
    if (x >= 1.0) {
      return acos(x - EPS);
    } else if (x <= -1.0) {
      return acos(x + EPS);
    } else {
      return acos(x);
    }
  }

  Real safe_asin(Real x) {
    if (x >= 1.0) {
      return asin(x - 1e-16);
    } else if (x <= -1.0) {
      return asin(x + 1e-16);
    } else {
      return asin(x);
    }
  }

  Mat3 RotX(Real angle) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R{
        {1.0, 0.0, 0.0},
        {0.0, c, s},
        {0.0, -s, c},
    };
    return R;
  }

  Mat3 RotY(Real angle) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R{
        {c, 0.0, -s},
        {0.0, 1.0, 0.0},
        {s, 0.0, c},
    };
    return R;
  }

  Mat3 RotZ(Real angle) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R{
        {c, s, 0.0},
        {-s, c, 0.0},
        {0.0, 0.0, 1.0},
    };
    return R;
  }

  Mat3 RotXdot(Real angle, Real angle_dot) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R_dot{
        {0.0, 0.0, 0.0},
        {0.0, -s * angle_dot, c * angle_dot},
        {0.0, -c * angle_dot, -s * angle_dot},
    };
    return R_dot;
  }

  Mat3 RotYdot(Real angle, Real angle_dot) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R_dot{
        {-s * angle_dot, 0.0, -c * angle_dot},
        {0.0, 0.0, 0.0},
        {c * angle_dot, 0.0, -s * angle_dot},
    };
    return R_dot;
  }

  Mat3 RotZdot(Real angle, Real angle_dot) {
    Real c = cos(angle);
    Real s = sin(angle);
    Mat3 R_dot{
        {-s * angle_dot, c * angle_dot, 0.0},
        {-c * angle_dot, -s * angle_dot, 0.0},
        {0.0, 0.0, 0.0},
    };
    return R_dot;
  }

  Mat3 Skew(const Vec3& x) {
    Mat3 skew{
        {0.0, -x(2), x(1)},
        {x(2), 0.0, -x(0)},
        {-x(1), x(0), 0.0},
    };
    return skew;
  }

  std::vector<double> EigenToVector(const VecX& x) {
    std::vector<double> y(x.size());
    for (int i = 0; i < x.size(); i++) {
      y[i] = x(i).val();
    }
    return y;
  }

  Real F(Real eta, Real m, Real l) {
    const double eps = 100.0 * EPS;
    Real w, W, a, n, g;
    w = m / (eta * eta) - l;
    if (abs(w) < 0.1) {  // Series expansion
      W = a = 4.0 / 3.0;
      n = 0.0;
      do {
        n += 1.0;
        a *= w * (n + 2.0) / (n + 1.5);
        W += a;
      } while (abs(a) >= eps);
    } else {
      if (w > 0.0) {
        g = 2.0 * asin(sqrt(w));
        W = (2.0 * g - sin(2.0 * g)) / pow(sin(g), 3);
      } else {
        g = 2.0 * log(sqrt(-w) + sqrt(1.0 - w));  // =2.0*arsinh(sqrt(-w))
        W = (sinh(2.0 * g) - 2.0 * g) / pow(sinh(g), 3);
      }
    }
    return (1.0 - eta + (w + l) * W);
  }

  Real RatioOfSectorToTriangleArea(const Vec3& r1, const Vec3& r2, Real tau) {
    const int max_it = 30;
    const double delta = 100 * EPS;

    Real s1 = r1.norm();
    Real s2 = r2.norm();
    Real kappa = sqrt(2.0 * (s1 * s2 + r1.dot(r2)));
    Real m = tau * tau / pow(kappa, 3);
    Real l = (s1 + s2) / (2.0 * kappa) - 0.5;
    Real eta_min = sqrt(m / (l + 1.0));

    // Start with Hansen's approximation
    Real eta2 = (12.0 + 10.0 * sqrt(1.0 + (44.0 / 9.0) * m / (l + 5.0 / 6.0))) / 22.0;
    Real eta1 = eta2 + 0.1;

    // Secant method
    Real F1 = F(eta1, m, l);
    Real F2 = F(eta2, m, l);

    int it = 0;
    while (abs(F2 - F1) > delta && it < max_it) {
      Real d_eta = -F2 * (eta2 - eta1) / (F2 - F1);
      eta1 = eta2;
      F1 = F2;
      while (eta2 + d_eta <= eta_min) d_eta *= 0.5;
      eta2 += d_eta;
      F2 = F(eta2, m, l);
      ++it;
    }
    if (it >= max_it) throw std::runtime_error("Hansen's method did not converge");
    return eta2;
  }

  VecXd SolveLinearEqSVD(const MatXd& A, const VecXd& b) {
    Eigen::JacobiSVD<MatXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
    return svd.solve(b);
  }

  // Compute A * X = B
  MatXd SolveLinearEqSVD(const MatXd& A, const MatXd& B) {
    Eigen::JacobiSVD<MatXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
    MatXd X(A.cols(), B.cols());
    for (int i = 0; i < B.cols(); i++) {
      X.col(i) = svd.solve(B.col(i));
    }
    return X;
  }

  MatXd PseudoInverse(const MatXd& A) {
    Eigen::JacobiSVD<MatXd> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
    double tol = 1e-6;
    MatXd S = svd.singularValues();
    MatXd S_inv = MatXd::Zero(S.rows(), S.cols());
    for (int i = 0; i < S.rows(); i++) {
      if (S(i) > tol) {
        S_inv(i, i) = 1.0 / S(i);
      }
    }
    MatXd S_pinv = svd.matrixV() * S_inv * svd.matrixU().transpose();
    return S_pinv;
  }

  VecX JacobianParallel(const std::function<VecX(const VecX&)>& func, const VecX& x0, MatXd& J) {
    int n = x0.size();
    std::vector<MatXd> J_vec(n);
    VecX xf;

#pragma omp parallel for
    for (int i = 0; i < n; i++) {
      VecX xi;
      MatXd Ji;
      VecX x0_tmp = x0.cast<double>();
      jacobian(func, wrt(x0_tmp(i)), at(x0_tmp), xi, Ji);
      if (i == 0) xf = xi;
      J_vec[i] = Ji;
    }

    int m = xf.size();
    J.resize(m, n);
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        J(j, i) = J_vec[i](j);
      }
    }
    return xf;
  }

  VecX Vech(const MatX& x) {
    int n = x.rows();
    VecX v(n * (n + 1) / 2);
    for (int i = 0; i < n; i++) {
      for (int j = 0; j <= i; j++) {
        v(i * (n + 1) / 2 + j) = x(i, j);
      }
    }
    return v;
  }

  Real RotationAngle(const Mat3& R) { return acos((R.trace() - 1.0) / 2.0); }
}  // namespace lupnt