EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
KfMatrix.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file KfMatrix.cxx
1 //
2 // AYK (ayk@bnl.gov), cleaned up 2014/10/14;
3 //
4 // Yet another matrix package;
5 //
6 
7 #include <cstdio>
8 #include <cmath>
9 #include <cstdlib>
10 #include <cstring>
11 #include <cassert>
12 
13 #ifndef _USE_ROOT_TMATRIX_
14 #include <TMatrixD.h>
15 #include <TArrayD.h>
16 #endif
17 
18 #include <ayk.h>
19 #include <KfMatrix.h>
20 
21 // Well, this code is explicitely written to be used inside
22 // generalized Kalman filter routines; therefore I don't really
23 // care how would it work in a different environment; in Kalman
24 // filter application there is a very limited set of matrix
25 // dimensions used, so it makes perfect sense to create a
26 // persistent chain of buffer matrices; in other programs where
27 // various dimensions may be used, this can be terribly RAM
28 // consuming and inefficient;
30  // Pointer to the actual matrix structure;
32 
33  // This buffer matrix may be in use; this flag must be reset
34  // upon every *external* entry; internally if KFM routines call
35  // each other they should pass an extra argument in order to
36  // avoid cleaning up (for instance triple multiplication uses
37  // calls to dual one); cleaning BUSY flags should be avoided in the
38  // latter;
39  int busy_flag;
40 
42 };
43 
45 
46 // Well, for a debugged code when it is known that all matrix
47 // dimensions match, makes no sense to spend CPU time on checks;
48 // so this flag may be set to 0 (see respective method);
50 
51 // =======================================================================================
52 // Well, it is nasty to always check return codes of Kfm*() routines; makes
53 // sense to check that dimensions match during debugging and then turn
54 // checks off; just assert() suffices then;
55 
56 int KfMatrix::KfmReturn(int ret)
57 {
58  if (ret && mDimensionCheckFlag) assert(0);
59 
60  return ret;
61 } // KfMatrix::KfmReturn()
62 
63 // =======================================================================================
64 // It will also perform intialization to 0.;
65 
66 //
67 // Should later somehow mark success/failure here (can not return values
68 // in this - constructor - call);
69 //
70 
71 KfMatrix::KfMatrix(unsigned dim1, unsigned dim2)
72 {
73  memset(this, 0x00, sizeof(KfMatrix));
74 
75  mDim1 = dim1;
76  mDim2 = dim2;
77  mDim = mDim1*mDim2;
78 
79 #ifdef _USE_ROOT_TMATRIX_
80  // CHECK: row/column switch?;
81  mMtx = new TMatrixD(mDim1, mDim2);
82  // CHECK: really needed?;
83  mMtx->Zero();
84 #else
85  mArr = new double[mDim];
86  memset(mArr, 0x00, mDim*sizeof(double));
87 #endif
88 } // KfMatrix::KfMatrix()
89 
90 // ---------------------------------------------------------------------------------------
91 
93 {
94 #ifdef _USE_ROOT_TMATRIX_
95  if (mMtx) delete mMtx;
96 #else
97  if (mArr) delete[] mArr;
98 #endif
99 } // KfMatrix::~KfMatrix()
100 
101 // =======================================================================================
102 // This is a simple two matrix dimension check for add/subtract/scale ops;
103 
105 {
106  // Sanity check;
107  if (!kfm2) return -1;
108 
109  if (GetDim1() != kfm2->GetDim1() || GetDim2() != kfm2->GetDim2()) return -1;
110 
111  return 0;
112 } // KfMatrix::DimensionMatchCheck()
113 
114 // ---------------------------------------------------------------------------------------
115 
116 void KfMatrix::AssignTrueDimensions(int trans, int dim[2])
117 {
118  // NB: exact contents of 'trans' bits is of no interest (just either 0 or not);
119  if (trans) {
120  dim[0] = mDim2;
121  dim[1] = mDim1;
122  }
123  else {
124  dim[0] = mDim1;
125  dim[1] = mDim2;
126  } //if
127 } // KfMatrix::AssignTrueDimensions()
128 
129 // ---------------------------------------------------------------------------------------
130 // This check is for matrix multiplication; it is more complicated since
131 // either of in1/in2 may be transposed;
132 
133 int KfMatrix::DimensionChainCheck(KfMatrix *in1, KfMatrix *in2, unsigned mode)
134 {
135  int din1[2], din2[2];
136 
137  // Sanity check;
138  if (!in1 || !in2) return -1;
139 
140  // Assign 'true' dimensions of in1/in2 depending on whether
141  // their transposition will take place;
142  in1->AssignTrueDimensions(mode & _TRANSPOSE_IN1_, din1);
143  in2->AssignTrueDimensions(mode & _TRANSPOSE_IN2_, din2);
144 
145  // Clear, neighboring matrices should have matching dimensions;
146  if (mDim1 != din1[0] || din1[1] != din2[0] || din2[1] != mDim2) return -1;
147 
148  return 0;
149 } // KfMatrix::DimensionChainCheck()
150 
151 // =======================================================================================
152 
153 //
154 // Do not mind to have these routines (and linked list) to be static;
155 // FIXME: may want to do this more elegant later;
156 //
157 
158 static void ResetBusyFlags( void )
159 {
160  t_kf_matrix_chain *mring;
161 
162  for(mring=buffer; mring; mring=mring->next)
163  mring->busy_flag = 0;
164 } // ResetBusyFlags()
165 
166 // ---------------------------------------------------------------------------------------
167 
168 static KfMatrix *GetBufferMatrix(int dim1, int dim2)
169 {
170  t_kf_matrix_chain *mring;
171 
172  // Loop through already available matrices;
173  for(mring=buffer; mring; mring=mring->next) {
174  if (mring->busy_flag) continue;
175 
176  // Appropriate matrix found (and is free!) --> break;
177  if (mring->kfm->GetDim1() == dim1 && mring->kfm->GetDim2() == dim2) break;
178  } //for
179 
180  // No appropriate (and free!) matrix found --> allocate new;
181  if (!mring) {
182  mring = *btail = new t_kf_matrix_chain();
183  mring->kfm = new KfMatrix(dim1, dim2);
184 
185  btail = &mring->next;
186  } //if
187 
188  mring->busy_flag = 1;
189 
190  return mring->kfm;
191 } // GetBufferMatrix()
192 
193 // ---------------------------------------------------------------------------------------
194 
196 {
197  KfMatrix *buffer = GetBufferMatrix(GetDim1(), GetDim2());
198 
199  if (!buffer) return NULL;
200 
201  // Since dimensions match, this can not fail;
202  buffer->CopyFrom(this);
203 
204  return buffer;
205 } // KfMatrix::Bufferize()
206 
207 // =======================================================================================
208 
210 {
211 #ifdef _USE_ROOT_TMATRIX_
212  mMtx->Zero();
213 #else
214  for(int ii=0; ii<mDim1; ii++)
215  for(int ik=0; ik<mDim2; ik++)
216  KFM(ii,ik) = 0.0;
217 #endif
218 } // KfMatrix::Reset()
219 
220 // ---------------------------------------------------------------------------------------
221 
223 {
224  if (mDim1 != mDim2) return KfmReturn(-1);
225 
226  for(int ii=0; ii<mDim1; ii++)
227  for(int ik=0; ik<mDim1; ik++)
228  KFM(ii,ik) = ii == ik ? 1. : 0.;
229 
230  return 0;
231 } // KfMatrix::Unity()
232 
233 // =======================================================================================
234 
236 {
237  if (!in) return KfMatrix::KfmReturn(-1);
238 
239  // Well, and no error?;
240  if (in == this) return 0;
241 
242  if (GetDimensionCheckFlag() && DimensionMatchCheck(in)) return KfmReturn(-1);
243 
244 #ifdef _USE_ROOT_TMATRIX_
245  *mMtx = *in->mMtx;
246 #else
247  for(unsigned ii=0; ii<in->mDim1; ii++)
248  for(unsigned ik=0; ik<in->mDim2; ik++)
249  KFM(ii,ik) = in->KFM(ii,ik);
250 #endif
251 
252  return 0;
253 } // KfMatrix::CopyFrom()
254 
255 // ---------------------------------------------------------------------------------------
256 // NB: buffering is not needed here (even if in1=in2==out, will work);
257 
258 // These 2 numbers are used in internal operations only;
259 #define _KF_MATRIX_ADD_ 0
260 #define _KF_MATRIX_SUBTRACT_ 1
261 
262 int KfMatrix::AddCore(KfMatrix *in1, KfMatrix *in2, int what)
263 {
264  if (GetDimensionCheckFlag() &&
266  return KfmReturn(-1);
267 
268 #ifdef _USE_ROOT_TMATRIX_
269  if (what == _KF_MATRIX_ADD_)
270  *mMtx = *in1->mMtx + *in2->mMtx;
271  else
272  *mMtx = *in1->mMtx - *in2->mMtx;
273 #else
274  // Avoid additional complication --> two separate branches;
275  if (what == _KF_MATRIX_ADD_)
276  for(unsigned ii=0; ii<in1->mDim1; ii++)
277  for(unsigned ik=0; ik<in1->mDim2; ik++)
278  KFM(ii,ik) = in1->KFM(ii,ik) + in2->KFM(ii,ik);
279  else
280  for(unsigned ii=0; ii<in1->mDim1; ii++)
281  for(unsigned ik=0; ik<in1->mDim2; ik++)
282  KFM(ii,ik) = in1->KFM(ii,ik) - in2->KFM(ii,ik);
283 #endif
284 
285  return 0;
286 } // KfMatrix::AddCore()
287 
288 // ---------------------------------------------------------------------------------------
289 
291 {
292  return AddCore(in1, in2, _KF_MATRIX_ADD_);
293 } // KfMatrix::SetToSum()
294 
295 // ---------------------------------------------------------------------------------------
296 
298 {
299  return AddCore(in1, in2, _KF_MATRIX_SUBTRACT_);
300 } // KfMatrix::SetToDifference()
301 
302 // ---------------------------------------------------------------------------------------
303 
304 //
305 // It seems there is no real need to optimize these two?;
306 //
307 
309 {
310  return AddCore(this, in, _KF_MATRIX_ADD_);
311 } // KfMatrix::Add()
312 
313 // ---------------------------------------------------------------------------------------
314 
316 {
317  return AddCore(this, in, _KF_MATRIX_SUBTRACT_);
318 } // KfMatrix::Subtract()
319 
320 // =======================================================================================
321 // NB: buffering is not needed here (even if in=out, will work);
322 
324 {
325  if (GetDimensionCheckFlag() && DimensionMatchCheck(in)) return KfmReturn(-1);
326 
327  // Perform scalar multiplication;
328 #ifdef _USE_ROOT_TMATRIX_
329  // Has not been checked;
330  assert(0);
331  *mMtx = cff * *in->mMtx;
332 #else
333  for(unsigned ii=0; ii<in->mDim1; ii++)
334  for(unsigned ik=0; ik<in->mDim2; ik++)
335  KFM(ii,ik) = cff*in->KFM(ii,ik);
336 #endif
337 
338  return 0;
339 } // KfMatrix::SetToProduct()
340 
341 // ---------------------------------------------------------------------------------------
342 
343 int KfMatrix::SetToProduct(KfMatrix *in1, KfMatrix *in2, unsigned mode)
344 {
345  // Reset BUSY flags on buffer matrices (user entry point!);
346  ResetBusyFlags();
347 
348  unsigned trans1 = mode & _TRANSPOSE_IN1_, trans2 = mode & _TRANSPOSE_IN2_;
349  // Since buffering of either in1 or in2 may be possible, introduce safe
350  // source pointers, which are just in1/2 if buffering is not needed;
351  KfMatrix *tin1 = in1, *tin2 = in2;
352 
353  // Do the check this way even if 'out' may coincide with either 'in1' or 'in2';
354  if (KfMatrix::GetDimensionCheckFlag() && DimensionChainCheck(in1, in2, mode))
356 
357 #ifdef _USE_ROOT_TMATRIX_
358  assert(!(trans1 && trans2));
359  switch (mode) {
360  case 0:
361  *mMtx = *in1->mMtx * *in2->mMtx;
362  break;
363  case _TRANSPOSE_IN1_:
364  *mMtx = TMatrixD(TMatrixD::kTransposed, *in1->mMtx) * *in2->mMtx;
365  break;
366  case _TRANSPOSE_IN2_:
367  *mMtx = *in1->mMtx * TMatrixD(TMatrixD::kTransposed, *in2->mMtx);
368  break;
369  } //switch
370 #else
371  // Figure out whether buffering is needed;
372  if (this == in1) tin1 = in1->Bufferize();
373  // This is also more or less clear (avoid double buffering);
374  if (this == in2) tin2 = (in2 == in1) ? tin1 : in2->Bufferize();
375 
376  // Set output matrix to 0.;
377  Reset();
378  // And eventually calculate it; 'qdim' is a bit of a subtlety;
379  int qdim = trans1 ? tin1->mDim1 : tin1->mDim2;
380 
381  for(int ii=0; ii<mDim1; ii++)
382  for(int ip=0; ip<qdim; ip++)
383  for(int ik=0; ik<mDim2; ik++)
384  {
385  double val1 = trans1 ? tin1->KFM(ip,ii) : tin1->KFM(ii,ip);
386  double val2 = trans2 ? tin2->KFM(ik,ip) : tin2->KFM(ip,ik);
387 
388  KFM(ii,ik) += val1*val2;
389  } /*for..for*/
390 #endif
391 
392  return 0;
393 } // KfMatrix::SetToProduct()
394 
395 // ---------------------------------------------------------------------------------------
396 
397 int KfMatrix::SetToProductCore(KfMatrix *in1, KfMatrix *in2, KfMatrix *in3, unsigned mode)
398 {
399  int ret, nl, nr, dim1, dim2, dim3, dim4;
400  unsigned trans1 = mode & _TRANSPOSE_IN1_;
401  unsigned trans2 = mode & _TRANSPOSE_IN2_;
402  unsigned trans3 = mode & _TRANSPOSE_IN3_;
403  KfMatrix *buffer;
404 
405  // Estimate number of operations needed to calculate ABC as (AB)C
406  // or A(BC) in order to minimize CPU losses; assume dimensions
407  // do match (otherwise multiplication will fail later anyway);
408  // a proper choice can drastically improve performance;
409  dim1 = mDim1; dim2 = trans1 ? in1->mDim1 : in1->mDim2;
410  dim3 = trans2 ? in2->mDim1 : in2->mDim2; dim4 = mDim2;
411  nl = dim1*dim2*dim3 + dim1*dim3*dim4;
412  nr = dim2*dim3*dim4 + dim1*dim2*dim4;
413 
414  if (nl < nr) {
415  // Request buffer matrix;
416  buffer = GetBufferMatrix(trans1 ? in1->mDim2 : in1->mDim1,
417  trans2 ? in2->mDim1 : in2->mDim2);
418  if (!buffer) return KfMatrix::KfmReturn(-1);
419 
420  // Perform first multiplication;
421  ret = buffer->SetToProduct(in1, in2, trans1 | trans2);
422  if (ret) return ret;
423 
424  // Perform second multiplication and return;
425  // NB: indeed 'buffer' is not transposed;
426  return SetToProduct(buffer, in3, trans3 >> 1);
427  }
428  else {
429  // Request buffer matrix;
430  buffer = GetBufferMatrix(trans2 ? in2->mDim2 : in2->mDim1,
431  trans3 ? in3->mDim1 : in3->mDim2);
432  if (!buffer) return KfMatrix::KfmReturn(-1);
433 
434  // Perform first multiplication;
435  ret = buffer->SetToProduct(in2, in3, (trans2 | trans3) >> 1);
436  if (ret) return ret;
437 
438  // Perform second multiplication and return;
439  return SetToProduct(in1, buffer, trans1);
440  } //if
441 } // KfMatrix::SetToProductCore()
442 
443 // ---------------------------------------------------------------------------------------
444 
445 //
446 // NB: both SetToProduct() and SetToProductCore() are needed, since otherwise
447 // VectorLengthSquare() will call ResetBusyFlags() twice;
448 //
449 
450 int KfMatrix::SetToProduct(KfMatrix *in1, KfMatrix *in2, KfMatrix *in3, unsigned mode)
451 {
452  // Reset BUSY flags on buffer matrices (user entry point!);
453  ResetBusyFlags();
454 
455  return SetToProductCore(in1, in2, in3, mode);
456 } // KfMatrix::SetToProduct()
457 
458 // ---------------------------------------------------------------------------------------
459 // This routine is not too much efficient in the easiest case of MDIM=1;
460 
461 int KfMatrix::VectorLengthSquare(KfMatrix *metric, double *lsqr)
462 {
463  // Reset BUSY flags on buffer matrices (user entry point!);
464  ResetBusyFlags();
465 
466  KfMatrix *value = GetBufferMatrix(1, 1);
467 
468  int ret = value->SetToProductCore(this, metric, this, _TRANSPOSE_IN1_);
469  if (ret) return ret;
470 
471  if (lsqr) *lsqr = value->KFM(0,0);
472 
473  return 0;
474 } // KfMatrix::VectorLengthSquare()
475 
476 // =======================================================================================
477 
479 {
480  if (mDim1 != mDim2) return KfmReturn(-1);
481 
482  // If mdim=1, take an easy way;
483  if (mDim1 == 1) {
484  // Well, symmetric actually means also "positively definite";
485  //NB: return code of this routine is always checked in my code;
486  // so no kfm_return() needed;
487  if (type == KfMatrix::Symmetric && KFM(0,0) <= 0.) return -1;
488 
489  KFM(0,0) = 1./KFM(0,0);
490 
491  return 0;
492  }
493  else
494  // Well, for mdim=2 also do not need to introduce extra complications;
495  // NB: this code has not yet been checked!;
496  if (mDim1 == 2)
497  {
498  // Order 0..1 does not matter here, but should be in sync with
499  // assignment back few lines below;
500  double a[2][2] = {{KFM(0,0), KFM(0,1)}, {KFM(1,0), KFM(1,1)}};
501  double det = a[0][0]*a[1][1] - a[0][1]*a[1][0];
502 
503  if (!det) return -1;
504 
505  // No positivity check?;
506  if (type == KfMatrix::Symmetric && a[0][1] != a[1][0]) return -1;
507 
508  KFM(0,0) = a[1][1]/det;
509  KFM(0,1) = -a[0][1]/det;
510  KFM(1,0) = -a[1][0]/det;
511  KFM(1,1) = a[0][0]/det;
512 
513  return 0;
514  }
515  else {
516  // FIXME: why does not it work for dim=1,2?!;
517 #ifdef _USE_ROOT_TMATRIX_
518  mMtx->Invert();
519 #else
520  // Well, want to avoid CERNLIB linkage alltogether; standard
521  // KF usage does not require high dimension matrix inversion,
522  // so do not care much about performance here;
523  TArrayD arr(mDim1*mDim1);
524 
525  // Think about the [ip,iq] order here!;
526  for(int ip=0; ip<mDim1*mDim1; ip++)
527  arr[ip] = ARR()[ip];
528 
529  TMatrixD bff; bff.Use(mDim1, mDim1, arr.GetArray());
530 
531  bff.Invert();
532 
533  for(int ip=0; ip<mDim1*mDim1; ip++)
534  ARR()[ip] = arr[ip];
535 #endif
536 
537  return 0;
538  } //if
539 } // KfMatrix::Invert()
540 
541 // =======================================================================================
542 
543 void KfMatrix::Print(const char *fmt)
544 {
545  printf("\n");
546  for(int ip=0; ip<mDim1; ip++) {
547  for(int iq=0; iq<mDim2; iq++)
548  printf(fmt, KFM(ip,iq));
549  printf("\n");
550  } //for ip
551 } // KfMatrix::Print()
552 
553 // ---------------------------------------------------------------------------------------
554 
555 int KfMatrix::CorrelationPrint(const char *fmt)
556 {
557  if (mDim1 != mDim2) return KfmReturn(-1);
558 
559  // Ok, just "return -1" here?;
560  for(int ip=0; ip<mDim1; ip++)
561  if (KFM(ip,ip) <= 0.) {
562  printf("Non-positive diagonal element: %20.15f\n", KFM(ip,ip));
563  return -1;
564  } //for ip .. if
565 
566  printf("\n");
567  for(int ip=0; ip<mDim1; ip++) {
568  for(int iq=0; iq<mDim2; iq++)
569  printf(fmt, KFM(ip,iq)/sqrt(KFM(ip,ip)*KFM(iq,iq)));
570  printf("\n");
571  } //for ip
572 
573  return 0;
574 } // KfMatrix::CorrelationPrint()
575 
576 // =======================================================================================
577 
579 {
580  if (mDim1 != mDim2) return KfmReturn(-1);
581 
582  for(int ip=0; ip<mDim1; ip++)
583  for(int iq=0; iq<mDim1; iq++) {
584  if (ip == iq) continue;
585 
586  KFM(ip,iq) = KFM(iq,ip) = (KFM(ip,iq) + KFM(iq,ip))/2.;
587  } //for ip..iq
588 
589  return 0;
590 } // KfMatrix::ForceSymmetric()
591 
592 // ---------------------------------------------------------------------------------------
593 // Well, this is a "naive" positivity check indeed;
594 
596 {
597  if (mDim1 != mDim2) return KfmReturn(-1);
598 
599  for(int ip=0; ip<mDim1; ip++)
600  if (KFM(ip,ip) <= 0.) return -1;
601 
602  for(int ip=0; ip<mDim1; ip++)
603  for(int iq=0; iq<mDim1; iq++) {
604  if (ip == iq) continue;
605 
606  if (SQR(KFM(ip,iq)) > KFM(ip,ip)*KFM(iq,iq)) return -1;
607  } //for ip..iq
608 
609  return 0;
610 } // KfMatrix::CheckPositivity()
611 
612 // ---------------------------------------------------------------------------------------
613 
614 int KfMatrix::FixPositivity(double maxFixablePositivityScrewup,
615  double positivityCorrelationFix)
616 {
617  // Well, cases with negative diagonal elements are fatal I guess;
618  for(int ip=0; ip<GetDim1(); ip++)
619  if (KFM(ip,ip) <= 0.) return -1;
620 
621  // Just rescale "too big" off-diagonal elements;
622  for(int ip=0; ip<GetDim1(); ip++)
623  for(int iq=0; iq<GetDim1(); iq++) {
624  if (ip == iq) continue;
625 
626  double corr = SQR(KFM(ip,iq));
627  double limit = KFM(ip,ip)*KFM(iq,iq);
628 
629  // Then this coefficient requires a fix;
630  if (corr > limit) {
631  // Too off; something is terribly wrong; give up;
632  if (corr > maxFixablePositivityScrewup) {
633  //if (_verbosityLevel >= 3)
634  //printf("Necessary correction (%7.2f) is higher than alllowed limit (%7.2f)\n",
635  // corr, limit);
636  return -1;
637  } //if
638 
639  // Othewise apply a fix;
640  KFM(ip,iq) = (KFM(ip,iq) > 0. ? 1. : -1.)*
641  sqrt(limit*positivityCorrelationFix);
642  } //if
643  } //for ip..iq
644 
645  // Be on a safe side; but this call should now really return 0;
646  return CheckPositivity();
647 } // FixPositivity()
648 
649 // =======================================================================================