EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
TrKalmanFilter.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file TrKalmanFilter.cxx
1 //
2 // AYK (ayk@bnl.gov)
3 //
4 // Forward detector tracking application of the Kalman filter stuff;
5 // ported from HERMES/OLYMPUS sources; cleaned up 2014/10/17;
6 //
7 
8 #include <cassert>
9 
10 #include <htclib.h>
11 #include <MediaSliceArray.h>
12 #include <TrKalmanFilter.h>
13 #include <SensitiveVolume.h>
14 
15 // =======================================================================================
16 
18 {
19  // First reset all the active flags; yes, go through the whole node pool;
20  for(std::map<double, KalmanNode*>::iterator it=mKalmanNodePool.begin();
21  it != mKalmanNodePool.end(); it++)
22  dynamic_cast<TrKalmanNode*>(it->second)->SetActiveFlag(false);
23 
24  // Loop through all locations and activate fired nodes; if there are no
25  // fired nodes at a given location, activate just one single "dummy" node
26  // (have to do this because media arrays are calculated between neighbor
27  // locations; would be prohibitevely expensive to calculate them either
28  // on the fly (CPU) of for all location pairs (RAM));
29  for(TrKalmanNodeLocation *location=mLocationHead; location;
30  location=location->GetNext(KalmanFilter::Forward)) {
31  // Loop through all the nodes; do it in descending order, just that
32  // the only active node in case of no hits will be the 0-th one (even
33  // that this doe not really matter);
34  bool fired = false;
35 
36  //printf("%f -> %d node(s)\n", location->GetZ(), location->GetNodeCount());
37  for(int nd=location->GetNodeCount()-1; nd>=0; nd--) {
38  TrKalmanNode *node = location->GetNode(nd);
39 
40  if (node->IsFired() || (!nd && !fired)) node->SetActiveFlag(true);
41 
42  // Ned at least one active node per location; keep track on fired ones;
43  if (node->IsFired()) fired = true;
44  } //for nd
45  } //for location
46 } // TrKalmanFilter::SelectActiveNodes()
47 
48 // ---------------------------------------------------------------------------------------
49 
51 {
52  TrKalmanNode *qfrom = static_cast <TrKalmanNode*> (from);
53  assert(qfrom);
54 
55  // Calculate process noise; want it here since it is applicable to linear case as well;
56  if (mode & _CALCULATE_PROCESS_NOISE_ && CalculateProcessNoise(qfrom, fb))
57  _RETURN_(_NFUN_FAILURE_, "Filter failed in root->nfun()!\n");
58 
59  // Account for energy losses if needed;
60  if (AccountIonizationLosses(qfrom, fb))
61  _RETURN_(_XFUN_FAILURE_, "Filter failed in root->xfun()!\n");
62 
63  return 0;
64 } // TrKalmanFilter::TransportExtra()
65 
66 // ---------------------------------------------------------------------------------------
67 
69 {
70  TrKalmanNode *node = static_cast <TrKalmanNode *>(GetHead());
71 
72  while (node) {
73  // System equation in a HERA-B parameterization; NB: diagonal elements
74  // of FF[]-matrices have been set to 1. already in add_kalman_node();
75  for(int fb=0; fb<2; fb++) {
76  switch (fb) {
78  if (node->GetNext(KalmanFilter::Forward))
79  node->FF[fb]->KFM(0,2) = node->FF[fb]->KFM(1,3) =
80  node->GetNext(KalmanFilter::Forward)->GetZ() - node->GetZ();
81  break;
83  if (node->GetNext(KalmanFilter::Backward))
84  node->FF[fb]->KFM(0,2) = node->FF[fb]->KFM(1,3) =
85  node->GetNext(KalmanFilter::Backward)->GetZ() - node->GetZ();
86  break;
87  } //switch
88  } //for fb
89 
90  node = node->GetNext(KalmanFilter::Forward);
91  } //while
92 
93  return 0;
94 } // TrKalmanFilter::CalculateMagnetOffTransportMatrices()
95 
96 // ---------------------------------------------------------------------------------------
97 
99 {
100  // Build linked list of Kalman node locations first;
101  for(std::map<double, KalmanNode*>::iterator it=mKalmanNodePool.begin();
102  it != mKalmanNodePool.end(); it++) {
103  TrKalmanNode *node = dynamic_cast<TrKalmanNode*>(it->second);
104 
105  //printf("%7.2f -> %s\n", node->GetZ(), node->GetName());
106 
107  TrKalmanNodeLocation *location;
110  location = new TrKalmanNodeLocation(node->GetZ());
111  //printf("location %7.3f\n", location->GetZ());
112 
113  if (!mLocationHead)
114  mLocationHead = location;
115  else {
116  mLocationTail->SetNext(location);
117  location->SetPrev(mLocationTail);
118  } //if
119 
120  mLocationTail = location;
121  }
122  else
123  location = mLocationTail;
124 
125  location->AddNode(node);
126  node->mLocation = location;
127 
128  // This call will also check, that pointer is non-zero;
129  location->AddSensitiveVolume(node->GetSensitiveVolume());
130  } //for it
131 
132  // Check, that all sensitive volumes at every location are of "similar" type;
133  // for now just require, that all have the same number of KF templates;
134  for(TrKalmanNodeLocation *location=mLocationHead; location;
135  location=location->GetNext(KalmanFilter::Forward)) {
136  // Loop through all its sensitive volumes;
137  for(std::set<SensitiveVolume*>::iterator it=location->GetSensitiveVolumes().begin();
138  it != location->GetSensitiveVolumes().end(); it++) {
139  unsigned nodeWrapperCount = (*it)->GetKfNodeWrapperCount();
140 
141  // FIXME: do better later;
142  if (location->GetSensitiveVolumeNodeWrapperCount()) {
143  assert(location->GetSensitiveVolumeNodeWrapperCount() == nodeWrapperCount);
144 
145  // And dimension check, please; FIXME: may want to do more tricky consistency checks later;
146  for(unsigned tmpl=0; tmpl<nodeWrapperCount; tmpl++) {
147  //assert((*it)->GetKfNodeWrapper(tmpl)->GetMdim() == location->GetMdim(tmpl));
148  assert((*it)->GetKfNodeWrapper(tmpl)->GetKfNodeTemplate()->IsCompatible(location->GetTemplate(tmpl)));
149 
150  //if ((*it)->GetKfNodeWrapper(tmpl)->GetKfNodeTemplate()->FavorCylindricalThreeDee())
151  //location->SetCylindricalPreference(tmpl);
152  } //for tmpl
153  } else {
154  for(unsigned tmpl=0; tmpl<nodeWrapperCount; tmpl++) {
155  // Well, location->mDims[] is an STL vector, filled out at once;
156  location->SetNextMdimValue((*it)->GetKfNodeWrapper(tmpl)->GetMdim());
157 
158  location->SetNextNodeToMaster((*it)->GetKfNodeWrapper(tmpl)->GetNodeToMasterMtx());
159  location->SetNextTemplate((*it)->GetKfNodeWrapper(tmpl)->GetKfNodeTemplate());
160  //location->SetNextCylindricalPreference((*it)->GetKfNodeWrapper(tmpl)->GetKfNodeTemplate()->FavorCylindricalThreeDee());
161  } //for tmpl
162  } //if
163  } //for it
164  } //for location
165 } // TrKalmanFilter::SetUpLocations()
166 
167 // ---------------------------------------------------------------------------------------
168 
170 {
171  // Just hardcode the format for gap nodes here for now; FIXME: this will not
172  // work anyway, since they should have been declared before setting up locations;
173  if (KalmanFilter::Configure(config/*, (char*)"Z@%05.1f"*/)) return -1;
174 
175  // Only after all nodes are defined;
177  _RETURN_(-1, "Failed to calculate magnet-off transport matrices!\n");
178 
179  // Map material distribution on top of the defined Kalman node grid;
180  InitializeMediaSlices(media_bank);
181 
182  // And also intialize Runge-Kutta frames right here;
184 
185  return 0;
186 } // TrKalmanFilter::Configure()
187 
188 // ---------------------------------------------------------------------------------------
189 
191 {
192  // Build media slice arrays for all nodes;
194  location=location->GetNext(KalmanFilter::Forward))
195  location->mMediaSliceArray =
196  new MediaSliceArray(media_bank, location->GetZ(),
197  location->GetNext(KalmanFilter::Forward)->GetZ());
198 
199  // And then calculate process noise basic matrices for F/B directions;
200  for(TrKalmanNodeLocation *location=mLocationHead; location;
201  location=location->GetNext(KalmanFilter::Forward)) {
202  if (location->GetPrev(KalmanFilter::Forward)) {
203  location->mProcessNoise[KalmanFilter::Backward] =
204  location->InitializeProcessNoiseMatrices(KalmanFilter::Backward);
205  if (!location->mProcessNoise[KalmanFilter::Backward]) return -1;
206  } //if
207 
208  if (location->GetNext(KalmanFilter::Forward)) {
209  location->mProcessNoise[KalmanFilter::Forward] =
210  location->InitializeProcessNoiseMatrices(KalmanFilter::Forward);
211  if (!location->mProcessNoise[KalmanFilter::Forward]) return -1;
212  } //if
213  } //for node
214 
215  return 0;
216 } // TrKalmanFilter::InitializeMediaSlices()
217 
218 // ---------------------------------------------------------------------------------------
219 
221 {
222  for(TrKalmanNodeLocation *location=mLocationHead; location; location=location->GetNext(KalmanFilter::Forward))
223  for(unsigned fb=KalmanFilter::Forward; fb<=KalmanFilter::Backward; fb++) {
224  if (!location->GetNext(fb)) continue;
225 
226  TrKalmanNodeLocation *to = location->GetNext(fb);
227  // NB: sign matters!;
228  double h = to->GetZ() - location->GetZ();
229  RungeKutta *rk = location->mRungeKutta + fb;
230 
231  // Figure out which order Runge-Kutta technique should be used;
232  // perhaps take into account field magnitude later as well; or even
233  // create both frames and take decision based on momentum for a given
234  // track; for now go simple;
235 #if 1
237 #else
238  rk->_order = _RK_ORDER_2_;
239 #endif
240  // Consider to use 4-th order, at least for the test phase;
241  //rk->_order = fabs(h) > RK_small_step_limit ? _RK_ORDER_4_ : RK_small_step_order;
242 
243  // m1: needed for all 3 Runge-Kutta cases;
244  rk->m1 = InitializeMgridSlice(location->GetZ());
245  if (!rk->m1) return -1;
246 
247  // Depending on the Runge-Kutta order initialize m2..m6;
248  switch (rk->_order) {
249  case _RK_ORDER_2_:
250  // Used by both 2-d and 4-th order algorithms;
251  rk->m2 = InitializeMgridSlice(location->GetZ() + h/2.);
252  if (!rk->m2) return -1;
253  break;
254  case _RK_ORDER_4_:
255  rk->m2 = InitializeMgridSlice(location->GetZ() + h/2.);
256  rk->m3 = rk->m2;
257  // Yes, explicitely use "to->z" instead of "z+h" in order to avoid
258  // boundary Z mismatch due to rounding errors;
259  //rk->m4 = initializeMgridSlice(to->z);
260  rk->m4 = InitializeMgridSlice(to->GetZ());
261  if (!rk->m2 || !rk->m4) return -1;
262  break;
263  case _RK_ORDER_5_:
264  // Yes, because want to use "to->z";
265  assert(a5 == 1.);
266  rk->m2 = InitializeMgridSlice(location->GetZ() + h*a2);
267  rk->m3 = InitializeMgridSlice(location->GetZ() + h*a3);
268  rk->m4 = InitializeMgridSlice(location->GetZ() + h*a4);
269  //rk->m5 = initializeMgridSlice(to->GetZ());
270  rk->m5 = InitializeMgridSlice(to->GetZ());
271  rk->m6 = InitializeMgridSlice(location->GetZ() + h*a6);
272  if (!rk->m2 || !rk->m3 || !rk->m4 || !rk->m5 || !rk->m6) return -1;
273  break;
274  } //switch
275  } //for node..fb
276 
277  return 0;
278 } // TrKalmanFilter::InitializeRungeKuttaFrames()
279 
280 // =======================================================================================
281 
283 {
284  double pout[6], rkd[5][5];
285  TrKalmanNode *trfrom = (static_cast <TrKalmanNode*>(from));
286  TrKalmanNode *trto = fb == KalmanFilter::Forward ?
288 
289  // Just use the same code as in HTC (which was in turn interfaced
290  // in such a way that it mimics the old HERA-B Runge-Kutta code);
291  if ((static_cast <TrKalmanNode*>(from))->
292  PerformRungeKuttaStep(fb, pout+1, mode & _CALCULATE_DERIVATIVES_ ? rkd : NULL))
293  return -1;
294 
295  // Assign [x,y,sx,sy] part of predicted state vector for the 'to' node;
296  for(int ip=_X_; ip<=_SY_; ip++)
297  trto->x0->KFV(ip) = pout[ip+1];
298 
299  if (mode & _CALCULATE_DERIVATIVES_) {
300  // Assign transport matrix; NB: transposition required!;
301  for(int ip=_X_; ip<=_SY_; ip++)
302  for(int iq=_X_; iq<=_SY_; iq++)
303  trfrom->FM->KFM(iq,ip) = rkd[ip][iq];
304 
305  // Also momentum part of transport matrix, if needed;
306  // NB: calculate_magnet_off_transport_matrices() resets diagonal
307  // FM[<1/p>][<1/p>] term to 1. and d(1/p)/d{x,y,sx,sy} terms to 0.
308  // once forever; so need to assign only 4 d{x,y,sx,sy}/d(1/p) ones;
309  for(int iq=_X_; iq<=_SY_; iq++)
310  trfrom->FM->KFM(iq,_QP_) = rkd[4][iq];
311  } //if
312 
313  return 0;
314 } // TrKalmanFilter::Transport()
315 
316 // ---------------------------------------------------------------------------------------
317 
319 {
320  TrKalmanNode *to = from->GetNext(fb);
321 
322  // Again, put this poor-man check; do it better later;
323  //if (dE_dx_flag && from->GetZ() != to->GetZ()) {
324  if (mAccountEnergyLosses && from->GetZ() != to->GetZ()) {
325  //assert(0);
326  MediaSliceArray *array = fb == KalmanFilter::Forward ?
328  //assert(array);
329  double invp0 = from->mInversedMomentum, losses, mass = mParticleGroup->mass;
330  // Yes, GDREL*() routines expect kinetic energy as an argument;
331  // this bug was fixed on 2009/09/22 only; funny enough, look-up
332  // tables in SC150 software have always been filled correctly;
333  double e0_kin = sqrt(1./SQR(invp0) + SQR(mass)) - mass;
334 
335  // Consider to use average as of Feb'2008;
336  double tx = (from->x0->KFV(2) + to->x0->KFV(2))/2.;
337  double ty = (from->x0->KFV(3) + to->x0->KFV(3))/2.;
338 
339  double len_cff = 1. + SQR(tx) + SQR(ty);
340 
341  // Call respective GEANT routine wrapper and get total energy loss;
342  // correct it for slope (thinckness) and direction (forward/backward: sign);
343  losses = array->GetDE(mParticleGroup, invp0 > 0.? 1 : -1, e0_kin)*sqrt(len_cff);
344  if (fb == KalmanFilter::Backward) losses = -losses;
345 
346  {
347  double e1_kin = e0_kin - losses, invp1;
348 
349  // Some crude regularization, please;
350  if (e1_kin > 0.) {
351  invp1 = 1./sqrt(e1_kin*(e1_kin+2.*mass));
352  if (invp0 < 0.) invp1 = -invp1;
353  }
354  else
355  invp1 = invp0;
356 
357  to->mInversedMomentum = invp1;
358  }
359  }
360  else
361  // Just copy over inversed momentum to the next node;
363 
364  return 0;
365 } // TrKalmanFilter::AccountIonizationLosses()
366 
367 // ---------------------------------------------------------------------------------------
368 
370 {
371  // FIXME: isn't it the only thing needed if both nodes are at the same Z-location?;
372  for(int ip=_X_; ip<=_SY_; ip++)
373  for(int iq=_X_; iq<=_SY_; iq++)
374  from->Q->KFM(ip,iq) = 0.0;
375 
376  KalmanNode *to = from->GetNext(fb);
377  if (from->GetZ() == to->GetZ()) return 0;
378 
379  ProcessNoise *noise = from->mLocation->mProcessNoise[fb];
380  // Helps bridge nodes (?);
381  if (!noise) return 0;
382 
383  //double len_cff, cxx, cyy, cxy, fcff;
384  // 'p': which units are expected? -> CHECK!;
385  double e = sqrt(1. + SQR(mParticleGroup->mass*from->mInversedMomentum))/fabs(from->mInversedMomentum);
386  double p = 1/from->mInversedMomentum;
387  double beta = 1./fabs(from->mInversedMomentum*e);
388 
389  // Consider to use average as of Feb'2008;
390  double tx = (from->x0->KFV(_SX_) + to->GetX0(_SX_))/2.;
391  double ty = (from->x0->KFV(_SY_) + to->GetX0(_SY_))/2.;
392 
393  double len_cff = 1. + SQR(tx) + SQR(ty);
394  double cxx = len_cff*(1. + SQR(tx));
395  double cyy = len_cff*(1. + SQR(ty));
396  double cxy = len_cff*tx*ty;
397 
398  // Use precalculated (approximate) C** matrices and put in missing
399  // slope- and momentum-dependent coefficients;
400  //#ifdef _USE_GEANT3_MOLIERE_CHC_
401  // MULS=3 model in GEANT; it turns out that MULS=1 model description
402  // using this ansatz is also significantly better than with a 'usual'
403  // formula (see above line);
404  //double fcff = sqrt(len_cff)/SQR(e*SQR(beta));
405  double fcff = sqrt(len_cff)/SQR(p*beta);
406  //#else
407  //double fcff = sqrt(len_cff)/SQR(p*beta);
408  //#endif
409 
410  // Fill upper triangle and diagonal terms;
411  for(int ip=0; ip<4; ip++)
412  for(int iq=ip; iq<4; iq++)
413  from->Q->KFM(ip,iq) = fcff*(cxx*noise->mCxx[ip][iq] + cyy*noise->mCyy[ip][iq] +
414  cxy*noise->mCxy[ip][iq]);
415  // Fill lower triangle;
416  for(int ip=1; ip<4; ip++)
417  for(int iq=0; iq<ip; iq++)
418  from->Q->KFM(ip,iq) = from->Q->KFM(iq,ip);
419 
420  return 0;
421 } // TrKalmanFilter::CalculateProcessNoise()
422 
423 // ---------------------------------------------------------------------------------------
424 // Parsing all intermediate variables for the 2-d time (if called from a
425 // "big N" wrapper function in case of HTC alignment mode) is suboptimal, but
426 // it is assumed that typically this calculate_tracking_H_matrix() is called standalone;
427 
429 {
430  TrKalmanNode *trnode = (static_cast <TrKalmanNode*>(node));
431 
432  t_3d_line line/* = parametrize_straight_line*/(trnode->x0->ARR(), trnode->GetZ());
433 
434  {
435  // '4': yes, no momentum component needed anyway;
436  double S[4], arr[2][4][trnode->mDim];
437 
438  //SensitiveVolume *sv = (SensitiveVolume*)trnode->mBackDoorPointer;
439 
440  // Calling routine should take care to assign used_hit pointer;
441  if (!trnode->mHit) return 0;
442 
443  //
444  // Requirement "knode->fired=1" would disturb the code which attaches
445  // new hits; perhaps introduce an extra parameter later and save some
446  // CPU time on this;
447  //
448 
449  // Calculate actual knode->m[] vector;
450  if (trnode->mSensitiveVolume->TrackToHitDistance(&line, trnode->mHit, trnode->m->ARR()))
451  return -1;
452 
453  // Then knode->H[][] projector;
454  for(int pm=0; pm<2; pm++)
455  for(int ip=_X_; ip<=_SY_; ip++) {
456  memcpy(S, trnode->x0->ARR(), sizeof(S));
457 
458  S[ip] += _drv_steps[ip]*(pm ? 0.5 : -0.5);
459 
460  //@@@ line = parametrize_straight_line(S, trnode->GetZ());
461  t_3d_line qline(S, trnode->GetZ());
462  if (trnode->mSensitiveVolume->TrackToHitDistance(&qline, trnode->mHit, arr[pm][ip]))
463  return -1;
464  } //for pm..ip
465 
466  for(int im=0; im<trnode->mDim; im++)
467  for(int ip=_X_; ip<=_SY_; ip++)
468  trnode->H->KFM(im,ip) = -(arr[1][ip][im] - arr[0][ip][im])/_drv_steps[ip];
469  }
470  // NB: H[0][_QP_] will remain 0 in all cases;
471 
472  return 0;
473 } // TrKalmanFilter::CalculateHMatrix()
474 
475 // =======================================================================================
476 
477 #define _FLYSUB_TYPICAL_XY_COORD_ERROR_ ( 0.010)
478 #define _FLYSUB_TYPICAL_SLOPE_ERROR_ ( 0.0001)
479 //#define _FLYSUB_TYPICAL_XY_COORD_ERROR_ ( 10.000)
480 //#define _FLYSUB_TYPICAL_SLOPE_ERROR_ ( 1.)
481 #define _FLYSUB_TYPICAL_MOMENTUM_ERROR_ ( 1.000)
482 #define _FLYSUB_COVARIANCE_BLOWUP_CFF_ (30)
483 
484 void TrKalmanFilter::ResetNode(TrKalmanNode *node, double S[], int assignmentMode)
485 {
486  // FIXME: next time when want to use this call make cov.matrix parameters
487  // configurable as well;
488  //@@@ assert(0);
489 
490  unsigned sdim = mFieldMode == NoField ? 4 : 5;
491 
492  if (S)
493  for(int ip=_X_; ip<sdim; ip++)
494  node->x0->KFV(ip) = S[ip];
495 
496  // Initialization depends on whether it's a 0-th iteration or not;
497  if (assignmentMode != _USE_00_) {
498  // Otherwise a normal Kalman filter iterative update;
499  KfVector *add = assignmentMode == _USE_XF_ ? node->xf : node->xs;
500 
501  for(int ip=_X_; ip<=_SY_; ip++)
502  node->x0->KFV(ip) += add->KFV(ip);
503 
504  // Momentum expansion point;
505  // NB: head->x0[_QP_] will remain 0. in all cases!;
506  if (sdim == 5) node->mInversedMomentum += add->KFV(_QP_);
507  } //if
508 
509  // Yes, predicted state vector at start of chain is set to 0.0;
510  for(int ip=_X_; ip<sdim; ip++)
511  node->xp->KFV(ip) = 0.;
512 
513  // Cook dummy (diagonal) covariance matrix;
514  node->CP->Reset();
515  // Just [0..3] components;
516  for(int ip=_X_; ip<=_SY_; ip++) {
517  double diag;
518 
519  switch (ip) {
520  case _X_:;
521  case _Y_:
523  break;
524  case _SX_:;
525  case _SY_:
527  break;
528  default:
529  assert(0);
530  } //switch
531 
532  node->CP->KFM(ip,ip) = SQR(diag*_FLYSUB_COVARIANCE_BLOWUP_CFF_);
533  } //for ip
534 
535  if (sdim == 5)
536  node->CP->KFM(_QP_,_QP_) =
539 } // TrKalmanFilter::ResetNode()
540 
541 // =======================================================================================