Source code for bsmart.tools.Zprime
"""
Tool to run very basic Z prime limits, based (for now) on Z' explorer arXiv:2109.13194
Takes as input the cross-section for the Z' prime and the branching ratios, read from the slha file
"""
__meta__ = {
"name": "Zprime",
"requires": ["scipy"],
"settings": {
"PDGs": "List of Z prime PDGs (optional: default is 31)"
}
}
import os
import shutil
from bsmart import debug
from bsmart.HEPRun import HepTool,DataPoint
from bsmart import zslha
#import fnmatch
import sys
from importlib import resources
#import math
from scipy.interpolate import UnivariateSpline
#from scipy.interpolate import interp1d
from scipy import interpolate
import numpy as np
[docs]
class XSBR():
def __init__(self, xsfile):
"""
Initialize XSBR with a file path or a file-like object.
"""
self.xsdata = []
self.xsint = None
self.minmass = 0.0
self.maxmass = 0.0
self.loadxs(xsfile)
[docs]
def loadxs(self, xsfile):
# Determine if xsfile is a path or a file-like object
close_file = False
if hasattr(xsfile, 'read'):
IF = xsfile
else:
try:
IF = open(xsfile, 'r')
close_file = True
except:
raise NameError("Couldn't open XS file " + str(xsfile))
xsdata = []
ncol = 2
cols_to_take = [0, 1]
try:
for line in IF:
if line.startswith('#'):
continue
sline = line.strip()
data = sline.split()
if len(data) < ncol: # try whitespace delimiter instead
data = sline.split(',')
if len(data) < ncol:
continue
if len(xsdata) > 1 and float(data[0]) <= xsdata[-1][0]:
continue
xsdata.append([float(data[x]) for x in cols_to_take])
finally:
if close_file:
IF.close()
self.xsdata = np.array(xsdata)
# Only create spline if data was successfully loaded
if len(self.xsdata) > 0:
self.xsint = interpolate.UnivariateSpline(self.xsdata[:, 0], self.xsdata[:, 1], k=3, s=0)
self.minmass = min(self.xsdata[:, 0])
self.maxmass = max(self.xsdata[:, 0])
[docs]
def getxs(self, mass):
if mass < self.minmass:
return 1e10
if mass > self.maxmass:
return 1e10
res = self.xsint(mass)
return res
[docs]
class NewTool(HepTool):
""" Runs madgraph and extracts the cross-section plus uncertainty """
def __init__(self, name, settings,global_settings=None):
HepTool.__init__(self, name, settings,global_settings)
#if 'Data Path' not in settings or not os.path.exists(settings['Data Path']):
# raise NameError('Require "Data Path" in settings for Z prime limits')
data_root = resources.files('bsmart') / 'data' / 'ZprimeExplorer'
def load_limit(filename):
try:
# Open resource wrapper that works for both files and zip-compressed packages
with (data_root / filename).open('r') as f:
return XSBR(f)
except Exception as e:
# Fallback or detailed error message
raise NameError(f"Failed to load data file '{filename}' from {data_root}: {e}")
self.limjj = load_limit('jj.dat')
self.limbb = load_limit('bb.dat')
self.limtt = load_limit('tt.dat')
self.limee = load_limit('ee.dat')
self.limmumu = load_limit('mumu.dat')
self.limtautau = load_limit('tautau.dat')
self.limWW = load_limit('WW.dat')
self.limZH = load_limit('ZH.dat')
if 'PDGs' not in settings:
self.PDGs=[31]
else:
self.PDGs=settings['PDGs']
[docs]
def run(self, spc_file, temp_dir, log,data_point):
if data_point is None or data_point.spc is None:
spc=zslha.read(spc_file)
else:
spc=data_point.spc
bestlim=0.0
bestlabel=''
bestpdg=0
limlabels=['jj','bb','tt','ee','mumu','tautau','WW','ZH']
for pdg in self.PDGs:
try:
pointXS=float(spc.Value('XSECTION',[1.3e4,2212,2212,pdg]))
except:
log.info('Could not find 13 TeV cross-section for Z prime %d, assuming that it is out of range' %pdg)
continue
#raise NameError('Could not find 13 TeV cross-section for Z prime %d' %pdg)
try:
zpmass=float(spc.Value('MASS',[pdg]))*1e-3 ### limits in TeV
except:
#self.log.info('Could not find mass Z prime %d,' %pdg)
raise NameError('Could not find mass for Z prime %d' %pdg)
ZHBR=float(spc.safeValue('BR',[pdg,[25,23]],0.0))
WWBR=float(spc.safeValue('BR',[pdg,[24,-24]],0.0))
visbr=sum([float(spc.safeValue('BR',[pdg,[x,-x]],0.0)) for x in range(1,7)])
jjbr=sum([float(spc.safeValue('BR',[pdg,[x,-x]],0.0)) for x in range(1,5)])
lims=[]
lims.append(pointXS*jjbr/self.limjj.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[5,-5]],0.0))/self.limbb.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[6,-6]],0.0))/self.limtt.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[11,-11]],0.0))/self.limee.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[13,-13]],0.0))/self.limmumu.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[15,-15]],0.0))/self.limtautau.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[24,-24]],0.0))/self.limWW.getxs(zpmass))
lims.append(pointXS*float(spc.safeValue('BR',[pdg,[25,23]],0.0))/self.limZH.getxs(zpmass))
maxlim=max(lims)
maxlabel=limlabels[lims.index(maxlim)]
if maxlim > bestlim:
bestlim=maxlim
bestlabel=maxlabel
bestpdg=pdg
OF = open(spc_file,"a")
OF.write('BLOCK ZPRIME #\n')
OF.write(' 1 %10.6E # Best Limit ratio (>1 excluded), from channel %s\n' % (bestlim,bestlabel))
OF.write(' 2 %d # pdg for best limit \n' % bestpdg)
OF.close()
if data_point is not None:
if data_point.spc is None:
data_point.spc = spc
data_point.spc.blocks['ZPRIME']={'1': bestlim, '2': bestpdg}
data_point.spc.blockcomments['ZPRIME'] = {'1': 'Best Limit ratio (>1 excluded), from channel '+bestlabel, '2': 'pdg for best limit'}