Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-04-06 12:33:25

0001 from builtins import range
0002 import re
0003 import sys
0004 import math
0005 import difflib
0006 import itertools
0007 import collections
0008 
0009 from operator import itemgetter, methodcaller
0010 
0011 from Validation.RecoTrack.plotting.ntupleDataFormat import *
0012 
0013 # Common track-track matching by hits (=clusters)
0014 def _commonHits(trk1, trk2):
0015     """Returns the number of common hits in trk1 and trk2. Matching is
0016     done via the hit type and index, so effectively the matching is
0017     done by clusters. Invalid hits are ignored.
0018 
0019     """
0020     hits1 = set()
0021     for hit in trk1.hits():
0022         if not hit.isValidHit(): continue
0023         hits1.add( (type(hit), hit.index()) )
0024 
0025     ncommon = 0
0026     for hit in trk2.hits():
0027         if not hit.isValidHit(): continue
0028         if (type(hit), hit.index()) in hits1:
0029             ncommon += 1
0030 
0031     return ncommon
0032 
0033 def _matchTracksByHits(reftrk, trklist):
0034     if len(trklist) == 0:
0035         return (None, 0)
0036 
0037     hits1 = set()
0038     for hit in reftrk.hits():
0039         if not hit.isValidHit(): continue
0040         hits1.add( (type(hit), hit.index()) )
0041 
0042     best = (None, 0)
0043     for trk in trklist:
0044         ncommon = 0
0045         for hit in trk.hits():
0046             if not hit.isValidHit(): continue
0047             if (type(hit), hit.index()) in hits1:
0048                 ncommon += 1
0049         if ncommon > best[1]:
0050             best = (trk, ncommon)
0051 
0052     return best
0053 
0054 class _TracksByHitsMatcher(object):
0055     def __init__(self, trklist):
0056         super(_TracksByHitsMatcher, self).__init__()
0057         self._hitsToTracks = collections.defaultdict(list)
0058         for trk in trklist:
0059             for hit in trk.hits():
0060                 if hit.isValidHit():
0061                     self._hitsToTracks[ (type(hit), hit.index()) ].append(trk)
0062 
0063     def match(self, trk):
0064         tracks = collections.defaultdict(int)
0065 
0066         for hit in trk.hits():
0067             if not hit.isValidHit(): continue
0068 
0069             idx = (type(hit), hit.index())
0070             try:
0071                 otherTracks = self._hitsToTracks[idx]
0072             except KeyError:
0073                 continue
0074 
0075             for ot in otherTracks:
0076                 tracks[ot] += 1
0077 
0078         best = (None, 0)
0079         for t, ncommon in tracks.items():
0080             if ncommon > best[1]:
0081                 best = (t, ncommon)
0082         return best
0083 
0084 
0085 # Common diff helpers, used in the printout helpers
0086 class _DiffResult(object):
0087     def __init__(self, diff=[], hasDifference=False):
0088         self._diff = []
0089         self._hasDifference = hasDifference
0090         self.extend(diff)
0091 
0092     def setDifference(self, diff=True):
0093         self._hasDifference = diff
0094 
0095     def hasDifference(self):
0096         return self._hasDifference
0097 
0098     def extend(self, diff):
0099         if isinstance(diff, _DiffResult):
0100             self._diff.append(diff)
0101             if diff.hasDifference():
0102                 self.setDifference()
0103         else:
0104             self._diff.extend(diff)
0105 
0106     def _highlightLine(self, line, plus, minus):
0107         char = " "
0108         if line[0] == "+":
0109             if plus: char = "+"
0110         elif line[0] == "-":
0111             if minus: char = "-"
0112         elif line[0] == "?":
0113             char = "?"
0114         return line[0]+char+line[1:]
0115 
0116     def highlight(self, plus=False, minus=False):
0117         if not (plus or minus):
0118             return
0119 
0120         for i, line in enumerate(self._diff):
0121             if isinstance(line, _DiffResult):
0122                 line.highlight(plus, minus)
0123             else:
0124                 self._diff[i] = self._highlightLine(line, plus, minus)
0125 
0126     def highlightLines(self, plusregexs=[], minusregexs=[]):
0127         if len(plusregexs) == 0 and len(minusregexs) == 0:
0128             return
0129 
0130         for i, line in enumerate(self._diff):
0131             if isinstance(line, _DiffResult):
0132                 raise Exception("highlightLines() is currently allowed only for text-only _DiffResult objects")
0133             plus = False
0134             minus = False
0135             for p in plusregexs:
0136                 if p.search(line):
0137                     plus = True
0138                     break
0139             for m in minusregexs:
0140                 if m.search(line):
0141                     plus = True
0142                     break
0143             self._diff[i] = self._highlightLine(line, plus, minus)
0144 
0145     def lines(self):
0146         for line in self._diff:
0147             if isinstance(line, _DiffResult):
0148                 for l in line.lines():
0149                     yield l
0150             else:
0151                 yield line
0152 
0153     def __str__(self):
0154         return "\n".join([s for s in (str(item) for item in self._diff) if s != ""])
0155 
0156     def __len__(self):
0157         return len(self._diff)
0158 
0159 def _difflist(list1, list2):
0160     diff = difflib.unified_diff(list1, list2, lineterm="", n=len(list1))
0161     for item in diff:
0162         if item[:2] == "@@":
0163             break
0164     return list(diff)
0165 
0166 def _makediff(list1, list2, equalPrefix=" "):
0167     diff = _difflist(list1, list2)
0168     if len(diff) == 0:
0169         return _DiffResult([equalPrefix+s for s in list1], hasDifference=False)
0170     else:
0171         return _DiffResult(diff, hasDifference=True)
0172 
0173 def _mapdiff(func, obj1, obj2):
0174     lst1 = func(obj1) if obj1 is not None else []
0175     lst2 = func(obj2) if obj2 is not None else []
0176     return _makediff(lst1, lst2)
0177 
0178 def _areSameTracks(trk1, trk2):
0179     ncommon = _commonHits(trk1, trk2)
0180 
0181     # if tracks have same hits, consider their reco as identical
0182     if not (ncommon == trk1.nValid() and ncommon == trk2.nValid()):
0183         return False
0184 
0185     # although if there is any change in their iterations, mark them different
0186     if not (trk1.algoMask() == trk2.algoMask() and trk1.algo() == trk2.algo() and trk1.originalAlgo() == trk2.originalAlgo()):
0187         return False
0188 
0189     # if reco is the same, check if they are matched to the
0190     # same TPs (in case the track-TP matching is modified
0191     # between ntuples)
0192     if trk1.nMatchedTrackingParticles() != trk2.nMatchedTrackingParticles():
0193         return False
0194 
0195     for tpInfo1, tpInfo2 in itertools.izip(trk1.matchedTrackingParticleInfos(), trk2.matchedTrackingParticleInfos()):
0196         if tpInfo1.trackingParticle().index() != tpInfo2.trackingParticle().index():
0197             return False
0198 
0199     return True
0200 
0201 def diffTrackListsFromSameTrackingParticle(trackPrinter, lst1, lst2, lst1extra=[], lst2extra=[], diffByHitsOnly=False):
0202     """lst1 and lst2 are the main lists to make the diff from.
0203 
0204     lst1extra and lst2extra are optional to provide suplementary
0205     tracks. Use case: lst1 and lst2 are subset of full tracks,
0206     lst1extra and lst2extra contain tracks matched to the same
0207     TrackingParticle but are outside of the selection of lst1/lst2.
0208     """
0209 
0210     diff = _DiffResult()
0211 
0212     _trks1extra = list(lst1extra)
0213     _trks2extra = list(lst2extra)
0214 
0215     trks1 = list(lst1)+_trks1extra
0216     trks2 = list(lst2)+_trks2extra # make copy because it is modified
0217 
0218     trks1extra = set([t.index() for t in _trks1extra])
0219     trks2extra = set([t.index() for t in _trks2extra])
0220 
0221     trks1Empty = (len(trks1) == 0)
0222     trks2Empty = (len(trks2) == 0)
0223 
0224     if trks1Empty and trks2Empty:
0225         return diff
0226 
0227     # make sure all tracks really come from a single TP
0228     # just to simplify the work loop, generalization can be considered later
0229     commonTP = None
0230     def _findCommonTP(_lst, _commonTP, _name):
0231         for trk in _lst:
0232             if trk.nMatchedTrackingParticles() != 1:
0233                 raise Exception("Track %d from %s is matched to %d TPs. This is not supported by this function yet." % (trk.index(), _name, trk.nMatchedTrackingParticles()))
0234             if _commonTP is None:
0235                 _commonTP = next(trk.matchedTrackingParticleInfos()).trackingParticle()
0236             else:
0237                 tp = next(trk.matchedTrackingParticleInfos()).trackingParticle()
0238                 if tp.index() != _commonTP.index():
0239                     raise Exception("Track %d from %s is matched to TP %d, which differs from the TP %d of already processed tracks." % (trk.index(), _name, _commonTP.index(), tp.index()))
0240         return _commonTP
0241     commonTP = _findCommonTP(trks1, commonTP, "lst1")
0242     commonTP = _findCommonTP(trks2, commonTP, "lst2")
0243 
0244     # Need some tracks from trks1 and trks2 to print the TrackingParticle information
0245     someTrk1 = trks1[0] if not trks1Empty else None
0246     someTrk2 = trks2[0] if not trks2Empty else None
0247 
0248     for trk1 in trks1:
0249         (matchedTrk2, ncommon) = _matchTracksByHits(trk1, trks2)
0250 
0251         # no more tracks in tp2
0252         if matchedTrk2 is None:
0253             if trk1.index() in trks1extra:
0254                 raise Exception("Track %d was found in trks1extra but matchedTrk2 is None, this should not happen" % trk1.index())
0255             diff.extend(_makediff(trackPrinter.printTrack(trk1), []))
0256         else: # diff trk1 to best-matching track from trks2
0257             someTrk2 = matchedTrk2
0258             trks2.remove(matchedTrk2)
0259             tmp = trackPrinter.diff(trk1, matchedTrk2, diffTrackingParticles=False)
0260             if diffByHitsOnly and _areSameTracks(trk1, matchedTrk2):
0261                 tmp.setDifference(False)
0262             tmp.highlight(plus=(matchedTrk2.index() in trks2extra), minus=(trk1.index() in trks1extra))
0263             diff.extend(tmp)
0264 
0265     for trk2 in trks2: # remaining tracks in trks2
0266         if trk2.index() in trks2extra:
0267             raise Exception("Track %d was found in trks2extra, but without matching track in trks1, this should not happen" % trk2.index())
0268         diff.extend(_makediff([], trackPrinter.printTrack(trk2)))
0269 
0270     # finally add information of the trackingParticle
0271     # easiest is to pass a track matched to the TP
0272     tmp = _mapdiff(trackPrinter.printMatchedTrackingParticles, someTrk1, someTrk2)
0273     tmp.setDifference(False) # we know the TP is the same, even if the "track match" information will differ
0274     def _makere(lst):
0275         r = []
0276         for i in lst: r.extend([re.compile("Tracks:.*%d:"%i), re.compile("matched to tracks.*%d"%i)])
0277         return r
0278     plusre = _makere(trks2extra)
0279     minusre = _makere(trks1extra)
0280     tmp.highlightLines(plusre, minusre)
0281     diff.extend(tmp)
0282 
0283     return diff
0284 
0285 class _TrackAssociation(object):
0286     def __init__(self):
0287         super(_TrackAssociation, self).__init__()
0288         self._trks1 = []
0289         self._trks2 = []
0290         self._trks1OutsideList = []
0291         self._trks2OutsideList = []
0292 
0293         self._trks1Ind = set()
0294         self._trks2Ind = set()
0295         self._trks1OutsideListInd = set()
0296         self._trks2OutsideListInd = set()
0297 
0298     def _extend(self, trks, name):
0299         lst = getattr(self, name)
0300         ind = getattr(self, name+"Ind")
0301         for t in trks:
0302             if not t.index() in ind:
0303                 lst.append(t)
0304                 ind.add(t.index())
0305 
0306     def extend(self, trks1=[], trks2=[], trks1OutsideList=[], trks2OutsideList=[]):
0307         self.extendTrks1(trks1)
0308         self.extendTrks2(trks2)
0309         self.extendTrks1OutsideList(trks1OutsideList)
0310         self.extendTrks2OutsideList(trks2OutsideList)
0311 
0312     def extendTrks1(self, trks):
0313         self._extend(trks, "_trks1")
0314 
0315     def extendTrks2(self, trks):
0316         self._extend(trks, "_trks2")
0317 
0318     def extendTrks1OutsideList(self, trks):
0319         self._extend(trks, "_trks1OutsideList")
0320 
0321     def extendTrks2OutsideList(self, trks):
0322         self._extend(trks, "_trks2OutsideList")
0323 
0324     def trks1(self): return self._trks1
0325     def trks2(self): return self._trks2
0326     def trks1OutsideList(self): return self._trks1OutsideList
0327     def trks2OutsideList(self): return self._trks2OutsideList
0328 
0329     def hasCommonTrackingParticle(self):
0330         trkGen = itertools.chain(self._trks1, self._trks2)
0331         try:
0332             first = next(trkGen)
0333         except StopIteration:
0334             return False
0335         if first.nMatchedTrackingParticles() != 1:
0336             return False
0337 
0338         tpIndex = next(first.matchedTrackingParticleInfos()).trackingParticle().index()
0339 
0340         for t in trkGen:
0341             if t.nMatchedTrackingParticles() != 1:
0342                 return False
0343             if next(t.matchedTrackingParticleInfos()).trackingParticle().index() != tpIndex:
0344                 return False
0345         return True
0346 
0347 
0348     def merge(self, other):
0349         self.extendTrks1(other._trks1)
0350         self.extendTrks2(other._trks2)
0351         self.extendTrks1OutsideList(other._trks1OutsideList)
0352         self.extendTrks2OutsideList(other._trks2OutsideList)
0353 
0354     def minEta(self):
0355         _min = lambda lst: min([t.eta() for t in lst])
0356 
0357         if len(self._trks1) > 0:
0358             return _min(self._trks1)
0359         if len(self._trks1OutsideList) > 0:
0360             return _min(self._trks1OutsideList)
0361         if len(self._trks2) > 0:
0362             return _min(self._trks2)
0363         if len(self._trks2_outsideList) > 0:
0364             return _min(self._trks2OutsideList)
0365         raise Exception("This _TrackAssociation is empty, minEta() makes no sense")
0366 
0367     def __str__(self):
0368         s = lambda l: str([t.index() for t in l])
0369         return s(self._trks1)+" "+s(self._trks2)+" "+s(self._trks1OutsideList)+" "+s(self._trks2OutsideList)
0370 
0371 def _associateTracksByTrackingParticlesAndHits(lst1, lst2):
0372     trks1 = list(lst1)
0373     trks2 = list(lst2)
0374 
0375     trks1Matcher = _TracksByHitsMatcher(trks1)
0376     trks2Matcher = _TracksByHitsMatcher(trks2)
0377 
0378     # Used to have exactly the same Track objects for the same index
0379     trks1Dict = {t.index(): t for t in trks1}
0380     trks2Dict = {t.index(): t for t in trks2}
0381 
0382     # Bit of a hack...
0383     tps1 = None
0384     tps2 = None
0385     if len(trks1) > 0:
0386         tps1 = TrackingParticles(trks1[0]._tree)
0387     if len(trks2) > 0:
0388         tps2 = TrackingParticles(trks2[0]._tree)
0389 
0390     trkAssoc1 = {}
0391     trkAssoc2 = {}
0392 
0393     def _getOrCreateAssoc(trk, d, **kwargs):
0394         if trk.index() in d:
0395             a = d[trk.index()]
0396         else:
0397             a = _TrackAssociation()
0398             d[trk.index()] = a
0399         a.extend(**kwargs)
0400         return a
0401 
0402     while len(trks1) > 0:
0403         trk1 = trks1.pop(0)
0404         assoc1 = _getOrCreateAssoc(trk1, trkAssoc1, trks1=[trk1])
0405 
0406         # First associate via TP
0407         if trk1.nMatchedTrackingParticles() > 0 and tps2:
0408             matched = False
0409 
0410             for tpInfo1 in trk1.matchedTrackingParticleInfos():
0411                 tp1 = tpInfo1.trackingParticle()
0412 
0413                 # Find possible duplicates within trks1
0414                 for trkInfo1 in tp1.matchedTrackInfos():
0415                     t1 = trkInfo1.track()
0416                     t1Index = t1.index()
0417                     if t1Index != trk1.index():
0418                         if t1Index in trks1Dict:
0419                             assoc1.extend(trks1=[t1]) # trk1 -> t1
0420                             _getOrCreateAssoc(t1, trkAssoc1, trks1=[t1, trk1]) # t1 -> trk1
0421                             #print "trk1 %d <-> t1 %d (TP)" % (trk1.index(), t1.index())
0422                             trks1.remove(trks1Dict[t1Index])
0423                         else:
0424                             #print "trk1 %d -> t1 %d (TP, not in list)" % (trk1.index(), t1.index())
0425                             assoc1.extend(trks1OutsideList=[t1]) # trk1 -> t1, if t1 is not in trks1
0426 
0427                 # Then look for the same tp in trks2
0428                 tp2 = tps2[tp1.index()]
0429                 for trkInfo2 in tp2.matchedTrackInfos():
0430                     matched = True
0431                     t2 = trkInfo2.track()
0432                     t2Index = t2.index()
0433                     if t2Index in trks2Dict:
0434                         assoc1.extend(trks2=[t2]) # trk1 -> t2
0435                         _getOrCreateAssoc(t2, trkAssoc2, trks1=[trk1], trks2=[t2]) # t2 -> trk1
0436                         #print "trk1 %d <-> t2 %d (TP)" % (trk1.index(), t2.index())
0437                         try:
0438                             trks2.remove(trks2Dict[t2Index]) # can fail if t2 has already been matched via hits
0439                         except ValueError:
0440                             pass
0441                     else:
0442                         #print "trk1 %d -> t2 %d (TP, not in list)" % (trk1.index(), t2.index())
0443                         assoc1.extend(trks2OutsideList=[t2]) # trk1 -> t2, if t2 is not in trks2
0444 
0445             if matched:
0446                 continue
0447 
0448         # If no matching tracks in trks2 via TrackingParticles, then
0449         # proceed finding the best match via hits
0450         (matchedTrk2, ncommon) = trks2Matcher.match(trk1)
0451         if matchedTrk2 is not None and ncommon >= 3:
0452             assoc1.extend(trks2=[matchedTrk2])
0453             assoc2 = _getOrCreateAssoc(matchedTrk2, trkAssoc2, trks1=[trk1], trks2=[matchedTrk2])
0454             #print "trk1 %d <-> t2 %d (hits)" % (trk1.index(), matchedTrk2.index())
0455             try:
0456                 trks2.remove(matchedTrk2) # can fail if matchedTrk2 has already been matched via TP
0457             except ValueError:
0458                 pass
0459 
0460             (matchedTrk1, ncommon1) = trks1Matcher.match(matchedTrk2)
0461             # if matchedTrk1 has TP, the link from matchedTrk1 -> matchedTrk2 will be created later
0462             if (matchedTrk1.nMatchedTrackingParticles() == 0 or not tps2) and matchedTrk1.index() != trk1.index():
0463                 assoc2.extend(trks1=[matchedTrk1])
0464                 _getOrCreateAssoc(matchedTrk1, trkAssoc1, trks1=[matchedTrk1], trks2=[matchedTrk2])
0465                 #print "trk1 %d <-> t2 %d (hits, via t2)" % (matchedTrk1.index(), matchedTrk2.index())
0466 
0467         # no match
0468 
0469     # remaining tracks in trks2
0470     for trk2 in trks2:
0471         assoc2 = _getOrCreateAssoc(trk2, trkAssoc2, trks2=[trk2])
0472         # collect duplicates
0473         if trk2.nMatchedTrackingParticles() > 0:
0474             for tpInfo2 in trk2.matchedTrackingParticleInfos():
0475                 tp2 = tpInfo2.trackingParticle()
0476                 for trkInfo2 in tp2.matchedTrackInfos():
0477                     t2 = trkInfo2.track()
0478                     t2Index = t2.index()
0479                     if t2Index in trks2Dict:
0480                         assoc2.extend(trks2=[t2])
0481                         #print "trk2 %d -> t2 %d (TP)" % (trk2.index(), t2.index())
0482                     else:
0483                         assoc2.extend(trks2OutsideList=[t2])
0484                         #print "trk2 %d -> t2 %d (TP, not in list)" % (trk2.index(), t2.index())
0485 
0486     # merge results
0487     # any good way to avoid copy-past?
0488     for ind, assoc in trkAssoc1.items():
0489         for t1 in assoc.trks1():
0490             a = trkAssoc1[t1.index()]
0491             assoc.merge(a)
0492             a.merge(assoc)
0493         for t2 in assoc.trks2():
0494             a = trkAssoc2[t2.index()]
0495             assoc.merge(a)
0496             a.merge(assoc)
0497     for ind, assoc in trkAssoc2.items():
0498         for t2 in assoc.trks2():
0499             a = trkAssoc2[t2.index()]
0500             assoc.merge(a)
0501             a.merge(assoc)
0502         for t1 in assoc.trks1():
0503             a = trkAssoc1[t1.index()]
0504             assoc.merge(a)
0505             a.merge(assoc)
0506 
0507     for ind, assoc in itertools.chain(trkAssoc1.items(), trkAssoc2.items()):
0508         #if ind in [437, 1101]:
0509         #    print "----"
0510         #    print ind, [t.index() for t in assoc.trks1()], [t.index() for t in assoc.trks2()]
0511         for t1 in assoc.trks1():
0512             a = trkAssoc1[t1.index()]
0513             assoc.merge(a)
0514             a.merge(assoc)
0515 
0516         #if ind in [437, 1101]:
0517         #    print ind, [t.index() for t in assoc.trks1()], [t.index() for t in assoc.trks2()]
0518 
0519         for t2 in assoc.trks2():
0520             a = trkAssoc2[t2.index()]
0521             assoc.merge(a)
0522             a.merge(assoc)
0523         #if ind in [437, 1101]:
0524         #    print ind, [t.index() for t in assoc.trks1()], [t.index() for t in assoc.trks2()]
0525         #    print "####"
0526 
0527     # collapse to a single collection of associations
0528     allAssocs = []
0529     while len(trkAssoc1) > 0:
0530         (t1Index, assoc) = trkAssoc1.popitem()
0531 
0532         #if t1Index == 1299:
0533         #    print t1Index, [t.index() for t in assoc.trks2()]
0534         for t1 in assoc.trks1():
0535             if t1.index() == t1Index: continue
0536             trkAssoc1.pop(t1.index())
0537         for t2 in assoc.trks2():
0538             trkAssoc2.pop(t2.index())
0539         allAssocs.append(assoc)
0540     while len(trkAssoc2) > 0:
0541         (t2Index, assoc) = trkAssoc2.popitem()
0542         if len(assoc.trks1()) > 0:
0543             raise Exception("len(assoc.trks1()) %d != 0 !!! %s for t2 %d" % (len(assoc.trks1()), str([t.index() for t in assoc.trks1()]), t2Index))
0544         for t2 in assoc.trks2():
0545             if t2.index() == t2Index: continue
0546             trkAssoc2.pop(t2.index())
0547         allAssocs.append(assoc)
0548 
0549     return allAssocs
0550 
0551 def diffTrackListsGeneric(trackPrinter, lst1, lst2, ignoreAdditionalLst2=False):
0552     associations = _associateTracksByTrackingParticlesAndHits(lst1, lst2)
0553 
0554     # sort in eta
0555     associations.sort(key=methodcaller("minEta"))
0556 
0557     diff = _DiffResult()
0558     for assoc in associations:
0559         if assoc.hasCommonTrackingParticle():
0560             if len(assoc.trks1()) == 0 and ignoreAdditionalLst2:
0561                 continue
0562 
0563             tmp = diffTrackListsFromSameTrackingParticle(trackPrinter, assoc.trks1(), assoc.trks2(), lst1extra=assoc.trks1OutsideList(), lst2extra=assoc.trks2OutsideList(), diffByHitsOnly=True)
0564             if tmp.hasDifference():
0565                 diff.extend(tmp)
0566                 diff.extend([" "])
0567         elif len(assoc.trks1()) == 1 and len(assoc.trks2()) == 1:
0568             trk1 = assoc.trks1()[0]
0569             trk2 = assoc.trks2()[0]
0570 
0571             if not _areSameTracks(trk1, trk2):
0572                 diff.extend(trackPrinter.diff(trk1, trk2))
0573                 diff.extend([" "])
0574         elif len(assoc.trks2()) == 0:
0575             for t in assoc.trks1():
0576                 diff.extend(trackPrinter.diff(t, None))
0577                 diff.extend([" "])
0578         elif len(assoc.trks1()) == 0:
0579             if ignoreAdditionalLst2:
0580                 continue
0581             for t in assoc.trks1():
0582                 diff.extend(trackPrinter.diff(None, t))
0583                 diff.extend([" "])
0584         else:
0585             # needs to be rather generic, let's start by sorting by the innermost hit
0586             trks1 = list(assoc.trks1())
0587             trks2 = list(assoc.trks2())
0588             trks1.sort(key=lambda t: next(t.hits()).r())
0589             trks2.sort(key=lambda t: next(t.hits()).r())
0590 
0591             # then calculate number of shared hits for each pair
0592             ncommon = []
0593             for i1, t1 in enumerate(trks1):
0594                 for i2, t2 in enumerate(trks2):
0595                     ncommon.append( (i1, i2, _commonHits(t1, t2)) )
0596 
0597             # sort that by number of common hits, descending order
0598             ncommon.sort(key=itemgetter(2), reverse=True)
0599 
0600             # then make the diffs starting from the pair with largest number of common hits
0601             pairs = [None]*len(trks1)
0602             usedT2 = [False]*len(trks2)
0603             for i1, i2, ncom in ncommon:
0604                 if pairs[i1] is None:
0605                     pairs[i1] = i2
0606                     usedT2[i2] = True
0607 
0608             for i1, i2 in enumerate(pairs):
0609                 t1 = trks1[i1]
0610                 t2 = trks2[i2]
0611                 diff.extend(trackPrinter.diff(t1, t2))
0612             for i2, used in enumerate(usedT2):
0613                 if not used:
0614                     diff.extend(trackPrinter.diff(None, trks2[i2]))
0615             diff.extend([" "])
0616 
0617     return diff
0618 
0619 def _formatHitDiffForTwiki(diffHits, prefix):
0620     line_re = re.compile("(?P<sign>[ \-+])\s+(?P<det>[a-zA-Z]+)(?P<lay>\d+)\D*?(\((?P<missing>missing|inactive)\))?\s+\d+")
0621 
0622     summary = []
0623     prevdet = ""
0624     prevsign = " "
0625     diffLines = diffHits.lines()
0626 
0627     # skip anything before the first line with "hits"
0628     for line in diffLines:
0629         if "hits" in line:
0630             break
0631 
0632     header = True
0633     for line in diffLines:
0634         # skip multiple occurrances of "hits" line, but only until
0635         # first line without "hits" is encountered
0636         if header:
0637             if "hits" in line:
0638                 continue
0639             else:
0640                 header = False
0641 
0642         m = line_re.search(line)
0643         if not m:
0644             break
0645             raise Exception("regex not found from line %s" % line.rstrip())
0646         sign = m.group("sign")
0647         det = m.group("det")
0648         lay = m.group("lay")
0649 
0650         if det != prevdet:
0651             if prevsign != " ":
0652                 summary.append("%ENDCOLOR%")
0653                 prevsign = " "
0654             summary.extend([" ", det])
0655             prevdet = det
0656 
0657         if sign != prevsign:
0658             if prevsign != " ":
0659                 summary.append("%ENDCOLOR%")
0660             if sign == "-":
0661                 summary.append("%RED%")
0662             elif sign == "+":
0663                 summary.append("%GREEN%")
0664             prevsign = sign
0665 
0666         #print sign, det, lay
0667         #if len(summary) > 0:
0668         #    print " ", summary[-1]
0669 
0670         #if det != prevdet:
0671         #    if prevsign != " ":
0672         #        #if len(summary) > 0:
0673         #        #    if 
0674         #        summary.append("%ENDCOLOR")
0675         #    summary.extend([" ", det])
0676         #    if prevsign == "-":
0677         #        summary.append("%RED%")
0678         #    elif prevsign == "+":
0679         #        summary.append("%GREEN%")
0680         #    prevdet = det
0681         summary.append(lay)
0682         if m.group("missing"):
0683             if m.group("missing") == "missing":
0684                 summary.append("(m)")
0685             elif m.group("missing") == "inactive":
0686                 summary.append("(i)")
0687 
0688     if prevsign != " ":
0689         summary.append("%ENDCOLOR%")
0690     # prune "changed" missing/inactive hits
0691     i = 2
0692     while i < len(summary)-5:
0693         if summary[i] == "(i)" or summary[i] == "(m)":
0694             if summary[i-2] == "%RED%" and summary[i+1] == "%ENDCOLOR%" and summary[i+2] == "%GREEN%" and summary[i+3] == summary[i-1] and summary[i+4] == summary[i] and summary[i+5] == "%ENDCOLOR%":
0695                 summary[i-2:i+6] = [summary[i-1], summary[i]]
0696         i += 1
0697 
0698     line = " "+"".join(summary)
0699     return ["?"+prefix+line]
0700 
0701 # Common detailed printout helpers
0702 def _hitPatternSummary(hits):
0703     summary = ""
0704 
0705     prevdet = 0
0706     for hit in hits:
0707         det = hit.subdet()
0708         lay = hit.layer()
0709 
0710         if det != prevdet:
0711             summary += " "+SubDet.toString(det)
0712             prevdet = det
0713 
0714         summary += str(lay)
0715         if isinstance(hit, InvalidHit):
0716             summary += "(%s)"%InvalidHit.Type.toString(hit.type())[0]
0717 
0718     return summary
0719 
0720 class _IndentPrinter(object):
0721     def __init__(self, indent=0):
0722         self._prefix = " "*indent
0723         self._backup = []
0724 
0725     def _indent(self, num):
0726         if num > 0:
0727             self._prefix += " "*num
0728         elif num < 0:
0729             self._prefix = self._prefix[:num]
0730 
0731     def indent(self, num):
0732         self._backup.append(self._prefix)
0733         self._indent(num)
0734 
0735     def setIndentFrom(self, printer, adjust=0):
0736         self._backup.append(self._prefix)
0737         self._prefix = printer._prefix
0738         self._indent(adjust)
0739 
0740     def restoreIndent(self):
0741         self._prefix = self._backup.pop()
0742 
0743 class _RecHitPrinter(_IndentPrinter):
0744     def __init__(self, indent=0):
0745         super(_RecHitPrinter, self).__init__(indent)
0746 
0747     def _printHits(self, hits):
0748         lst = []
0749         for hit in hits:
0750             matched = ""
0751             glued = ""
0752             coord = ""
0753             if hit.isValidHit():
0754                 if hasattr(hit, "matchedSimHitInfos"):
0755                     matched = " from %s " % HitSimType.toString(hit.simType())
0756                     matches = []
0757                     hasChargeFraction = False
0758                     for shInfo in hit.matchedSimHitInfos():
0759                         m = "%d:%d" % (shInfo.simHit().trackingParticle().index(), shInfo.simHit().index())
0760                         if hasattr(shInfo, "chargeFraction"):
0761                             m += "(%.1f)"%(shInfo.chargeFraction()*100)
0762                             hasChargeFraction = True
0763                         matches.append(m)
0764                     if len(matches) == 0:
0765                         matched += "not matched to any TP/SimHit"
0766                     else:
0767                         matched += "matched to TP:SimHit"
0768                         if hasChargeFraction:
0769                             matched += "(%)"
0770                         matched += " "+",".join(matches)
0771 
0772                 coord = "x,y,z %f,%f,%f" % (hit.x(), hit.y(), hit.z())
0773                 if isinstance(hit, GluedHit):
0774                     glued = "monoHit %d stereoHit %d " % (hit.monoHit().index(), hit.stereoHit().index())
0775 
0776             lst.append(self._prefix+"{layer} {hit} detid {detid} {detidStr} {glued}{coord}{matched}".format(layer=hit.layerStr(), hit=hit.index(),
0777                                                                                                                detid=hit.detId(), detidStr=hit.detIdStr(),
0778                                                                                                                glued=glued, coord=coord, matched=matched))
0779         return lst
0780 
0781 class _TrackingParticleMatchPrinter(object):
0782     def __init__(self, trackingParticles, trackingParticlePrinter, bestMatchingTrackingParticle):
0783         self._trackingParticles = trackingParticles
0784         self._trackingParticlePrinter = trackingParticlePrinter
0785         self._bestMatchingTrackingParticle = bestMatchingTrackingParticle
0786 
0787     def bestMatchingTrackingParticle(self):
0788         return self._bestMatchingTrackingParticle
0789 
0790     def _printTrackingParticles(self, prefix, tps, header):
0791         lst = []
0792         if self._trackingParticlePrinter is None:
0793             lst.append(prefix+header+" "+",".join([str(tp.index()) for tp in tps]))
0794         else:
0795             lst.append(prefix+header)
0796             for tp in tps:
0797                 lst.extend(self._trackingParticlePrinter.printTrackingParticle(tp))
0798                 lst.extend(self._trackingParticlePrinter.printHits(tp))
0799                 lst.extend(self._trackingParticlePrinter.printMatchedTracks(tp, useTrackPrinter=False))
0800         return lst
0801 
0802     def printMatchedTrackingParticles(self, prefix, track):
0803         lst = []
0804         if not self._trackingParticles:
0805             return lst
0806 
0807         pfx = prefix+" "
0808         if self._trackingParticlePrinter is not None:
0809             self._trackingParticlePrinter.indent(len(pfx)+1)
0810 
0811         if track.nMatchedTrackingParticles() == 0:
0812             if self._bestMatchingTrackingParticle:
0813                 bestTP = track.bestMatchingTrackingParticle()
0814                 if bestTP is not None:
0815                     lst.extend(self._printTrackingParticles(pfx, [bestTP], "not matched to any TP, but a following TP with >= 3 matched hits is found (shared hit fraction %.2f)" % track.bestMatchingTrackingParticleShareFrac()))
0816                 else:
0817                     lst.append(prefix+"not matched to any TP")
0818             else:
0819                 lst.append(prefix+"not matched to any TP")
0820         else:
0821             lst.extend(self._printTrackingParticles(pfx, [tpInfo.trackingParticle() for tpInfo in track.matchedTrackingParticleInfos()], "matched to TPs"))
0822 
0823         if self._trackingParticlePrinter is not None:
0824             self._trackingParticlePrinter.restoreIndent()
0825 
0826         return lst
0827 
0828 class SeedPrinter(_RecHitPrinter):
0829     def __init__(self, indent=0, hits=True, trackingParticles=False, trackingParticlePrinter=None, bestMatchingTrackingParticle=True):
0830         super(SeedPrinter, self).__init__(indent)
0831         self._hits = hits
0832         self._trackingParticleMatchPrinter = _TrackingParticleMatchPrinter(trackingParticles, trackingParticlePrinter, bestMatchingTrackingParticle)
0833 
0834     def printHeader(self, seed):
0835         lst = []
0836         track = seed.track()
0837         if track.isValid():
0838             madeTrack = "made track %d" % track.index()
0839         else:
0840             madeTrack = "did not make a track, stopReason %s" % SeedStopReason.toString(seed.stopReason())
0841             if seed.stopReason() == SeedStopReason.NOT_STOPPED:
0842                 madeTrack += " (usually this means that the track was reconstructed, but rejected by the track selection)"
0843 
0844         lst.append(self._prefix+"Seed %d algo %s %s" % (seed.indexWithinAlgo(), Algo.toString(seed.algo()), madeTrack))
0845         lst.append(self._prefix+" starting state: pT %f local pos x,y %f,%f mom x,y,z %f,%f,%f" % (seed.statePt(), seed.stateTrajX(), seed.stateTrajY(), seed.stateTrajPx(), seed.stateTrajPy(), seed.stateTrajPz()))
0846         return lst
0847 
0848     def printHits(self, seed):
0849         lst = []
0850         if self._hits:
0851             lst.append(self._prefix+" hits"+_hitPatternSummary(seed.hits()))
0852             self.indent(2)
0853             lst.extend(self._printHits(seed.hits()))
0854             self.restoreIndent()
0855         return lst
0856 
0857     def printMatchedTrackingParticles(self, seed):
0858         return self._trackingParticleMatchPrinter.printMatchedTrackingParticles(self._prefix, seed)
0859 
0860     def printSeed(self, seed):
0861         lst = []
0862         lst.extend(self.printHeader(seed))
0863         lst.extend(self.printHits(seed))
0864         lst.extend(self.printMatchedTrackingParticles(seed))
0865         return lst
0866 
0867     def __call__(self, seed, out=sys.stdout):
0868         if isinstance(out, list):
0869             lst = out
0870         else:
0871             lst = []
0872 
0873         lst.extend(self.printSeed(seed))
0874 
0875         if not isinstance(out, list):
0876             for line in lst:
0877                 out.write(line),
0878                 out.write("\n")
0879 
0880     def diff(self, seed1, seed2, diffForTwiki=False, diffTrackingParticles=False):
0881         if seed1 is None:
0882             return _makediff([], self.printSeed(seed2))
0883         if seed2 is None:
0884             return _makediff(self.printSeed(seed1), [])
0885 
0886         ret = _DiffResult()
0887         ret.extend(_mapdiff(self.printHeader, seed1, seed2))
0888         diffHits = _mapdiff(self.printHits, seed1, seed2)
0889         ret.extend(diffHits)
0890         if diffForTwiki:
0891             ret.extend(_formatHitDiffForTwiki(diffHits, self._prefix))
0892         if diffTrackingParticles:
0893             ret.extend(_mapdiff(self.printMatchedTrackingParticles, seed1, seed2))
0894         return ret
0895 
0896 class TrackPrinter(_RecHitPrinter):
0897     def __init__(self, indent=0, hits=True, seedPrinter=SeedPrinter(), trackingParticles=True, trackingParticlePrinter=None, bestMatchingTrackingParticle=True, diffForTwiki=False):
0898         super(TrackPrinter, self).__init__(indent)
0899         self._hits = hits
0900         self._seedPrinter = seedPrinter
0901         self._trackingParticleMatchPrinter = _TrackingParticleMatchPrinter(trackingParticles, trackingParticlePrinter, bestMatchingTrackingParticle)
0902         self._diffForTwiki = diffForTwiki
0903 
0904     def printHeader(self, track):
0905         lst = []
0906         lst.append(self._prefix+"Track %d pT %f eta %f phi %f dxy %f err %f dz %f err %f" % (track.index(), track.pt(), track.eta(), track.phi(), track.dxy(), track.dxyErr(), track.dz(), track.dzErr()))
0907 
0908         hp = "loose"
0909         if track.isHP():
0910             hp = "HP"
0911 
0912         algo = track.algo()
0913         oriAlgo = track.originalAlgo()
0914         algos = []
0915         algoMask = track.algoMask()
0916         for i in range(Algo.algoSize):
0917             if algoMask & 1:
0918                 algos.append(Algo.toString(i))
0919             algoMask = algoMask >> 1
0920         algoMaskStr = ""
0921         if len(algos) >= 2:
0922             algoMaskStr = " algoMask "+",".join(algos)
0923 
0924 
0925         lst.append(self._prefix+" pixel hits %d strip hits %d chi2/ndof %f" % (track.nPixel(), track.nStrip(), track.nChi2()))
0926         lst.append(self._prefix+" is %s algo %s originalAlgo %s%s stopReason %s" % (hp, Algo.toString(track.algo()), Algo.toString(track.originalAlgo()), algoMaskStr, StopReason.toString(track.stopReason())))
0927         lst.append(self._prefix+" px %f py %f pz %f p %f" % (track.px(), track.py(), track.pz(), math.sqrt(track.px()**2+track.py()**2+track.pz()**2)))
0928         if self._trackingParticleMatchPrinter.bestMatchingTrackingParticle():
0929             bestTP = track.bestMatchingTrackingParticle()
0930             if bestTP:
0931                 lst.append(self._prefix+" best-matching TP %d" % bestTP.index())
0932                 lst.append(self._prefix+"  shared hits %d reco denom %.3f sim denom %.3f sim cluster denom %.3f" % (track.bestMatchingTrackingParticleShareFrac()*track.nValid(), track.bestMatchingTrackingParticleShareFrac(), track.bestMatchingTrackingParticleShareFracSimDenom(), track.bestMatchingTrackingParticleShareFracSimClusterDenom()))
0933                 lst.append(self._prefix+"  matching chi2/ndof %f" % track.bestMatchingTrackingParticleNormalizedChi2())
0934                 lst.append(self._prefix+"  pulls pt %f theta %f phi %f dxy %f dz %f" % (track.ptPull(), track.thetaPull(), track.phiPull(), track.dxyPull(), track.dzPull()))
0935         return lst
0936 
0937     def printHits(self, track):
0938         lst = []
0939         if self._hits:
0940             lst.append(self._prefix+" hits"+_hitPatternSummary(track.hits()))
0941             self.indent(2)
0942             lst.extend(self._printHits(track.hits()))
0943             self.restoreIndent()
0944         return lst
0945 
0946     def printSeed(self, track):
0947         lst = []
0948         if self._seedPrinter:
0949             self._seedPrinter.setIndentFrom(self, adjust=1)
0950             lst.extend(self._seedPrinter.printSeed(track.seed()))
0951             self._seedPrinter.restoreIndent()
0952         return lst
0953 
0954     def diffSeeds(self, track1, track2):
0955         ret = _DiffResult()
0956         if self._seedPrinter:
0957             self._seedPrinter.setIndentFrom(self, adjust=1)
0958             ret.extend(self._seedPrinter.diff(track1.seed(), track2.seed(), self._diffForTwiki))
0959             self._seedPrinter.restoreIndent()
0960         return ret
0961 
0962     def printTrack(self, track):
0963         lst = self.printHeader(track)
0964         lst.extend(self.printHits(track))
0965         lst.extend(self.printSeed(track))
0966         return lst
0967 
0968     def printMatchedTrackingParticles(self, track):
0969         return self._trackingParticleMatchPrinter.printMatchedTrackingParticles(self._prefix, track)
0970 
0971     def printTrackAndMatchedTrackingParticles(self, track):
0972         lst = []
0973         lst.extend(self.printTrack(track))
0974         lst.extend(self.printMatchedTrackingParticles(track))
0975         return lst
0976 
0977     def __call__(self, track, out=sys.stdout):
0978         if isinstance(out, list):
0979             lst = out
0980         else:
0981             lst = []
0982 
0983         lst.extend(self.printTrackAndMatchedTrackingParticles(track))
0984 
0985         if not isinstance(out, list):
0986             for line in lst:
0987                 out.write(line)
0988                 out.write("\n")
0989 
0990     def diff(self, track1, track2, diffTrackingParticles=True):
0991         if track1 is None:
0992             lst = self.printTrack(track2) + self.printMatchedTrackingParticles(track2)
0993             return _makediff([], lst)
0994         if track2 is None:
0995             lst = self.printTrack(track1) + self.printMatchedTrackingParticles(track1)
0996             return _makediff(lst, [])
0997 
0998         ret = _DiffResult()
0999         ret.extend(_mapdiff(self.printHeader, track1, track2))
1000         if self._diffForTwiki:
1001             trk1TPs = [tpInfo.trackingParticle() for tpInfo in track1.matchedTrackingParticleInfos()]
1002             trk2TPs = [tpInfo.trackingParticle() for tpInfo in track2.matchedTrackingParticleInfos()]
1003 
1004             pt_pull1 = "None"
1005             pt_pull2 = "None"
1006             dxy_pull1 = "None"
1007             dxy_pull2 = "None"
1008             dz_pull1 = "None"
1009             dz_pull2 = "None"
1010 
1011             ptPull1 = track1.ptPull()
1012             ptPull2 = track2.ptPull()
1013             if ptPull1 is not None and ptPull2 is not None:
1014                 fmt = "{pull:.3g}"
1015                 pt_pull1 = fmt.format(pull=ptPull1)
1016                 pt_pull2 = fmt.format(pull=ptPull2)
1017                 dxy_pull1 = fmt.format(pull=track1.dxyPull())
1018                 dxy_pull2 = fmt.format(pull=track2.dxyPull())
1019                 dz_pull1 = fmt.format(pull=track1.dzPull())
1020                 dz_pull2 = fmt.format(pull=track2.dzPull())
1021 
1022             lst = [
1023                 self._prefix+" parameters",
1024                 self._prefix+"  pt %RED%{pt1:.3g}%ENDCOLOR% %GREEN%{pt2:.3g}%ENDCOLOR%".format(pt1=track1.pt(), pt2=track2.pt()),
1025             ]
1026             if pt_pull1 != "None":
1027                 lst.append(self._prefix+"   pull %RED%{pull1}%ENDCOLOR% %GREEN%{pull2}%ENDCOLOR%".format(pull1=pt_pull1, pull2=pt_pull2))
1028             lst.extend([
1029                 self._prefix+"  eta %RED%{eta1:.3g}%ENDCOLOR% %GREEN%{eta2:.3g}%ENDCOLOR%".format(eta1=track1.eta(), eta2=track2.eta()),
1030                 self._prefix+"  phi %RED%{phi1:.3g}%ENDCOLOR% %GREEN%{phi2:.3g}%ENDCOLOR%".format(phi1=track1.phi(), phi2=track2.phi()),
1031                 self._prefix+"  dxy %RED%{dxy1:.3g}%ENDCOLOR% %GREEN%{dxy2:.3g}%ENDCOLOR% ({dxy1rel:.2f}*err1, {dxy2rel:.2f}*err2)".format(dxy1=track1.dxy(), dxy2=track2.dxy(), dxy1rel=(track2.dxy()-track1.dxy())/track1.dxyErr(), dxy2rel=(track2.dxy()-track1.dxy())/track2.dxyErr()),
1032             ])
1033             if dxy_pull1 != "None":
1034                 lst.append(self._prefix+"   pull %RED%{pull1}%ENDCOLOR% %GREEN%{pull2}%ENDCOLOR%".format(pull1=dxy_pull1, pull2=dxy_pull2))
1035             lst.extend([
1036                 self._prefix+"  dz %RED%{dz1:.3g}%ENDCOLOR% %GREEN%{dz2:.3g}%ENDCOLOR% ({dz1rel:.2f}*err1, {dz2rel:.2f}*err2)".format(dz1=track1.dz(), dz2=track2.dz(), dz1rel=(track2.dz()-track1.dz())/track1.dzErr(), dz2rel=(track2.dz()-track1.dz())/track2.dzErr()),
1037             ])
1038             if dz_pull1 != "None":
1039                 lst.append(self._prefix+"   pull %RED%{pull1}%ENDCOLOR% %GREEN%{pull2}%ENDCOLOR%".format(pull1=dz_pull1, pull2=dz_pull2))
1040             lst.extend([
1041                 self._prefix+"  chi2/ndof %RED%{chi1:.3g}%ENDCOLOR% %GREEN%{chi2:.3g}%ENDCOLOR%".format(chi1=track1.nChi2(), chi2=track2.nChi2()),
1042             ])
1043             ret.extend(_makediff(lst, lst, equalPrefix="?"))
1044 
1045         diffHits = _mapdiff(self.printHits, track1, track2)
1046         ret.extend(diffHits)
1047         if self._hits and self._diffForTwiki:
1048             ret.extend(_formatHitDiffForTwiki(diffHits, self._prefix))
1049 
1050         ret.extend(self.diffSeeds(track1, track2))
1051         if diffTrackingParticles:
1052             ret.extend(_mapdiff(self.printMatchedTrackingParticles, track1, track2))
1053         return ret
1054 
1055 class TrackingParticlePrinter(_IndentPrinter):
1056     def __init__(self, indent=0, parentage=True, hits=True, tracks=True, trackPrinter=None, bestMatchingTrack=True, seedPrinter=SeedPrinter()):
1057         super(TrackingParticlePrinter, self).__init__(indent)
1058         self._parentage = parentage
1059         self._hits = hits
1060         self._tracks = tracks
1061         self._trackPrinter = trackPrinter
1062         self._bestMatchingTrack = bestMatchingTrack
1063         self._seedPrinter = seedPrinter
1064 
1065     def _printTP(self, tp):
1066         genIds = ""
1067         if len(tp.genPdgIds()) > 0:
1068             genIds = " genPdgIds "+",".join([str(pdgId) for pdgId in tp.genPdgIds()])
1069         fromB = ""
1070         if tp.isFromBHadron():
1071             fromB = " from B hadron"
1072         return [
1073             self._prefix+"TP %d pdgId %d%s%s ev:bx %d:%d pT %f eta %f phi %f" % (tp.index(), tp.pdgId(), genIds, fromB, tp.event(), tp.bunchCrossing(), tp.pt(), tp.eta(), tp.phi()),
1074             self._prefix+" pixel hits %d strip hits %d numberOfTrackerHits() %d associated reco clusters %d dxy %f dz %f" % (tp.nPixel(), tp.nStrip(), tp.nTrackerHits(), tp.nRecoClusters(), tp.pca_dxy(), tp.pca_dz())
1075         ]
1076         
1077 
1078     def _parentageChain(self, tp):
1079         lst = []
1080         prodVtx = tp.parentVertex()
1081         if prodVtx.nSourceTrackingParticles() == 1:
1082             lst.extend(self._printTP(next(prodVtx.sourceTrackingParticles())))
1083         elif prodVtx.nSourceTrackingParticles() >= 2:
1084             self.indent(1)
1085             for tp in prodVtx.sourceTrackingParticles():
1086                 self._printTP(tp, out)
1087                 self.indent(1)
1088                 lst.extend(self._parentageChain(tp))
1089                 self.indent(-1)
1090             self.indent(-1)
1091         return lst
1092 
1093     def printTrackingParticle(self, tp):
1094         lst = []
1095         lst.extend(self._printTP(tp))
1096         if self._parentage:
1097             if tp.parentVertex().nSourceTrackingParticles() > 0:
1098                 lst.append(self._prefix+" parentage chain")
1099                 self.indent(2)
1100                 lst.extend(self._parentageChain(tp))
1101                 self.indent(-2)
1102         return lst
1103 
1104     def printHits(self, tp):
1105         lst = []
1106         if self._hits:
1107             lst.append(self._prefix+" sim hits"+_hitPatternSummary(tp.simHits()))
1108             for simhit in tp.simHits():
1109                 tmp = []
1110                 for h in simhit.hits():
1111                     tmp.append(",".join([str(trk.index()) for trk in h.tracks()]) + ":%d"%h.index())
1112                 if len(tmp) == 0:
1113                     matched = "not matched to any Track/RecHit"
1114                 else:
1115                     matched = "matched to Tracks:RecHits "+";".join(tmp)
1116 
1117                 lst.append(self._prefix+"  %s %d pdgId %d process %d detId %d %s x,y,z %f,%f,%f %s" % (simhit.layerStr(), simhit.index(), simhit.particle(), simhit.process(), simhit.detId(), simhit.detIdStr(), simhit.x(), simhit.y(), simhit.z(), matched))
1118         return lst
1119 
1120     def _printMatchedTracksHeader(self):
1121         return [self._prefix+" matched to tracks"]
1122 
1123     def _printMatchedTracks(self, tracks, header=None, useTrackPrinter=True):
1124         lst = []
1125         if header is not None:
1126             lst.append(self._prefix+" "+header)
1127         else:
1128             lst.extend(self._printMatchedTracksHeader())
1129         if self._trackPrinter is None or not useTrackPrinter:
1130             lst[-1] += " "+",".join([str(track.index()) for track in tracks])
1131         else:
1132             self._trackPrinter.indent(2)
1133             for track in tracks:
1134                 lst.extend(self._trackPrinter.printTrack(track))
1135             self._trackPrinter.restoreIndent()
1136         return lst
1137 
1138     def printMatchedTracks(self, tp, useTrackPrinter=True):
1139         lst = []
1140         if tp.nMatchedTracks() == 0:
1141             header = "not matched to any track"
1142             lst.append(self._prefix+" "+header)
1143             if self._bestMatchingTrack:
1144                 bestTrack = tp.bestMatchingTrack()
1145                 if bestTrack is not None:
1146                     lst.pop()
1147                     lst.extend(self._printMatchedTracks([bestTrack], header+", but a following track with >= 3 matched hits is found", useTrackPrinter=useTrackPrinter))
1148         else:
1149             lst.extend(self._printMatchedTracks([trkInfo.track() for trkInfo in tp.matchedTrackInfos()], useTrackPrinter=useTrackPrinter))
1150         return lst
1151 
1152     def diffMatchedTracks(self, tp1, tp2):
1153         ntrk1 = tp1.nMatchedTracks()
1154         ntrk2 = tp2.nMatchedTracks()
1155 
1156         if ntrk1 == 0 or ntrk2 == 0 or self._trackPrinter is None:
1157             return _makediff(self.printMatchedTracks(tp1), self.printMatchedTracks(tp2))
1158 
1159         self._trackPrinter.indent(2)
1160 
1161         diff = _makediff(self._printMatchedTracksHeader(), self._printMatchedTracksHeader())
1162         trks1 = [trkInfo1.track() for trkInfo1 in tp1.matchedTrackInfos()]
1163         trks2 = [trkInfo2.track() for trkInfo2 in tp2.matchedTrackInfos()]
1164         #for trkInfo1 in tp1.matchedTrackInfos():
1165         #    trk1 = trkInfo1.track()
1166         #    matchedTrk2 = _matchTracksByHits(trk1, trks2)
1167         #
1168         #    if matchedTrk2 is None: # no more tracks in tp2
1169         #        diff.extend(_makediff(self._trackPrinter.printTrack(trk1), []))
1170         #    else: # diff trk1 to best-matching track from tp2
1171         #        trks2.remove(matchedTrk2)
1172         #        diff.extend(self._trackPrinter.diff(trk1, matchedTrk2))
1173         #
1174         #for trk2 in trks2: # remaining tracks in tp2
1175         #    diff.extend(_makediff([], self._trackPrinter.printTrack(trk2)))
1176         diff.extend(diffTrackListsFromSameTrackingParticle(self._trackPrinter, trks1, trks2))
1177 
1178         self._trackPrinter.restoreIndent()
1179         return diff
1180 
1181     def _printMatchedSeeds0(self):
1182         return [self._prefix+ " not matched to any seed"]
1183 
1184     def _printMatchedSeedsHeader(self):
1185         return [self._prefix+" matched to seeds"]
1186 
1187     def printMatchedSeeds(self, tp):
1188         lst = []
1189         if self._seedPrinter:
1190             if tp.nMatchedSeeds() == 0:
1191                 lst.extend(self._printMatchedSeeds0())
1192             else:
1193                 lst.extend(self._printMatchedSeedsHeader())
1194                 self._seedPrinter.setIndentFrom(self, adjust=2)
1195                 for seedInfo in tp.matchedSeedInfos():
1196                     lst.extend(self._seedPrinter.printSeed(seedInfo.seed()))
1197                 self._seedPrinter.restoreIndent()
1198         return lst
1199 
1200     def diffMatchedSeeds(self, tp1, tp2):
1201         if not self._seedPrinter:
1202             return []
1203 
1204         nseed1 = tp1.nMatchedSeeds()
1205         nseed2 = tp2.nMatchedSeeds()
1206         if nseed1 == 0 or nseed2 == 0:
1207             return _makediff(self.printMatchedSeeds(tp1), self.printMatchedSeeds(tp2))
1208 
1209         self._seedPrinter.setIndentFrom(self, adjust=2)
1210 
1211         diff = _makediff(self._printMatchedSeedsHeader(), self._printMatchedSeedsHeader())
1212         seeds2 = [seedInfo2.seed() for seedInfo2 in tp2.matchedSeedInfos()]
1213         for seedInfo1 in tp1.matchedSeedInfos():
1214             seed1 = seedInfo1.seed()
1215             matchedSeed2 = _matchTracksByHits(seed1, seeds2)[0]
1216 
1217             if matchedSeed2 is None: # no more seeds in tp2
1218                 diff.extend(_makediff(self._seedPrinter.printSeed(seed1), []))
1219             else: # diff seed1 to best-matching seed from tp2
1220                 seeds2.remove(matchedSeed2)
1221                 diff.extend(_makediff(self._seedPrinter.printSeed(seed1), self._seedPrinter.printSeed(matchedSeed2)))
1222 
1223         for seed2 in seeds2: # remiaining seeds in tp2
1224             diff.extend(_makediff([], self._seedPrinter.printSeed(seed2)))
1225 
1226         self._seedPrinter.restoreIndent()
1227 
1228         return diff
1229 
1230     def __call__(self, tp, out=sys.stdout):
1231         if isinstance(out, list):
1232             lst = out
1233         else:
1234             lst = []
1235 
1236         lst.extend(self.printTrackingParticle(tp))
1237         lst.extend(self.printHits(tp))
1238         lst.extend(self.printMatchedTracks(tp))
1239         lst.extend(self.printMatchedSeeds(tp))
1240 
1241         for line in lst:
1242             out.write(line)
1243             out.write("\n")
1244 
1245     def diff(self, tp1, tp2):
1246         ret = _DiffResult()
1247         ret.extend(_mapdiff(self.printTrackingParticle, tp1, tp2))
1248         ret.extend(_mapdiff(self.printHits, tp1, tp2))
1249         ret.extend(self.diffMatchedTracks(tp1, tp2))
1250         ret.extend(self.diffMatchedSeeds(tp1, tp2))
1251         return ret
1252