EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
KalmanFilter.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file KalmanFilter.cxx
1 //
2 // AYK (ayk@bnl.gov)
3 //
4 // Kalman filter class routines; ported from HERMES/OLYMPUS sources;
5 // cleaned up 2014/10/10;
6 //
7 
8 #include <cassert>
9 #include <cstdlib>
10 
11 #include <Math/DistFunc.h>
12 
13 #include <Splitter.h>
14 #include <htclib.h>
15 #include <KalmanFilter.h>
16 
17 // =======================================================================================
18 
20 {
21  // CHECK: this (void*) cast was required by Clang; need to check that the stuff still works;
22  //@@@ assert(0);
23  // Ok, this apparently kills "nodes" set initialization ...;
24  memset((void*)this, 0x00, sizeof(KalmanFilter));
25 
26  // NB: do not need xm[] vector calculation usually -> can disable
27  // via SetXmCalculationFlag() call;
28  mXmCalculationFlag = true;
29 
30  // Just copy over all the stuff;
31  sDim = sdim;
32 
33  // Allocate buffer matrices for intermediate calculations;
34  SMTX = new KfMatrix(sdim, sdim);
35  SVEC = new KfVector(sdim);
36  QVEC = new KfVector(sdim);
37 
39 
40  // Unity matrix can be global;
41  SU = new KfMatrix(sdim, sdim);
42  SU->Unity();
43 
44  // Set reasonable default values for these 2 variables ...;
47 
48  // ... and for these two as well;
51 
52  // Force CP, CF & CS matrices positivity check at each step; think twice
53  // befor making these options configurable -> high-level routines expect that
54  // if Kalman chain succeeded, all nodes have "clean" CS[][], at least;
56 
57  // NB: this value may require some task-specific tuning;
59 } // KalmanFilter::KalmanFilter()
60 
61 // ---------------------------------------------------------------------------------------
62 // Put a new node in a proper place in the chain accoring to it's Z coordinate;
63 
64 KalmanNode *KalmanFilter::AddNode(const char *name, double z, int mdim,
65  const bool nonLinearTransportFlags[2]/*, void *ptr*/)
66 {
67  KalmanNode *node = AllocateNode();
68 
69  if (!node) return NULL;
70 
71  assert(mdim >= 0);
72  node->mDim = mdim;
73 
74  // Assign user back-door pointer;
75  //node->mBackDoorPointer = ptr;
76 
77  // Assign non-linear transport flags;
78  for(unsigned fb=KalmanFilter::Forward; fb<=KalmanFilter::Backward; fb++)
79  node->mNonLinearTransportFlags[fb] = nonLinearTransportFlags[fb];
80 
81  // Allocate name if given;
82  if (name) {
83  node->mName = strdup(name);
84  if (!node->mName) return NULL;
85  } //if
86 
87  node->SetZ(z);
88 
89  node->AllocateKfMatrices(sDim);
90 
91  // As of Oct'2015 the double-linked list will be created later; just put
92  // everything in the Z-ordered multi-map and return;
93  mKalmanNodePool.insert(std::pair<double, KalmanNode*>(z, node));
94 
95  return node;
96 } // KalmanFilter::AddNode()
97 
98 // ---------------------------------------------------------------------------------------
99 
100 KalmanNode *KalmanFilter::AddNodeWrapper(const char *name, const char *format, double z, int mdim)
101 {
102  char buffer[STRING_LEN_MAX];
103  // No tricks in this call -> either both FB-directions "true" or both "false";
104  bool nonLinearFlags[2] = {NeedNonLinearTransport(z), NeedNonLinearTransport(z)};
105 
106  // Construct name if NULL; use provided format and 'z' variable otherwise;
107  if (!name) snprintf(buffer, STRING_LEN_MAX-1, format, z);
108 
109  return AddNode(name ? name : buffer, z, mdim, nonLinearFlags);
110 } // KalmanFilter::AddNodeWrapper()
111 
112 // ---------------------------------------------------------------------------------------
113 
115 {
116  mHead = mTail = 0;
117 
118  // Nodes are Z-ordered in the multi-map; so just go arrange a double-linked list;
119  for(std::map<double, KalmanNode*>::iterator it=mKalmanNodePool.begin();
120  it != mKalmanNodePool.end(); it++) {
121  KalmanNode *node = it->second;
122 
123  // Part of the nodes may be de-activated on purpose (say, no hit);
124  if (!node->IsActive()) continue;
125 
126  if (!mHead)
127  mHead = node;
128  else {
129  mTail->SetNext(node);
130  node->SetPrev(mTail);
131  } //if
132 
133  // This is needed since BuildNodeList() can be called more than once;
134  node->SetNext(0);
135 
136  mTail = node;
137 
138  //printf("KalmanFilter::BuildNodeList(): %7.2f %s -> %d %d\n",
139  // it->first, node->GetName(), node->IsActive(), node->IsFired());
140  } //for it
141 } // KalmanFilter::BuildNodeList()
142 
143 // ---------------------------------------------------------------------------------------
144 
145 int KalmanFilter::Configure(const StringList *config/*, const char *format*/)
146 {
147  // Use presently available nodes to build the linked list;
148  BuildNodeList();
149 
150  for(const StringList *str=config; str; str=str->mNextString) {
151  NodeGroup **gtail = &mNodeGroups;
152 
153  // Copy over and save original qstr pointer (help free());
154  char *qstr = strdup(str->mString), *qqstr = qstr;
155 
156  assert(qqstr);
157  qqstr += strlen(_FIRED_RPLANE_PREFIX_);
158 
159  // Ok, now parse the string and create node groups;
160  for( ; ; ) {
161  char *comma = strchr(qqstr, ',');
162 
163  // Substring is over --> break;
164  if (!*qqstr) break;
165 
166  // If comma was found, set to 0;
167  if (comma) *comma = 0;
168 
169  {
170  StringList **ltail;
171  // Create new group;
172  NodeGroup *group = *gtail = new NodeGroup();
173 
174  assert(group);
175 
176  // Parse group substring; first suffix: min number of fired nodes;
177  {
178  char *semicolon = strchr(qqstr, ':');
179 
180  // Semicolon must be there;
181  if (!semicolon)
182  _RETURN_(-1, "'fired-node-num': no semicolon found!\n");
183 
184  // Determine min number of fired rplanes in this group;
185  group->mFiredNodeNumMin = atoi(semicolon+1);
186  if (group->mFiredNodeNumMin < 0)
187  _RETURN_(-1, "'fired-node-num': value >=0 expected!\n");
188 
189  *semicolon = 0;
190  }
191 
192  // Then group rplane member prefices;
193  ltail = &group->mPrefices;
194  for( ; ; ) {
195  char *plus = strchr(qqstr, '+');
196 
197  // Substring is over --> break;
198  if (!*qqstr) break;
199 
200  // If plus was found, set to 0;
201  if (plus) *plus = 0;
202 
203  {
204  StringList *list = *ltail = new StringList();
205 
206  assert(list);
207 
208  list->mString = strdup(qqstr);
209  assert(list->mString);
210 
211  ltail = &list->mNextString;
212  }
213 
214  // Substring is over --> break;
215  if (!plus) break;
216 
217  // Switch to the next group substring;
218  qqstr = plus + 1;
219  } /*for inf*/
220 
221  // And now loop through all nodes and allocate
222  // those which match one of the prefix patterns
223  // in a linked list;
224  {
225  NodeList **ntail = &group->mNodeList;
226 
227  for(KalmanNode *node=mHead; node; node=node->GetNext(KalmanFilter::Forward)) {
228  if (!node->mName) continue;
229 
230  for(StringList *list=group->mPrefices; list; list=list->mNextString)
231  if (!check_prefix(node->mName, list->mString)) {
232  NodeList *nlist = *ntail = new NodeList();
233 
234  assert(nlist);
235 
236  group->mAllNodeNum++;
237  nlist->mNode = node;
238 
239  // And fill out back door pointer in node frame;
240  node->mNodeGroupNum++;
241  node->mNodeGroups =
242  (NodeGroup**)realloc(node->mNodeGroups,
243  node->mNodeGroupNum*sizeof(void*));
244  assert(node->mNodeGroups);
245  node->mNodeGroups[node->mNodeGroupNum-1] = group;
246 
247  ntail = &nlist->mNextNode;
248 
249  break;
250  } //for list .. if
251  } //for node
252  }
253 
254  // Sanity check, please;
255  //printf("%d %d\n", group->mFiredNodeNumMin, group->mAllNodeNum);
256  if (group->mFiredNodeNumMin > group->mAllNodeNum)
257  _RETURN_(-1, "'fired-node-num': limit too high!\n");
258 
259  gtail = &group->mNextGroup;
260  }
261 
262  // Substring is over --> break;
263  if (!comma) break;
264 
265  // Switch to the next group substring;
266  qqstr = comma + 1;
267  } //for inf
268 
269  free(qstr);
270  } //for str
271 
272  if (!mNodeGroups)
273  _RETURN_(-1, "'--kalman-tuning fired-node-min' option missing!\n");
274 
275 #if _LATER_
276  // Well, it looks this code is of interest for the generic Kalman filter as well;
277  {
278  // Loop through all nodes and fill "too big" gaps in Z;
279  // NB: this call should be done AFTER parser (see above);
280  if (mNodeGapMax)
281  for(KalmanNode *knode=mHead; knode && knode->GetNext(KalmanFilter::Forward);
282  knode=knode->GetNext(KalmanFilter::Forward))
283  {
284  double zz = knode->GetZ() + mNodeGapMax;
285 
286  // Well, if there is no magnetic field inbetween, no sense
287  // to introduce extra nodes; unless this is forced by respective
288  // mode bit;
289  if (mode & _FILL_ALL_GAPS_ || knode->mNonLinearTransportFlags[KalmanFilter::Forward] ||
290  knode->GetNext(KalmanFilter::Forward)->mNonLinearTransportFlags[KalmanFilter::Backward])
291  {
292  if (zz < knode->GetNext(KalmanFilter::Forward)->GetZ() &&
293  !AddNodeWrapper(NULL, format, zz, 0, NULL))
294  return -1;
295  } /*if*/
296  } /*if..for knode*/
297  }
298 #endif
299 
300  return 0;
301 } // KalmanFilter::Configure()
302 
303 // ---------------------------------------------------------------------------------------
304 
306 {
307  // Consider to reset ALL node fired flags, even those who are not presently included
308  // in the actually used double-linked list;
309  for(std::map<double, KalmanNode*>::iterator it=mKalmanNodePool.begin();
310  it != mKalmanNodePool.end(); it++)
311  // FIXME: yes, for now do it by hand; group counters will be reset below;
312  // see node->ResetFiredFlag() source code for more details;
313  it->second->mFired = false;
314 
315  // Reset group fired counters;
316  for(NodeGroup *group=mNodeGroups; group; group=group->mNextGroup)
317  group->mFiredNodeNum = group->mNdfControlFlag = 0;
318 } // KalmanFilter::ResetFiredFlags()
319 
320 // ---------------------------------------------------------------------------------------
321 
323 {
324  for(NodeGroup *group=mNodeGroups; group; group=group->mNextGroup)
325  if (group->mFiredNodeNum) group->mNdfControlFlag = 1;
326 } // KalmanFilter::LatchGroupNdfControlFlags()
327 
328 // ---------------------------------------------------------------------------------------
329 
330 //
331 // FIXME: this hack indeed assumes a single global node group;
332 //
333 
335 {
336  for(NodeGroup *group=mNodeGroups; group; group=group->mNextGroup)
337  group->mFiredNodeNumMin = min;
338 } // KalmanFilter::HackGroupHitCountLimit()
339 
340 // =======================================================================================
341 
343 {
344  int ret;
345  // If 'start' pointer is specified, use it; otherwise take head/tail;
346  KalmanNode *snode = start ? start : (fb == KalmanFilter::Forward ? mHead : mTail);
347 
349 
350  // Save start/end node pointers for the (possible) smoother pass;
351  mLastStart = snode;
352  mLastEnd = end ? end : (fb == KalmanFilter::Forward ? mTail : mHead);
353 
354  // Calculate x0[], FR[][], Q[][] for all requested nodes; NB: it is
355  // however assumed that snode->x0[] is given correctly;
356  if ((ret = Calculate_x0_FR_Q(snode, end, fb,
358  return ret;
359 
360  // Do Kalman filter matrix calculations;
361  if ((ret = DoFilterAlgebra(snode, end, fb))) return ret;
362 
363  // Make smoother aware of the filter arrays status;
364  mLastFilterPass = fb;
365 
367 
368  return 0;
369 } // KalmanFilter::FilterPass()
370 
371 // ---------------------------------------------------------------------------------------
372 
374  KalmanFilter::Direction fb, unsigned mode)
375 {
376  for(KalmanNode *node=start; node; node=node->GetNext(fb)) {
377  if (node != start) {
378  KalmanNode *from = node->GetPrev(fb);
379 
380  // Extrapolate x0[]; depends on the transport non-linearity
381  // (magnetic field presence in tracking case);
382  if (node->GetZ() == from->GetZ()) {
383  node->x0->CopyFrom(from->x0);
384 
385  from->FR = SU;
386  }
387  else {
388  if (node->mNonLinearTransportFlags[(fb+1)%2] ||
389  from->mNonLinearTransportFlags[ fb ]) {
390  if (Transport(from, fb, mode))
391  _RETURN_(_TFUN_FAILURE_, "Filter failed in root->transport()!\n");
392 
393  from->FR = from->FM;
394  }
395  else {
396  // Just use pre-calculated FF[][]-matrix for the pure linear case;
397  node->x0->SetToProduct(from->FF[fb], from->x0);
398 
399  //assert(0);
400  from->FR = from->FF[fb];
401  } //if
402  } //if
403 
404  // There may be extra stuff, application-specific (say, multiple scattering
405  // calculation in case of tracking Kalman Filter;
406  if (TransportExtra(from, fb, mode))
407  _RETURN_(_NFUN_FAILURE_, "Filter failed in root->extra()!\n");
408  } //if
409 
410  if (end && node == end) break;
411  } //for node
412 
413  return 0;
414 } // KalmanFilter::Calculate_x0_FR_Q()
415 
416 // ---------------------------------------------------------------------------------------
417 
420 {
421  // Calculate everything (but tnode->x0[]) in one pass;
422  for(KalmanNode *node=start; node; node=node->GetNext(fb)) {
423  // All the stuff which makes sense only if there were
424  // calculated nodes before in the chain;
425  if (node != start) {
426  KalmanNode *from = node->GetPrev(fb);
427 
428  node->xp->SetToProduct(from->FR, from->xf);
429 
430  node->CP->SetToProduct(from->FR, from->CF, from->FR, _TRANSPOSE_IN3_);
431 
432  node->CP->Add(from->Q);
433 
434  if (mPositivityCheck && node->CP->CheckPositivity())
436  "node->CP is not positive-definite (filter)!\n", KalmanFilter::Error);
437  if (mForceSymmetrization) node->CP->ForceSymmetric();
438  } //if
439 
440  // If node was fired (has it's own valuable measurement),
441  // usual Kalman technique is applied;
442  if (node->mFired) {
443  // Calculate H-matrix (and perhaps m[]) if root->hfun() != NULL;
444  // yes, it looks like this is needed for fired nodes only;
445  if (CalculateHMatrix(node))
446  _RETURN_(_HFUN_FAILURE_, "Filter failed in node->hfun()!\n");
447 
448  // Prediction cov.matrix and Kalman gain matrix calculation;
449  node->MMTX->SetToProduct(node->H, node->CP, node->H, _TRANSPOSE_IN3_);
450  node->RPI->SetToSum(node->MMTX, node->V);
451  if (mForceSymmetrization) node->RPI->ForceSymmetric();
452  if (node->RPI->Invert(KfMatrix::Symmetric))
453  _RETURN_(_DSINV_FAILURE_, "DSINV() failed (filter) #1!\n");
454  node->K->SetToProduct(node->CP, node->H, node->RPI, _TRANSPOSE_IN2_);
455 
456  // Prediction error; state vector update;
457  node->ep->SetToProduct(node->H, node->xp);
458  node->ep->SetToDifference(node->m, node->ep);
459  SVEC->SetToProduct(node->K, node->ep);
460  node->xf->SetToSum(node->xp, SVEC);
461 
462  // Filtered covariance matrix update; since now use De Jong smoother
463  // formalism, which requires "I-KH" matrix anyway, makes sense to
464  // introduce it as intermediate LB[][] matrix right here;
465  SMTX->SetToProduct(node->K, node->H);
466  node->LB->SetToDifference(SU, SMTX);
467  node->CF->SetToProduct(node->LB, node->CP, node->LB, _TRANSPOSE_IN3_);
468  SMTX->SetToProduct(node->K, node->V, node->K, _TRANSPOSE_IN3_);
469  node->CF->Add(SMTX);
470  if (mPositivityCheck && node->CF->CheckPositivity())
472  "node->CF is not positive-definite (filter)!\n", KalmanFilter::Error);
473  if (mForceSymmetrization) node->CF->ForceSymmetric();
474 
475  // The rest in principle makes sense if only there were already sufficient
476  // fired nodes passed before; there is however a complication that say for
477  // non-fixed-momentum tracking and weak fringe field 5-th fired node can be
478  // still out of strong field, so mNdf>0 check is perhaps not the best criterion;
479  // since these days node->chi2_filter_increment is actually used to calculate
480  // overall filter pass chi^2 sum (and not to determine outlier
481  // nodes), one can simply set a cut on RF[0][0] (assuming
482  // mdim=1) and forget about the problem;
483  {
484  // Covariance matrix of filtered residuals;
485  node->RF->SetToProduct(node->H, node->CF, node->H, _TRANSPOSE_IN3_);
486  node->RF->SetToDifference(node->V, node->RF);
487  if (mForceSymmetrization) node->RF->ForceSymmetric();
488 
489  bool ok = true;
490 
491  // Check that there are no "too small" cov.matrix diagonal elements;
492  for(unsigned iq=0; iq<node->GetMdim(); iq++)
493  if (node->RF->KFM(0,0) <= mRFCutoffValue) {
494  ok = false;
495  break;
496  } //for iq..if
497 
498  if (ok) {
499  // Filtered residuals;
500  node->rf->SetToProduct(node->H, node->xf);
501  node->rf->SetToDifference(node->m, node->rf);
502 
503  // Calculate chi^2 stuff;
504  node->MMTX->CopyFrom(node->RF);
505  if (node->MMTX->Invert(KfMatrix::Symmetric))
506  _KF_RETURN_(_DSINV_FAILURE_, "DSINV() failed (filter) #2!\n", KalmanFilter::Error);
507  node->rf->VectorLengthSquare(node->MMTX, &node->mFilterChiSquareIncrement);
508  }
509  else {
510  node->rf->Reset();
511  node->mFilterChiSquareIncrement = 0.;
512  } //if
513  }
514  }
515  else
516  {
517  // Yes, just predicted values in this case;
518  node->xf->CopyFrom(node->xp);
519  node->CF->CopyFrom(node->CP);
520 
521  // Effectively this means that Kalman gain is 0;
522  node->LB->Unity();
523  } //if
524 
525  if (end && node == end) break;
526  } //for node
527 
528  return 0;
529 } // KalmanFilter::DoFilterAlgebra()
530 
531 // ---------------------------------------------------------------------------------------
532 
534 {
535  if (mLastFilterPass == KalmanFilter::Undefined) return -1;
536 
538 
539  for(KalmanNode *node=mLastStart; node; node=node->GetNext(mLastFilterPass)) {
540  if (node->mFired) {
541  // Ok, this is clear; fix 2009/09/25; fired _PIXEL_ node
542  // for instance will increment ndf by 2;
543  mNdf += node->mDim;
544 
545  //printf("%s %f -> %f\n", node->GetName(), node->GetZ(), node->mFilterChiSquareIncrement);
546  mFilterChiSquare += node->mFilterChiSquareIncrement;
547  } //if
548 
549  if (node == mLastEnd) break;
550  } //for node
551  //exit(0);
552 
553  // Prefer to always calculate this value;
554  mFilterChiSquareCCDF = mNdf > 0 ? ROOT::Math::chisquared_cdf_c(mFilterChiSquare, mNdf) : 0.;
555 
556  return 0;
557 } // KalmanFilter::CalculateFilterStat()
558 
559 // =======================================================================================
560 
562 {
563  // This variable determines *smoother* direction; NB: it is per definition
564  // opposite to the filter direction which is supposed to be ran before;
565  // and indeed smoother starts at the node where filter finished its work;
566  int fb;
567 
568  switch (mLastFilterPass) {
569  // Yes, just return _CHAIN_FAILURE_ here, who cares;
571  return _CHAIN_FAILURE_;
574  break;
577  break;
578  default:
579  assert(0);
580  } //switch
581 
583 
584  // Loop through all the start-end chain in [fb] direction;
585  for(KalmanNode *node=mLastEnd; node; node=node->GetNext(fb)) {
586  // This initialization is correct for root->mLastEnd node as well;
587  if (node->mFired) {
588  node->QQ->SetToProduct(node->H, node->RPI, node->H, _TRANSPOSE_IN1_);
589  if (mForceSymmetrization) node->QQ->ForceSymmetric();
590  node->qq->SetToProduct(node->H, node->RPI, node->ep, _TRANSPOSE_IN1_);
591  }
592  else {
593  node->QQ->Reset();
594  node->qq->Reset();
595  } //if
596 
597  if (node == mLastEnd) {
598  node->CS->CopyFrom(node->CF);
599  node->xs->CopyFrom(node->xf);
600  }
601  else {
602  KalmanNode *prev = node->GetPrev(fb);
603 
604  // Accomplish node->L[][] calculation; NB: do NOT follow original
605  // Merkus-Pollock-Vos node counting scheme; namely calculate
606  // L-matrix for CURRENT node, not for the PREVIOUS one; thus node->L
607  // is used in the below formulae instead of prev->L;
608  node->L->SetToProduct(node->FR, node->LB);
609 
610  // Calculate smoothed covariance matrix; use De Jong formalism
611  // (no smoother gain matrix usage), since it is indeed more
612  // numerically stable (no CP-matrix inversion needed);
613  SMTX->SetToProduct(node->L, prev->QQ, node->L, _TRANSPOSE_IN1_);
614  node->QQ->Add(SMTX);
615  if (mForceSymmetrization) node->QQ->ForceSymmetric();
616  SMTX->SetToProduct(node->CP, node->QQ, node->CP);
617  node->CS->SetToDifference(node->CP, SMTX);
618  if (mPositivityCheck && node->CS->CheckPositivity()) {
619  // Append positivity fix attempt to the overall mask;
621 
623  printf("CS[][] positivity check failed at Z=%7.2f\n", node->GetZ());
624 
625  // Actually try to fix the problem; assume that if off-diagonal
626  // correlation coefficients are a bit higher than 1.0 (happens
627  // rarely for high-momenta tracks), this is caused by certain
628  // numerical instability and is probably not that harmful;
629  // if fail, return right here; NB: CheckPositivity() will
630  // be called by positivity_fix() internally;
631  if (node->CS->FixPositivity(mMaxFixablePositivityScrewup,
634  node->CS->Print();
635  node->CS->CorrelationPrint();
636  } //if
637 
639  "node->CS is not positive-definite (smoother)!\n", KalmanFilter::Error);
640  } //if
641  } //if
642  if (mForceSymmetrization) node->CS->ForceSymmetric();
643 
644  // Calculate smoothed state vector;
645  SVEC->SetToProduct(node->L, prev->qq, _TRANSPOSE_IN1_);
646  node->qq->Add(SVEC);
647  SVEC->SetToProduct(node->CP, node->qq);
648  node->xs->SetToSum(node->xp, SVEC);
649  } //if
650 
651  // Perform xm[] calculation if needed; (12d) & (12e) in original 1987 paper;
652  if (mXmCalculationFlag) {
653  node->CM->CopyFrom(node->CS);
654  node->CM->Invert(KfMatrix::Symmetric);
655 
656  node->MMTX->CopyFrom(node->V);
657  node->MMTX->Invert(KfMatrix::Symmetric);
658 
659  // Use inverted smoother cov.matrix to calculate (12d);
660  SVEC->SetToProduct(node->CM, node->xs);
661  QVEC->SetToProduct(node->H, node->MMTX, node->m, _TRANSPOSE_IN1_);
662  SVEC->Subtract(QVEC);
663 
664  SMTX->SetToProduct(node->H, node->MMTX, node->H, _TRANSPOSE_IN1_);
665 
666  node->CM->Subtract(SMTX);
667  node->CM->Invert(KfMatrix::Symmetric);
668 
669  node->xm->SetToProduct(node->CM, SVEC);
670 
671  // And respective residuals;
672  node->rm->SetToProduct(node->H, node->xm);
673  node->rm->SetToDifference(node->m, node->rm);
674 
675  // And eventually project node->CM onto the node space;
676  node->RM->SetToProduct(node->H, node->CM, node->H, _TRANSPOSE_IN3_);
677  } //if
678 
679  // If node was dead for this event, skip all the rest;
680  if (!node->mFired) continue;
681 
682  // Smoothed residuals;
683  node->rs->SetToProduct(node->H, node->xs);
684  node->rs->SetToDifference(node->m, node->rs);
685 
686  // Covariance matrix of smoothed residuals;
687  node->RS->SetToProduct(node->H, node->CS, node->H, _TRANSPOSE_IN3_);
688  //node->RS->Print();
689  node->RS->SetToDifference(node->V, node->RS);
690  if (mForceSymmetrization) node->RS->ForceSymmetric();
691 
692  // Smoothed chi^2;
693  node->MMTX->CopyFrom(node->RS);
694  if (node->MMTX->Invert(KfMatrix::Symmetric)) {
696  printf(" --> Z=%7.1f, RS[0][0] = %20.15f\n", node->GetZ(), node->RS->KFM(0,0));
697  _KF_RETURN_(_DSINV_FAILURE_, "DSINV() failed (smoother)!\n", KalmanFilter::Error);
698  } //if
699  node->rs->VectorLengthSquare(node->MMTX, &node->mSmootherChiSquare);
700  if (node->mSmootherChiSquare <= 0.0) {
701  //printf("%f %f %f %f\n", node->MMTX->KFM(0,0), node->MMTX->KFM(1,0),
702  // node->MMTX->KFM(0,1), node->MMTX->KFM(1,1));
703  //node->V->Print();
704  printf("%f: smother chi^2 = %f\n", node->GetZ(), node->mSmootherChiSquare);
705  //exit(0);
706  }
707  // FIXME: use this value in all places rather than calculating it every time new;
708  node->mSmootherChiSquareCCDF =
709  ROOT::Math::chisquared_cdf_c(node->GetSmootherChiSquare(), node->GetMdim());
710 
711  // Assign worst knode pointers;
712  if (!mWorstSmootherNode ||
713  node->mSmootherChiSquare >
715  mWorstSmootherNode = node;
717  node->mSmootherChiSquare >
719  // Check that knode can be reset;
720  int gr, resettable = 1;
721 
722  for(gr=0; gr<node->mNodeGroupNum; gr++) {
723  NodeGroup *group = (NodeGroup*)node->mNodeGroups[gr];
724 
725  if (group->mNdfControlFlag &&
726  group->mFiredNodeNum <= group->mFiredNodeNumMin) {
727  resettable = 0;
728  break;
729  } //if
730  } //for gr
731 
732  if (resettable) mWorstResettableSmootherNode = node;
733  } //if
734 
735  // Really so?;
736  if (node == mLastStart) break;
737  } //for node
738 
739  return 0;
740 } // KalmanFilter::SmootherPass()
741 
742 // =======================================================================================
743 
745  KalmanNode *end, KalmanFilter::Direction fb, int mode)
746 {
747  unsigned ret, outlier_order_violation = 0;
748 
749  // Sanity check; do not mind to do it every time and set special bit;
750  if (((mode & _TRUST_SMOOTHER_FCN_) && !mMinSmootherChiSquareCCDF) ||
752  return _CHAIN_FAILURE_;
753 
754  // Initialize few global root frame variables;
756 
757  // Run forward filter; if failed, return immediately;
758  if ((ret = FilterPass(start, end, fb))) _CHAIN_RETURN_(ret);
759 
760  // Then iteratively run smoother, remove worst outlier and rerun
761  // forward filter starting from the removed node;
762  for( ; ; )
763  {
764  // If smoother failed, return failure code immediately;
765  if ((ret = SmootherPass())) _CHAIN_RETURN_(ret|outlier_order_violation);
766 
767  // Check that have enough fired nodes at start up; but do it after
768  // smoother so that some track fit is available anyway;
769  {
770  // NB: assume that if there were no hits in this group at all,
771  // this is NOT a failure (for instance short HERMES tracks may
772  // have no BC3/4 hits at all; or long tracks may have no MC hits);
773  // this is a ~hack, I admit; and actually this loop should be
774  // run only once, since 'group->mFiredNodeNum' can not go below
775  // 'group->mFiredNodeNumMin' in the above infinite loop;
776  for( NodeGroup *group=mNodeGroups; group; group=group->mNextGroup) {
777  //printf("%d fired nodes ...\n", group->mFiredNodeNum);
778 
779  if (group->mNdfControlFlag &&
780  group->mFiredNodeNum < group->mFiredNodeNumMin)
781  // So if _NDF_FAILURE_ bit is set and _WORST_NODE_IMMUTABLE_
782  // is not, this means start-up failure here, right?;
784  } //for group
785  }
786 
787  // Ok, so now take a decision; looks tricky;
788  {
789  double worst_smoother_prob =
790  ROOT::Math::chisquared_cdf_c(mWorstSmootherNode->mSmootherChiSquare,
792 
793  // The only way to return 0 code; both overall filter chi^2
794  // and worst node smoother chi^2 are either within limits or
795  // should not be trusted at all;
796  if ((worst_smoother_prob > mMinSmootherChiSquareCCDF ||
797  !(mode & _TRUST_SMOOTHER_FCN_)) &&
799  !(mode & _TRUST_FILTER_FCN_)))
800  _CHAIN_RETURN_(outlier_order_violation);
801 
802  // Ok, then something is not good; if smoother chi^2 is trusted
803  // and there is a bad node, outlier removal procedure will
804  // continue, no care needed; if filter chi^2 is trusted, it is
805  // a bit more complicated since smoother chi^2 may also be
806  // trusted and there may be no bad nodes left;
808  (mode & _TRUST_FILTER_FCN_) &&
809  worst_smoother_prob > mMinSmootherChiSquareCCDF &&
810  (mode & _TRUST_SMOOTHER_FCN_))
811  _CHAIN_RETURN_(_STRUST_FAILURE_|outlier_order_violation);
812  }
813 
814  // NB: there can be no node which can be removed -->
815  // then return immediately; and set both bits;
818 
819  // Check that node to be removed is really the worst one;
822  outlier_order_violation = _WORST_NODE_IMMUTABLE_;
823 
824  // Turn worst node off and redo forward filtering algebra
825  // starting from this node; NB: no need to recalculate
826  // x0[], FR[][] & Q[][] --> save CPU time;
828  printf("chi^2 = %8.2f -> prob %10.7f -> removing %s (chi^2 incr %8.2f)\n",
833 
834  // Repeat Kalman filter matrix calculations starting from
835  // the removed outlier node;
836  if ((ret = DoFilterAlgebra(mWorstResettableSmootherNode, end, fb)))
837  _CHAIN_RETURN_(ret|outlier_order_violation);
838 
839  // Recalculate filter ndf & chi^2 from scratch;
841  } //for inf
842 } // KalmanFilter::FullChain()
843 
844 // =======================================================================================