forked from sjgardiner/stv-analysis-new
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMatrixUtils.hh
73 lines (63 loc) · 2.56 KB
/
MatrixUtils.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#pragma once
// Standard library includes
#include <cfloat>
#include <cmath>
#include <limits>
#include <memory>
// ROOT includes
#include "TDecompQRH.h"
#include "TMatrixD.h"
constexpr double DEFAULT_MATRIX_INVERSION_TOLERANCE = 1e-4;
std::unique_ptr< TMatrixD > invert_matrix( const TMatrixD& mat,
const double inversion_tolerance = DEFAULT_MATRIX_INVERSION_TOLERANCE )
{
// Pre-scale before inversion to avoid numerical problems. Here we choose a
// scaling factor such that the smallest nonzero entry in the original matrix
// has an absolute value of unity. Note the use of the zero-based element
// indices for TMatrixD.
constexpr double BIG_DOUBLE = std::numeric_limits<double>::max();
double min_abs = BIG_DOUBLE;
int num_bins = mat.GetNrows();
for ( int a = 0; a < num_bins; ++a ) {
for ( int b = 0; b < num_bins; ++b ) {
double element = mat( a, b );
double abs_el = std::abs( element );
if ( abs_el > 0. && abs_el < min_abs ) min_abs = abs_el;
}
}
// If all matrix elements are zero, then this scaling won't work
// and something is wrong. Complain if this is the case.
if ( min_abs == BIG_DOUBLE ) {
throw std::runtime_error( "Cannot invert a null matrix" );
}
// We're ready. Do the actual pre-scaling here. Keep the first TMatrixD
// and invert a copy. This allows us to check that the inversion was
// successful below.
auto inverse_matrix = std::make_unique< TMatrixD >( mat );
double scaling_factor = 1. / min_abs;
inverse_matrix->operator*=( scaling_factor );
// Do the inversion. Here we try the QR method. Other options involve using
// TDecompLU, TDecompBK, TDecompChol, TDecompQRH and TDecompSVD. The
// Invert() member function of TMatrixDSym uses a TDecompBK object to do
// the same thing internally.
//inverse_matrix->Invert();
TDecompQRH qr_decomp( *inverse_matrix, DBL_EPSILON );
qr_decomp.Invert( *inverse_matrix );
// Undo the scaling by re-applying it to the inverse matrix
inverse_matrix->operator*=( scaling_factor );
// Double-check that we get a unit matrix by multiplying the
// original by its inverse
TMatrixD unit_mat( mat, TMatrixD::kMult, *inverse_matrix );
for ( int a = 0; a < num_bins; ++a ) {
for ( int b = 0; b < num_bins; ++b ) {
double element = unit_mat( a, b );
double expected_element = 0.;
if ( a == b ) expected_element = 1.;
double abs_diff = std::abs( element - expected_element );
if ( abs_diff > inversion_tolerance ) {
throw std::runtime_error( "Matrix inversion failed" );
}
}
}
return inverse_matrix;
}