#################################################################################
# Basf2 (Belle II Analysis Software Framework) #
# Author: The Belle II Collaboration #
# #
# See git log for contributors and copyright holders. #
# This file is licensed under LGPL-3.0, see LICENSE.md. #
# e-e+ -> tau+(sig) (mode 303) tau-(tag) (mode = 1 or 2 or 303 or 163 or 112) #
# | | #
# | +-> e- nu_tau #
# | +-> mu- nu_tau #
# | +-> pi- nu_tau #
# | +-> pi- pi0 nu_tau #
# | +-> pi- pi+ pi- nu_tau #
# | #
# +-> pi+ nu_tau #
#################################################################################
from typing import List, Dict
import pandas as pd
import basf2 as b2
import modularAnalysis as ma
import variables.collections as vc
import variables.utils as vu
from variables import variables as va
from vibe.core.utils.misc import fancy_validation_mode_header
from vibe.core.validation_mode import ValidationModeBaseClass
from vibe.core.helper.histogram_tools import HistVariable, Histogram, HistComponent
from vibe.core.helper.root_helper import makeROOTCompatible
# Show warnings and above (ignore info/debug)
b2.logging.log_level = b2.LogLevel.INFO
# Only show each maximum 3 times
b2.logging.max_repetitions = 3
__all__ = [
"Tau1x1or3",
]
[docs]
@fancy_validation_mode_header
class Tau1x1or3(ValidationModeBaseClass):
name = "Tau1x1or3"
latex_str = r"$\tau^+ \rightarrow \pi^+ \nu, \tau^- \rightarrow e^-/\mu^-/\pi^-/K^-/(\pi^+ \pi^- \pi^+) \nu$"
# Define trigger categories and their corresponding names
L1_TRIGGER_CATEGORIES: Dict[str, List[str]] = {
"ECL triggers": [
"hie",
"c4",
"eclmumu",
"ecltaub2b",
"lml0",
"lml1",
"lml2",
"lml4",
"lml6",
"lml7",
"lml8",
"lml9",
"lml10",
"lml12",
"lml13",
],
"CDC triggers": ["fff", "ffo", "ffy", "fyo", "stt", "ff30", "fy30", "fso", "sso"],
"KLM triggers": [
"klm2",
"eklm2",
"eklmhit",
"beklm",
"seklm1",
"seklm2",
"fwd_seklm",
"bwd_seklm",
"ieklm1",
"mu_pair",
"mu_eb2b",
],
"Combined triggers": ["cdcklm1", "cdcklm2", "cdcecl3", "cdcecl4", "ecleklm1"],
"Other triggers": ["bhabha", "lml16"],
}
HLT_TRIGGER_CATEGORIES: Dict[str, List[str]] = {"HLT triggers": ["hlt_filter", "hlt_all", "hlt_skim", "hlt_tautau"]}
[docs]
def create_basf2_path(self, include_HLT=True, include_L1=True):
mypath = b2.Path()
ma.labelTauPairMC(path=mypath)
# Fill all 'good' tracks
trackCuts = "dr <= 0.5 and -2.0 <= dz <= 2.0 and thetaInCDCAcceptance and useCMSFrame(p) < 5.29"
ma.fillParticleList("pi+:track", trackCuts, path=mypath)
###########################################
# Photons selections
###########################################
ma.fillParticleList("gamma:all", "", path=mypath)
gammaAndPi0Cuts = "-0.8660 < cosTheta < 0.9563"
###########################################
# pi0s selections and reconstruction
###########################################
gammaForPi0Cuts = gammaAndPi0Cuts + " and E > 0.1"
ma.cutAndCopyLists("gamma:looseForPi0", "gamma:all", gammaForPi0Cuts, path=mypath)
ma.reconstructDecay(
"pi0:fromLooseGammas -> gamma:looseForPi0 gamma:looseForPi0", "[0.115 < M < 0.155]", path=mypath
)
ma.rankByLowest(particleList="pi0:fromLooseGammas", variable="abs(dM)", path=mypath)
###########################################
# gammas list with/without pi0 daughters
###########################################
gammaCuts = gammaAndPi0Cuts + " and E > 0.2 and isDescendantOfList(pi0:fromLooseGammas,1) == 0"
ma.cutAndCopyLists("gamma:notPi0", "gamma:all", gammaCuts, path=mypath)
ma.cutAndCopyLists("gamma:pi0", "gamma:looseForPi0", "isDescendantOfList(pi0:fromLooseGammas) == 1", path=mypath)
###########################################
# Event Shape/Kinematics
###########################################
listforEvent = ["pi+:track", "pi0:fromLooseGammas", "gamma:notPi0"]
ma.buildEventShape(
listforEvent,
foxWolfram=True,
cleoCones=True,
jets=False,
harmonicMoments=False,
allMoments=False,
collisionAxis=False,
sphericity=False,
thrust=True,
path=mypath,
)
ma.buildEventKinematics(listforEvent, path=mypath)
###########################################
# Event variables and selection cuts
###########################################
eventVariables = [
"tauPlusMCMode",
"tauMinusMCMode",
"tauMinusMCProng",
"tauPlusMCProng",
"thrust",
"visibleEnergyOfEventCMS",
"missingMomentumOfEventCMS",
"missingMomentumOfEvent",
"missingMomentumOfEvent_theta",
"missingMomentumOfEventCMS_theta",
"totalPhotonsEnergyOfEvent",
"missingMass2OfEvent",
]
va.addAlias("totalChargeInEvent", "sumValueInList(pi+:track, charge)")
## First event cuts for total charge = 0
ma.applyEventCuts("totalChargeInEvent==0", path=mypath)
###########################################
# Pi0 topology and variables
###########################################
va.addAlias("Npi0", "countInList(pi0:fromLooseGammas)")
va.addAlias("pi0InParaThrust", "countInList(pi0:fromLooseGammas, cosToThrustOfEvent>0)")
va.addAlias("pi0InAntiThrust", "countInList(pi0:fromLooseGammas, cosToThrustOfEvent<0)")
va.addAlias("pi0Topology", "formula(10*pi0InParaThrust + pi0InAntiThrust)")
eventVariables += ["Npi0", "pi0InParaThrust", "pi0InAntiThrust", "pi0Topology"]
###########################################
# Photon topology and variables
###########################################
ma.copyLists("gamma:good", ["gamma:notPi0", "gamma:pi0"], path=mypath)
va.addAlias("nAllPhotons", "countInList(gamma:good)") # Number of all gammas used in the event
va.addAlias("nPhotons", "countInList(gamma:notPi0)") # Number of gammas used in the event that are not pi0s
va.addAlias("gammaInParaThrust", "countInList(gamma:good, cosToThrustOfEvent>0)")
va.addAlias("gammaInAntiThrust", "countInList(gamma:good, cosToThrustOfEvent<0)")
va.addAlias("gammaTopology", "formula(10*gammaInParaThrust + gammaInAntiThrust)")
eventVariables += ["nAllPhotons", "nPhotons", "gammaInParaThrust", "gammaInAntiThrust", "gammaTopology"]
###########################################
# Track topology and variables
###########################################
va.addAlias("nAllTracks", "countInList(pi+:track)") # Number of all tracks used in the event
ma.cutAndCopyLists("pi+:parathrust", "pi+:track", "cosToThrustOfEvent>0", path=mypath)
ma.cutAndCopyLists("pi+:antithrust", "pi+:track", "cosToThrustOfEvent<0", path=mypath)
va.addAlias("trackInParaThrust", "countInList(pi+:parathrust)")
va.addAlias("trackInAntiThrust", "countInList(pi+:antithrust)")
va.addAlias("trackTopology", "formula(10*trackInParaThrust + trackInAntiThrust)")
eventVariables += ["nAllTracks", "trackTopology"]
###########################################
# 1prong and 3prong tracks separation
###########################################
ma.cutAndCopyLists("pi+:1prongpara", "pi+:parathrust", "trackInParaThrust==1", path=mypath)
ma.cutAndCopyLists("pi+:1pronganti", "pi+:antithrust", "trackInAntiThrust==1", path=mypath)
ma.copyLists("pi+:1prong", ["pi+:1prongpara", "pi+:1pronganti"], path=mypath)
ma.cutAndCopyLists("pi+:3prongpara", "pi+:parathrust", "trackInParaThrust==3", path=mypath)
ma.cutAndCopyLists("pi+:3pronganti", "pi+:antithrust", "trackInAntiThrust==3", path=mypath)
ma.copyLists("pi+:3prong", ["pi+:3prongpara", "pi+:3pronganti"], path=mypath)
## Second event cut for track topology
ma.applyEventCuts("trackTopology==11 or trackTopology==13 or trackTopology==31", path=mypath)
# Rank the 3prong tracks by momentum ( helpful for 3prong to get the slow/fast pi+ and pi- combination )
ma.rankByLowest(particleList="pi+:3prong", variable="p", outputVariable="p_rank", path=mypath)
va.addAlias("p_rank", "extraInfo(p_rank)") # variable to store the rank of the 3prong tracks
###########################################
# Reconstruction
###########################################
reconstruction_condition_1 = "[[trackTopology==13] or [trackTopology==31]] and [Npi0==0]" # 1 vs 3 prong
reconstruction_condition_2 = "[trackTopology==11] and [Npi0==0]" # 1 vs 1 prong
reconstruction_condition_3 = "[trackTopology==11] and [Npi0>0]" # 1 vs 1 prong + pi0
ma.reconstructDecay(
"tau-:tag3prong -> pi-:3prong pi+:3prong pi-:3prong ?nu", reconstruction_condition_1, path=mypath, dmID=1
)
ma.reconstructDecay("tau-:tag1prong -> pi-:1prong ?nu", reconstruction_condition_2, path=mypath, dmID=2)
ma.reconstructDecay(
"tau-:tag1prongPi0 -> pi-:1prong pi0:fromLooseGammas ?nu", reconstruction_condition_3, path=mypath, dmID=3
)
ma.reconstructDecay("tau+:sig -> pi+:1prong ?nu", "", path=mypath)
ma.copyLists("tau-:tag", ["tau-:tag3prong", "tau-:tag1prong", "tau-:tag1prongPi0"], path=mypath)
# Select vpho candidates with signal and tag in opposite sides of the event.
va.addAlias(
"prod1", "formula(daughter(0, daughter(0, cosToThrustOfEvent))*daughter(1, daughter(0,cosToThrustOfEvent)))"
)
va.addAlias(
"prod2", "formula(daughter(0, daughter(0, cosToThrustOfEvent))*daughter(1, daughter(1,cosToThrustOfEvent)))"
)
va.addAlias(
"prod3", "formula(daughter(0, daughter(0, cosToThrustOfEvent))*daughter(1, daughter(2,cosToThrustOfEvent)))"
)
ma.reconstructDecay("vpho:1X3 -> tau+:sig tau-:tag3prong", "prod1 < 0 and prod2 < 0 and prod3 < 0", path=mypath)
ma.reconstructDecay("vpho:1X1 -> tau+:sig tau-:tag1prong", "prod1 < 0", path=mypath)
ma.reconstructDecay("vpho:1X1Pi0 -> tau+:sig tau-:tag1prongPi0", "prod1 < 0 and prod2 < 0", path=mypath)
va.addAlias("dmIDTag", "daughter(1, extraInfo(decayModeID))")
# Select vpho candidates with signal and tag in opposite sides of the event.
ma.copyLists("vpho:tau", ["vpho:1X3", "vpho:1X1", "vpho:1X1Pi0"], path=mypath)
############################################################################################
# Match MC Truth
############################################################################################
# ma.matchMCTruth('vpho:tau', path=mypath)
ma.matchMCTruth("tau+:sig", path=mypath)
ma.matchMCTruth("tau-:tag", path=mypath)
va.addAlias("isSignal_tau1", "daughter(0, isSignal)")
va.addAlias("isSignal_tau2", "daughter(1, isSignal)")
eventVariables += ["dmIDTag", "isSignal_tau1", "isSignal_tau2"]
############################################################################################
# number of photons and pi0 on signal and tag side of thrust axis
############################################################################################
# Check in which hemisphere is the tag tau located
va.addAlias("tagInPosThrust", "countInList(vpho:tau, daughter(1, daughter(0, cosToThrustOfEvent)) > 0)")
va.addAlias("tagInNegThrust", "countInList(vpho:tau, daughter(1, daughter(0, cosToThrustOfEvent)) < 0)")
eventVariables += ["tagInPosThrust", "tagInNegThrust"]
###########################################
# Variables for vpho:tau decay
###########################################
# select the tracks that enter the reconstruction of tau+ and tau- and vpho
ma.cutAndCopyList("tau+:sigFromVpho", "tau+:sig", "isDescendantOfList(vpho:tau, 1)==1", path=mypath)
ma.cutAndCopyList("tau-:tagFromVpho", "tau-:tag", "isDescendantOfList(vpho:tau, 1)==1", path=mypath)
# energy of signal tau
va.addAlias("ECMS_sig", "useCMSFrame(totalEnergyOfParticlesInList(tau+:sigFromVpho))")
# energy of tagged tau
va.addAlias("ECMS_tag", "useCMSFrame(totalEnergyOfParticlesInList(tau-:tagFromVpho))")
eventVariables += ["ECMS_sig", "ECMS_tag"]
va.addAlias("Nvpho", "countInList(vpho:tau)")
va.addAlias("missingPtCMS", "formula(missingMomentumOfEventCMS*sin(missingMomentumOfEventCMS_theta))")
va.addAlias(
"tau3prongPseudoMass_Mmin",
"formula( [ daughter(1,M) * daughter(1,M) + 2. * \
( 5.29 - useCMSFrame(daughter(1,E)) ) * \
( useCMSFrame(daughter(1,E)) - useCMSFrame(daughter(1,p)) ) \
]^0.5 )",
)
eventVariables += ["Nvpho", "missingPtCMS", "tau3prongPseudoMass_Mmin"]
###########################################
# Variables for L1 and HLT triggers
###########################################
if include_L1:
# Combine all trigger lists
trigger_bits = []
for trigger_category in self.L1_TRIGGER_CATEGORIES:
trigger_bits += self.L1_TRIGGER_CATEGORIES[trigger_category]
for trigger_name in trigger_bits:
# https://software.belle2.org/development/sphinx/analysis/doc/Variables.html#variable-L1FTDLBit:~:text=of%20trigger%20variables%3A-,L1FTDL,-(name)
va.addAlias("ftdl_" + trigger_name, "L1FTDL(" + trigger_name + ")")
eventVariables.append("ftdl_" + trigger_name)
if include_HLT:
va.addAlias("hlt_filter", "SoftwareTriggerResult(software_trigger_cut&filter&total_result)")
va.addAlias("hlt_all", "SoftwareTriggerResult(software_trigger_cut&all&total_result)")
va.addAlias("hlt_skim", "SoftwareTriggerResult(software_trigger_cut&skim&total_result)")
va.addAlias("hlt_tautau", "SoftwareTriggerResult(software_trigger_cut&skim&accept_tau_tau)")
eventVariables += ["hlt_filter", "hlt_skim", "hlt_all", "hlt_tautau"]
###########################################
# Variables for reconstructed particles
###########################################
# Rho mass (pi + pi0) variable will be NaN for non-rho decays
va.addAlias("rho_mass", "daughterInvM(0, 1)")
va.addAlias("rho_asym", "formula((daughter(0, E) - daughter(1, E))/(daughter(0, E) + daughter(1, E)))")
# pi0 mass
va.addAlias("pi0_mass", "daughter(1, daughterInvM(0, 1))")
va.addAlias("pi0_0_clusterHasNPhotons", "daughter(1, daughter(0, clusterHasNPhotons))")
va.addAlias("pi0_1_clusterHasNPhotons", "daughter(1, daughter(1, clusterHasNPhotons))")
va.addAlias("pi0_0_clusterNHits", "daughter(1, daughter(0, clusterNHits))")
va.addAlias("pi0_1_clusterNHits", "daughter(1, daughter(1, clusterNHits))")
va.addAlias("pi0_0_minC2TDist", "daughter(1, daughter(0, minC2TDist))")
va.addAlias("pi0_1_minC2TDist", "daughter(1, daughter(1, minC2TDist))")
va.addAlias("pi0_0_clusterTiming", "daughter(1, daughter(0, clusterTiming))")
va.addAlias("pi0_1_clusterTiming", "daughter(1, daughter(1, clusterTiming))")
pi0_variables = [
"rho_mass",
"rho_asym",
"pi0_mass",
"pi0_0_clusterHasNPhotons",
"pi0_1_clusterHasNPhotons",
"pi0_0_clusterNHits",
"pi0_1_clusterNHits",
"pi0_0_minC2TDist",
"pi0_1_minC2TDist",
"pi0_0_clusterTiming",
"pi0_1_clusterTiming",
]
# Tau 3prong mass variables
va.addAlias("M_12", "daughterInvM(0, 1)") # slow pi- pi+ same charge combination
va.addAlias("M_23", "daughterInvM(1, 2)") # fast pi+ pi- combination
va.addAlias("M_31", "daughterInvM(0, 2)") # same pi- pi- combination
va.addAlias("M_123", "daughterInvM(0, 1, 2)") # pi- pi+ pi- combination
prong3_mass_variables = ["M_12", "M_23", "M_31", "M_123"]
# Kinematics variables using alias creation
kinematics = vu.create_aliases(vc.kinematics, "useCMSFrame({variable})", "km")
mc_kinematics = vu.create_aliases(vc.mc_kinematics, "useCMSFrame({variable})", "mc_km")
track_hits = vu.create_aliases(vc.track_hits, "{variable}", "track_hits")
va.addAlias("EoverP", "formula( ifNANgiveX( clusterE, -1 )/p )")
va.addAlias("hasAncTau", "hasAncestor(15)")
track_variables = ["charge", "p_rank", "pValue", "mcPDG", "EoverP", "hasAncTau"]
# PID
pid = vu.create_aliases(vc.pid, "{variable}", "pid")
pid += ["muonID_noSVD", "electronID_noSVD_noTOP"]
variableList = (
vu.create_aliases_for_selected(list_of_variables=eventVariables, decay_string="^vpho")
+ vu.create_aliases_for_selected(
list_of_variables=kinematics + mc_kinematics + track_hits + track_variables + pid,
decay_string="vpho -> [tau+ -> ^pi+] [tau- -> ^pi- ^pi- ^pi+]",
)
+ vu.create_aliases_for_selected(
list_of_variables=prong3_mass_variables + pi0_variables,
decay_string="vpho -> tau+ ^tau- ",
prefix=["tau"],
)
)
self.variables_to_validation_ntuple(
decay_str="vpho:tau",
variables=variableList,
path=mypath,
)
return mypath
@property
def plotting_strategies(self):
return ["per_dataset_comparison", "overlay_datasets_per_component_comparison", "tau_trg_eff"]
@property
def analysis_validation_histograms(self) -> List[Histogram]:
return [
Histogram(
name="Thrust",
title="Thrust",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="thrust"),
label="Thrust",
unit="",
bins=60,
scope=(0.7, 1.0),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
Histogram(
name="Visible_Energy",
title=r"$E_{vis}$",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="visibleEnergyOfEventCMS"),
label=r"$E_{visible}$ in CMS",
unit="GeV",
bins=50,
scope=(0.0, 12.0),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
Histogram(
name="M_12",
title=r"$M_{12} & M_{23}$ of 3Prong",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="tau_M_12"),
label=r"$M_{\pi^{\pm} \pi^{\mp}}$",
unit=r"GeV/$c^2$",
bins=80,
scope=(0.0, 1.8),
merged_variable=makeROOTCompatible(variable="tau_M_23"),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
Histogram(
name="M_31",
title=r"$M_{31}$ of 3Prong",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="tau_M_31"),
label=r"$M_{\pi^{\pm}\pi^{\pm}}$",
unit=r"GeV/$c^2$",
bins=80,
scope=(0.0, 1.8),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
Histogram(
name="M_123",
title=r"$M_{123}$ of 3Prong",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="tau_M_123"),
label=r"$M_{2\pi^{\mp}\pi^{\pm}}$",
unit=r"GeV/$c^2$",
bins=80,
scope=(0.0, 1.8),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
Histogram(
name="Momentum_1prong",
title=r"$P_{tag-track}$",
hist_variable=HistVariable(
df_label=makeROOTCompatible(variable="tau_0_pi_km_p"),
label=r"$Momentum_{tag-track}$",
unit="GeV",
bins=80,
scope=(0.0, 8.0),
),
hist_components=[
HistComponent(
label="e3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 11) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="mu3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 13) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
HistComponent(
label="pi3pi",
additional_cut_str="(dmIDTag == 1) and (abs(tau_0_pi_mcPDG) == 211) and (isSignal_tau2 == 1) and (nAllPhotons == 0)",
),
],
),
]
[docs]
def get_number_of_signal_for_efficiency(self, df: pd.DataFrame) -> float:
return ((abs(df["tau_0_pi_mcPDG"]) == 211) & (df["nAllPhotons"] == 0)).sum()