Using CMS Open Data to Measure the Mass of Z Boson

In this problem, we will use open data from the CMS experiment of the Large Hadron Collider (LHC) to measure the mass of the Z boson. This should give you a flavor of what particle physics analyses look like and should help you learn to solve other particle physics data analyses problems on the site.

To process the data, we need to use a CMS specific Virtual Machine to run the analysis software. To get started on installing and setting up the Virtual Machine, head over to You will have to turn Secure Boot off and Virtualization on in your BIOS settings to be able to run virtual machines.

Once you've setup the VM, proceed with the Set up the CMS environment and run a demo analyzer section and create "DemoAnalyzer" as instructed in the link above.

After you've completed the above step, replace the source URL to this:


To make sure that you have done everything correct so far, answer the following question:

  1. How many events are there in the above file?

Now, let us review a little bit about the Z boson before actually attempting to measure its mass. The Z boson is the mediator particle for the weak force. It is neutral and is its own anti-particle. Along with the $W^+$, $W^-$ and $\gamma$, these form the complete set of the vector bosons for the electroweak interaction. Z (and $W^{\pm}$) bosons are very short-lived. They have a half-life of approximately $3 \times 10^{-25}\;\mathrm{s}$. This is a problem because not only that makes it hard to detect a Z boson but also makes it impossible to measure its mass directly. Think of a way we can indirectly measure its mass.

A Z boson decays into fermions and anti-fermions. One such decay is decay into leptons and anti-leptons. Since leptons provide a much cleaner channel, for this problem, we will use the decay of a Z-boson into a lepton and an anti-lepton, particularly into a muon and an anti-muon to measure its mass. This decay is represented by: $Z \rightarrow \mu^- + \mu^+$ and corresponds to the following Feynman diagram:

Realize that this is not a complete Feynman diagram in the sense that isolated Z-bosons do not exist and so, the left part of the Feynman diagram is missing. The Z-boson in question here comes from a collision between two protons, the details of which we will explore in a future problem. For now, let's just focus on the decay products.

If you'd like to review more about Z-bosons and how they mediate the weak force, read Chapter 4 of Griffiths' Introduction to Elementary Particles, 2nd Edition.

Now, our goal is to measure the invariant mass of the Z-boson. Because the invariant mass is, well, invariant, we can use kinematics of the decay products to reconstruct its mass: $$ m_o^Z = \sqrt{ A^2 + B^2 + 2 \; \Big( C \cdot D \; F \; \vec{G} \cdot \vec{H} \Big) } $$ What are A, B, C, D, F, G and H? F is a mathematical operation. The rest are among $m_{\mu^-}, m_{\mu^+}$; $E_{\mu^-}, E_{\mu^+}$ and $p_{\mu^-}, p_{\mu^+}$ which represent the mass and 4-momentum respectively of $\mu^-, \mu^+$. The system needs exact answers to recognize your response as correct. So, enter the following LaTeX: m_{\mu^-}, m_{\mu^+}, E_{\mu^-}, E_{\mu^+}, p_{\mu^-}, p_{\mu^+}

  1. What is A?
  1. What is B?
  1. What is C?
  1. What is D?
  1. What is F?
  1. What is G?
  1. What is H?

Now that we know how to express the invariant mass of the $Z$-boson in terms of 4-momenta of the decaying muons, let's proceed to extract information about those muons out of our data. We will be writing code on C++. For this problem, we will be editing pre-existing code to fit our purpose.

Begin by opening the file src/ in a text/code-editor. The methods we are primarily interested in is analyze(). This method is called for each event. An event corresponds to one particular collision, and so, we will loop our information-extraction code for each of those events.

Go to the analyze() event and remove the example code from there. It should look something like the following:

    Handle pIn;
    ESHandle pSetup;

Realize that for our measurement, we need a pair of muons. These are called dimuons. For simplicity, we will ignore any event that does not have exactly two muons in it.

Let's start by declaring a bunch of variables that we will be using. You are free to choose your own variables of course, but they should, more or less, correspond to the list: charge, pt, eta, phi, energy, px, py, pz. Because we will be looking at two muons, there also needs to be another set of these variables- charge_2, pt_2, eta_2, phi_2,.... Think carefully about what the data types of these variables need to be.

Next, let us check whether or not the current event (referenced by the iEvent object) contains exactly two muons. But first, we need to create a "muonCollection". To do so, start by declaring:

edm::Handle muonCollection;
iEvent.getByLabel(muonInputTag_, muonCollection);

