Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LHCb B -> hhh Analysis

Modern Python workflow for a compact experimental particle-physics analysis using CERN Open Data from LHCb. The project reconstructs three-body B-meson candidates, studies the invariant-mass spectrum, builds a sideband-labelled signal/background classifier, and extends the workflow toward CP-asymmetry and Dalitz-plot studies.

Physics Context

Heavy-flavour decays are a central probe of flavour physics, CP violation, and possible indirect effects of physics beyond the Standard Model. This project focuses on reconstructed B-meson candidates decaying into three charged hadrons, using a simple kaon-mass hypothesis for the daughters. The analysis follows the structure of a small HEP workflow: ROOT data loading, candidate selection, invariant-mass reconstruction, signal modelling, charge-separated comparisons, phase-space visualisation, and machine-learning diagnostics.

Dataset

The input samples are LHCb Open Data ROOT ntuples stored in data/raw/:

  • B2HHH_MagnetDown.root
  • B2HHH_MagnetUp.root
  • PhaseSpaceSimulation.root

The main notebooks use the DecayTree from B2HHH_MagnetDown.root. The additional samples are kept for future extensions such as magnet-polarity comparisons, acceptance studies, or simulation-based checks.

Objectives

  • Explore the ROOT tree and document relevant reconstructed branches.
  • Reconstruct the invariant mass of B -> hhh candidates.
  • Apply transparent physics-motivated quality and PID selections.
  • Fit the selected B-candidate mass spectrum with a Gaussian signal plus linear background model.
  • Define a signal window and sideband regions for labelled ML studies.
  • Train and evaluate a Random Forest signal/background classifier.
  • Compare B+ and B- candidates through a raw integrated CP-asymmetry estimator.
  • Build pairwise invariant-mass variables for Dalitz-plot studies.
  • Produce publication-style figures saved in figures/.

Methods

Invariant-Mass Reconstruction

The B-candidate mass is computed from the relativistic four-vector sum of the three daughter tracks:

m_B^2 = E_total^2 - |p_total|^2

The helper function compute_b_mass(df) in src/reconstruction/invariant_mass.py performs this calculation using the daughter momentum branches H*_PX, H*_PY, and H*_PZ.

Event Selection

The baseline selection keeps candidates with:

  • good B-vertex quality,
  • sizable flight distance,
  • daughter tracks compatible with the kaon PID hypothesis.

These cuts are intentionally simple and interpretable, making the workflow suitable for a portfolio or thesis-application project.

Signal/Background Classification

The notebook notebooks/03_signal_background.ipynb builds proxy labels from mass regions:

  • signal-like candidates: central B-mass window,
  • background-like candidates: lower and upper mass sidebands.

The classifier uses reconstruction-quality and PID variables, excluding the mass itself to avoid learning the label definition directly.

CP Asymmetry

The notebook notebooks/04_cp_asymmetry.ipynb separates candidates by total reconstructed charge:

  • B+ candidates have total daughter charge +1,
  • B- candidates have total daughter charge -1.

It overlays the B+ and B- invariant-mass spectra and computes a simple raw integrated asymmetry in the signal window:

A_CP = (N(B-) - N(B+)) / (N(B-) + N(B+))

This is a first diagnostic quantity. A realistic measurement would fit signal yields, subtract backgrounds, combine magnet polarities, and correct detector and production asymmetries.

Dalitz-Plot Analysis

The notebook notebooks/05_dalitz_analysis.ipynb reconstructs the pairwise two-body invariant-mass-squared variables:

  • m_K1K2_sq
  • m_K2K3_sq
  • m_K1K3_sq

These variables are used to produce Dalitz-style two-dimensional density maps. The reusable kinematic functions live in src/physics/dalitz.py.

Example Figures

Mass reconstruction after selection:

B mass after cuts

Signal-plus-background mass fit:

B mass fit

Signal/background ROC curve:

ROC curve

Random Forest feature importance:

Feature importance

Charge-separated B-candidate mass spectra:

CP mass overlay

Dalitz density map:

Dalitz plot

Project Structure

lhcb-b2hhh-analysis/
├── data/
│   ├── raw/                  # ROOT inputs, ignored by Git
│   └── processed/            # derived tables, ignored by Git
├── figures/                  # saved analysis figures
├── notebooks/
│   ├── 01_data_exploration.ipynb
│   ├── 02_mass_reconstruction.ipynb
│   ├── 03_signal_background.ipynb
│   ├── 04_cp_asymmetry.ipynb
│   ├── 05_dalitz_analysis.ipynb
│   └── 05_machine_learning.ipynb
├── reports/
├── src/
│   ├── physics/
│   │   ├── dalitz.py
│   │   └── selection.py
│   ├── reconstruction/
│   │   └── invariant_mass.py
│   ├── plotting/
│   │   └── mass_plots.py
│   └── ml/
│       └── signal_background.py
├── environment.yml
├── requirements.txt
└── README.md

