Measuring the J/$\psi$ Meson Mass

This notebook will walk you through a simplified measurement of the mass of the J/$\psi$ meson.

We will use data taken by the CMS experiment hosted on CERN's opendata portal.

After importing some modules and defining helper functions we download the data and then start exploring it.


In [1]:
# Required imports and setup
import os
import numpy as np

from rootpy.plotting import Hist, Canvas, set_style, get_style
from rootpy import asrootpy, log
from root_numpy import root2array, fill_hist

from ROOT import (RooFit, RooRealVar, RooDataHist, RooArgList,
                  RooVoigtian, RooAddPdf, RooPolynomial, TLatex)

style = get_style('ATLAS')
style.SetCanvasDefW(900)
style.SetCanvasDefH(600)
set_style('ATLAS')
log['/ROOT.TClassTable.Add'].setLevel(log.ERROR)


INFO:rootpy.plotting.style:using ROOT style 'ATLAS'
Welcome to ROOTaaS 6.05/02

In [2]:
def get_masses(filename, treename='events'):
    # Get array of squared mass values selecting opposite charge events
    expr = '(e1 + e2)**2 - ((px1 + px2)^2 + (py1 + py2)^2 + (pz1 + pz2)^2)'
    m2 = root2array(filename, treename=treename,
                    branches=expr, selection='q1 * q2 == -1')
    # Remove bad events
    m2 = m2[m2 > 0]
    # Return the masses
    return np.sqrt(m2)

In [3]:
def plot_hist(hist, logy=True, logx=True):
    # A function to plot the mass values
    canvas = Canvas()
    if logy:
        canvas.SetLogy()
    if logx:
        canvas.SetLogx()
    hist.xaxis.title = 'm_{#mu#mu} [GeV]'
    hist.yaxis.title = 'Events'
    hist.Draw('hist')
    return canvas

Fetch the data

The data has been preprocessed and converted to a ROOT nTuple.

These events were extracted from the CMS Mu Primary Dataset. Thanks to http://openstack.cern.ch for providing a CernVM running SL5 where we could set up CMSSW 4.2.8 and to https://github.com/tpmccauley/dimuon-filter for generating the CSV from CMS AODs.


In [4]:
if not os.path.exists('events.root'):
    !curl https://cernbox.cern.ch/index.php/s/ZE45HERahm7DeZ6/download -o events.root

Plot the dimuon mass spectrum

Calculate the dimuon invariant mass for each event and plot the full spectrum. This shows many of the well known dimuon resonances. Several of the strange features on the plot are explained by varying trigger threshold for each of the regions.


In [5]:
masses = get_masses('events.root')

mass_hist = Hist(1500, 0.5, 120)
fill_hist(mass_hist, masses)
plot_hist(mass_hist)


Out[5]:

Zoom in on the J/$\psi$ resonance

The whole spectrum does not show anything weird, time to zoom in on the region of the J/$\psi$ meson.


In [6]:
mass_hist_zoomed = Hist(100, 2.8, 3.4, drawstyle='EP')
fill_hist(mass_hist_zoomed, masses)
plot_hist(mass_hist_zoomed, logx=False, logy=False)


Out[6]:

Measure the mass

The final step of the analysis is to build a fit model using RooFit and use it to extract the mass of the J/$\psi$ meson.


In [7]:
def fit(hist):
    hmin = hist.GetXaxis().GetXmin()
    hmax = hist.GetXaxis().GetXmax()

    # Declare observable x
    x = RooRealVar("x","x",hmin,hmax)
    dh = RooDataHist("dh","dh",RooArgList(x),RooFit.Import(hist))

    frame = x.frame(RooFit.Title("Z mass"))
    # this will show histogram data points on canvas 
    dh.plotOn(frame,RooFit.MarkerColor(2),RooFit.MarkerSize(0.9),RooFit.MarkerStyle(21))

    # Signal PDF
    mean = RooRealVar("mean","mean",3.1, 0, 5)
    width = RooRealVar("width","width",1, 0, 100)
    sigma = RooRealVar("sigma","sigma",5, 0, 100)
    voigt = RooVoigtian("voigt","voigt",x,mean,width,sigma)
    
    # Background PDF
    pol0 = RooPolynomial("pol0","pol0",x,RooArgList())
    
    # Combined model
    jpsi_yield = RooRealVar("jpsi_yield","yield of j/psi",0.9,0,1)
    model = RooAddPdf("model","pol0+gauss",RooArgList(voigt,pol0),RooArgList(jpsi_yield))

    result = asrootpy(model.fitTo(dh, RooFit.Save(True)))

    mass = result.final_params['mean'].value
    bin1 = hist.FindFirstBinAbove(hist.GetMaximum()/2)
    bin2 = hist.FindLastBinAbove(hist.GetMaximum()/2)
    hwhm = (hist.GetBinCenter(bin2) - hist.GetBinCenter(bin1)) / 2
    # this will show fit overlay on canvas
    model.plotOn(frame,RooFit.LineColor(4))

    # Draw all frames on a canvas
    canvas = Canvas()
    frame.GetXaxis().SetTitle("m_{#mu#mu} [GeV]")
    frame.GetXaxis().SetTitleOffset(1.2)
    frame.Draw()
    
    # Draw the mass and error label
    label = TLatex(0.6, 0.8, "m_{{J/#psi}} = {0:.2f} #pm {1:.2f} GeV".format(mass, hwhm))
    label.SetNDC()
    label.Draw()
    
    return canvas

import ROOT
_=asrootpy(ROOT.RooFitResult)


INFO:rootpy.stl:generating dictionary for deque<RooAbsArg*> ...
INFO:rootpy.stl:generating dictionary for stack<RooAbsArg*, deque<RooAbsArg*> > ...

