Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-core/src/model/point-trajectory.piece.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * A data-model piece for "PointTrajectory" typed data (DEX v1).
0003  *
0004  * Track parameters live in per-name columns (parallel arrays, trajectory id
0005  * equals array index). The per-trajectory point lists are ragged, so they
0006  * stay nested: `points[i]` is the point-tuple list of trajectory i, tuple
0007  * layout declared by `pointColumns`.
0008  *
0009  *   {
0010  *     "name": "CentralTrackSegments",
0011  *     "type": "PointTrajectory",
0012  *     "version": "1.0",
0013  *     "count": 2,
0014  *     "columns": { "theta": [...], "phi": [...], "pdg": [...] },
0015  *     "pointColumns": ["x", "y", "z", "t", "dx", "dy", "dz", "dt"],
0016  *     "points": [ [[x,y,z,t,...], ...], [[x,y,z,t,...], ...] ],
0017  *     "refs": { "particle": "McParticles" }        // optional
0018  *   }
0019  *
0020  * A writer declares only the columns it has: a simulation writer has pdg and
0021  * momentum, a reconstruction writer has theta/phi/qOverP. Readers bind to
0022  * declared columns, never to a fixed set.
0023  */
0024 
0025 import {
0026   EntityRefLink,
0027   EventPiece,
0028   EventPieceFactory,
0029   readPieceCount,
0030 } from './event-piece';
0031 
0032 /** Common PDG codes -> short particle names, for entity labels. */
0033 const PDG_NAMES: Record<number, string> = {
0034   11: 'e-', [-11]: 'e+', 13: 'mu-', [-13]: 'mu+', 22: 'gamma', [-22]: 'opt.photon',
0035   211: 'pi+', [-211]: 'pi-', 321: 'K+', [-321]: 'K-', 111: 'pi0',
0036   2212: 'p', [-2212]: 'p~', 2112: 'n', [-2112]: 'n~',
0037 };
0038 
0039 /** One parameter column: numbers for measured values, strings for labels. */
0040 export type ParamColumn = (number | string | null)[];
0041 
0042 export class PointTrajectoryPiece extends EventPiece {
0043   static type = 'PointTrajectory';
0044 
0045   /** Number of trajectories. */
0046   count = 0;
0047 
0048   /**
0049    * Parameter columns by name; each holds `count` values, trajectory id is
0050    * the index. Example keys: "theta", "phi", "q_over_p", "pdg", "charge".
0051    */
0052   columns: Record<string, ParamColumn> = {};
0053 
0054   /**
0055    * The point columns define the meaning of each entry in a point tuple.
0056    * Example: ["x","y","z","t","dx","dy","dz","dt"]
0057    */
0058   pointColumns: string[] = [];
0059 
0060   /**
0061    * Ragged per-trajectory point lists: `points[i]` belongs to trajectory i
0062    * and holds point tuples laid out per `pointColumns`.
0063    */
0064   points: number[][][] = [];
0065 
0066   /** Reference declarations: column name -> target piece name; -1 = no reference. */
0067   refs: Record<string, string> = {};
0068 
0069   constructor(name: string, origin?: any) {
0070     super(name, PointTrajectoryPiece.type, origin);
0071   }
0072 
0073   /** Reads one parameter of one trajectory; null when the column is absent. */
0074   param(columnName: string, trajectoryIndex: number): number | string | null {
0075     const column = this.columns[columnName];
0076     if (column === undefined) return null;
0077     const value = column[trajectoryIndex];
0078     return value === undefined ? null : value;
0079   }
0080 
0081   /**
0082    * Label from the columns painters already use for naming: type or pdg
0083    * (mapped to a particle name), charge as a fallback, momentum when the
0084    * writer declared px/py/pz.
0085    */
0086   override entityLabel(trajectoryIndex: number): string {
0087     let name = '';
0088     const type = this.param('type', trajectoryIndex);
0089     const pdg = this.param('pdg', trajectoryIndex);
0090     const charge = this.param('charge', trajectoryIndex);
0091     if (typeof type === 'string' && type) {
0092       name = type;
0093     } else if (typeof pdg === 'number') {
0094       name = PDG_NAMES[Math.floor(pdg)] ?? `pdg=${Math.floor(pdg)}`;
0095     } else if (typeof charge === 'number') {
0096       name = charge > 0 ? 'q+' : charge < 0 ? 'q-' : 'q0';
0097     }
0098 
0099     let momentum = '';
0100     const px = this.param('px', trajectoryIndex);
0101     const py = this.param('py', trajectoryIndex);
0102     const pz = this.param('pz', trajectoryIndex);
0103     if (typeof px === 'number' && typeof py === 'number' && typeof pz === 'number') {
0104       momentum = ` p=${(Math.sqrt(px * px + py * py + pz * pz) / 1000).toFixed(2)}`;
0105     }
0106 
0107     return `#${trajectoryIndex}${name ? ' ' + name : ''}${momentum}`;
0108   }
0109 
0110   /** Resolves the `refs` declaration for one trajectory (-1 = no reference). */
0111   override entityRefs(trajectoryIndex: number): EntityRefLink[] {
0112     const links: EntityRefLink[] = [];
0113     for (const [column, targetPiece] of Object.entries(this.refs)) {
0114       const value = this.param(column, trajectoryIndex);
0115       if (typeof value === 'number' && value >= 0) {
0116         links.push({ column, targetPiece, targetIndex: Math.floor(value) });
0117       }
0118     }
0119     return links;
0120   }
0121 
0122   /** calculate time range */
0123   override get timeRange(): [number, number] | null {
0124     if (this.count === 0) return null;
0125 
0126     // Find the index of time column
0127     const timeIndex = this.pointColumns.indexOf("t");
0128     if (timeIndex === -1) return null;
0129 
0130     let minTime: number | null = null;
0131     let maxTime: number | null = null;
0132 
0133     for (const trajectoryPoints of this.points) {
0134       for (const point of trajectoryPoints) {
0135         const time = point[timeIndex];
0136         if (time == null) continue;
0137         if (minTime === null || time < minTime) minTime = time;
0138         if (maxTime === null || time > maxTime) maxTime = time;
0139       }
0140     }
0141 
0142     if (minTime !== null && maxTime !== null) {
0143       return [minTime, maxTime];
0144     }
0145     return null;
0146   }
0147 
0148   /**
0149    * Convert this piece to a DEX v1 piece object
0150    */
0151   override toDexObject(): any {
0152     const result: any = {
0153       name: this.name,
0154       type: this.type,
0155       version: '1.0',
0156       count: this.count,
0157       columns: { ...this.columns },
0158       pointColumns: [...this.pointColumns],
0159       points: this.points,
0160     };
0161     if (Object.keys(this.refs).length > 0) result.refs = { ...this.refs };
0162     if (this.origin !== undefined) result.origin = this.origin;
0163     return result;
0164   }
0165 }
0166 
0167 /**
0168  * Factory class to deserialize from the DEX piece object to our piece instance.
0169  * Sizes are checked loudly — a malformed file throws instead of pairing
0170  * parameters with the wrong trajectory.
0171  */
0172 export class PointTrajectoryPieceFactory implements EventPieceFactory {
0173   type = PointTrajectoryPiece.type;
0174 
0175   fromDexObject(obj: any): PointTrajectoryPiece {
0176     const piece = new PointTrajectoryPiece(obj["name"], obj["origin"]);
0177     piece.count = readPieceCount(obj);
0178 
0179     if (Array.isArray(obj["pointColumns"])) {
0180       piece.pointColumns = [...obj["pointColumns"]];
0181     }
0182 
0183     const points = obj["points"];
0184     if (!Array.isArray(points) || points.length !== piece.count) {
0185       throw new Error(`DEX piece '${piece.name}': "points" must hold count=${piece.count} ` +
0186         `entries, got ${Array.isArray(points) ? points.length : typeof points}`);
0187     }
0188 
0189     const columns = obj["columns"] ?? {};
0190     for (const columnName of Object.keys(columns)) {
0191       const column = columns[columnName];
0192       if (!Array.isArray(column) || column.length !== piece.count) {
0193         throw new Error(`DEX piece '${piece.name}': column "${columnName}" must hold ` +
0194           `count=${piece.count} values (trajectory id must equal array index)`);
0195       }
0196       piece.columns[columnName] = column;
0197     }
0198 
0199     if (obj["refs"] && typeof obj["refs"] === 'object') {
0200       piece.refs = { ...obj["refs"] };
0201     }
0202 
0203     // Copy point lists sorted by time so partial-track painting can assume
0204     // monotonic time per trajectory
0205     const sortStart = performance.now();
0206     const timeIndex = piece.pointColumns.indexOf("t");
0207     let totalPoints = 0;
0208     piece.points = points.map((trajectoryPoints: number[][]) => {
0209       const copy = Array.isArray(trajectoryPoints) ? [...trajectoryPoints] : [];
0210       totalPoints += copy.length;
0211       if (timeIndex !== -1 && copy.length > 1) {
0212         copy.sort((a, b) => {
0213           if (a.length <= timeIndex || b.length <= timeIndex) return 0;
0214           return a[timeIndex] - b[timeIndex];
0215         });
0216       }
0217       return copy;
0218     });
0219     const sortMs = performance.now() - sortStart;
0220     if (sortMs > 10) {
0221       console.log(`[load-timing] adopt PointTrajectory '${piece.name}': copy+sort ` +
0222         `${sortMs.toFixed(1)} ms (${piece.count} trajectories, ${totalPoints} points)`);
0223     }
0224 
0225     return piece;
0226   }
0227 }