Technologies

  • ROOT files for HEP ntuple storage
  • uproot for reading ROOT data in Python
  • pandas for tabular event processing
  • numpy and scipy for numerical reconstruction and fitting
  • matplotlib for publication-style plots
  • scikit-learn for Random Forest classification

Installation

Using conda:

conda env create -f environment.yml
conda activate lhcb-b2hhh
python -m ipykernel install --user --name lhcb-b2hhh --display-name "Python (lhcb-b2hhh)"

Using pip:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m ipykernel install --user --name lhcb-b2hhh --display-name "Python (lhcb-b2hhh)"

Running the Analysis

Start JupyterLab from the project root:

jupyter lab

Recommended notebook order:

  1. notebooks/01_data_exploration.ipynb
  2. notebooks/02_mass_reconstruction.ipynb
  3. notebooks/03_signal_background.ipynb
  4. notebooks/04_cp_asymmetry.ipynb
  5. notebooks/05_dalitz_analysis.ipynb

Figures are saved automatically in figures/.

First Nominal Mass Fit

The reusable fitting infrastructure lives in src/fitting/. To run the first nominal binned invariant-mass fit of the selected B-candidate spectrum:

python scripts/run_mass_fit.py

The script reuses the shared loading, reconstruction, and baseline-selection helpers from src.physics.selection, then writes:

  • results/fits/mass_fit_results.json
  • results/fits/mass_fit_plot.png

The default nominal signal model is now a common-mean double Gaussian with a finite-range exponential background. This is still a first nominal mass fit, not yet a full systematic study with alternative shapes, fit ranges, or charge-separated yield corrections.

Charge-Separated Raw Asymmetry

To fit the B+ and B- candidate mass spectra separately and compute the raw fitted-yield asymmetry:

python scripts/run_charge_separated_fits.py

Outputs are saved in results/fits/charge_separated/, including the two charge-specific fit JSON files, fit plots, and raw_asymmetry.json.

This is a raw asymmetry only. It is not corrected for detector-induced charge asymmetry, B production asymmetry, trigger or reconstruction effects, or selection-induced charge biases.

Toy-MC Raw-Asymmetry Validation

To validate the charge-separated fit and raw-asymmetry extraction on pseudo-data generated from the nominal fitted model:

python studies/toy_mc/run_raw_asymmetry_toys.py

The study writes per-toy results, a JSON summary, and diagnostic plots to results/toy_mc/raw_asymmetry/. This checks whether the statistical fitting machinery recovers injected yields and raw asymmetry on nominal toys; it is not a detector-level systematic study.

A 10-toy run is only a technical smoke test. Use at least 100 toys for a meaningful first validation, and prefer 500 or more toys when quoting stable pull-width estimates.

Fit-Model Systematics

To compare charge-separated raw-asymmetry fits across implemented fit-model variations:

python studies/systematics/run_fit_model_variations.py

A model variation is only used as a systematic uncertainty if it provides an acceptable description of both charge-separated mass spectra. Stress-test models are kept in the results table, but rejected models are not included in the recommended model systematic. The first accepted background-shape alternative compares the nominal double-Gaussian plus exponential model with a double-Gaussian plus finite-range linear background.

Local Dalitz Raw-Asymmetry Diagnostic

To compute a bin-by-bin raw asymmetry over the selected-candidate Dalitz plane:

python studies/dalitz/run_local_raw_asymmetry.py

Outputs are saved in results/dalitz/local_asymmetry/, including the per-bin CSV table, a JSON summary, and maps of local raw asymmetry, local significance, and selected-candidate counts. This is a raw local asymmetry diagnostic only. It is not corrected for detector efficiency, background contamination, production asymmetry, migration across Dalitz bins, or phase-space acceptance.

Notes for GitHub

ROOT files and processed data products are ignored by Git to keep the repository lightweight. The README references figures in figures/, so keep representative output plots tracked when preparing the public repository.

About

CERN LHCb Open Data analysis of three-body B-meson decays with invariant-mass reconstruction, CP-asymmetry diagnostics and Dalitz-plot studies.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages