Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/root2dex/src/mc-particles.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * MCParticles (vector<edm4hep::MCParticleData>) -> Firebird DEX.
0003  *
0004  * Mirrors pyrobird's `pyrobird/mc_particles.py` VALUE FOR VALUE - parity.spec.ts
0005  * pins the two against the same reference documents. Keep the arithmetic
0006  * expression-for-expression identical when changing either side.
0007  *
0008  * Both EDM4hep (ddsim) and EDM4eic (eicrecon) files carry this collection, so
0009  * the same conversion serves both models. Every particle becomes one straight
0010  * line from its vertex to its endpoint - no filtering, trajectory id equals the
0011  * MCParticle index. The line is subdivided on a fixed time grid so the event
0012  * display time animation reveals it gradually instead of popping it in whole.
0013  *
0014  * EDM4hep stores no endpoint time; the flight duration is computed from the
0015  * relativistic speed beta = p/E with E = sqrt(p^2 + m^2), which is exact for
0016  * the straight-line (no field, no energy loss) picture this piece draws.
0017  */
0018 
0019 import { PIECE_VERSION, type DexPiece } from './dex';
0020 import { numbers, type ColumnBag, type PodioEventFile } from './podio-file';
0021 
0022 /** Time step [ns] of the interpolation grid; matches the `pyrobird smooth` default. */
0023 export const DEFAULT_MC_STEP_TIME = 0.2;
0024 
0025 /**
0026  * Size guard: a particle flying far (a neutrino crossing the world volume) gets
0027  * its grid coarsened so one line never exceeds this many points.
0028  */
0029 export const DEFAULT_MC_MAX_POINTS = 128;
0030 
0031 /** Speed of light [mm/ns]. */
0032 export const C_LIGHT = 299.792458;
0033 
0034 export interface McParticlesOptions {
0035   /** Collection name, 'MCParticles' in every known file. */
0036   mcBranch?: string;
0037   /** Time step in ns of the straight-line interpolation grid. */
0038   mcStepTime?: number;
0039   /** Maximum points per particle line; longer flights get a coarser grid. */
0040   mcMaxPoints?: number;
0041   onWarning?: (message: string) => void;
0042 }
0043 
0044 const MC_PARTICLE_FIELDS = [
0045   'PDG',
0046   'generatorStatus',
0047   'charge',
0048   'time',
0049   'mass',
0050   'vertex.x',
0051   'vertex.y',
0052   'vertex.z',
0053   'endpoint.x',
0054   'endpoint.y',
0055   'endpoint.z',
0056   'momentum.x',
0057   'momentum.y',
0058   'momentum.z',
0059 ];
0060 
0061 /** Every branch the mc_particles conversion reads. */
0062 export function mcParticlesBranchNames(options: McParticlesOptions = {}): string[] {
0063   const mcBranch = options.mcBranch ?? 'MCParticles';
0064   return MC_PARTICLE_FIELDS.map(field => `${mcBranch}.${field}`);
0065 }
0066 
0067 /**
0068  * Subdivides the straight line vertex->endpoint on a fixed time grid.
0069  *
0070  * Returns [x, y, z, t] points: the vertex at `startTime`, interior points every
0071  * `stepTime` ns of flight, and the endpoint at the arrival time. A particle
0072  * that cannot move (beta <= 0) or does not move (zero distance) yields the two
0073  * end points with equal times. `maxPoints` bounds the list; when the flight
0074  * needs more grid steps, the step widens to duration / (n + 1) so the
0075  * subdivision stays uniform.
0076  */
0077 export function interpolateLinePoints(
0078   vx: number, vy: number, vz: number,
0079   ex: number, ey: number, ez: number,
0080   startTime: number, beta: number,
0081   stepTime = DEFAULT_MC_STEP_TIME, maxPoints = DEFAULT_MC_MAX_POINTS,
0082 ): number[][] {
0083   const dx = ex - vx;
0084   const dy = ey - vy;
0085   const dz = ez - vz;
0086   const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
0087   const duration = beta > 0 ? dist / (beta * C_LIGHT) : 0.0;
0088 
0089   const points: number[][] = [[vx, vy, vz, startTime]];
0090   if (duration > 0) {
0091     let nMid = Math.max(0, Math.ceil(duration / stepTime) - 1);
0092     if (nMid > maxPoints - 2) {
0093       nMid = maxPoints - 2;
0094     }
0095     const step = duration / (nMid + 1);
0096     for (let k = 1; k <= nMid; k++) {
0097       const tOff = k * step;
0098       const alpha = tOff / duration;
0099       points.push([vx + alpha * dx, vy + alpha * dy, vz + alpha * dz, startTime + tOff]);
0100     }
0101   }
0102   points.push([ex, ey, ez, startTime + duration]);
0103   return points;
0104 }
0105 
0106 /**
0107  * Converts the MCParticles collection of one read entry to a PointTrajectory
0108  * piece. Every particle is one straight line vertex->endpoint (trajectory id
0109  * equals the MCParticle index), subdivided on a fixed time grid - see the
0110  * module doc. Returns a count-0 piece when the collection is absent.
0111  */
0112 export function mcParticlesToTrajectories(
0113   file: PodioEventFile,
0114   bag: ColumnBag,
0115   options: McParticlesOptions = {},
0116 ): DexPiece {
0117   const mcBranch = options.mcBranch ?? 'MCParticles';
0118   const stepTime = options.mcStepTime ?? DEFAULT_MC_STEP_TIME;
0119   const maxPoints = options.mcMaxPoints ?? DEFAULT_MC_MAX_POINTS;
0120 
0121   const piece: DexPiece = {
0122     name: mcBranch,
0123     type: 'PointTrajectory',
0124     version: PIECE_VERSION,
0125     origin: { type: 'edm4hep::MCParticleData', name: mcBranch },
0126     count: 0,
0127     columns: {},
0128     pointColumns: ['x', 'y', 'z', 't'],
0129     points: [],
0130   };
0131 
0132   if (!file.hasBranch(mcBranch)) {
0133     options.onWarning?.(`'${mcBranch}' collection not found, no MC particle lines are built`);
0134     return piece;
0135   }
0136 
0137   const field = (name: string) => numbers(bag, `${mcBranch}.${name}`);
0138   const pdg = field('PDG');
0139   const genStatus = field('generatorStatus');
0140   const charge = field('charge');
0141   const time = field('time');
0142   const mass = field('mass');
0143   const vtxX = field('vertex.x');
0144   const vtxY = field('vertex.y');
0145   const vtxZ = field('vertex.z');
0146   const endX = field('endpoint.x');
0147   const endY = field('endpoint.y');
0148   const endZ = field('endpoint.z');
0149   const momX = field('momentum.x');
0150   const momY = field('momentum.y');
0151   const momZ = field('momentum.z');
0152 
0153   const count = pdg.length;
0154   const momentum: number[] = [];
0155   const allPoints: number[][][] = [];
0156   for (let i = 0; i < count; i++) {
0157     const p = Math.sqrt(momX[i] * momX[i] + momY[i] * momY[i] + momZ[i] * momZ[i]);
0158     const energy = Math.sqrt(p * p + mass[i] * mass[i]);
0159     const beta = energy > 0 ? p / energy : 0.0;
0160     momentum.push(p);
0161     allPoints.push(interpolateLinePoints(
0162       vtxX[i], vtxY[i], vtxZ[i], endX[i], endY[i], endZ[i],
0163       time[i], beta, stepTime, maxPoints));
0164   }
0165 
0166   piece.count = count;
0167   piece.points = allPoints;
0168   if (count) {
0169     piece.columns = {
0170       pdg,
0171       charge,
0172       gen_status: genStatus,
0173       px: momX,
0174       py: momY,
0175       pz: momZ,
0176       p: momentum,
0177       mass,
0178     };
0179   }
0180   return piece;
0181 }