Three short Monte Carlo simulations, each checked against an exact result and analysed with least-squares fits and error estimates. Written in Python with NumPy, SciPy and Matplotlib.
| Script | Physics | Key result |
|---|---|---|
random_walk.py |
Diffusion | Slope of ⟨x²⟩ vs N = 1.002 ± 0.011 (theory: 1) |
radioactive_decay.py |
Exponential decay | Half-life recovered to within 0.1%; scatter falls as 1/√N₀ |
bump_hunt.py |
Resonance search | 125 GeV peak fitted at 124.85 ± 0.22 GeV |
pip install -r requirements.txt
python3 random_walk.py
python3 radioactive_decay.py
python3 bump_hunt.pyEach script prints its results and saves a plot in the repository's plots/ folder,
whichever directory you run it from. Random seeds are fixed, so you get the same numbers
shown here.
To run the tests (theory checks, output shapes and values, reproducibility, parameter checks, and the documented numbers below):
python3 -m pytest10,000 walkers each take 1,000 steps of +1 or −1. After
By the central limit theorem, the final positions are approximately Gaussian with
variance
- Fitted slope: 1.002 ± 0.011 (theory: 1)
- Variance of final positions: 1008 (theory: 1000)
Note on the error bar. Every point on the
Start with
so the true half-life is 68.97 steps. The half-life is extracted with a simple unweighted least-squares fit of that curve (see Limitations).
One experiment with N₀ = 1,000 gives a fitted half-life of 68.91 steps (true: 68.97).
Repeating each experiment 200 times shows how the result scatters:
| N₀ | Fitted half-life (mean ± spread) | Relative spread |
|---|---|---|
| 100 | 69.23 ± 7.66 | 11.1% |
| 1,000 | 69.04 ± 2.52 | 3.7% |
| 10,000 | 68.93 ± 0.72 | 1.0% |
Each tenfold increase in
A simplified version of how a new particle shows up in collider data. 10,000 background
events follow a falling exponential mass spectrum between 100 and 160 GeV. Signal events
form a Gaussian peak at 125 GeV with a 2 GeV width. The masses are histogrammed in
1 GeV bins and fitted with background + peak, using
with background normalisation
| Signal events | Fitted mass (GeV) | Fitted signal | Rough S/√B |
|---|---|---|---|
| 500 | 124.85 ± 0.22 | 574 ± 55 | 14.9 |
| 200 | 125.33 ± 0.53 | 136 ± 41 | 4.0 |
| 50 | 124.92 ± 1.48 | 56 ± 43 | 1.5 |
With 500 signal events the peak is obvious. With 200 it is still visible, but the mass uncertainty more than doubles. With 50 it disappears into the background: the fitted signal is consistent with zero, and the fit lands near 125 GeV mainly because that is where it was told to start looking.
These are teaching-scale simulations. The main statistical simplifications:
- Random-walk Gaussian is an approximation. The exact distribution of final positions is binomial and only takes even values after an even number of steps. The Gaussian curve is its central-limit-theorem approximation, which is close for 1,000 steps but not exact, especially far out in the tails.
- Decay fitting is illustrative. The fit treats every point of the survivor curve as independent with equal weight. In reality the points are correlated (each count is the previous count minus new decays, so a fluctuation carries forward to every later point) and their scatter changes over time, so the fit's own error estimate would not be reliable and is not used. The spread quoted above comes from repeating the experiment 200 times instead. A rigorous analysis would use a maximum-likelihood fit of the decay times.
-
$S/\sqrt{B}$ is a rough toy estimate. It ignores the uncertainty on the background shape, uses$\sqrt{n_i}$ errors that are only approximate in low-count bins, and does not account for the look-elsewhere effect (searching for a peak at any mass, not only 125 GeV). It is not a formal discovery significance and not a p-value. Real searches use likelihood-ratio tests.
Monte Carlo sampling (uniform, binomial, exponential and Gaussian random numbers),
histogramming, linear and non-linear least-squares fitting (numpy.polyfit,
scipy.optimize.curve_fit), and statistical error estimation.
MIT


