From 629d260daff01d1de26a3975a6dab688258ab64f Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Sun, 3 Aug 2025 20:42:51 -0400 Subject: [PATCH 01/10] Update setup instructions and add development files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix broken Google Colab badge URL in setup.md - Add GitHub Codespaces as modern cloud alternative - Modernize local setup with conda and pip/venv options - Emphasize JupyterLab as recommended interface - Add requirements.txt with HEP-specific dependencies - Create CLAUDE.md for future Claude Code instances 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 22 ++++++++++++++++ setup.md | 45 ++++++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 CLAUDE.md create mode 100644 requirements.txt diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6cb33e1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a Jekyll-based training module for the HEP Software Foundation (HSF) teaching "Matplotlib for HEP". It introduces matplotlib plotting library and creates plots commonly used in High Energy Physics, including specialized HEP styling via `mplhep`. + +## Common Development Commands + +### Local Development +- **Serve locally**: `make serve` - Builds and serves the Jekyll site locally +- **Build site**: `make site` - Builds the site without serving +- **Clean**: `make clean` - Removes generated files and caches + +### Docker Alternative +- **Docker serve**: `make docker-serve` - Uses Docker to serve the site (requires Docker) + +### Repository Maintenance +- **Repository check**: `make repo-check` - Validates repository settings +- **Lesson validation**: `make lesson-check` - Validates lesson Markdown files +- **Complete validation**: `make lesson-check-all` - Full validation including line lengths and whitespace +- **Unit tests**: `make unittest` - Runs tests on checking tools + +### Pre-commit Hooks +This repository uses pre-commit hooks for code quality: +```bash +pip3 install pre-commit +pre-commit install +``` + +## Site Architecture + +### Jekyll Structure +- **_config.yml**: Main Jekyll configuration for HSF training theme +- **_episodes/**: Lesson content in Markdown format (7 episodes total) +- **_extras/**: Additional pages (about, discussion, figures, guide) +- **_includes/**: Reusable template components +- **fig/**: Image assets for lessons +- **Gemfile**: Ruby dependencies including hsf-training-theme + +### Content Organization +- Episodes are numbered sequentially (01-07) covering: + - Introduction to Matplotlib + - Coffee breaks (interactive elements) + - Physics background and applications + - Higgs search analysis + - mplhep library usage + - Dimuon spectrum analysis +- Uses Carpentries lesson template structure +- Physics-focused content with LaTeX math support + +### Build System +- Uses Jekyll with HSF training theme +- Ruby-based build system via Bundler +- Makefile provides convenience commands +- GitHub Pages deployment via gh-pages branch +- Pre-commit hooks enforce markdown and code formatting + +## Development Notes + +- Main development branch: `gh-pages` +- Uses HSF training theme from remote repository +- Includes physics equation rendering via MathJax +- Associated notebooks repository for interactive content +- Binder integration for live coding sessions \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d65432d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +# Core scientific computing packages +numpy>=1.21.0 +pandas>=1.5.0 +matplotlib>=3.6.0 + +# HEP-specific packages +mplhep>=0.3.0 +uproot>=4.0.0 +hist>=2.6.0 + +# Data storage and I/O +h5py>=3.7.0 + +# Jupyter ecosystem (for local development) +jupyterlab>=4.0.0 +notebook>=6.5.0 +ipywidgets>=8.0.0 + +# Optional: Additional useful packages for HEP analysis +# awkward>=2.0.0 # Uncomment if working with awkward arrays +# scikit-learn>=1.1.0 # Uncomment if doing ML analysis +# scipy>=1.9.0 # Uncomment if doing statistical analysis \ No newline at end of file diff --git a/setup.md b/setup.md index d129b30..fcac961 100644 --- a/setup.md +++ b/setup.md @@ -30,7 +30,7 @@ Binder is good for live coding sessions and thus has a 10-20 min inactivity time ## Plan B: Google Colab If for whatever reason Binder is not working. All notebooks should have a -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)]() +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/hsf-training/hsf_matplotlib_notebooks/blob/main/) Just click and run the cells that say **If on Colab** This only requires the user to have a Google account but it is much more stable and does not have the inactivity limit like Binder. @@ -41,33 +41,54 @@ Some details on Google Colab: - However, when running this cell for the first time, Colab will not be able to make use of some of the libraries installed unless we click on `Runtime` and then click on `Restart Runtime`. Rerun the first cell and we are good to go! - The caveat is that this procedure would need to be executed for each notebook in the lesson. -## Plan C: SWAN +## Plan C: GitHub Codespaces + +GitHub Codespaces provides a cloud-based development environment with pre-configured dependencies. This is a modern alternative that works well for educational content. + +1. Go to the [notebooks repository](https://github.com/hsf-training/hsf_matplotlib_notebooks) +2. Click the green "Code" button and select "Codespaces" +3. Create a new codespace or use an existing one +4. The environment will be automatically configured with Python and Jupyter + +## Plan D: SWAN SWAN is a CERN based notebook hosting service. It is stable like Colab and setup is easy like Binder. -Simply click on the Badge below and log in to SWAN with your CERN account. +Simply click on the Badge below and log in to SWAN with your CERN account (CERN account required). [![SWAN](https://swan.web.cern.ch/sites/swan.web.cern.ch/files/pictures/open_in_swan.svg)](https://cern.ch/swanserver/cgi-bin/go?projurl=https://github.com/hsf-training/hsf_matplotlib_notebooks.git) ## If you want to run locally -We highly recommend you create a virtual environment first. Open your terminal and do - +We highly recommend you create a virtual environment first. Choose one of the following approaches: +### Option 1: Using conda (recommended for scientific Python) ```bash -conda create --name mpl +conda create --name mpl python=3.11 conda activate mpl git clone https://github.com/hsf-training/hsf_matplotlib_notebooks.git cd hsf_matplotlib_notebooks -# Now install the requirements +# Install requirements conda install -c conda-forge --file requirements.txt -conda install notebook jupyterlab +conda install jupyterlab ``` -Now you can launch your jupyter notebook or jupyter-lab session + +### Option 2: Using pip and venv +```bash +python -m venv mpl-env +source mpl-env/bin/activate # On Windows: mpl-env\Scripts\activate +git clone https://github.com/hsf-training/hsf_matplotlib_notebooks.git +cd hsf_matplotlib_notebooks +# Install requirements +pip install -r requirements.txt +pip install jupyterlab +``` + +Now you can launch JupyterLab (recommended) or the classic notebook: ```bash -jupyter notebook --no-browser -# Or jupyter lab --no-browser +# Or for classic notebook interface +jupyter notebook --no-browser ``` -Then copy one of the urls printed on the terminal window and paste in your browser. +Then copy one of the URLs printed on the terminal window and paste in your browser. Your feedback is very welcome! Most helpful for us is if you "[Improve this page on GitHub](https://github.com/hsf-training/hsf-training-matplotlib/edit/gh-pages/setup.md)". If you prefer anonymous feedback, please [fill this form](https://forms.gle/9ge6rkYk6UMUt2WT8). From 5858efd4b6abd8cf2c10ed0c17c9bc8097df5020 Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Sun, 3 Aug 2025 20:51:05 -0400 Subject: [PATCH 02/10] Modernize matplotlib introduction episode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix outdated matplotlib documentation image URL - Add modern best practices callout highlighting matplotlib 3.x improvements - Update mplhep usage with context managers and clearer style options - Improve figure creation recommendations (aspect ratio and DPI) - Enhance histogram example with better styling and error bar practices - Add proper figure creation, labels, and grid styling to examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- _episodes/01-introduction.md | 52 +++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/_episodes/01-introduction.md b/_episodes/01-introduction.md index ccd3077..9fdf0cb 100644 --- a/_episodes/01-introduction.md +++ b/_episodes/01-introduction.md @@ -20,6 +20,15 @@ The example-based nature of [Matplotlib documentation](https://matplotlib.org/) Matplotlib is the standard when it comes to making plots in Python. It is versatile and allows for lots of functionality and different ways to produce many plots. We will be focusing on using matplotlib for High Energy Physics. +> ## Modern Best Practices +> +> While matplotlib has evolved significantly, some key modern practices include: +> - Using explicit figure and axes creation with `fig, ax = plt.subplots()` +> - Leveraging context managers for temporary styling changes +> - Using more descriptive parameter names (e.g., `alpha=0.8` instead of just transparency) +> - Taking advantage of improved default styling in matplotlib 3.x+ +{: .callout} + # A simple example As with any Python code it is always good practice to import the necessary libraries as a first step. @@ -38,8 +47,7 @@ plt.show() # Show the figure This code produces the following figure: - -![basic_plot](https://matplotlib.org/3.5.1/_images/sphx_glr_usage_001_2_0x.png) +![basic_plot](https://matplotlib.org/stable/_images/sphx_glr_usage_001.png) > ## Notice > If you look at the plot and the order of the list of numbers you can clearly see that the order of the arguments is of the form @@ -130,11 +138,11 @@ plt.show() As mentioned, by default `fig, ax = plt.subplots()` creates the canvas automatically. We can have finer control over the shape and quality of the plots by using the keyword arguments `figsize` and `dpi` as follows. ```python -fig, ax = plt.subplots(figsize=(10, 10), dpi=150) +fig, ax = plt.subplots(figsize=(10, 8), dpi=100) # More common aspect ratio ``` -This has to be set **before** any instance of `ax.plot` and it sets the width and height to 10 and 10 inches respectively. The keyword `dpi` refers to a density of *Dots Per Inch*. -There is no particular reason to choose 150 as the value for dpi but there is a visually a noticeable difference in the size and quality of the plot. +This has to be set **before** any instance of `ax.plot` and it sets the width and height to 10 and 8 inches respectively. The keyword `dpi` refers to a density of *Dots Per Inch*. +Modern displays typically use 100 DPI as a good default, though you can increase to 150 or 200 for higher quality output. ### Title @@ -231,12 +239,17 @@ We have available a useful python package called [mplhep](https://mplhep.readthe ```python import mplhep as hep -hep.style.use(hep.style.ROOT) # For now ROOT defaults to CMS -# Or choose one of the experiment styles +# Modern mplhep usage - choose one of the experiment styles hep.style.use(hep.style.ATLAS) -# or -hep.style.use("CMS") # string aliases work too -# {"ALICE" | "ATLAS" | "CMS" | "LHCb1" | "LHCb2"} +# or using string aliases (recommended) +hep.style.use("CMS") +# Available styles: {"ALICE" | "ATLAS" | "CMS" | "LHCb1" | "LHCb2" | "ROOT"} + +# You can also use context managers for temporary styling +with hep.style.use("ATLAS"): + fig, ax = plt.subplots() + ax.plot(x, y) + plt.show() ``` and with just this addition we can produce the same plot as before with this new look. @@ -267,14 +280,23 @@ We will discuss histograms more in detail later but here is an example code and # first lets get some fake data data = np.random.normal(size=10_000) +# create figure +fig, ax = plt.subplots() + # now lets make the plot -counts, bin_edges, _ = ax.hist(data, bins=50, histtype="step") +counts, bin_edges, _ = ax.hist(data, bins=50, histtype="step", alpha=0.8) + +# we need to get the centers in order get the correct location for the error bars +bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2 # More explicit bin center calculation -# we need to get the centers in order get the correct location for the errobars -bin_centers = bin_edges[:-1] + np.diff(bin_edges) / 2 +# add error bars (Poisson errors for counts) +ax.errorbar(bin_centers, counts, yerr=np.sqrt(counts), fmt="none", capsize=2) -# add error bars -ax.errorbar(bin_centers, counts, yerr=np.sqrt(counts), fmt="none") +# Add labels and improve appearance +ax.set_xlabel("Value") +ax.set_ylabel("Counts") +ax.set_title("Histogram with Error Bars") +ax.grid(True, alpha=0.3) plt.show() ``` From 34ad696c1bdc8b02c64c1f12e4b24c88cf81e891 Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Sun, 3 Aug 2025 20:58:02 -0400 Subject: [PATCH 03/10] Modernize physics and Higgs search episodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Physics episode (03): - Update MathJax CDN from deprecated cdn.mathjax.org to modern cdn.jsdelivr.net Higgs search episode (04): - Replace outdated mpl.rcParams with modern plt.rc_context() approach - Add figsize parameter directly to plt.subplots() calls - Enhance histogram plots with alpha transparency and subtle grids - Improve plot_data() function with proper documentation and error handling - Add plt.tight_layout() for better subplot spacing - Include capsize and markersize for better error bar styling - Fix minor grammar issues in keypoints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- _episodes/03-physics.md | 4 +- _episodes/04-higgs-search.md | 85 ++++++++++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 20 deletions(-) diff --git a/_episodes/03-physics.md b/_episodes/03-physics.md index b5ba127..8fd61e7 100644 --- a/_episodes/03-physics.md +++ b/_episodes/03-physics.md @@ -10,9 +10,9 @@ keypoints: - "Analysis studies Higgs boson decays" --- - + You can take a look at the mathematical structure of the Standard Model of Particle Physics in the next section, or go directly to the main Higgs production mechanisms at hadron colliders. diff --git a/_episodes/04-higgs-search.md b/_episodes/04-higgs-search.md index 881a08b..f9636e9 100644 --- a/_episodes/04-higgs-search.md +++ b/_episodes/04-higgs-search.md @@ -13,7 +13,7 @@ objectives: keypoints: - "In High-energy physics, histograms are used to analyze different data and MC distributions." - "With Matplotlib, data can be binned and histograms can be plotted in a few lines." -- "Using [Uproot](https://github.com/scikit-hep/uproot5) and Matplotlib, data in ROOT files can be display without need of a full ROOT installation." +- "Using [Uproot](https://github.com/scikit-hep/uproot5) and Matplotlib, data in ROOT files can be displayed without need of a full ROOT installation." - "Histograms can be stacked and/or overlapped to make comparison between recorded and simulated data." --- In this episode, we will go through a first HEP analysis where you will be able to apply your knowledge of matplotlib and learn something new. @@ -150,14 +150,19 @@ ax.hist(branches["data_A"]["m4l"]) ![m4lep_histogram_0]({{ page.root }}/fig/m4lep_histogram_0.png) -**Tip:** In the previous plot the numbers in the axis are very small, we can change the font size (and font family) for all the following plots, including in our code: +**Tip:** In the previous plot the numbers in the axis are very small, we can change the font size (and font family) for all the following plots. Modern best practice is to use context managers or explicit parameter setting: ```python -# Update the matplotlib configuration parameters: -mpl.rcParams.update({"font.size": 16, "font.family": "serif"}) +# Modern approach - use context manager for temporary changes +with plt.rc_context({"font.size": 16, "font.family": "serif"}): + fig, ax = plt.subplots() + # your plotting code here + +# Or update global settings (affects all subsequent plots) +plt.rcParams.update({"font.size": 16, "font.family": "serif"}) ``` -Note that this changes the global setting, but it can still be overwritten later. +The context manager approach is preferred when you want temporary styling changes, while `plt.rcParams.update()` is better for permanent changes in your session. Let's do the plot again to see the changes: @@ -412,13 +417,14 @@ bins = 24 ``` ```python -fig, (ax_1, ax_2) = plt.subplots(1, 2) -fig.set_size_inches((12, 8)) +fig, (ax_1, ax_2) = plt.subplots(1, 2, figsize=(12, 8)) # Modern figsize parameter ax_1.set_title("MC samples without weights") -ax_1.hist(stack_mc_list_m4l, range=ranges[0], label=mc_samples, stacked=True, bins=bins) +ax_1.hist(stack_mc_list_m4l, range=ranges[0], label=mc_samples, stacked=True, bins=bins, alpha=0.8) ax_1.set_ylabel("Events") ax_1.set_xlabel(f"{var_name}{units}") ax_1.legend(frameon=False) +ax_1.grid(True, alpha=0.3) # Add subtle grid + ax_2.set_title("MC samples with weights") ax_2.hist( stack_mc_list_m4l, @@ -427,11 +433,14 @@ ax_2.hist( stacked=True, weights=stack_weights_list, bins=bins, + alpha=0.8 ) ax_2.set_ylabel("Events") ax_2.set_xlabel(f"{var_name}{units}") ax_2.tick_params(which="both", direction="in", top=True, right=True, length=6, width=1) +ax_2.grid(True, alpha=0.3) # Add subtle grid ax_2.legend(frameon=False) +plt.tight_layout() # Better subplot spacing ``` ![MC_histogram_4]({{ page.root }}/fig/MC_histogram_4.png) @@ -463,25 +472,62 @@ To make more easy the data vs. MC final plot, we can define the following helper When we want to make a plot that includes uncertainties we need to use the `ax.errorbar` function. ```python -def plot_data(data_var, range_ab, bins_samples): +def plot_data(data_var, range_ab, bins_samples, ax=None): + """ + Plot data histogram with Poisson error bars. + + Parameters: + ----------- + data_var : array-like + Data to histogram + range_ab : tuple + Range for histogram (min, max) + bins_samples : int + Number of bins + ax : matplotlib.axes.Axes, optional + Axes to plot on. If None, creates new figure + + Returns: + -------- + fig : matplotlib.figure.Figure or None + Figure object if ax was None, otherwise None + """ data_hist, bins = np.histogram(data_var, range=range_ab, bins=bins_samples) - print(f"{data_hist} {bins}") + print(f"Data histogram: {data_hist}") + print(f"Bin edges: {bins}") + + # Poisson errors (sqrt(N) for each bin) data_hist_errors = np.sqrt(data_hist) - bin_center = (bins[1:] + bins[:-1]) / 2 - fig, ax = plt.subplots() + + # Calculate bin centers more explicitly + bin_centers = (bins[:-1] + bins[1:]) / 2 + + if ax is None: + fig, ax = plt.subplots() + return_fig = True + else: + fig = None + return_fig = False + ax.errorbar( - x=bin_center, y=data_hist, yerr=data_hist_errors, fmt="ko", label="Data" + x=bin_centers, + y=data_hist, + yerr=data_hist_errors, + fmt="ko", + label="Data", + capsize=3, + markersize=5 ) - return fig + + return fig if return_fig else None ``` # Data vs. MC plot Finally, we can include the MC and data in the same figure, and see if they are in agreement :). ```python -fig, ax = plt.subplots() -fig.set_size_inches((10, 8)) -plot_data(stack_data_list_m4l, ranges[0], bins) +fig, ax = plt.subplots(figsize=(10, 8)) +plot_data(stack_data_list_m4l, ranges[0], bins, ax=ax) ax.hist( stack_mc_list_m4l, range=ranges[0], @@ -489,11 +535,14 @@ ax.hist( stacked=True, weights=stack_weights_list, bins=bins, + alpha=0.8 ) ax.set_ylabel("Events") ax.set_xlabel(f"{var_name}{units}") ax.set_ylim(0, 30) -ax.legend(fontsize=18, frameon=False) +ax.grid(True, alpha=0.3) # Add subtle grid +ax.legend(fontsize=16, frameon=False) # Slightly smaller font +plt.tight_layout() # Better spacing ``` ![m4lep_histogram_5]({{ page.root }}/fig/m4lep_histogram_5.png) From 2c1736ca4a9423cd7c6df9a515c4fe14d89c627b Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Sun, 3 Aug 2025 21:22:58 -0400 Subject: [PATCH 04/10] Modernize mplhep and dimuon spectrum episodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mplhep episode (05): - Fix deprecated ax.title() method to ax.set_title() - Update fig.show() to modern plt.show() approach - Add context manager examples for temporary styling - Fix grammar in keypoints description - Enhance mplhep usage guidance with available experiment styles Dimuon spectrum episode (07): - Update MathJax CDN from deprecated to modern jsdelivr version - Fix critical missing charge variable (q) definition and data cleaning - Modernize matplotlib plotting with figsize, alpha, linewidth parameters - Add consistent grids, font sizes, and plt.tight_layout() for better appearance - Fix variable name consistency (M -> masses) in subplot examples - Enhance histogram styling with modern best practices - Fix typo in numpy explanation text 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- _episodes/05-mplhep.md | 22 +++++++----- _episodes/07-dimuonspectrum.md | 62 +++++++++++++++++++++++----------- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/_episodes/05-mplhep.md b/_episodes/05-mplhep.md index ab72da0..043fb3c 100644 --- a/_episodes/05-mplhep.md +++ b/_episodes/05-mplhep.md @@ -8,7 +8,7 @@ questions: objectives: - "Learn how to reproduce a plot with HEP experiments style using mplhep" keypoints: -- "[Mplhep](https://github.com/scikit-hep/mplhep) is a wrapper for easily apply plotting styles approved in the HEP collaborations." +- "[Mplhep](https://github.com/scikit-hep/mplhep) is a wrapper for easily applying plotting styles approved in the HEP collaborations." - "Styles for LHC experiments (CMS, ATLAS, LHCb and ALICE) are available." - "If you would like to include a style for your collaboration, ask for it [opening an issue](https://github.com/scikit-hep/mplhep/issues)!" --- @@ -34,9 +34,13 @@ import numpy as np import pandas as pd import matplotlib.pyplot as plt +# Modern mplhep style setting hep.style.use("CMS") -# or use any of the following -# {CMS | ATLAS | ALICE | LHCb1 | LHCb2} +# Available experiment styles: {CMS | ATLAS | ALICE | LHCb1 | LHCb2 | ROOT} + +# Alternative: use context manager for temporary styling +# with hep.style.use("CMS"): +# # your plotting code here ``` ## Getting the data @@ -125,7 +129,7 @@ ax.set_xlabel("4l invariant mass (GeV)", fontsize=15) ax.set_ylabel("Events / 3 GeV", fontsize=15) ax.set_xlim(rmin, rmax) ax.legend() -fig.show() +plt.show() ``` This would plot the following figure. @@ -198,7 +202,7 @@ yerrs = np.sqrt(hist) > label="Data" >) > ->ax.title( +>ax.set_title( > "$ \sqrt{s} = 7$ TeV, L = 2.3 $fb^{-1}$; $\sqrt{s} = 8$ TeV, L = 11.6 $fb^{-1}$ \n", > fontsize=15, >) @@ -209,7 +213,7 @@ yerrs = np.sqrt(hist) >ax.legend(fontsize=15) >hep.cms.label(rlabel="") > ->fig.show() +>plt.show() >``` > ![](../fig/background+data.png) {: .solution} @@ -245,7 +249,7 @@ ax.set_xlabel("4l invariant mass (GeV)", fontsize=15) ax.set_ylabel("Events / 3 GeV\n", fontsize=15) ax.set_xlim(rmin, rmax) -fig.show() +plt.show() ``` ![](../fig/hzz.png) @@ -292,7 +296,7 @@ fig.show() > label="Data" >) > ->ax.title( +>ax.set_title( > "$ \sqrt{s} = 7$ TeV, L = 2.3 $fb^{-1}$; $\sqrt{s} = 8$ TeV, L = 11.6 $fb^{-1}$ \n", > fontsize=16, >) @@ -303,7 +307,7 @@ fig.show() >ax.legend(fontsize=15) > >fig.savefig("final-plot.png", dpi=140) ->fig.show() +>plt.show() >``` {: .solution} diff --git a/_episodes/07-dimuonspectrum.md b/_episodes/07-dimuonspectrum.md index 59ba3b3..8e434be 100644 --- a/_episodes/07-dimuonspectrum.md +++ b/_episodes/07-dimuonspectrum.md @@ -17,9 +17,9 @@ keypoints: ## Looking at the dimuon spectrum over a wide energy range - +

