|
| 1 | +/** A Lorentz 4-momentum vector (E, px, py, pz) in MeV. */ |
| 2 | +export interface FourMomentum { |
| 3 | + /** Total energy in MeV. */ |
| 4 | + E: number; |
| 5 | + /** Momentum x-component in MeV. */ |
| 6 | + px: number; |
| 7 | + /** Momentum y-component in MeV. */ |
| 8 | + py: number; |
| 9 | + /** Momentum z-component in MeV. */ |
| 10 | + pz: number; |
| 11 | +} |
| 12 | + |
| 13 | +/** A tagged particle with its physics properties. */ |
| 14 | +export interface TaggedParticle { |
| 15 | + /** Unique identifier of the underlying 3D object. */ |
| 16 | + uuid: string; |
| 17 | + /** Tag id assigned to this particle (e.g. 'electron', 'muon'). */ |
| 18 | + tag: string; |
| 19 | + /** Lorentz 4-momentum computed from track/cluster kinematics. */ |
| 20 | + fourMomentum: FourMomentum; |
| 21 | + /** Transverse momentum in MeV. */ |
| 22 | + pT: number; |
| 23 | + /** Pseudorapidity. */ |
| 24 | + eta: number; |
| 25 | + /** Azimuthal angle in radians. */ |
| 26 | + phi: number; |
| 27 | +} |
| 28 | + |
| 29 | +/** Definition of a particle tag for use in masterclass exercises. */ |
| 30 | +export interface ParticleTagDef { |
| 31 | + /** Unique identifier, e.g. 'electron', 'kaon'. */ |
| 32 | + id: string; |
| 33 | + /** Human-readable label, e.g. 'Electron'. */ |
| 34 | + label: string; |
| 35 | + /** Symbol for display, e.g. 'e\u00B1', 'K\u00B1'. */ |
| 36 | + symbol: string; |
| 37 | + /** CSS color for the tag button and badge. */ |
| 38 | + color: string; |
| 39 | + /** Rest mass in MeV/c\u00B2. */ |
| 40 | + mass: number; |
| 41 | +} |
| 42 | + |
| 43 | +/** |
| 44 | + * Configuration for experiment-specific masterclass exercises. |
| 45 | + * Each experiment (ATLAS, LHCb, CMS, ...) provides its own config. |
| 46 | + */ |
| 47 | +export interface MasterclassConfig { |
| 48 | + /** Panel title, e.g. 'ATLAS Z-Path Masterclass'. */ |
| 49 | + title: string; |
| 50 | + /** Available particle tags for this exercise. */ |
| 51 | + particleTags: ParticleTagDef[]; |
| 52 | + /** Educational hints shown when invariant mass is computed. */ |
| 53 | + hints: string[]; |
| 54 | + /** |
| 55 | + * Classify an event from the tag counts. |
| 56 | + * Receives a map of tag id to count, e.g. { electron: 2, muon: 0 }. |
| 57 | + * Returns a short label like "e", "4e", "2e2m". |
| 58 | + */ |
| 59 | + classifyEvent: (tagCounts: Record<string, number>) => string; |
| 60 | +} |
| 61 | + |
| 62 | +/** |
| 63 | + * Extract a 4-momentum vector from track userData. |
| 64 | + * Tracks have pT, eta/phi (or dparams), and we assign mass from the tag definition. |
| 65 | + * @param userData Track user data containing kinematic properties. |
| 66 | + * @param mass Particle rest mass in MeV/c². |
| 67 | + */ |
| 68 | +export function fourMomentumFromTrack( |
| 69 | + userData: any, |
| 70 | + mass: number, |
| 71 | +): FourMomentum | null { |
| 72 | + const pT = userData.pT; |
| 73 | + if (pT == null) return null; |
| 74 | + |
| 75 | + const phi = userData.phi ?? userData.dparams?.[2]; |
| 76 | + // theta from dparams, or compute from eta |
| 77 | + let theta = userData.dparams?.[3]; |
| 78 | + if (theta == null && userData.eta != null) { |
| 79 | + theta = 2 * Math.atan(Math.exp(-userData.eta)); |
| 80 | + } |
| 81 | + if (phi == null || theta == null) return null; |
| 82 | + |
| 83 | + const px = pT * Math.cos(phi); |
| 84 | + const py = pT * Math.sin(phi); |
| 85 | + const pz = pT / Math.tan(theta); |
| 86 | + const p2 = px * px + py * py + pz * pz; |
| 87 | + const E = Math.sqrt(p2 + mass * mass); |
| 88 | + |
| 89 | + return { E, px, py, pz }; |
| 90 | +} |
| 91 | + |
| 92 | +/** |
| 93 | + * Extract a 4-momentum vector from a calorimeter cluster. |
| 94 | + * Clusters have energy, eta, phi (treated as massless). |
| 95 | + */ |
| 96 | +export function fourMomentumFromCluster(userData: any): FourMomentum | null { |
| 97 | + const energy = userData.energy; |
| 98 | + const eta = userData.eta; |
| 99 | + const phi = userData.phi; |
| 100 | + if (energy == null || eta == null || phi == null) return null; |
| 101 | + |
| 102 | + const theta = 2 * Math.atan(Math.exp(-eta)); |
| 103 | + const px = energy * Math.sin(theta) * Math.cos(phi); |
| 104 | + const py = energy * Math.sin(theta) * Math.sin(phi); |
| 105 | + const pz = energy * Math.cos(theta); |
| 106 | + |
| 107 | + return { E: energy, px, py, pz }; |
| 108 | +} |
| 109 | + |
| 110 | +/** |
| 111 | + * Compute the invariant mass of a set of particles in MeV. |
| 112 | + * M² = (ΣE)² - (Σpx)² - (Σpy)² - (Σpz)² |
| 113 | + */ |
| 114 | +export function invariantMass(momenta: FourMomentum[]): number { |
| 115 | + if (momenta.length < 2) return 0; |
| 116 | + |
| 117 | + let sumE = 0, |
| 118 | + sumPx = 0, |
| 119 | + sumPy = 0, |
| 120 | + sumPz = 0; |
| 121 | + for (const p of momenta) { |
| 122 | + sumE += p.E; |
| 123 | + sumPx += p.px; |
| 124 | + sumPy += p.py; |
| 125 | + sumPz += p.pz; |
| 126 | + } |
| 127 | + |
| 128 | + const m2 = sumE * sumE - sumPx * sumPx - sumPy * sumPy - sumPz * sumPz; |
| 129 | + return m2 > 0 ? Math.sqrt(m2) : 0; |
| 130 | +} |
| 131 | + |
| 132 | +/** |
| 133 | + * Default event classifier for ATLAS Z-path masterclass. |
| 134 | + * Classifies events by electron/muon/photon counts. |
| 135 | + */ |
| 136 | +export function atlasClassifyEvent(tagCounts: Record<string, number>): string { |
| 137 | + const e = tagCounts['electron'] ?? 0; |
| 138 | + const m = tagCounts['muon'] ?? 0; |
| 139 | + const g = tagCounts['photon'] ?? 0; |
| 140 | + |
| 141 | + if (e === 2 && m === 0 && g === 0) return 'e'; |
| 142 | + if (e === 0 && m === 2 && g === 0) return 'm'; |
| 143 | + if (e === 0 && m === 0 && g === 2) return 'g'; |
| 144 | + if (e === 4 && m === 0 && g === 0) return '4e'; |
| 145 | + if (e === 2 && m === 2 && g === 0) return '2e2m'; |
| 146 | + if (e === 0 && m === 4 && g === 0) return '4m'; |
| 147 | + |
| 148 | + const parts: string[] = []; |
| 149 | + if (e > 0) parts.push(`${e}e`); |
| 150 | + if (m > 0) parts.push(`${m}m`); |
| 151 | + if (g > 0) parts.push(`${g}g`); |
| 152 | + return parts.join('') || '?'; |
| 153 | +} |
| 154 | + |
| 155 | +/** Default masterclass configuration for ATLAS Z-path exercises. */ |
| 156 | +export const ATLAS_MASTERCLASS_CONFIG: MasterclassConfig = { |
| 157 | + title: 'Masterclass: Invariant Mass', |
| 158 | + particleTags: [ |
| 159 | + { |
| 160 | + id: 'electron', |
| 161 | + label: 'Electron', |
| 162 | + symbol: 'e\u00B1', |
| 163 | + color: '#f0c040', |
| 164 | + mass: 0.511, |
| 165 | + }, |
| 166 | + { |
| 167 | + id: 'muon', |
| 168 | + label: 'Muon', |
| 169 | + symbol: '\u03BC\u00B1', |
| 170 | + color: '#40c060', |
| 171 | + mass: 105.658, |
| 172 | + }, |
| 173 | + { |
| 174 | + id: 'photon', |
| 175 | + label: 'Photon', |
| 176 | + symbol: '\u03B3', |
| 177 | + color: '#e04040', |
| 178 | + mass: 0, |
| 179 | + }, |
| 180 | + ], |
| 181 | + hints: [ |
| 182 | + 'Z boson \u2248 91 GeV', |
| 183 | + 'Higgs \u2248 125 GeV', |
| 184 | + 'J/\u03C8 \u2248 3.1 GeV', |
| 185 | + ], |
| 186 | + classifyEvent: atlasClassifyEvent, |
| 187 | +}; |
0 commit comments