View Javadoc

1   package org.lcsim.recon.util;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.Comparator;
6   import java.util.HashMap;
7   import java.util.Iterator;
8   import java.util.LinkedHashMap;
9   import java.util.LinkedList;
10  import java.util.List;
11  import java.util.Map;
12  
13  import org.lcsim.event.CalorimeterHit;
14  import org.lcsim.event.Cluster;
15  import org.lcsim.event.EventHeader;
16  import org.lcsim.event.LCRelation;
17  import org.lcsim.event.MCParticle;
18  import org.lcsim.event.ReconstructedParticle;
19  import org.lcsim.event.RelationalTable;
20  import org.lcsim.event.SimCalorimeterHit;
21  import org.lcsim.event.Track;
22  import org.lcsim.event.TrackerHit;
23  import org.lcsim.event.MCParticle.SimulatorStatus;
24  import org.lcsim.event.base.BaseLCRelation;
25  import org.lcsim.event.base.BaseRelationalTable;
26  import org.lcsim.util.Driver;
27  import org.lcsim.lcio.LCIOConstants;
28  import org.lcsim.lcio.LCIOUtil;
29  
30  /**
31   * A Driver to create several LCRelations between high level reconstructed
32   * objects and their true mc particles.
33   * <p>
34   * By default three LCRelations are created
35   * <ul>
36   * <li><b>Tracks to MCParticles:</b><br>
37   * This requires the name of the track collection and an LCRelation between
38   * TrackerHits and MCParticles.</li>
39   * <li><b>Clusters to MCParticles:</b><br>
40   * This requires the name of the cluster collection and an LCRelation between
41   * CalorimeterHits and SimCalorimeterHits.</li>
42   * <li><b>PFOs to MCParticles:</b><br>
43   * This requires the name of the PFO collection and the creation of both
44   * LCRelations described above.</li>
45   * </ul>
46   * Putting any of these into the event can be prevented by setting the
47   * respective name to an empty String.
48   * <p>
49   * The weights of these relations are based on fractions contributed by the mc
50   * particle. For the track relation it is based on the fraction of hits and for
51   * clusters it is based on the fraction of energy. Non-charged PFOs use the
52   * weight of the clusters, while charged PFOs use a combined weight of the
53   * tracks and the clusters, based on a global track to cluster weight. As a
54   * default only the track relations are used for charged PFOs.
55   * <p>
56   * Instead of the simple fraction-based weights described above a weight based
57   * on a Tanimoto metric can be used. Then also the hits produced by a mc
58   * particle which are not part of the reconstructed object are taken into
59   * account. This leads to a lower weight for missed hits.
60   * <p>
61   * For all objects, only the relation to the MCParticle which has the highest
62   * weight is kept. Setting the fullRecoRelation switch to true will keep all
63   * relations instead.
64   * <p>
65   * By default a reduced set of MCParticles is created. This skimmed list
66   * contains only those MCParticles created by the generator (intermediate and
67   * final state particles) and emitted bremsstrahlung photons. In addition all
68   * particles which are of a pre-defined set of particle types and which decay in
69   * flight in the tracking system and their intermediate daughters are kept. By
70   * default these particle types are gamma, pi0 and K0s. There is also an energy
71   * cut applied to which of these daughter particles are kept. All relations
72   * which would point to an MCParticle not contained in this reduced list point
73   * to their closest ancestor which is in this list instead. Again, this behavior
74   * can be switched off, by setting the name of the skimmed mc particle
75   * collection to an empty String.
76   * 
77   * @author <a href="mailto:christian.grefe@cern.ch">Christian Grefe</a>
78   */
79  public class McTruthLinker extends Driver {
80  
81  	protected String trackHitMcRelationName = "HelicalTrackMCRelations";
82  	protected String trackCollectionName = "Tracks";
83  	protected String trackMcRelationName = "TrackMCTruthLink";
84  	protected String caloHitSimHitRelationName = "CalorimeterHitRelations";
85  	protected String clusterCollectionName = "ReconClusters";
86  	protected String clusterMcRelationName = "ClusterMCTruthLink";
87  	protected String pfoCollectionName = "PandoraPFOCollection";
88  	protected String pfoMcRelationName = "RecoMCTruthLink";
89  	protected String mcParticleCollectionName = EventHeader.MC_PARTICLES;
90  	protected String mcParticlesSkimmedName = "MCParticlesSkimmed";
91  	protected double pfoTrackWeight = 1.0;
92  	protected double pfoClusterWeight = 0.0;
93  	protected boolean fullRecoRelation = false;
94  	protected boolean useTanimotoDistance = false;
95  	protected boolean useSkimmedMcParticles = true;
96  	protected List<MCParticle> mcParticlesSkimmed;
97  	protected List<Integer> keepDaughtersPDGID = new ArrayList<Integer>();
98  	protected Map<MCParticle, MCParticle> mcParticleToSkimmed;
99  	protected double daughterEnergyCut = 0.01;
100 
101 	// -------------------- Constructors --------------------
102 
103 	public McTruthLinker() {
104 		keepDaughtersPDGID.add(22); // gamma
105 		keepDaughtersPDGID.add(111); // pi0
106 		keepDaughtersPDGID.add(310); // K0s
107 	}
108 
109 	// -------------------- Driver Interface Methods --------------------
110 
111 	@Override
112 	protected void startOfData() {
113 		if (mcParticlesSkimmedName.equals("")) {
114 			this.useSkimmedMcParticles = false;
115 		}
116 	}
117 
118 	@Override
119 	protected void process(EventHeader event) {
120 
121 		List<LCRelation> trackMcRelation = null;
122 		List<LCRelation> caloHitMcRelation = null;
123 		List<LCRelation> clusterMcRelation = null;
124 		List<LCRelation> pfoMcRelation = null;
125 		mcParticlesSkimmed = null;
126 		mcParticleToSkimmed = null;
127 
128 		// skimmed mc particles
129 		if (useSkimmedMcParticles) {
130 			try {
131 				List<MCParticle> mcParticles = event.get(MCParticle.class, mcParticleCollectionName);
132 				mcParticlesSkimmed = createSkimmedMcParticleList(mcParticles);
133 				mcParticleToSkimmed = fillMcParticleToSkimmedMap(mcParticles, mcParticlesSkimmed);
134 				int flags = event.getMetaData(mcParticles).getFlags();
135 				flags = LCIOUtil.bitSet(flags, LCIOConstants.BITSubset, true);
136 				event.put(mcParticlesSkimmedName, mcParticlesSkimmed, MCParticle.class, flags);
137 				print(HLEVEL_NORMAL, "Added skimmed mc particles \"" + mcParticlesSkimmedName + "\" to the event.");
138 			} catch (IllegalArgumentException e) {
139 				print(HLEVEL_DEFAULT, "WARNING: no skimmed mc particle collection created.\n" + "e.getMessage()", true);
140 			}
141 		}
142 
143 		// track to mc particle relation
144 		if (!trackHitMcRelationName.equals("") && !trackCollectionName.equals("")) {
145 			try {
146 				List<Track> tracks = event.get(Track.class, trackCollectionName);
147 				List<LCRelation> trackHitMcRelation = event.get(LCRelation.class, trackHitMcRelationName);
148 				trackMcRelation = createTrackMcRelation(tracks, trackHitMcRelation);
149 				if (!trackMcRelationName.equals("")) {
150 					int flags = 0;
151 					flags = LCIOUtil.bitSet(flags, LCIOConstants.LCREL_WEIGHTED, true);
152 					event.put(trackMcRelationName, trackMcRelation, LCRelation.class, flags);
153 					print(HLEVEL_NORMAL, "Added track to mc particle relations \"" + trackMcRelationName
154 							+ "\" to the event.");
155 				}
156 			} catch (IllegalArgumentException e) {
157 				print(HLEVEL_DEFAULT, "WARNING: no track to mc particle relation created.\n" + "e.getMessage()", true);
158 			}
159 		}
160 
161 		// calorimeter hit to mc particle relation
162 		if (!caloHitSimHitRelationName.equals("")) {
163 			try {
164 				caloHitMcRelation = createCaloHitMcRelation(event.get(LCRelation.class, caloHitSimHitRelationName));
165 			} catch (IllegalArgumentException e) {
166 				print(HLEVEL_DEFAULT, "WARNING: no calorimeter hit to mc particle relation created.\n"
167 						+ "e.getMessage()", true);
168 			}
169 		}
170 
171 		// cluster to mc particle relation
172 		if (!clusterCollectionName.equals("")) {
173 			try {
174 				List<Cluster> clusters = event.get(Cluster.class, clusterCollectionName);
175 				clusterMcRelation = createClusterMcRelation(clusters, caloHitMcRelation);
176 				if (!clusterMcRelationName.equals("")) {
177 					int flags = 0;
178 					flags = LCIOUtil.bitSet(flags, LCIOConstants.LCREL_WEIGHTED, true);
179 					event.put(clusterMcRelationName, clusterMcRelation, LCRelation.class, flags);
180 					print(HLEVEL_NORMAL, "Added cluster to mc particle relations \"" + clusterMcRelationName
181 							+ "\" to the event.");
182 				}
183 			} catch (IllegalArgumentException e) {
184 				print(HLEVEL_DEFAULT, "WARNING: no cluster to mc particle relation created.\n" + "e.getMessage()", true);
185 			}
186 		}
187 
188 		// PFO to mc particle relation
189 		if (!pfoCollectionName.equals("")) {
190 			try {
191 				List<ReconstructedParticle> PFOs = event.get(ReconstructedParticle.class, pfoCollectionName);
192 				pfoMcRelation = createPfoMcRelation(PFOs, trackMcRelation, clusterMcRelation);
193 				if (!pfoMcRelationName.equals("")) {
194 					int flags = 0;
195 					flags = LCIOUtil.bitSet(flags, LCIOConstants.LCREL_WEIGHTED, true);
196 					event.put(pfoMcRelationName, pfoMcRelation, LCRelation.class, flags);
197 					print(HLEVEL_NORMAL, "Added PFO to mc particle relations \"" + pfoMcRelationName
198 							+ "\" to the event.");
199 				}
200 			} catch (IllegalArgumentException e) {
201 				print(HLEVEL_DEFAULT, "WARNING: no PFO to mc particle relation created.\n" + "e.getMessage()", true);
202 			}
203 		}
204 	}
205 
206 	// -------------------- Setter Methods --------------------
207 
208 	public void setFullRecoRelation(boolean fullRecoRelation) {
209 		this.fullRecoRelation = fullRecoRelation;
210 	}
211 
212 	public void setUseTanimotoDistance(boolean useTanimotoDistance) {
213 		this.useTanimotoDistance = useTanimotoDistance;
214 	}
215 
216 	public void setPfoTrackWeight(double pfoTrackWeight) throws IllegalArgumentException {
217 		if (pfoTrackWeight < 0)
218 			throw new IllegalArgumentException("PFO track weight can not be negative.");
219 		this.pfoTrackWeight = pfoTrackWeight;
220 	}
221 
222 	public void setPfoClusterWeight(double pfoClusterWeight) throws IllegalArgumentException {
223 		if (pfoTrackWeight < 0)
224 			throw new IllegalArgumentException("PFO cluster weight can not be negative.");
225 		this.pfoClusterWeight = pfoClusterWeight;
226 	}
227 
228 	public void setTrackHitMcRelationName(String trackHitMcRelationName) {
229 		this.trackHitMcRelationName = trackHitMcRelationName;
230 	}
231 
232 	public void setTrackCollectionName(String trackCollectionName) {
233 		this.trackCollectionName = trackCollectionName;
234 	}
235 
236 	public void setTrackMcRelationName(String trackMcRelationName) {
237 		this.trackMcRelationName = trackMcRelationName;
238 	}
239 
240 	public void setCaloHitSimHitRelationName(String caloHitSimHitRelationName) {
241 		this.caloHitSimHitRelationName = caloHitSimHitRelationName;
242 	}
243 
244 	public void setClusterCollectionName(String clusterCollectionName) {
245 		this.clusterCollectionName = clusterCollectionName;
246 	}
247 
248 	public void setClusterMcRelationName(String clusterMcRelationName) {
249 		this.clusterMcRelationName = clusterMcRelationName;
250 	}
251 
252 	public void setPfoCollectionName(String pfoCollectionName) {
253 		this.pfoCollectionName = pfoCollectionName;
254 	}
255 
256 	public void setPfoMcRelationName(String pfoMcRelationName) {
257 		this.pfoMcRelationName = pfoMcRelationName;
258 	}
259 
260 	public void setMcParticleCollectionName(String mcParticleCollectionName) {
261 		this.mcParticleCollectionName = mcParticleCollectionName;
262 	}
263 
264 	public void setMcParticlesSkimmedName(String mcParticlesSkimmedName) {
265 		this.mcParticlesSkimmedName = mcParticlesSkimmedName;
266 	}
267 
268 	public void setKeepDaughtersPDGID(int[] keepDaughtersPDGID) {
269 		this.keepDaughtersPDGID.clear();
270 		for (int pdgid : keepDaughtersPDGID) {
271 			this.keepDaughtersPDGID.add(pdgid);
272 		}
273 	}
274 
275 	public void setDaughterEnergyCut(double daughterEnergyCut) {
276 		this.daughterEnergyCut = daughterEnergyCut;
277 	}
278 
279 	// -------------------- Protected Methods --------------------
280 
281 	/**
282 	 * Creates a list of skimmed mc particles which are kept together with all
283 	 * their ancestors. First of all, all the particles that are created by the
284 	 * generator (IntermediateState, Documentation or FinalState) are kept. In
285 	 * addition bremsstrahlung photons created by these particles are kept.
286 	 * Finally all the particles from a given list (default: gamma, pi0, K0s)
287 	 * are kept together with their direct daughters.
288 	 */
289 	protected List<MCParticle> createSkimmedMcParticleList(List<MCParticle> mcParticles) {
290 
291 		List<MCParticle> skimmedMcParticles = new ArrayList<MCParticle>();
292 
293 		for (MCParticle mcParticle : mcParticles) {
294 			SimulatorStatus simStatus = mcParticle.getSimulatorStatus();
295 			if (mcParticle.getGeneratorStatus() == MCParticle.INTERMEDIATE) {
296 				// first add all intermediate particles
297 				addMcParticleWithParents(mcParticle, skimmedMcParticles);
298 			}
299 			if (mcParticle.getGeneratorStatus() == MCParticle.DOCUMENTATION) {
300 				// add all documentation particles.
301 				addMcParticleWithParents(mcParticle, skimmedMcParticles);
302 			}
303 			if (mcParticle.getGeneratorStatus() > 3) {
304 				// add all particles with unknown generator status.
305 				// Mokka adds 100 to the generator status of particles that
306 				// should not be passed through simulation.
307 				addMcParticleWithParents(mcParticle, skimmedMcParticles);
308 			}
309 			if (mcParticle.getGeneratorStatus() == MCParticle.FINAL_STATE) {
310 				// add all mc particles created by the generator
311 				addMcParticleWithParents(mcParticle, skimmedMcParticles);
312 				// check if there is some interaction in the tracking region
313 				if (simStatus.isDecayedInCalorimeter()) {
314 					// keep bremsstrahlung
315 					for (MCParticle daughter : mcParticle.getDaughters()) {
316 						if (daughter.getPDGID() == 22 && daughter.getEnergy() > daughterEnergyCut
317 								&& !daughter.getSimulatorStatus().isBackscatter()) {
318 							addMcParticleWithParents(daughter, skimmedMcParticles);
319 						}
320 					}
321 				}
322 				//
323 			} else if (mcParticle.getSimulatorStatus().isDecayedInTracker()) {
324 				// now add all daughters of the particles that decayed in flight
325 				// and should be kept
326 				if (keepDaughtersPDGID.contains(mcParticle.getPDGID())) {
327 					for (MCParticle daughter : mcParticle.getDaughters()) {
328 						if (daughter.getEnergy() > daughterEnergyCut && !daughter.getSimulatorStatus().isBackscatter()) {
329 							addMcParticleWithParents(daughter, skimmedMcParticles);
330 						}
331 					}
332 				}
333 
334 			}
335 		}
336 
337 		print(HLEVEL_NORMAL, "Keeping " + skimmedMcParticles.size() + " of " + mcParticles.size()
338 				+ " mc particles in skimmed list.");
339 
340 		return skimmedMcParticles;
341 	}
342 
343 	/**
344 	 * Fills a map connecting an mc particle with its closest ancestor that is
345 	 * present in the skimmed mc particle list. If no suitable ancestor is found
346 	 * the map is filled with null for that mc particle.
347 	 * 
348 	 * @param mcParticles
349 	 *            The list of all mc particles
350 	 * @param skimmedMcParticles
351 	 *            A subset of the mc particles
352 	 * @return A mapping between all mc particles and their closest ancestor
353 	 *         present in the skimmed mc particles
354 	 */
355 	protected Map<MCParticle, MCParticle> fillMcParticleToSkimmedMap(List<MCParticle> mcParticles,
356 			List<MCParticle> skimmedMcParticles) {
357 
358 		Map<MCParticle, MCParticle> mcParticleToSkimmedMap = new HashMap<MCParticle, MCParticle>();
359 
360 		for (MCParticle mcParticle : mcParticles) {
361 			MCParticle ancestor = findMcParticleAncestor(mcParticle, skimmedMcParticles);
362 			mcParticleToSkimmedMap.put(mcParticle, ancestor);
363 			String motherPDGID = "none";
364 			if (mcParticle.getParents().size() > 0) {
365 				motherPDGID = Integer.toString(mcParticle.getParents().get(0).getPDGID());
366 			}
367 			if (mcParticle != ancestor) {
368 				print(HLEVEL_FULL, "Warning: Rejecting mc particle." + "\tEnergy: " + mcParticle.getEnergy() + "\n"
369 						+ "\tCharge: " + mcParticle.getCharge() + "\n" + "\tPDGID: " + mcParticle.getPDGID() + "\n"
370 						+ "\tGenStatus: " + mcParticle.getGeneratorStatus() + "\n" + "\tCreated in simulation: "
371 						+ mcParticle.getSimulatorStatus().isCreatedInSimulation() + "\n" + "\tBackscatter: "
372 						+ mcParticle.getSimulatorStatus().isBackscatter() + "\n" + "\tDecay in calorimeter: "
373 						+ mcParticle.getSimulatorStatus().isDecayedInCalorimeter() + "\n" + "\tDecay in tracker: "
374 						+ mcParticle.getSimulatorStatus().isDecayedInTracker() + "\n" + "\tStopped: "
375 						+ mcParticle.getSimulatorStatus().isStopped() + "\n" + "\tMother: " + motherPDGID, true);
376 			}
377 		}
378 		return mcParticleToSkimmedMap;
379 	}
380 
381 	/**
382 	 * Creates the relations from tracks to mc particles by using a list of
383 	 * LCRelations from hits to mc particles. In case of a skimmed mc particle
384 	 * list the relations are pointing to the closest ancestor present in the
385 	 * skimmed list.
386 	 * <p>
387 	 * The relations are weighted by the fraction of hits belonging to a certain
388 	 * mc particle (N_{match}/N_{track}).
389 	 * <p>
390 	 * In case of Tanimoto distance also the total number of hits produced by
391 	 * the mc particle are taken into account. It gives less weight to tracks
392 	 * that miss true hits. The weight is then calculated as 1 -
393 	 * (N_{track}+N_{mc}-2*N_{match})/(N_{track}+N_{mc}-N_{match).
394 	 * 
395 	 * @param tracks
396 	 *            The list of tracks to be truth linked
397 	 * @param trackHitMcRelation
398 	 *            The LCRelations between track hits and mc particles
399 	 * @return The weighted LCRelations between tracks and mc particles
400 	 */
401 	protected List<LCRelation> createTrackMcRelation(List<Track> tracks, List<LCRelation> trackHitMcRelation) {
402 
403 		if (trackHitMcRelation == null) {
404 			throw new IllegalArgumentException("No tracker hit to mc relations given.");
405 		}
406 
407 		RelationalTable<TrackerHit, MCParticle> trackHitMcRelationTable = createRelationalTable(trackHitMcRelation);
408 		List<LCRelation> trackMcRelation = new ArrayList<LCRelation>();
409 
410 		for (Track track : tracks) {
411 			// Store number of hits contributed by each mc particle
412 			Map<MCParticle, Integer> mcParticleContribution = new HashMap<MCParticle, Integer>();
413 			List<TrackerHit> trackHitsList = track.getTrackerHits();
414 			double trackHits = trackHitsList.size();
415 			double sumOfWeights = 0;
416 			for (TrackerHit trackHit : trackHitsList) {
417 				for (MCParticle mcParticle : trackHitMcRelationTable.allFrom(trackHit)) {
418 					if (useSkimmedMcParticles)
419 						mcParticle = mcParticleToSkimmed.get(mcParticle);
420 					if (mcParticleContribution.containsKey(mcParticle)) {
421 						mcParticleContribution.put(mcParticle, mcParticleContribution.get(mcParticle) + 1);
422 					} else {
423 						mcParticleContribution.put(mcParticle, 1);
424 					}
425 				}
426 			}
427 			mcParticleContribution = sortMapByHighestValue(mcParticleContribution);
428 			for (MCParticle mcParticle : mcParticleContribution.keySet()) {
429 				double weight = 0.0;
430 				double recoHits = mcParticleContribution.get(mcParticle);
431 				if (useTanimotoDistance) {
432 					double trueHits = trackHitMcRelationTable.allTo(mcParticle).size();
433 					weight = 1 - (trackHits + trueHits - 2 * recoHits) / (trackHits + trueHits - recoHits);
434 				} else {
435 					weight = recoHits / trackHits;
436 				}
437 				sumOfWeights += weight;
438 				trackMcRelation.add(new BaseLCRelation(track, mcParticle, weight));
439 				print(HLEVEL_FULL, "Added a track to mc particle relation with weight " + weight + ".");
440 				if (!fullRecoRelation)
441 					break;
442 			}
443 			print(HLEVEL_HIGH, "Total weight of track contributions is " + sumOfWeights + ".");
444 		}
445 
446 		print(HLEVEL_NORMAL, "Created " + trackMcRelation.size() + " track to mc particle relations.");
447 
448 		return trackMcRelation;
449 	}
450 
451 	/**
452 	 * Creates the relations from calorimeter hits to mc particles by using a
453 	 * list of LCRelations from CalorimeterHits to SimCalorimeterHits and the
454 	 * intrinsic link to mc particles of the sim hits.
455 	 * <p>
456 	 * The produced relations are weighted by the energy fraction contributed by
457 	 * the mc particle to the SimCalorimeterHit (E_{MC,Hit}/E_{Hit})
458 	 * 
459 	 * @param caloHitSimHitRelation
460 	 *            The relations between CalorimeterHits and SimCalorimeterHits
461 	 * @return The weighted LCRelations between CalorimeterHits and MCParticles
462 	 */
463 	protected List<LCRelation> createCaloHitMcRelation(List<LCRelation> caloHitSimHitRelation) {
464 
465 		List<LCRelation> caloHitMcRelation = new ArrayList<LCRelation>();
466 
467 		for (LCRelation relation : caloHitSimHitRelation) {
468 			CalorimeterHit digiHit = (CalorimeterHit) relation.getFrom();
469 			SimCalorimeterHit simHit = (SimCalorimeterHit) relation.getTo();
470 			double hitEnergy = simHit.getRawEnergy();
471 			double sumOfWeights = 0;
472 			for (int i = 0; i < simHit.getMCParticleCount(); i++) {
473 				double weight = simHit.getContributedEnergy(i) / hitEnergy;
474 				sumOfWeights += weight;
475 				caloHitMcRelation.add(new BaseLCRelation(digiHit, simHit.getMCParticle(i), weight));
476 				print(HLEVEL_FULL, "Added a calorimeter hit to mc particle relation with weight " + weight + ".");
477 			}
478 			print(HLEVEL_FULL, "Total weight of calorimeter hit contributions is " + sumOfWeights + ".");
479 		}
480 
481 		print(HLEVEL_NORMAL, "Created " + caloHitMcRelation.size() + " calorimeter hit to mc particle relations.");
482 
483 		return caloHitMcRelation;
484 	}
485 
486 	/**
487 	 * Creates the relations from Clusters to MCParticles by using a list of
488 	 * LCRelations from CalorimeterHits to MCParticles.
489 	 * <p>
490 	 * The produced relations are weighted by the energy fraction contributed by
491 	 * the MCParticle to the Cluster (E_{MC,Cluster}/E_{Cluster})
492 	 * 
493 	 * @param clusters
494 	 *            The list of clusters to be truth linked
495 	 * @param caloHitMcRelation
496 	 *            The relations between CalorimeterHits and MCParticles
497 	 * @return The weighted LCRelations between Clusters and MCParticles
498 	 * @throws IllegalArgumentException
499 	 */
500 	protected List<LCRelation> createClusterMcRelation(List<Cluster> clusters, List<LCRelation> caloHitMcRelation)
501 			throws IllegalArgumentException {
502 
503 		if (caloHitMcRelation == null) {
504 			throw new IllegalArgumentException("No calorimeter hit to mc relations given.");
505 		}
506 
507 		RelationalTable<CalorimeterHit, MCParticle> caloHitMcRelationTable = createRelationalTable(caloHitMcRelation);
508 		List<LCRelation> clusterMcRelation = new ArrayList<LCRelation>();
509 
510 		for (Cluster cluster : clusters) {
511 			double sumOfWeights = 0;
512 			double clusterEnergy = cluster.getEnergy();
513 			Map<MCParticle, Double> mcParticlesWeight = new HashMap<MCParticle, Double>();
514 			for (CalorimeterHit hit : cluster.getCalorimeterHits()) {
515 				double hitEnergy = hit.getCorrectedEnergy();
516 				double hitWeight = hitEnergy / clusterEnergy;
517 				Map<MCParticle, Double> hitMcParticlesWeight = caloHitMcRelationTable.allFromWithWeights(hit);
518 				for (MCParticle mcParticle : hitMcParticlesWeight.keySet()) {
519 					// TODO implement optional use of Tanimoto distance
520 					double weight = hitWeight * hitMcParticlesWeight.get(mcParticle);
521 					if (useSkimmedMcParticles)
522 						mcParticle = mcParticleToSkimmed.get(mcParticle);
523 					if (mcParticlesWeight.containsKey(mcParticle)) {
524 						mcParticlesWeight.put(mcParticle, mcParticlesWeight.get(mcParticle) + weight);
525 					} else {
526 						mcParticlesWeight.put(mcParticle, weight);
527 					}
528 				}
529 			}
530 			mcParticlesWeight = sortMapByHighestValue(mcParticlesWeight);
531 			for (MCParticle mcParticle : mcParticlesWeight.keySet()) {
532 				double weight = mcParticlesWeight.get(mcParticle);
533 				sumOfWeights += weight;
534 				clusterMcRelation.add(new BaseLCRelation(cluster, mcParticle, weight));
535 				print(HLEVEL_FULL, "Added a cluster to mc particle relation with weight " + weight + ".");
536 				if (!fullRecoRelation)
537 					break;
538 			}
539 			print(HLEVEL_HIGH, "Total weight of cluster contributions is " + sumOfWeights + ".");
540 		}
541 
542 		print(HLEVEL_NORMAL, "Created " + clusterMcRelation.size() + " cluster to mc particle relations.");
543 
544 		return clusterMcRelation;
545 	}
546 
547 	/**
548 	 * Creates the relations from PFOs to MCParticles by using a list of
549 	 * LCRelations from Tracks to MCParticles and a second list of LCRelations
550 	 * from Clusters to MCParticles.
551 	 * <p>
552 	 * In case of a non-charged PFO the relation is weighted using the weights
553 	 * from the contributing Cluster to MCParticle relations.
554 	 * <p>
555 	 * For charged PFOs the weight of the relations are calculated separately
556 	 * for tracks and clusters and then combined depending on a global track to
557 	 * cluster weight. By default the track weight is 1 and the cluster weight
558 	 * is 0. Thus, only the relation via track is taken into account.
559 	 * 
560 	 * @param recoParticles
561 	 *            The list of PFOs to be truth linked
562 	 * @param trackMcRelation
563 	 *            The relations between Tracks and MCParticles
564 	 * @param clusterMcRelation
565 	 *            The relations between Clusters and MCParticles
566 	 * @return The weighted LCRelations between PFOs and MCParticles
567 	 * @throws IllegalArgumentException
568 	 */
569 	protected List<LCRelation> createPfoMcRelation(List<ReconstructedParticle> recoParticles,
570 			List<LCRelation> trackMcRelation, List<LCRelation> clusterMcRelation) throws IllegalArgumentException {
571 
572 		if (trackMcRelation == null) {
573 			throw new IllegalArgumentException("No track to mc relations given.");
574 		}
575 		if (clusterMcRelation == null) {
576 			throw new IllegalArgumentException("No cluster to mc relations given.");
577 		}
578 
579 		RelationalTable<Track, MCParticle> trackMcRelationTable = createRelationalTable(trackMcRelation);
580 		RelationalTable<Cluster, MCParticle> clusterMcRelationTable = createRelationalTable(clusterMcRelation);
581 		List<LCRelation> pfoMcRelation = new ArrayList<LCRelation>();
582 
583 		for (ReconstructedParticle recoParticle : recoParticles) {
584 			double sumOfWeights = 0;
585 			int pfoTrackHits = 0;
586 			double pfoEnergy = recoParticle.getEnergy();
587 			double thisPfoClusterWeight = pfoClusterWeight;
588 			double trackClusterNormalization = pfoTrackWeight + pfoClusterWeight;
589 			Map<MCParticle, Double> mcParticlesWeight = new HashMap<MCParticle, Double>();
590 			// if PFO has tracks use them for truth link and ignore cluster
591 			if (pfoTrackWeight != 0) {
592 				for (Track track : recoParticle.getTracks()) {
593 					pfoTrackHits += track.getTrackerHits().size();
594 				}
595 				for (Track track : recoParticle.getTracks()) {
596 					Map<MCParticle, Double> trackMcParticlesWeight = trackMcRelationTable.allFromWithWeights(track);
597 					double trackWeight = track.getTrackerHits().size() / (double) pfoTrackHits;
598 					// weigh the contribution by track to cluster weight
599 					trackWeight *= pfoTrackWeight / trackClusterNormalization;
600 					for (MCParticle mcParticle : trackMcParticlesWeight.keySet()) {
601 						double weight = trackWeight * trackMcParticlesWeight.get(mcParticle);
602 						if (useSkimmedMcParticles)
603 							mcParticle = mcParticleToSkimmed.get(mcParticle);
604 						if (mcParticlesWeight.containsKey(mcParticle)) {
605 							mcParticlesWeight.put(mcParticle, mcParticlesWeight.get(mcParticle) + weight);
606 						} else {
607 							mcParticlesWeight.put(mcParticle, weight);
608 						}
609 					}
610 				}
611 			}
612 			// If no tracks attached, only use clusters
613 			if (pfoTrackHits == 0) {
614 				thisPfoClusterWeight = 1.0;
615 				trackClusterNormalization = 1.0;
616 			}
617 			// if PFO has no tracks use clusters for truth link
618 			if (thisPfoClusterWeight != 0) {
619 				for (Cluster cluster : recoParticle.getClusters()) {
620 					Map<MCParticle, Double> clusterMcParticlesWeight = clusterMcRelationTable
621 							.allFromWithWeights(cluster);
622 					double clusterWeight = cluster.getEnergy() / pfoEnergy;
623 					// weigh the contribution by cluster to cluster weight
624 					clusterWeight *= thisPfoClusterWeight / trackClusterNormalization;
625 					for (MCParticle mcParticle : clusterMcParticlesWeight.keySet()) {
626 						double weight = clusterWeight * clusterMcParticlesWeight.get(mcParticle);
627 						if (useSkimmedMcParticles)
628 							mcParticle = mcParticleToSkimmed.get(mcParticle);
629 						if (mcParticlesWeight.containsKey(mcParticle)) {
630 							mcParticlesWeight.put(mcParticle, mcParticlesWeight.get(mcParticle) + weight);
631 						} else {
632 							mcParticlesWeight.put(mcParticle, weight);
633 						}
634 					}
635 				}
636 			}
637 			mcParticlesWeight = sortMapByHighestValue(mcParticlesWeight);
638 			for (MCParticle mcParticle : mcParticlesWeight.keySet()) {
639 				double weight = mcParticlesWeight.get(mcParticle);
640 				// need to normalize to total number of track hits
641 				sumOfWeights += weight;
642 				pfoMcRelation.add(new BaseLCRelation(recoParticle, mcParticle, weight));
643 				print(HLEVEL_FULL, "Added a PFO to mc particle relation with weight " + weight + ".\n" + "\tEnergy: "
644 						+ mcParticle.getEnergy() + "\n" + "\tCharge: " + mcParticle.getCharge() + "\n" + "\tPDGID: "
645 						+ mcParticle.getPDGID());
646 				if (!fullRecoRelation)
647 					break;
648 			}
649 			print(HLEVEL_HIGH, "Total weight of PFO contributions is " + sumOfWeights + ".");
650 		}
651 
652 		print(HLEVEL_NORMAL, "Created " + pfoMcRelation.size() + " PFO to mc particle relations.");
653 
654 		return pfoMcRelation;
655 	}
656 
657 	// -------------------- Static Methods --------------------
658 	// TODO These can most likely move to a more general class
659 
660 	/**
661 	 * Helper method to write a message to the output stream if the histogram
662 	 * level set for the driver is equal or higher than the given value.
663 	 * 
664 	 * @param histogramLevel
665 	 *            The level at which the message is printed
666 	 * @param message
667 	 *            The message, which will be printed to the stream
668 	 */
669 	protected void print(int histogramLevel, String message) {
670 		print(histogramLevel, message, false);
671 	}
672 
673 	/**
674 	 * Helper method to write a message to the output stream if the histogram
675 	 * level set for the driver is equal or higher than the given value.
676 	 * 
677 	 * @param histogramLevel
678 	 *            The level at which the message is printed
679 	 * @param message
680 	 *            The message, which will be printed to the stream
681 	 * @param error
682 	 *            If true, writes to error stream instead of standard
683 	 */
684 	protected void print(int histogramLevel, String message, boolean error) {
685 		if (getHistogramLevel() >= histogramLevel) {
686 			message = getName() + ": " + message;
687 			if (error) {
688 				System.err.println(message);
689 			} else
690 				System.out.println(message);
691 		}
692 	}
693 
694 	/**
695 	 * Adds an mc particle to a list of mc particles if it is not yet in the
696 	 * list. Also adds all its ancestors recursively to the same list.
697 	 * 
698 	 * @param mcParticle
699 	 *            The mc particle to be added to the list
700 	 * @param mcParticles
701 	 *            The list to add the mc particle to
702 	 */
703 	protected void addMcParticleWithParents(MCParticle mcParticle, List<MCParticle> mcParticles) {
704 		if (!mcParticles.contains(mcParticle)) {
705 			mcParticles.add(mcParticle);
706 			print(HLEVEL_FULL, "Adding mc particle to skimmed list.\n" + "\tEnergy: " + mcParticle.getEnergy() + "\n"
707 					+ "\tCharge: " + mcParticle.getCharge() + "\n" + "\tPDGID: " + mcParticle.getPDGID() + "\n"
708 					+ "\tGenStatus: " + mcParticle.getGeneratorStatus() + "\n" + "\tSimStatus: "
709 					+ mcParticle.getSimulatorStatus().getValue());
710 			for (MCParticle parent : mcParticle.getParents()) {
711 				addMcParticleWithParents(parent, mcParticles);
712 			}
713 		}
714 	}
715 
716 	/**
717 	 * Finds the first ancestor of a given mc particle within a list of mc
718 	 * particles. Used to find the relevant particle in the skimmed list, when
719 	 * trying to find out which true particle caused a hit.
720 	 * 
721 	 * @param mcParticle
722 	 *            The mc particle
723 	 * @param mcParticles
724 	 *            The list of mc particles containing possible ancestors
725 	 * @return The mc particle ancestor. Null if none is found.
726 	 */
727 	protected MCParticle findMcParticleAncestor(MCParticle mcParticle, List<MCParticle> mcParticles) {
728 		MCParticle ancestor = null;
729 
730 		if (mcParticles.contains(mcParticle)) {
731 			ancestor = mcParticle;
732 		} else {
733 			List<MCParticle> parents = mcParticle.getParents();
734 			if (parents.size() > 0) {
735 				// just look for the first ancestor here if multiple are present
736 				ancestor = findMcParticleAncestor(parents.get(0), mcParticles);
737 			}
738 		}
739 		if (ancestor == null) {
740 			print(HLEVEL_DEFAULT,
741 					"Warning: no ancestor found in mc particle list." + "\tEnergy: " + mcParticle.getEnergy() + "\n"
742 							+ "\tCharge: " + mcParticle.getCharge() + "\n" + "\tPDGID: " + mcParticle.getPDGID() + "\n"
743 							+ "\tGenStatus: " + mcParticle.getGeneratorStatus() + "\n" + "\tSimStatus: "
744 							+ mcParticle.getSimulatorStatus().getValue(), true);
745 		}
746 		return ancestor;
747 	}
748 
749 	/**
750 	 * Converts a List of LCRelations (one to one relations with weights) into a
751 	 * RelationalTable (many to many relations with weights). This improves
752 	 * access if the relations in the LCRelation are actually many to many or
753 	 * one to many relations described with multiple one to one relations.
754 	 * 
755 	 * @param relations
756 	 *            A list of LCRelations
757 	 * @return A RelationalTable with the same content as the given list
758 	 */
759 	public static <F, T> RelationalTable<F, T> createRelationalTable(List<LCRelation> relations) {
760 		RelationalTable<F, T> relationalTable = new BaseRelationalTable<F, T>();
761 		for (LCRelation relation : relations) {
762 			relationalTable.add((F) relation.getFrom(), (T) relation.getTo(), relation.getWeight());
763 		}
764 		return relationalTable;
765 	}
766 
767 	/**
768 	 * Creates a map with its keys sorted by its values in descending order from
769 	 * an existing map. The values have to be comparable.
770 	 * 
771 	 * @param map
772 	 *            The original map which should be sorted
773 	 * @return A new map with keys sorted by values
774 	 */
775 	public static Map sortMapByHighestValue(Map map) {
776 		List list = new LinkedList(map.entrySet());
777 		Collections.sort(list, new Comparator() {
778 			public int compare(Object o1, Object o2) {
779 				return -((Comparable) ((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue());
780 			}
781 		});
782 
783 		Map result = new LinkedHashMap();
784 		for (Iterator it = list.iterator(); it.hasNext();) {
785 			Map.Entry entry = (Map.Entry) it.next();
786 			result.put(entry.getKey(), entry.getValue());
787 		}
788 		return result;
789 	}
790 }