diff --git a/.github/workflows/linux-eic-shell.yml b/.github/workflows/linux-eic-shell.yml index fe7a81a42..c20fab6d2 100644 --- a/.github/workflows/linux-eic-shell.yml +++ b/.github/workflows/linux-eic-shell.yml @@ -189,6 +189,37 @@ jobs: root -b -q "scripts/test_ACTS.cxx+(\"${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml\")" | tee check_tracking_geometry.out bin/acts_geo_check check_tracking_geometry.out + validate-material-map: + runs-on: ubuntu-latest + needs: + - build + - check-tracking-geometry + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: build-gcc-full-eic-shell + path: install/ + - uses: cvmfs-contrib/github-action-cvmfs@v4 + - uses: eic/run-cvmfs-osg-eic-shell@main + with: + platform-release: "jug_xl:nightly" + setup: install/setup.sh + run: | + pushd scripts/material_map + export DETECTOR_CONFIG=epic_craterlake_material_map + ./run_material_map_validation.sh + popd + - uses: actions/upload-artifact@v4 + with: + name: material_map + path: | + "scripts/material_map/*.json" + "scripts/material_map/*.root" + scripts/material_map/Surfaces/ + scripts/material_map/Validation/ + if-no-files-found: error + convert-to-gdml: runs-on: ubuntu-latest needs: diff --git a/scripts/material_map/.gitignore b/scripts/material_map/.gitignore new file mode 100644 index 000000000..302d91010 --- /dev/null +++ b/scripts/material_map/.gitignore @@ -0,0 +1,5 @@ +Examples/ +Surfaces/ +Validation/ +calibrations/ +*.json diff --git a/scripts/material_map/epic.py b/scripts/material_map/epic.py new file mode 100644 index 000000000..4e8fc2858 --- /dev/null +++ b/scripts/material_map/epic.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +## Stand alone function to build ePIC geometry with ACTS python bindings +## for material mapping +## Shujie Li, 03, 2024 + +from pathlib import Path + +import acts +import acts.examples.dd4hep + +from acts import ( + Vector4, + MaterialMapJsonConverter +) + +import json + +def getDetector( + xmlFile, + jsonFile="", + logLevel=acts.logging.WARNING, +): + customLogLevel = acts.examples.defaultLogging(logLevel=logLevel) + logger = acts.logging.getLogger("epic.getDetector") + + matDeco = None + if len(jsonFile)>0: + file = Path(jsonFile) + logger.info("Adding material from %s", file.absolute()) + matDeco = acts.IMaterialDecorator.fromFile( + file, + level=customLogLevel(maxLevel=acts.logging.INFO), + ) + + dd4hepConfig = acts.examples.dd4hep.DD4hepGeometryService.Config( + xmlFileNames=[xmlFile], + logLevel=logLevel, + dd4hepLogLevel=customLogLevel(), + ) + detector = acts.examples.dd4hep.DD4hepDetector() + + config = acts.MaterialMapJsonConverter.Config() + + trackingGeometry, deco = detector.finalize(dd4hepConfig, matDeco) + + return detector, trackingGeometry, deco diff --git a/scripts/material_map/geometry_epic.py b/scripts/material_map/geometry_epic.py new file mode 100644 index 000000000..596afa6ca --- /dev/null +++ b/scripts/material_map/geometry_epic.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +import os +import argparse + +import acts + +import epic +from geometry import runGeometry + +if "__main__" == __name__: + p = argparse.ArgumentParser( + description="Script to generate geometry-map.json for ePIC geometry" + ) + p.add_argument( + "-i", + "--xmlFile", + default=( + os.environ.get("DETECTOR_PATH", "") + + "/" + + os.environ.get("DETECTOR_CONFIG", "") + + ".xml" + ), + help="Input xml file containing ePIC geometry", + ) + args = p.parse_args() + + detector, trackingGeometry, decorators = epic.getDetector(args.xmlFile) + + runGeometry( + trackingGeometry, + decorators, + outputDir=os.getcwd(), + outputObj=False, + outputCsv=False, + outputJson=True, + outputRoot=True, + ) diff --git a/scripts/material_map/material_mapping_epic.py b/scripts/material_map/material_mapping_epic.py new file mode 100644 index 000000000..ded6fc7d8 --- /dev/null +++ b/scripts/material_map/material_mapping_epic.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +import os +import argparse + +import acts +from acts.examples import JsonFormat + +import epic +from material_mapping import runMaterialMapping + +if "__main__" == __name__: + + p = argparse.ArgumentParser( + description="Script to generate material map for ePIC geometry" + ) + p.add_argument( + "--xmlFile", + default=os.environ.get("DETECTOR_PATH", "")+"epic_craterlake.xml", + help="input xml file containing ePIC geometry", + ) + p.add_argument( + "--geoFile", + type=str, + default="geometry-map.json", + help="input json file to define volumes and layers used in material mapping", + ) + p.add_argument( + "--matFile", + type=str, + default="material-map.json", + help="output filename for the generated material map, can be json and cbor formats", + ) + args = p.parse_args() + + mapName = args.matFile.split('.')[0] + + detector, trackingGeometry, decorators = epic.getDetector( + args.xmlFile, args.geoFile) + + runMaterialMapping( + trackingGeometry, + decorators, + outputDir = os.getcwd(), + inputDir = os.getcwd(), + readCachedSurfaceInformation=False, + mapVolume= False, + mapName = mapName, + ).run() diff --git a/scripts/material_map/material_recording_epic.py b/scripts/material_map/material_recording_epic.py new file mode 100644 index 000000000..85290e955 --- /dev/null +++ b/scripts/material_map/material_recording_epic.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +import os +import warnings +from pathlib import Path +import argparse + +import acts +from acts.examples import ( + GaussianVertexGenerator, + ParametricParticleGenerator, + FixedMultiplicityGenerator, + EventGenerator, + RandomNumbers, +) + +import acts.examples.dd4hep +import acts.examples.geant4 +import acts.examples.geant4.dd4hep + +import epic +from material_recording import runMaterialRecording + +u = acts.UnitConstants + +_material_recording_executed = False + + +def main(): + + p = argparse.ArgumentParser() + p.add_argument( + "-n", "--events", type=int, default=1000, help="Number of events to generate" + ) + p.add_argument( + "-t", "--tracks", type=int, default=100, help="Particle tracks per event" + ) + p.add_argument( + "-i", "--xmlFile", type=str, default=os.environ.get("DETECTOR_PATH", "") + os.environ.get("DETECTOR_CONFIG", "") + ".xml", help="DD4hep input file" + ) + p.add_argument( + "-o", "--outputName", type=str, default="geant4_material_tracks.root", help="Name of the output rootfile" + ) + p.add_argument( + "--eta_min", + type=float, + default=-8.0, + help="eta min (optional)", + ) + p.add_argument( + "--eta_max", + type=float, + default=8.0, + help="eta max (optional)", + ) + args = p.parse_args() + + detector, trackingGeometry, decorators = epic.getDetector( + args.xmlFile) + + detectorConstructionFactory = ( + acts.examples.geant4.dd4hep.DDG4DetectorConstructionFactory(detector) + ) + + runMaterialRecording( + detectorConstructionFactory=detectorConstructionFactory, + tracksPerEvent=args.tracks, + outputDir=os.getcwd(), + etaRange=(args.eta_min, args.eta_max), + s=acts.examples.Sequencer(events=args.events, numThreads=1), + ).run() + + +if "__main__" == __name__: + main() diff --git a/scripts/material_map/material_validation_epic.py b/scripts/material_map/material_validation_epic.py new file mode 100644 index 000000000..6317ba08f --- /dev/null +++ b/scripts/material_map/material_validation_epic.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +import os +import argparse + +import acts +import acts.examples.dd4hep +from acts.examples import Sequencer + +import epic +from material_validation import runMaterialValidation + + +if "__main__" == __name__: + + p = argparse.ArgumentParser( + description="Script to produce propogation validation for ePIC material mapping." + ) + p.add_argument( + "--xmlFile", + default=os.environ.get("DETECTOR_PATH", "") + os.environ.get("DETECTOR_CONFIG", "") + ".xml", + help="input xml file containing ePIC geometry", + ) + p.add_argument( + "--matFile", + type=str, + default="material-map.json", + help="input material map file, can be either Json or Cbor", + ) + p.add_argument( + "--outputName", + type=str, + default="propagation-material.root", + help="customized name of the output rootfile", + ) + p.add_argument( + "-n","--nevents", + type=int, + default=100, + help="number of events to run", + ) + args = p.parse_args() + + detector, trackingGeometry, decorators = epic.getDetector(args.xmlFile, args.matFile) + + field = acts.ConstantBField(acts.Vector3(0, 0, 0)) + + runMaterialValidation( + trackingGeometry, decorators, field, + outputDir=os.getcwd(), outputName=args.outputName, + s=Sequencer(events=args.nevents, numThreads=-1) + ).run() diff --git a/scripts/material_map/materialmap_config.py b/scripts/material_map/materialmap_config.py new file mode 100644 index 000000000..ead046234 --- /dev/null +++ b/scripts/material_map/materialmap_config.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# Copyright (C) 2024 Shujie Li + +## Generate ePIC material map for ACTS +## read config-map.json and turn on mapping for approach 1 and 2 of each sensitive surface. +import pandas as pd +import numpy as np +import json +import os +import argparse + +if "__main__" == __name__: + p = argparse.ArgumentParser( + description="Script to turn on all approach 1 and 2, and also the beampipe surface in config json file for matieral mapping" + ) + p.add_argument( + "-i","--inputFile", + type=str, + default="config-map.json", + help=" input json file to be modified", + ) + p.add_argument( + "-o","--outputFile", + type=str, + default="config-map_new.json", + help=" output json file", + ) + +args = p.parse_args() +print(args) +fname = args.inputFile +out_name = args.outputFile + + +## load json file +f = open(fname) +dd = json.load(f) + +ee=dd['Volumes']['entries'] + +## print volume name and ID +print ("Volume ID Name Approaches") +for vv in np.arange(len(ee)): + nn = ee[vv]['value']['NAME'] + + if "|" not in nn and "Gap" not in nn: + print(ee[vv]['volume'], nn,"1, 2")#print(ee[vv]['value'])#['NAME']) + if "acts_beampipe_central::Barrel" in nn: + v_beampipe = vv+1 + print(v_beampipe, nn, "X") + +## find apporach 1 and 2 to turn on mapping +for vv in np.arange(1,1+len(dd['Surfaces'])): + for ii,tt in enumerate(dd['Surfaces'][str(vv)]): + if 'approach' in tt: + dd['Surfaces'][str(vv)][ii]['value']['material']['mapMaterial']=True + ## turn on beampipe surface and defind binning + elif vv==v_beampipe: + if tt['value']['bounds']['type']=='CylinderBounds': + # print (dd['Surfaces'][str(vv)][ii]) + dd['Surfaces'][str(vv)][ii]['value']['material']['mapMaterial']=True + dd['Surfaces'][str(vv)][ii]['value']['material']['binUtility']['binningdata'][0]['bins']=36 + dd['Surfaces'][str(vv)][ii]['value']['material']['binUtility']['binningdata'][1]['bins']=200 + + +with open(out_name, "w") as outfile: + json.dump(dd, outfile, indent=4) + +print("Done! Updated config file at "+out_name) diff --git a/scripts/material_map/run_material_map_validation.sh b/scripts/material_map/run_material_map_validation.sh new file mode 100755 index 000000000..3daafd1bd --- /dev/null +++ b/scripts/material_map/run_material_map_validation.sh @@ -0,0 +1,127 @@ +#!/bin/bash +set -e +# script for material map validation with ACTS python bindings +# run as : ./run_material_map_validation.sh --nevents 1000 +# Shujie Li, 03. 2024 (https://github.com/eic/snippets/pull/3) + +# Check if DETECTOR_PATH and DETECTOR_CONFIG are set +if [[ -z ${DETECTOR_PATH} || -z ${DETECTOR_CONFIG} ]] ; then + echo "You must set \$DETECTOR_PATH and \$DETECTOR_CONFIG before running this script." + exit -1 +fi + +# Download required Acts files +ACTS_VERSION="682d2080d36712ac15975340c92f860b25169213" +ACTS_URL="https://github.com/acts-project/acts/raw/" +ACTS_FILES=( + "Examples/Scripts/Python/geometry.py" + "Examples/Scripts/Python/material_mapping.py" + "Examples/Scripts/Python/material_recording.py" + "Examples/Scripts/Python/material_validation.py" + "Examples/Scripts/MaterialMapping/writeMapConfig.py" + "Examples/Scripts/MaterialMapping/configureMap.py" + "Examples/Scripts/MaterialMapping/Mat_map.C" + "Examples/Scripts/MaterialMapping/Mat_map_surface_plot.C" + "Examples/Scripts/MaterialMapping/Mat_map_surface_plot_ratio.C" + "Examples/Scripts/MaterialMapping/Mat_map_surface_plot_dist.C" + "Examples/Scripts/MaterialMapping/Mat_map_surface_plot_1D.C" + "Examples/Scripts/MaterialMapping/materialPlotHelper.cpp" + "Examples/Scripts/MaterialMapping/materialPlotHelper.hpp" +) +for file in ${ACTS_FILES[@]} ; do + if [ ! -f ${file} ] ; then + curl --silent --location --create-dirs --output ${file} ${ACTS_URL}/${ACTS_VERSION}/${file} + fi +done +export PYTHONPATH=$PWD/Examples/Scripts/Python:$PYTHONPATH + +# Default arguments +nevents=1000 +nparticles=1000 +while [[ $# -gt 1 ]] +do + key="$1" + case $key in + --nevents) + nevents=$2 + shift # past value + shift + ;; + --nparticles) + nparticles=$2 + shift # past value + shift + ;; + *) # unknown option + #POSITIONAL+=("$1") # save it in an array for later + echo "unknown option $1" + print_the_help + shift # past argument + ;; + esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +recordingFile=geant4_material_tracks.root +geoFile=geometry-map.json +matFile=material-map.json +trackFile=material-map_tracks.root +propFile=propagation_material + +echo "::group::----GEANTINO SCAN------" +# output geant4_material_tracks.root +# The result of the geantino scan will be a root file containing material tracks. Those contain the direction and production vertex of the geantino, the total material accumulated and all the interaction points in the detector. +python material_recording_epic.py -i ${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml -n ${nevents} -t ${nparticles} -o ${recordingFile} +echo "::endgroup::" + +echo "::group::-----MAPPING Configuration-----" +# map geometry to geometry-map.json +python geometry_epic.py -i ${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml + +# take geometry-map.json and read out config-map.json +python Examples/Scripts/MaterialMapping/writeMapConfig.py ${geoFile} config-map.json + +# turn on approaches and beampipe surfaces for material mapping +# you can always manually adjust the mapmaterial flag and binnings in config-map.json +python materialmap_config.py -i config-map.json -o config-map_new.json + +# turn config-map.json into modified geometry-map.json +python Examples/Scripts/MaterialMapping/configureMap.py ${geoFile} config-map_new.json +echo "::endgroup::" + +echo "::group::----MAPPING------------" +# input: geant4_material_tracks.root, geometry-map.json +# output: material-maps.json or cbor. This is the material map that you want to provide to EICrecon, i.e. -Pacts:MaterialMap=XXX .Please --matFile to specify the name and type +# material-maps_tracks.root(recorded steps from geantino, for validation purpose) +python material_mapping_epic.py --xmlFile ${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml --geoFile ${geoFile} --matFile ${matFile} +echo "::endgroup::" + +echo "::group::----Prepare validation rootfile--------" +# output propagation-material.root +python material_validation_epic.py --xmlFile ${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml --outputName ${propFile}_new --matFile ${matFile} -n ${nevents} +python material_validation_epic.py --xmlFile ${DETECTOR_PATH}/${DETECTOR_CONFIG}.xml --outputName ${propFile}_old --matFile "calibrations/materials-map.cbor" -n ${nevents} +echo "::endgroup::" + +echo "::group::-------Comparison plots---------" +rm -rf Validation/new +mkdir -p Validation/new +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map.C'("'${propFile}_new'.root","'${trackFile}'","Validation/new")' +rm -rf Validation/old +mkdir -p Validation/old +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map.C'("'${propFile}_old'.root","'${trackFile}'","Validation/old")' + +rm -rf Surfaces +mkdir -p Surfaces/new/ratio_plot +mkdir -p Surfaces/new/prop_plot +mkdir -p Surfaces/new/map_plot +mkdir -p Surfaces/old/ratio_plot +mkdir -p Surfaces/old/prop_plot +mkdir -p Surfaces/old/map_plot +mkdir -p Surfaces/dist_plot +mkdir -p Surfaces/1D_plot + +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map_surface_plot_ratio.C'("'${propFile}_new'.root","'${trackFile}'",-1,"Surfaces/new/ratio_plot","Surfaces/new/prop_plot","Surfaces/new/map_plot")' +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map_surface_plot_ratio.C'("'${propFile}_old'.root","'${trackFile}'",-1,"Surfaces/old/ratio_plot","Surfaces/old/prop_plot","Surfaces/old/map_plot")' +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map_surface_plot_dist.C'("'${trackFile}'",-1,"Surfaces/dist_plot")' +root -l -b -q Examples/Scripts/MaterialMapping/Mat_map_surface_plot_1D.C'("'${trackFile}'",-1,"Surfaces/1D_plot")' +echo "::endgroup::"