diff --git a/firebird-ng/src/app/geometry.service.ts b/firebird-ng/src/app/geometry.service.ts index 654495a..77d0b3e 100644 --- a/firebird-ng/src/app/geometry.service.ts +++ b/firebird-ng/src/app/geometry.service.ts @@ -12,109 +12,117 @@ import { import {build} from 'jsrootdi/geom'; +export class DetectorGeometryFineTuning { + namePattern: string = ""; + editRules: GeoNodeEditRule[] = []; +} -export class DetectorGeometryFineTuning { - public namePattern: string; - public editRules: GeoNodeEditRule[]; - - constructor( - namePattern: string, - editRules:GeoNodeEditRule[] = [] - ) - { - this.namePattern = namePattern; - this.editRules = editRules; +function pruneTopLevelDetectors(geoManager: any, removeNames: string[]): any { + const volume = geoManager.fMasterVolume === undefined ? geoManager.fVolume : geoManager.fMasterVolume; + const nodes: any[] = volume?.fNodes?.arr ?? []; + let removedNodes: any[] = []; + + // Don't have nodes? Have problems? + if(!nodes.length) { + return {nodes, removedNodes}; } + + // Collect nodes to remove + for(let node of nodes) { + let isRemoving = removeNames.some(substr => node.fName.startsWith(substr)) + if(isRemoving) { + removedNodes.push(node); + } + } + + // Now remove nodes + for(let node of removedNodes) { + removeGeoNode(node); + } + + return {nodes, removedNodes} } + + @Injectable({ providedIn: 'root' }) export class GeometryService { /** - * Simple to fill list of patterns of elements to drop - * @typedef {Array.} FullGeometryPruneList + * Detectors (top level TGeo nodes) to be removed. + * (!) startsWith function is used for filtering (aka: detector.fName.startsWith(removeDetectorNames[i]) ... ) */ - removeDetectorsStartsWith: string[]; - - detectorTopNodes=[]; - - /// This inted to become users rule - fineTuneRules: GeoNodeEditRule[] = []; - totalRules: GeoNodeEditRule[] = []; - - subDetectors: DetectorGeometryFineTuning[] = [ + removeDetectorNames: string[] = [ + "Lumi", + "Magnet", + "B0", + "B1", + "B2", + "Q0", + "Q1", + "Q2", + "BeamPipe", + "Pipe", + "ForwardOffM", + "Forward", + "Backward", + "Vacuum", + "SweeperMag", + "AnalyzerMag", + "ZDC", + "LFHCAL", + "HcalFarForward" + ]; + + subDetectorsRules: DetectorGeometryFineTuning[] = [ { namePattern: "*/EcalBarrelScFi*", editRules: [ - {pattern: "*/fiber_grid*", prune:PruneRuleActions.Remove, pruneSubLevel:0}, + {pattern: "*/fiber_grid*", prune:PruneRuleActions.Remove}, ] }, { namePattern: "*/EcalBarrelImaging*", editRules: [ - {pattern: "*/stav*", prune:PruneRuleActions.RemoveChildren, pruneSubLevel:0}, + {pattern: "*/stav*", prune:PruneRuleActions.RemoveChildren}, ] }, { namePattern: "*/DRICH*", editRules: [ - {pattern: "*/DRICH_cooling*", prune:PruneRuleActions.RemoveSiblings, pruneSubLevel:0}, + {pattern: "*/DRICH_cooling*", prune:PruneRuleActions.RemoveSiblings}, ] }, { namePattern: "*/EcalEndcapN*", editRules: [ - {pattern: "*/crystal*", prune:PruneRuleActions.RemoveSiblings, pruneSubLevel:0}, + {pattern: "*/crystal*", prune:PruneRuleActions.RemoveSiblings}, + ] + }, + { + namePattern: "*/HcalBarrel*", + editRules: [ + {pattern: "*/Tile*", prune:PruneRuleActions.Remove}, + {pattern: "*/ChimneyTile*", prune:PruneRuleActions.Remove}, + ] + }, + { + namePattern: "*/EndcapTOF*", + editRules: [ + {pattern: "*/suppbar*", prune:PruneRuleActions.Remove}, + {pattern: "*/component*3", prune:PruneRuleActions.RemoveSiblings}, ] } - ] + ] constructor() { - this.removeDetectorsStartsWith = [ - "Lumi", - "Magnet", - "B0", - "B1", - "B2", - "Q0", - "Q1", - "Q2", - "BeamPipe", - "Pipe", - "ForwardOffM", - "Forward", - "Backward", - "Vacuum", - "SweeperMag*", - "AnalyzerMag*", - "ZDC" - //"LFHCAL" - ]; - - this.fineTuneRules = [ - {pattern: "Default/EcalBarrel*/sector*", prune: PruneRuleActions.RemoveChildren, pruneSubLevel: 0} - ] - - // Fill rules with prune - for(let pattern of this.removeDetectorsStartsWith) { - this.totalRules.push(new GeoNodeEditRule(pattern, PruneRuleActions.Remove )); - } - - // Copy users rules - for(let rule of this.fineTuneRules) { - this.totalRules.push(rule); - } - - } - - async loadEicGeometry() { //let url: string = 'assets/epic_pid_only.root'; //let url: string = 'https://eic.github.io/epic/artifacts/tgeo/epic_dirc_only.root'; @@ -135,38 +143,13 @@ export class GeometryService { console.timeEnd('Reading geometry from file'); // Getting main detector nodes - - const nodeName = rootGeoManager.fName; - const volume = rootGeoManager.fMasterVolume === undefined ? rootGeoManager.fVolume : rootGeoManager.fMasterVolume; - const allTopNodes = volume?.fNodes?.arr ?? null; - if(!allTopNodes) { - console.log("No top level detector nodes found. Wrong geometry? ") - return {rootGeoManager: null, rootObject3d: null}; - } - - for(let topNode of allTopNodes) { - let isRemoving = this.removeDetectorsStartsWith.some(substr => topNode.fName.startsWith(substr)) - - console.log(`NODE ${topNode.fName}: ${topNode} isRemoving: ${isRemoving}`); - - if(isRemoving) { - removeGeoNode(topNode); - } else { - printAllGeoBitsStatus(topNode); - console.log(`VOLUME: ${topNode.fVolume.fName}: ${topNode.fVolume}`); - printAllGeoBitsStatus(topNode.fVolume); - } - } - - // >oO debug: analyzeGeoNodes(rootGeoManager, 1); - console.time('Prune nodes coarse'); - editGeoNodes(rootGeoManager, this.totalRules, 1) - console.timeEnd('Prune nodes coarse'); - - // >oO + let result = pruneTopLevelDetectors(rootGeoManager, this.removeDetectorNames); + console.log("Filtered top level detectors: ", result); - for(let detector of this.subDetectors) { + // >oO analyzeGeoNodes(rootGeoManager, 1); + // Now we go with the fine tuning each detector + for(let detector of this.subDetectorsRules) { let topDetNode = findSingleGeoNode(rootGeoManager, detector.namePattern, 1); console.log(`Processing ${topDetNode}`); if(!topDetNode) { @@ -180,12 +163,14 @@ export class GeometryService { console.timeEnd(`Process sub-detector: ${detector.namePattern}`); } - console.log(`Done processing ${this.subDetectors.length} detectors`); + console.log(`Done processing ${this.subDetectorsRules.length} detectors`); + console.log(`---- DETECTOR ANALYSIS ----`); analyzeGeoNodes(rootGeoManager, 1); + console.log(`---- END DETECTOR ANALYSIS ----`); //analyzeGeoNodes(geoManager, 1); - return {rootGeoManager: null, rootObject3d: null}; + // return {rootGeoManager: null, rootObject3d: null}; // console.time('Build geometry'); diff --git a/firebird-ng/src/app/utils/cern-root.utils.ts b/firebird-ng/src/app/utils/cern-root.utils.ts index 54cd947..46da754 100644 --- a/firebird-ng/src/app/utils/cern-root.utils.ts +++ b/firebird-ng/src/app/utils/cern-root.utils.ts @@ -76,18 +76,9 @@ export enum PruneRuleActions { * Defines editing rules for geographic nodes based on a pattern. */ export class GeoNodeEditRule { - /** - * Constructs an instance of GeoNodeEditRule. - * @param pattern Matching pattern for node selection. Default is an empty string. - * @param prune Type of pruning action to take. Default is PruneRuleActions.Nothing. - * @param pruneSubLevel How many sublevels to prune. Defaults to Infinity. - * Effective only if prune rule is `RemoveBySubLevel`. - */ - constructor( - public pattern: string = '', - public prune: PruneRuleActions = PruneRuleActions.Nothing, - public pruneSubLevel: number = Infinity - ) { } + public pattern: string = ''; + public prune: PruneRuleActions = PruneRuleActions.Nothing; + public pruneSubLevel?: number = Infinity } /** @@ -177,11 +168,17 @@ export function analyzeGeoNodes(node: any, level:number=2) { let highLevelNodes = getGeoNodesByLevel(node, 1); + let totalNodes = 0; + + console.log(`--- Detector subcomponents analysis --- Detectors: ${highLevelNodes.length}`); for(let item of highLevelNodes) { // Now run walkNodes for each of high level node to get number of subnodes let numSubNodes = walkGeoNodes(item.geoNode, null, Infinity); + totalNodes += numSubNodes; console.log(`${numSubNodes}: ${item.fullPath}`); } + console.log(`--- End of analysis --- Total elements: ${totalNodes}`); + } export function getGeoNodesByLevel(topNode: any, selectLevel:number=1) { diff --git a/firebird-ng/src/app/utils/list.utils.spec.ts b/firebird-ng/src/app/utils/list.utils.spec.ts new file mode 100644 index 0000000..104330c --- /dev/null +++ b/firebird-ng/src/app/utils/list.utils.spec.ts @@ -0,0 +1,52 @@ +import { filterIntersectingItems, removeIntersectingItemsInPlace } from './list.utils'; + +describe('List Utilities', () => { + + describe('filterIntersectingItems', () => { + it('should filter out items from list1 that are present in list2', () => { + const list1 = [1, 2, 3, 4, 5]; + const list2 = [3, 4, 5, 6, 7]; + const result = filterIntersectingItems(list1, list2); + expect(result).toEqual([1, 2]); + expect(list1).toEqual([1, 2, 3, 4, 5]); // Ensure list1 is not modified + }); + + it('should return an empty array if all items in list1 are in list2', () => { + const list1 = [1, 2, 3]; + const list2 = [1, 2, 3]; + const result = filterIntersectingItems(list1, list2); + expect(result).toEqual([]); + }); + + it('should return the original list1 if no items in list1 are in list2', () => { + const list1 = [1, 2, 3]; + const list2 = [4, 5, 6]; + const result = filterIntersectingItems(list1, list2); + expect(result).toEqual([1, 2, 3]); + }); + }); + + describe('removeIntersectingItemsInPlace', () => { + it('should remove items from list1 that are present in list2 in place', () => { + const list1 = [1, 2, 3, 4, 5]; + const list2 = [3, 4, 5, 6, 7]; + removeIntersectingItemsInPlace(list1, list2); + expect(list1).toEqual([1, 2]); + }); + + it('should remove all items from list1 if they are all in list2', () => { + const list1 = [1, 2, 3]; + const list2 = [1, 2, 3]; + removeIntersectingItemsInPlace(list1, list2); + expect(list1).toEqual([]); + }); + + it('should not modify list1 if no items in list1 are in list2', () => { + const list1 = [1, 2, 3]; + const list2 = [4, 5, 6]; + removeIntersectingItemsInPlace(list1, list2); + expect(list1).toEqual([1, 2, 3]); + }); + }); + +}); diff --git a/firebird-ng/src/app/utils/list.utils.ts b/firebird-ng/src/app/utils/list.utils.ts new file mode 100644 index 0000000..1ddee25 --- /dev/null +++ b/firebird-ng/src/app/utils/list.utils.ts @@ -0,0 +1,27 @@ +/** + * @summary Filters out items from list1 that are present in list2 and returns a new list. + * @param {T[]} list1 - The first list of items. + * @param {T[]} list2 - The second list of items to be removed from the first list. + * @return {T[]} A new list with items from list1 that are not in list2. + */ +export function filterIntersectingItems(list1: T[], list2: T[]): T[] { + const set2 = new Set(list2); + return list1.filter(item => !set2.has(item)); +} + +/** + * @summary Removes items from list1 that are present in list2 in place. + * @param {T[]} list1 - The first list of items. + * @param {T[]} list2 - The second list of items to be removed from the first list. + */ +export function removeIntersectingItemsInPlace(list1: T[], list2: T[]): void { + const set2 = new Set(list2); + let i = 0; + while (i < list1.length) { + if (set2.has(list1[i])) { + list1.splice(i, 1); // Remove the item at index i + } else { + i++; + } + } +}