Warning, /firebird/firebird-ng/src/app/model/point-trajectory.group.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * A data-model component for "PointTrajectory" typed data.
0003 *
0004 * This represents data in the Firebird Dex format:
0005 *
0006 * {
0007 * "name": "CentralTrackSegments",
0008 * "type": "PointTrajectory",
0009 * "origin": [...],
0010 * "paramColumns": [...],
0011 * "pointColumns": [...],
0012 * "trajectories": [
0013 * {
0014 * "points": [ [x, y, z, t, dx, dy, dz, dt], ... ],
0015 * "params": [ ... ]
0016 * },
0017 * ...
0018 * ]
0019 * }
0020 *
0021 * For example, each trajectory may correspond to one track segment,
0022 * and "points" is an array of arrays containing position/time/uncertainties.
0023 */
0024
0025 import {
0026 EventGroup,
0027 EventGroupFactory,
0028 registerEventGroupFactory
0029 } from "./event-group";
0030
0031 /**
0032 * Representation of a single line in the trajectory.
0033 */
0034 export interface PointTrajectory {
0035 /**
0036 * Array of points, each point is a numeric array matching the "pointColumns"
0037 * e.g. [ x, y, z, t, dx, dy, dz, dt ] or however many columns.
0038 */
0039 points: number[][];
0040 /**
0041 * Array of track parameters matching "paramColumns"
0042 * e.g. [theta, phi, qOverP, pdg, etc...]
0043 */
0044 params: number[];
0045 }
0046
0047 /**
0048 * The main component class that holds multiple lines (track segments)
0049 * along with the definitions of paramColumns and pointColumns.
0050 */
0051 export class PointTrajectoryGroup extends EventGroup {
0052 static type = "PointTrajectory";
0053
0054 /**
0055 * The param columns define the meaning of the `params` array in each line.
0056 * Example: ["theta","phi","qOverP","charge","pdg"]
0057 */
0058 paramColumns: string[] = [];
0059
0060 /**
0061 * The point columns define the meaning of the each entry in `points`.
0062 * Example: ["x","y","z","t","dx","dy","dz","dt"]
0063 */
0064 pointColumns: string[] = [];
0065
0066 /**
0067 * Trajectory that is built connecting points
0068 */
0069 trajectories: PointTrajectory[] = [];
0070
0071 constructor(name: string, origin?: string) {
0072 super(name, PointTrajectoryGroup.type, origin);
0073 }
0074
0075 /** calculate time range */
0076 override get timeRange(): [number, number] | null {
0077 // Check if there are any lines
0078 if (this.trajectories.length === 0) return null;
0079
0080 // Find the index of time column
0081 const timeIndex = this.pointColumns.indexOf("t");
0082
0083 // If time column doesn't exist, return null
0084 if (timeIndex === -1) return null;
0085
0086 // Check if there are any points in any line
0087 let hasPoints = false;
0088 for (const line of this.trajectories) {
0089 if (line.points.length > 0) {
0090 hasPoints = true;
0091 break;
0092 }
0093 }
0094 if (!hasPoints) return null;
0095
0096 // Initialize min and max times as null
0097 let minTime: number | null = null;
0098 let maxTime: number | null = null;
0099
0100 // Loop through all lines and points
0101 for (const line of this.trajectories) {
0102 for (const point of line.points) {
0103 const time = point[timeIndex];
0104
0105 // Skip if time is null
0106 if (time == null) continue;
0107
0108 // Initialize min/max if not initialized yet
0109 if (minTime === null) minTime = time;
0110 if (maxTime === null) maxTime = time;
0111
0112 // Update min/max
0113 if (time < minTime) minTime = time;
0114 if (time > maxTime) maxTime = time;
0115 }
0116 }
0117
0118 // Return range if both min and max are not null
0119 if (minTime !== null && maxTime !== null) {
0120 return [minTime, maxTime];
0121 }
0122
0123 return null;
0124 }
0125
0126 /**
0127 * Convert this component to a Dex-format JS object
0128 */
0129 override toDexObject(): any {
0130 // Serialize lines
0131 const trajectoriesObj = this.trajectories.map((trajectory) => {
0132 return {
0133 points: trajectory.points,
0134 params: trajectory.params
0135 };
0136 });
0137
0138 return {
0139 name: this.name,
0140 type: this.type,
0141 origin: this.origin,
0142 paramColumns: this.paramColumns,
0143 pointColumns: this.pointColumns,
0144 trajectories: trajectoriesObj
0145 };
0146 }
0147 }
0148
0149 /**
0150 * Factory class to deserialize from the Dex object to our component instance.
0151 */
0152 export class PointTrajectoryGroupFactory implements EventGroupFactory {
0153 type = PointTrajectoryGroup.type;
0154
0155 fromDexObject(obj: any): PointTrajectoryGroup {
0156 const comp = new PointTrajectoryGroup(obj["name"], obj["origin"]);
0157
0158 // paramColumns
0159 if (Array.isArray(obj["paramColumns"])) {
0160 comp.paramColumns = [...obj["paramColumns"]];
0161 }
0162
0163 // pointColumns
0164 if (Array.isArray(obj["pointColumns"])) {
0165 comp.pointColumns = [...obj["pointColumns"]];
0166 }
0167
0168 // Find time column index
0169 const timeIndex = comp.pointColumns.indexOf("t");
0170
0171 // trajectories
0172 comp.trajectories = [];
0173 if (Array.isArray(obj["trajectories"])) {
0174 for (const lineObj of obj["trajectories"]) {
0175 // Create a copy of points to avoid modifying the original data
0176 let points = Array.isArray(lineObj["points"]) ? [...lineObj["points"]] : [];
0177
0178 // Sort points by time (4th column) if time column exists
0179 if (timeIndex !== -1 && points.length > 1) {
0180 // Sort using Array.sort which uses TimSort algorithm in most modern browsers
0181 // TimSort is efficient for arrays that are partially sorted, which is often the case here
0182 points.sort((a, b) => {
0183 // Make sure points have time value at the specified index
0184 if (a.length <= timeIndex || b.length <= timeIndex) {
0185 return 0; // Keep original order if time index is out of bounds
0186 }
0187 return a[timeIndex] - b[timeIndex]; // Sort by time ascending
0188 });
0189 }
0190
0191 comp.trajectories.push({
0192 points: points,
0193 params: Array.isArray(lineObj["params"]) ? lineObj["params"] : []
0194 });
0195 }
0196 }
0197 return comp;
0198 }
0199 }
0200
0201 /** Register the factory so it gets picked up by the Entry deserialization. */
0202 registerEventGroupFactory(new PointTrajectoryGroupFactory());