Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/root2dex/src/parity.spec.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * The parity contract: for the same ROOT file and the same entries, this
0003  * package must produce the DEX document pyrobird produces - not approximately,
0004  * value for value. Both sides read the same IEEE754 bytes out of the file and
0005  * do the same arithmetic on them, so every number matches exactly.
0006  *
0007  * The reference documents in ../test-data were written by `pyrobird convert`;
0008  * see the README there for how to regenerate them. The ROOT inputs are
0009  * pyrobird's own test files - the same bytes both implementations read.
0010  */
0011 
0012 import { describe, expect, it } from 'vitest';
0013 import { readFileSync, existsSync } from 'node:fs';
0014 import { resolve } from 'node:path';
0015 import { convertRootToDex, type ConvertOptions } from './convert';
0016 import type { DexDocument } from './dex';
0017 
0018 const REPO = resolve(__dirname, '../../..');
0019 const ROOT_DATA = resolve(REPO, 'pyrobird/tests/unit_tests/data');
0020 const REFERENCE = resolve(__dirname, '../test-data');
0021 
0022 /**
0023  * pyrobird's default trajectory exclusion list (DEFAULT_TRAJECTORY_EXCLUDED_COLLECTIONS
0024  * in pyrobird/edm4hep.py). root2dex excludes nothing unless told, so the
0025  * edm4hep case passes the list; without it the Cherenkov hits join the
0026  * MC-truth trajectories and the documents differ.
0027  */
0028 const PYROBIRD_TRAJECTORY_EXCLUDED_COLLECTIONS = ['DIRCBarHits', 'DRICHHits', 'PFRICHHits'];
0029 
0030 const CASES: Array<{ label: string; root: string; reference: string; model: string; options: ConvertOptions }> = [
0031   {
0032     label: 'edm4eic (eicrecon reconstruction)',
0033     root: 'reco_2024-09_craterlake_2evt.edm4eic.root',
0034     reference: 'reco_2024-09_craterlake_2evt.pyrobird.json',
0035     model: 'edm4eic',
0036     options: {},
0037   },
0038   {
0039     label: 'edm4hep (ddsim simulation)',
0040     root: 'k_lambda_10x100_2evt.edm4hep.root',
0041     reference: 'k_lambda_10x100_2evt.pyrobird.json',
0042     model: 'edm4hep',
0043     options: { trajectoryExcludedCollections: PYROBIRD_TRAJECTORY_EXCLUDED_COLLECTIONS },
0044   },
0045 ];
0046 
0047 describe('root2dex / pyrobird parity', () => {
0048   for (const testCase of CASES) {
0049     const rootPath = resolve(ROOT_DATA, testCase.root);
0050     const referencePath = resolve(REFERENCE, testCase.reference);
0051     const available = existsSync(rootPath) && existsSync(referencePath);
0052 
0053     it.runIf(available)(`converts ${testCase.label} exactly like pyrobird`, async () => {
0054       const expected = JSON.parse(readFileSync(referencePath, 'utf8')) as DexDocument;
0055       // The JSON round trip drops undefined optional fields the same way
0056       // writing the document to a file would
0057       const actual = JSON.parse(
0058         JSON.stringify(await convertRootToDex(rootPath, '0-1', testCase.options)),
0059       ) as DexDocument;
0060 
0061       expect(actual.type).toBe(expected.type);
0062       expect(actual.version).toBe(expected.version);
0063 
0064       // origin.file is the path each side was invoked with, so only the parts
0065       // derived from the file itself are compared
0066       const origin = actual.origin as Record<string, unknown>;
0067       const expectedOrigin = expected.origin as Record<string, unknown>;
0068       expect(origin['entries_count']).toBe(expectedOrigin['entries_count']);
0069       expect(origin['file_type']).toBe(testCase.model);
0070       expect(expectedOrigin['file_type']).toBe(testCase.model);
0071 
0072       expect(actual.events.length).toBe(expected.events.length);
0073       actual.events.forEach((event, e) => {
0074         const expectedEvent = expected.events[e];
0075         expect(event.id, `events[${e}].id`).toBe(expectedEvent.id);
0076         // Piece names first: a mismatch here reads far better than a diff of
0077         // two thousand-element column arrays
0078         expect(
0079           event.pieces.map(piece => `${piece.name}:${piece.type}:${piece.count}`),
0080           `events[${e}] pieces`,
0081         ).toEqual(expectedEvent.pieces.map(piece => `${piece.name}:${piece.type}:${piece.count}`));
0082         event.pieces.forEach((piece, p) => {
0083           expect(piece, `events[${e}].pieces[${p}] (${piece.name})`).toEqual(
0084             expectedEvent.pieces[p],
0085           );
0086         });
0087       });
0088     });
0089   }
0090 });