What these lines do is, they look at the current event object and look for things that look like muons and add them to the collection muonCollection. However, to do that, as you can see above, we need to supply the correct inputTag, which is simply a way of communicating to the CMSSW library that we want muons. These input tags are typically supplied as a part of a configuration that runs this C++ file so we will come back to it later. For now, just remember that we need to fill it in.

Now, a good sanity check is to make sure that we get a valid collection of muons. To do that, we simply have to do:

if ( ! muonCollection.isValid() ) { 
std::cerr << "Not a valid collection!" << std::endl; return false; }

If you've not written C++ code before, the std::cerr object simply adds whatever follows it to the error log, which the user might have chosen either to simply print on the screen or write to a log file.

Now, let's check whether or not there are just two muons:

if (collection->size() != 2 ) {
    return false;

Now, we simply loop through our collection and study various attributes for our muons.

for (reco::MuonCollection::const_iterator it = collection->begin(); end = collection->end(); it != end; it++) {
    // Magic happens here.

First, let us make sure that this is not a stand-alone muon and actually is either a tracker muon or a global muon. After that, we proceed differently based on whether it is a global muon or a tracker muon.

// Check that this is not a stand-alone muon.
if ( ! ( (*it).combinedMuon().isNonnull() || (*it).track().isNonnull() ) ) {
    return false;

// Next, get the pT, eta, and phi of the muon.

// For track muons,
if ( (*it).track().isNonnull() ) {
    pt = (*it).track()->pt();
    eta = (*it).track()->eta();
    phi = (*it).track()->phi();

// Code for finding pT, eta, and phi of a global muon right after this.

Fill in the code above to find the momentum 3-vector for global muons.

We also want the charge. This is a little tricky because ABC. To get the charge, we simply call the charge() method on the iterator.

Here onward, what we do depends on whether this particular muon is the first or second of the pair. We don't want to write the information about the first muon before taking a look at the second muon. This is because we are going to discard the event if both of our muons are from the tracker. So, we need to hold on to the first muon, look at the second muon, and then if only one (or none) of them is a tracker muon, then write information about both of them out. That means, we need to devise a way to find that out. One way to do that is to use a counter (with something like an integer) and check whether the counter is at 1 or 2 at any given loop run. But I am going to use the charge of the muon for that. For the first muon, Each event has just two muons (think why I can say that with certainity). So, if I initialize a variable that stores the charge of the first muon as 0, if it's still 0 inside the loop, that means the current muon is the first one. Do not worry too much about this particular method. There is nothing wrong with using counters. I am just trying to reuse variables here.

I am not going to fill in the code to get the energies, and momenta in each of the three directions because they are incredibly easy. So do them yourself.

if (charge_1 == 0) {
    // This means, this is the first muon.
    charge_1 = charge;
    // Get the muon's four momentum and store them in the variables energy_1, px_1, py_1, pz_1. 

    // You could also store the muon's pT, eta and phi if you want to but that's redundant if you store px, py, pz. Choose whichever you like.

    // Code to store what kind of muon we have here.

Now, remember that we want to make sure that both muons are not tracker muons. So we also need to somehow store whether or not the first muon is global or tracker. Realize that it will be one or the other. Fill in the code to do that yourself.

We store both pt stuff and px stuff because the latter represents invariant 4-momentum vector.

Now, let's proceed with the second muon. For that we just add an else statement to the if (charge1 == 0) conditional we had above.

else {
    netCharge = charge1 * charge;
    // Get px, py, pz, and energy again.

    // Now, we add the two energies to get the total 4-momentum vector.
    float totalE = energy1 + energy2;
    float totalPx = px1 + px2;
    float totalPy = py1 + py2;
    float totalPz = pz1 + pz2;

    // Calculate the total invariant mass.

    // Check what kind of muon this is and if both of them were tracker, then just reject the event. Remember that we do that with simply a return false statement.


Finally, we write those information down. In C++, if we want to write out to a file, we just do the following:

fileOutput_ << << "\t" << << "\t" << muon1Type << endl;

Now, run the code and fill in the following for the first event that you get in your result.

For the first muon,

  1. What is the muon type?
  1. What is the muon's energy?
  1. What is its 4-momentum?
  1. What is its charge?

For the second muon,

  1. What is the muon type?
  1. What is the muon's energy?
  1. What is its 4-momentum?
  1. What is its charge?

And then, finally, write down the invariant mass you calculate to 4 decimal places:

  1. What's is the muon's invariant mass?

This is the invariant mass of the $Z$-boson you have calculated.