6.8.2. How to Veto


In many physics analyses one of the (final state) particles originates from a specific decay and is not part of the signal decay chain. Such candidates contribute to the background and need to rejected as effectively as possible. Examples:

  • in studies of radiative B meson or charm decays, such as \(B^0\to\rho^0\gamma\), the photon candidate, which is used in the reconstruction of the \(B^0\) meson may, in reality, originate from the decay of either \(\pi^0\to\gamma\gamma\) or \(\eta\to\gamma\gamma\).

  • in studies of semileptonic B meson or charm decays the lepton sometimes originates from the decay of charmonium, \(J/\psi\) for example.

To deal with these backgrounds, we construct a veto in order to suppress the background sources. Vetoing comes in two steps:

  1. calculate or construct some sort of a probability or likelihood that particle \(X\) (used in the reconstruction of the signal decay of interest) in reality originates from some other decay, \(M \to X Y\).

  2. if the calculated probability is larger then some threshold value then we reject X and with it the signal candidate. We veto \(M\).

Any veto (\(\pi^0,\ \eta,\ J/\psi,\ ...\)) can be fully constructed and configured within the python steering file.

Overview of the workflow

The analysis of some specific signal side decay with a veto has the following steps. Here we use the worked example of a veto for background photons from a \(\pi^0\to\gamma\gamma\) decay but the procedure generalises.

  1. Reconstruct signal decay candidates
    • this includes filling of final-state particle lists, making combinations, performing vertex fits, applying cuts, etc…

  2. Create a Rest Of Event object for each signal candidate

  3. Create new basf2.Path to be executed for each Rest Of Event object in the event (one for each signal candidate)


    It is important to check if the current Rest Of Event object is related to a Particle from our signal ParticleList. This is a subtlety, but you may find unrelated Rest Of Event objects in the DataStore (from some other decay mode). In this case we simply skip the Rest Of Event by executing a so called “dead end” path.

  4. Fill a ParticleList with all (or selected) photons from the Rest Of Event

  5. Fill a ParticleList with signal photon candidates which is used in the reconstruction of the current signal side candidate (related to current Rest Of Event)

  6. Combine the two lists to form pi0 candidates

  7. Use TMVA or any other method to determine the probability or likelihood that signal photon originates from \(\pi^0\) decay

  8. Select the best \(\pi^0\) candidate

  9. Write the probability (or likelihood value) calculated in step iv for the best \(\pi^0\) candidate as extraInfo to the current \(B^0\) candidate

  10. Continue with signal side reconstruction
    • continuum suppression, flavour tagging, tag vertex, etc…

  11. Fill flat ntuple for offline analysis.

All analysis actions coloured in blue are executed once per event and all coloured in green are executed once per each Rest Of Event object in the event. The analysis actions coloured with green together construct a veto.

See also

This is an important usecase of the basf2.Path.for_each functionality.


\(\pi^0\) veto in \(B^0 \to \rho^0 \gamma\) decays The code is taken from an existing tutorial: B2A306-B02RhoGamma-withPi0Veto.py

  1. The B0 -> rho0 gamma candidates are reconstructed and collected in the ParticleList with name B0.

  2. Create Rest Of Event objects

import basf2
from modularAnalysis import buildRestOfEvent

mymainpath = basf2.Path()
buildRestOfEvent('B0', path=mymainpath)
  1. Create roe_path in which the veto will be constructed. In addition another dead-end path needs to be created, which will be used in step o)

roe_path = basf2.Path()
deadEndPath = basf2.Path()

In next steps the veto is constructed. In this example the veto works in the following way:

  • combine photon (gamma) used in the reconstruction of the B0 candidate with all other photons found in the event with energy above 50 MeV to form \(\pi^0\) candidates

  • find best pi0 candidate with invariant mass closest to \(\pi^0\)’s nominal mass

  • write value of invariant mass of the best \(\pi^0\) as ‘pi0veto’ extraInfo

  1. check if Rest Of Event is related to any Particle from B0 list

signalSideParticleFilter('B0', '', roe_path, deadEndPath)
  1. fill ParticleList with all photons that have ‘E>0.050’ from Rest Of Event (using isInRestOfEvent variable)

fillParticleList('gamma:roe', 'isInRestOfEvent == 1 and E > 0.050', path=roe_path)
  1. fill ParticleList with signal photon candidate which is used in the reconstruction of the current signal side candidate (related to current Rest Of Event)

fillSignalSideParticleList('gamma:sig', 'B0 -> rho0 ^gamma', roe_path)
  1. combine the two lists to form \(\pi^0\to\gamma\gamma\) candidates

reconstructDecay('pi0:veto -> gamma:sig gamma:roe', '0.080 < M < 0.200', path=roe_path)
  1. select the best \(\pi^0\) candidate

rankByLowest('pi0:veto', 'abs(dM)', 1, path=roe_path)
  1. write the probability or likelihood value calculated in step iv) for the best pi0 candidate as extraInfo to the current B0 candidate

variableToSignalSideExtraInfo('pi0:veto', {'M': 'pi0veto'}, path=roe_path)
  1. Connect the roe_path with the main path

mymainpath.for_each('RestOfEvent', 'RestOfEvents', roe_path)
  1. Continue with signal side reconstruction. At this point the B0 candidates have extraInfo(pi0veto) attached.

printVariableValues('B0', ['pi0veto'], path=mymainpath)

If the signal photon candidate could not be paired with any other photon candidate from the Rest Of Event to form a \(\pi^0\) candidate, then extraInfo(pi0veto) = NaN.