Learning goals

@@ -111,17 +111,23 @@ import h5py import matplotlib.pyplot as plt import mplhep as hep +# Modern styling approach plt.style.use("default") ``` Decide which styling you want to use ```python -# This is the default style for matplotlib, do not change this cell if you desire this option +# Option 1: Default matplotlib style plt.style.use("default") -# This is the mplhep style, uncomment this line for this styling. -# hep.style.use("ROOT") +# Option 2: Modern mplhep style (uncomment to use) +# hep.style.use("ROOT") # or "CMS", "ATLAS", "ALICE", "LHCb1", "LHCb2" + +# Option 3: Use context manager for temporary styling +# with hep.style.use("ROOT"): +# # your plotting code here +# pass ``` Load the data @@ -138,6 +144,7 @@ e = event["muons/e"][:] px = event["muons/px"][:] py = event["muons/py"][:] pz = event["muons/pz"][:] +q = event["muons/q"][:] # Extract charge information - needed for later analysis # We will check for muons that do not pass the kinematics print(len(px)) # Number of muons @@ -157,6 +164,7 @@ We can use numpy to clean our arrays from anomalous events ```python e = np.delete(e, cut) px, py, pz = np.delete(px, cut), np.delete(py, cut), np.delete(pz, cut) +q = np.delete(q, cut) # Also clean the charge array ``` Let's calculate the mass @@ -168,15 +176,20 @@ M = (e**2 - (px**2 + py**2 + pz**2)) ** 0.5 Make a histogram of the values of the Mass ```python -fig, ax = plt.subplots() +fig, ax = plt.subplots(figsize=(10, 6)) # Better figure size ax.hist( M, bins=100, histtype="step", + alpha=0.8, + linewidth=2 ) -ax.set_xlabel(r"$\mu_{mass}$ [GeV]") -ax.set_title("Muon Mass spectrum") +ax.set_xlabel(r"$\mu_{mass}$ [GeV]", fontsize=12) +ax.set_ylabel("Number of muons", fontsize=12) +ax.set_title("Muon Mass spectrum", fontsize=14) +ax.grid(True, alpha=0.3) # Add subtle grid +plt.tight_layout() # Better spacing plt.show() ``` @@ -192,8 +205,13 @@ Using the code above, zoom in and fix the above plot to help **visually** estima > ## Solution >```python ->fig, ax = plt.subplots() ->ax.hist(M, bins=100, log=False, histtype="step", range=(0.1, 0.11)) +>fig, ax = plt.subplots(figsize=(10, 6)) +>ax.hist(M, bins=100, histtype="step", range=(0.1, 0.11), alpha=0.8, linewidth=2) +>ax.set_xlabel(r"$\mu_{mass}$ [GeV]", fontsize=12) +>ax.set_ylabel("Number of muons", fontsize=12) +>ax.set_title("Muon Mass spectrum (zoomed)", fontsize=14) +>ax.grid(True, alpha=0.3) +>plt.tight_layout() >plt.show() >``` > @@ -282,7 +300,7 @@ for i in range(0, len(q) - 1, 2): # loop every 2 muons print("Done!") ``` -Hoewver, again the *proper* way to do this is with numpy. It might be harder to read at first, but once you get used to the syntax, it is actually more transparent: +However, again the *proper* way to do this is with numpy. It might be harder to read at first, but once you get used to the syntax, it is actually more transparent: ```python # Use "reshape" to create pairs of particles @@ -327,10 +345,12 @@ Below I will give you some code to get you started. Please make your changes/add kwargs = dict( bins=100, histtype="step", + alpha=0.8, + linewidth=2 ) fig, ax = plt.subplots(2, 2, figsize=(16, 10)) -ax[0][0].hist(M, range=(0, 120), label="All charge combinations", **kwargs) +ax[0][0].hist(masses, range=(0, 120), label="All charge combinations", **kwargs) ax[0][1].hist(pp, range=(0, 120), label="$2+$", **kwargs) ax[1][0].hist(nn, range=(0, 120), label="$2-$", **kwargs) ax[1][1].hist(pm, range=(0, 120), label="Electrically neutral", **kwargs) @@ -338,9 +358,12 @@ ax[1][1].hist(pm, range=(0, 120), label="Electrically neutral", **kwargs) for irow in range(2): for icol in range(2): ax[irow][icol].set_xlabel(r"Mass (GeV/c$^2$)", fontsize=14) - ax[irow][icol].legend(fontsize=18) + ax[irow][icol].set_ylabel("Events", fontsize=12) + ax[irow][icol].legend(fontsize=16) # Slightly smaller font + ax[irow][icol].grid(True, alpha=0.3) # Add subtle grid plt.tight_layout() +plt.show() # Add explicit show ``` ## Exercise: Now calculate the mass per event and make the plot. @@ -351,14 +374,15 @@ You could use the `np.logspace()` function for the binning. It helps in returnin > ## Solution >```python >logbins = np.logspace(0, 2.5, 200) ->fig, ax = plt.subplots() ->ax.hist(pm, bins=logbins, histtype="step") ->ax.set_xlabel("mass (GeV/$c^2$)") ->ax.set_ylabel("Events") +>fig, ax = plt.subplots(figsize=(10, 6)) +>ax.hist(pm, bins=logbins, histtype="step", alpha=0.8, linewidth=2) +>ax.set_xlabel("Mass (GeV/$c^2$)", fontsize=12) +>ax.set_ylabel("Events", fontsize=12) >ax.set_xscale("log") ->ax.set_title("Mass of dimuons per event") +>ax.set_title("Mass of dimuons per event", fontsize=14) +>ax.grid(True, alpha=0.3) >ax.autoscale() -> +>plt.tight_layout() >plt.show() >``` {: .solution} From abbaed0814499ea1cb7ee7de61df978a00d84734 Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Sun, 3 Aug 2025 21:24:15 -0400 Subject: [PATCH 05/10] Update CLAUDE.md with comprehensive modernization summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Recent Updates section documenting matplotlib 3.x+ modernization - Enhance Content Organization with detailed episode descriptions - Expand Development Notes with modern MathJax and cloud platform info - Add Python Dependencies section documenting requirements.txt contents - Add Code Style Guidelines section with modern matplotlib best practices - Document context manager usage, error bar styling, and visual enhancements This update provides future Claude Code instances with complete guidance on the repository's modernized structure and coding standards. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6cb33e1..3f23715 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is a Jekyll-based training module for the HEP Software Foundation (HSF) teaching "Matplotlib for HEP". It introduces matplotlib plotting library and creates plots commonly used in High Energy Physics, including specialized HEP styling via `mplhep`. +## Recent Updates + +The training materials have been modernized to align with current matplotlib and Python best practices: +- Updated to modern matplotlib 3.x+ styling and API usage +- Enhanced with mplhep context managers and experiment-specific styles +- Fixed deprecated functions and improved plot aesthetics +- Added comprehensive requirements.txt for dependency management +- Updated MathJax to modern CDN for equation rendering + ## Common Development Commands ### Local Development @@ -41,14 +50,15 @@ pre-commit install ### Content Organization - Episodes are numbered sequentially (01-07) covering: - - Introduction to Matplotlib - - Coffee breaks (interactive elements) - - Physics background and applications - - Higgs search analysis - - mplhep library usage - - Dimuon spectrum analysis + - **01-introduction.md**: Matplotlib basics, modern best practices, figure creation + - **02-coffee-break.md**: Interactive break elements + - **03-physics.md**: Standard Model background, particle physics theory + - **04-higgs-search.md**: Real ATLAS data analysis, histogram techniques + - **05-mplhep.md**: HEP-specific styling, experiment themes (CMS, ATLAS, etc.) + - **06-coffee-break.md**: Interactive break elements + - **07-dimuonspectrum.md**: Advanced analysis with invariant mass calculations - Uses Carpentries lesson template structure -- Physics-focused content with LaTeX math support +- Physics-focused content with LaTeX math support via modern MathJax ### Build System - Uses Jekyll with HSF training theme @@ -61,6 +71,24 @@ pre-commit install - Main development branch: `gh-pages` - Uses HSF training theme from remote repository -- Includes physics equation rendering via MathJax -- Associated notebooks repository for interactive content -- Binder integration for live coding sessions \ No newline at end of file +- Includes physics equation rendering via modern MathJax 3.x +- Associated notebooks repository for interactive content: `hsf_matplotlib_notebooks` +- Multiple cloud platforms supported: Binder, Google Colab, GitHub Codespaces, CERN SWAN +- **requirements.txt** available for local Python environment setup + +## Python Dependencies + +The repository includes a comprehensive `requirements.txt` with: +- Core: matplotlib>=3.6.0, numpy, pandas +- HEP-specific: mplhep>=0.3.0, uproot>=4.0.0, hist>=2.6.0 +- Jupyter ecosystem: jupyterlab>=4.0.0, notebook, ipywidgets +- Data handling: h5py for HDF5 files + +## Code Style Guidelines + +- Use explicit `fig, ax = plt.subplots()` pattern +- Prefer `plt.show()` over `fig.show()` +- Use context managers for temporary styling: `with hep.style.use("CMS"):` +- Include `plt.tight_layout()` for better subplot spacing +- Add subtle grids with `alpha=0.3` for better readability +- Use modern error bar styling with `capsize` and `markersize` parameters \ No newline at end of file From de20e72bc2f9525157998036adcbd223e84dfcf9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 01:30:28 +0000 Subject: [PATCH 06/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- _episodes/01-introduction.md | 8 ++++--- _episodes/04-higgs-search.md | 39 ++++++++++++++++++++-------------- _episodes/05-mplhep.md | 2 +- _episodes/07-dimuonspectrum.md | 15 ++----------- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/_episodes/01-introduction.md b/_episodes/01-introduction.md index 9fdf0cb..99d33d5 100644 --- a/_episodes/01-introduction.md +++ b/_episodes/01-introduction.md @@ -21,7 +21,7 @@ Matplotlib is the standard when it comes to making plots in Python. It is versat We will be focusing on using matplotlib for High Energy Physics. > ## Modern Best Practices -> +> > While matplotlib has evolved significantly, some key modern practices include: > - Using explicit figure and axes creation with `fig, ax = plt.subplots()` > - Leveraging context managers for temporary styling changes @@ -242,7 +242,7 @@ import mplhep as hep # Modern mplhep usage - choose one of the experiment styles hep.style.use(hep.style.ATLAS) # or using string aliases (recommended) -hep.style.use("CMS") +hep.style.use("CMS") # Available styles: {"ALICE" | "ATLAS" | "CMS" | "LHCb1" | "LHCb2" | "ROOT"} # You can also use context managers for temporary styling @@ -287,7 +287,9 @@ fig, ax = plt.subplots() counts, bin_edges, _ = ax.hist(data, bins=50, histtype="step", alpha=0.8) # we need to get the centers in order get the correct location for the error bars -bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2 # More explicit bin center calculation +bin_centers = ( + bin_edges[:-1] + bin_edges[1:] +) / 2 # More explicit bin center calculation # add error bars (Poisson errors for counts) ax.errorbar(bin_centers, counts, yerr=np.sqrt(counts), fmt="none", capsize=2) diff --git a/_episodes/04-higgs-search.md b/_episodes/04-higgs-search.md index f9636e9..d42897d 100644 --- a/_episodes/04-higgs-search.md +++ b/_episodes/04-higgs-search.md @@ -419,7 +419,14 @@ bins = 24 ```python fig, (ax_1, ax_2) = plt.subplots(1, 2, figsize=(12, 8)) # Modern figsize parameter ax_1.set_title("MC samples without weights") -ax_1.hist(stack_mc_list_m4l, range=ranges[0], label=mc_samples, stacked=True, bins=bins, alpha=0.8) +ax_1.hist( + stack_mc_list_m4l, + range=ranges[0], + label=mc_samples, + stacked=True, + bins=bins, + alpha=0.8, +) ax_1.set_ylabel("Events") ax_1.set_xlabel(f"{var_name}{units}") ax_1.legend(frameon=False) @@ -433,7 +440,7 @@ ax_2.hist( stacked=True, weights=stack_weights_list, bins=bins, - alpha=0.8 + alpha=0.8, ) ax_2.set_ylabel("Events") ax_2.set_xlabel(f"{var_name}{units}") @@ -475,7 +482,7 @@ When we want to make a plot that includes uncertainties we need to use the `ax.e def plot_data(data_var, range_ab, bins_samples, ax=None): """ Plot data histogram with Poisson error bars. - + Parameters: ----------- data_var : array-like @@ -486,7 +493,7 @@ def plot_data(data_var, range_ab, bins_samples, ax=None): Number of bins ax : matplotlib.axes.Axes, optional Axes to plot on. If None, creates new figure - + Returns: -------- fig : matplotlib.figure.Figure or None @@ -495,30 +502,30 @@ def plot_data(data_var, range_ab, bins_samples, ax=None): data_hist, bins = np.histogram(data_var, range=range_ab, bins=bins_samples) print(f"Data histogram: {data_hist}") print(f"Bin edges: {bins}") - + # Poisson errors (sqrt(N) for each bin) data_hist_errors = np.sqrt(data_hist) - + # Calculate bin centers more explicitly bin_centers = (bins[:-1] + bins[1:]) / 2 - + if ax is None: fig, ax = plt.subplots() return_fig = True else: fig = None return_fig = False - + ax.errorbar( - x=bin_centers, - y=data_hist, - yerr=data_hist_errors, - fmt="ko", + x=bin_centers, + y=data_hist, + yerr=data_hist_errors, + fmt="ko", label="Data", capsize=3, - markersize=5 + markersize=5, ) - + return fig if return_fig else None ``` @@ -526,7 +533,7 @@ def plot_data(data_var, range_ab, bins_samples, ax=None): Finally, we can include the MC and data in the same figure, and see if they are in agreement :). ```python -fig, ax = plt.subplots(figsize=(10, 8)) +fig, ax = plt.subplots(figsize=(10, 8)) plot_data(stack_data_list_m4l, ranges[0], bins, ax=ax) ax.hist( stack_mc_list_m4l, @@ -535,7 +542,7 @@ ax.hist( stacked=True, weights=stack_weights_list, bins=bins, - alpha=0.8 + alpha=0.8, ) ax.set_ylabel("Events") ax.set_xlabel(f"{var_name}{units}") diff --git a/_episodes/05-mplhep.md b/_episodes/05-mplhep.md index 043fb3c..652f340 100644 --- a/_episodes/05-mplhep.md +++ b/_episodes/05-mplhep.md @@ -129,7 +129,7 @@ ax.set_xlabel("4l invariant mass (GeV)", fontsize=15) ax.set_ylabel("Events / 3 GeV", fontsize=15) ax.set_xlim(rmin, rmax) ax.legend() -plt.show() +plt.show() ``` This would plot the following figure. diff --git a/_episodes/07-dimuonspectrum.md b/_episodes/07-dimuonspectrum.md index 8e434be..956bc73 100644 --- a/_episodes/07-dimuonspectrum.md +++ b/_episodes/07-dimuonspectrum.md @@ -177,13 +177,7 @@ Make a histogram of the values of the Mass ```python fig, ax = plt.subplots(figsize=(10, 6)) # Better figure size -ax.hist( - M, - bins=100, - histtype="step", - alpha=0.8, - linewidth=2 -) +ax.hist(M, bins=100, histtype="step", alpha=0.8, linewidth=2) ax.set_xlabel(r"$\mu_{mass}$ [GeV]", fontsize=12) ax.set_ylabel("Number of muons", fontsize=12) @@ -342,12 +336,7 @@ Below I will give you some code to get you started. Please make your changes/add ```python # Arguments shared by the .hist calls: -kwargs = dict( - bins=100, - histtype="step", - alpha=0.8, - linewidth=2 -) +kwargs = dict(bins=100, histtype="step", alpha=0.8, linewidth=2) fig, ax = plt.subplots(2, 2, figsize=(16, 10)) ax[0][0].hist(masses, range=(0, 120), label="All charge combinations", **kwargs) From bc39e4b361b2e38c4e0d3411af843d8bb898d1fd Mon Sep 17 00:00:00 2001 From: "Michel H. Villanueva" Date: Mon, 4 Aug 2025 14:58:57 -0400 Subject: [PATCH 07/10] Update _episodes/07-dimuonspectrum.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- _episodes/07-dimuonspectrum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_episodes/07-dimuonspectrum.md b/_episodes/07-dimuonspectrum.md index 956bc73..1b324da 100644 --- a/_episodes/07-dimuonspectrum.md +++ b/_episodes/07-dimuonspectrum.md @@ -339,7 +339,7 @@ Below I will give you some code to get you started. Please make your changes/add kwargs = dict(bins=100, histtype="step", alpha=0.8, linewidth=2) fig, ax = plt.subplots(2, 2, figsize=(16, 10)) -ax[0][0].hist(masses, range=(0, 120), label="All charge combinations", **kwargs) +ax[0][0].hist(M, range=(0, 120), label="All charge combinations", **kwargs) ax[0][1].hist(pp, range=(0, 120), label="$2+$", **kwargs) ax[1][0].hist(nn, range=(0, 120), label="$2-$", **kwargs) ax[1][1].hist(pm, range=(0, 120), label="Electrically neutral", **kwargs) From 7fc9e9717afa91b8a9533311d5952846844dd162 Mon Sep 17 00:00:00 2001 From: "Michel H. Villanueva" Date: Mon, 4 Aug 2025 15:01:38 -0400 Subject: [PATCH 08/10] Apply suggestions from code review Apply changes from review by @ariostas Co-authored-by: Andres Rios Tascon --- _episodes/01-introduction.md | 4 ++-- _episodes/04-higgs-search.md | 2 +- _episodes/07-dimuonspectrum.md | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/_episodes/01-introduction.md b/_episodes/01-introduction.md index 99d33d5..dc2698f 100644 --- a/_episodes/01-introduction.md +++ b/_episodes/01-introduction.md @@ -47,7 +47,7 @@ plt.show() # Show the figure This code produces the following figure: -![basic_plot](https://matplotlib.org/stable/_images/sphx_glr_usage_001.png) +![basic_plot](https://matplotlib.org/3.5.1/_images/sphx_glr_usage_001_2_0x.png) > ## Notice > If you look at the plot and the order of the list of numbers you can clearly see that the order of the arguments is of the form @@ -138,7 +138,7 @@ plt.show() As mentioned, by default `fig, ax = plt.subplots()` creates the canvas automatically. We can have finer control over the shape and quality of the plots by using the keyword arguments `figsize` and `dpi` as follows. ```python -fig, ax = plt.subplots(figsize=(10, 8), dpi=100) # More common aspect ratio +fig, ax = plt.subplots(figsize=(10, 8), dpi=100) ``` This has to be set **before** any instance of `ax.plot` and it sets the width and height to 10 and 8 inches respectively. The keyword `dpi` refers to a density of *Dots Per Inch*. diff --git a/_episodes/04-higgs-search.md b/_episodes/04-higgs-search.md index d42897d..984b081 100644 --- a/_episodes/04-higgs-search.md +++ b/_episodes/04-higgs-search.md @@ -417,7 +417,7 @@ bins = 24 ``` ```python -fig, (ax_1, ax_2) = plt.subplots(1, 2, figsize=(12, 8)) # Modern figsize parameter +fig, (ax_1, ax_2) = plt.subplots(1, 2, figsize=(12, 8)) ax_1.set_title("MC samples without weights") ax_1.hist( stack_mc_list_m4l, diff --git a/_episodes/07-dimuonspectrum.md b/_episodes/07-dimuonspectrum.md index 1b324da..7e13d9a 100644 --- a/_episodes/07-dimuonspectrum.md +++ b/_episodes/07-dimuonspectrum.md @@ -123,11 +123,6 @@ plt.style.use("default") # Option 2: Modern mplhep style (uncomment to use) # hep.style.use("ROOT") # or "CMS", "ATLAS", "ALICE", "LHCb1", "LHCb2" - -# Option 3: Use context manager for temporary styling -# with hep.style.use("ROOT"): -# # your plotting code here -# pass ``` Load the data From b425ccc7b21beca84c9a53d776de425f289e25d8 Mon Sep 17 00:00:00 2001 From: "Michel H. Villanueva" Date: Mon, 4 Aug 2025 15:02:07 -0400 Subject: [PATCH 09/10] Update _episodes/01-introduction.md Co-authored-by: Andres Rios Tascon --- _episodes/01-introduction.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/_episodes/01-introduction.md b/_episodes/01-introduction.md index dc2698f..19106a3 100644 --- a/_episodes/01-introduction.md +++ b/_episodes/01-introduction.md @@ -244,12 +244,6 @@ hep.style.use(hep.style.ATLAS) # or using string aliases (recommended) hep.style.use("CMS") # Available styles: {"ALICE" | "ATLAS" | "CMS" | "LHCb1" | "LHCb2" | "ROOT"} - -# You can also use context managers for temporary styling -with hep.style.use("ATLAS"): - fig, ax = plt.subplots() - ax.plot(x, y) - plt.show() ``` and with just this addition we can produce the same plot as before with this new look. From 4af7d993ca2b985ff7350f67ea262fba7d5faac2 Mon Sep 17 00:00:00 2001 From: Michel Villanueva Date: Mon, 18 Aug 2025 10:58:31 -0400 Subject: [PATCH 10/10] Removing unnecessary info like charge of muons --- _episodes/07-dimuonspectrum.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/_episodes/07-dimuonspectrum.md b/_episodes/07-dimuonspectrum.md index 7e13d9a..ec9f3b2 100644 --- a/_episodes/07-dimuonspectrum.md +++ b/_episodes/07-dimuonspectrum.md @@ -139,7 +139,6 @@ e = event["muons/e"][:] px = event["muons/px"][:] py = event["muons/py"][:] pz = event["muons/pz"][:] -q = event["muons/q"][:] # Extract charge information - needed for later analysis # We will check for muons that do not pass the kinematics print(len(px)) # Number of muons @@ -159,7 +158,6 @@ We can use numpy to clean our arrays from anomalous events ```python e = np.delete(e, cut) px, py, pz = np.delete(px, cut), np.delete(py, cut), np.delete(pz, cut) -q = np.delete(q, cut) # Also clean the charge array ``` Let's calculate the mass