In [8]:
fit(mass_hist_zoomed)


Out[8]:
[#1] INFO:DataHandling -- RooDataHist::adjustBinning(dh): fit range of variable x expanded to nearest bin boundaries: [2.8,3.4] --> [2.8,3.4]
[#1] INFO:NumericIntegration -- RooRealIntegral::init(voigt_Int[x]) using numeric integrator RooIntegrator1D to calculate Int(x)
[#1] INFO:Minization -- RooMinuit::optimizeConst: activating const optimization
[#1] INFO:NumericIntegration -- RooRealIntegral::init(voigt_Int[x]) using numeric integrator RooIntegrator1D to calculate Int(x)
[#1] INFO:Minization --  The following expressions have been identified as constant and will be precalculated and cached: (pol0)
[#1] INFO:Minization --  The following expressions will be evaluated in cache-and-track mode: (voigt)
 **********
 **   13 **MIGRAD        2000           1
 **********
 FIRST CALL TO USER FUNCTION AT NEW START POINT, WITH IFLAG=4.
 START MIGRAD MINIMIZATION.  STRATEGY  1.  CONVERGENCE WHEN EDM .LT. 1.00e-03
 FCN=-236203 FROM MIGRAD    STATUS=INITIATE       79 CALLS          80 TOTAL
                     EDM= unknown      STRATEGY= 1      NO ERROR MATRIX       
  EXT PARAMETER               CURRENT GUESS       STEP         FIRST   
  NO.   NAME      VALUE            ERROR          SIZE      DERIVATIVE 
   1  jpsi_yield   9.00000e-01   5.00000e-02   0.00000e+00  -5.87205e+02
   2  mean         8.59915e-01   5.00000e-01   8.52460e-01   1.10649e+02
   3  sigma        2.05619e+00   2.50000e+00   0.00000e+00   1.10265e+04
   4  width        1.00000e+00   5.00000e-01   0.00000e+00   2.71010e+03
                               ERR DEF= 0.5
 MIGRAD MINIMIZATION HAS CONVERGED.
 FCN=-499021 FROM MIGRAD    STATUS=CONVERGED     654 CALLS         655 TOTAL
                     EDM=4.52326e-07    STRATEGY= 1  ERROR MATRIX UNCERTAINTY   2.1 per cent
  EXT PARAMETER                                   STEP         FIRST   
  NO.   NAME      VALUE            ERROR          SIZE      DERIVATIVE 
   1  jpsi_yield   7.30934e-01   1.61929e-03  -1.58744e-06   6.55353e-02
   2  mean         3.09195e+00   7.46992e-05  -2.60707e-07  -4.16278e-01
   3  sigma        2.22841e-02   2.32619e-04  -1.48699e-06  -9.50308e+00
   4  width        3.28755e-02   4.94770e-04   1.89099e-06  -3.20096e+00
                               ERR DEF= 0.5
 EXTERNAL ERROR MATRIX.    NDIM=  25    NPAR=  4    ERR DEF=0.5
  2.622e-06  4.547e-09 -2.466e-07  6.185e-07 
  4.547e-09  5.580e-09  1.701e-10  3.120e-10 
 -2.466e-07  1.701e-10  5.411e-08 -1.058e-07 
  6.185e-07  3.120e-10 -1.058e-07  2.448e-07 
 PARAMETER  CORRELATION COEFFICIENTS  
       NO.  GLOBAL      1      2      3      4
        1  0.78488   1.000  0.038 -0.655  0.772
        2  0.06053   0.038  1.000  0.010  0.008
        3  0.92333  -0.655  0.010  1.000 -0.919
        4  0.94637   0.772  0.008 -0.919  1.000
 **********
 **   18 **HESSE        2000
 **********
 COVARIANCE MATRIX CALCULATED SUCCESSFULLY
 FCN=-499021 FROM HESSE     STATUS=OK             29 CALLS         684 TOTAL
                     EDM=2.56515e-07    STRATEGY= 1      ERROR MATRIX ACCURATE 
  EXT PARAMETER                                INTERNAL      INTERNAL  
  NO.   NAME      VALUE            ERROR       STEP SIZE       VALUE   
   1  jpsi_yield   7.30934e-01   1.67135e-03   1.09005e-03   2.66149e+00
   2  mean         3.09195e+00   7.41372e-05   1.48553e-05   2.39051e-01
   3  sigma        2.22841e-02   2.36863e-04   2.89734e-05  -1.60065e+00
   4  width        3.28755e-02   5.18970e-04   4.30546e-05  -1.60706e+00
                               ERR DEF= 0.5
 EXTERNAL ERROR MATRIX.    NDIM=  25    NPAR=  4    ERR DEF=0.5
  2.793e-06  1.782e-09 -2.637e-07  6.848e-07 
  1.782e-09  5.496e-09  5.696e-10 -3.126e-10 
 -2.637e-07  5.696e-10  5.610e-08 -1.133e-07 
  6.848e-07 -3.126e-10 -1.133e-07  2.693e-07 
 PARAMETER  CORRELATION COEFFICIENTS  
       NO.  GLOBAL      1      2      3      4
        1  0.80526   1.000  0.014 -0.666  0.789
        2  0.06724   0.014  1.000  0.032 -0.008
        3  0.92733  -0.666  0.032  1.000 -0.922
        4  0.95128   0.789 -0.008 -0.922  1.000
[#1] INFO:Minization -- RooMinuit::optimizeConst: deactivating const optimization
[#1] INFO:NumericIntegration -- RooRealIntegral::init(voigt_Int[x]) using numeric integrator RooIntegrator1D to calculate Int(x)

In [ ]: