diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0837e710daeb293a95758cb89fa40f356eff1ca3..1f4b7b2be9e1220e78f0cc2ff7d2f91236bc34a2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -42,7 +42,7 @@ dd4hep_configure_output() #========================================================================== -SET(DD4HEP_EXAMPLES "AlignDet CLICSiD ClientTests Conditions DDCMS DDCodex DDDigi DDG4 DDG4_MySensDet LHeD OpticalSurfaces Persistency DDCAD SimpleDetector" +SET(DD4HEP_EXAMPLES "AlignDet CLICSiD ClientTests Conditions DDCMS DDCodex DDDigi DDG4 DDG4_MySensDet LHeD OpticalSurfaces OpticalTracker Persistency DDCAD SimpleDetector" CACHE STRING "List of DD4hep Examples to build") SEPARATE_ARGUMENTS(DD4HEP_EXAMPLES) diff --git a/examples/OpticalTracker/.gitignore b/examples/OpticalTracker/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ad9a4fb9702f225a233850321259fef29eaf4a23 --- /dev/null +++ b/examples/OpticalTracker/.gitignore @@ -0,0 +1,2 @@ +install +*.root diff --git a/examples/OpticalTracker/CMakeLists.txt b/examples/OpticalTracker/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7c5b04ebe38f31554b4d90081eeb2637a2d7852c --- /dev/null +++ b/examples/OpticalTracker/CMakeLists.txt @@ -0,0 +1,59 @@ +#========================================================================== +# AIDA Detector description implementation +#-------------------------------------------------------------------------- +# Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN) +# All rights reserved. +# +# For the licensing terms see $DD4hepINSTALL/LICENSE. +# For the list of contributors see $DD4hepINSTALL/doc/CREDITS. +# +#========================================================================== +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) + +IF(NOT TARGET DD4hep::DDCore) + find_package ( DD4hep REQUIRED ) + include ( ${DD4hep_DIR}/cmake/DD4hep.cmake ) + include ( ${DD4hep_DIR}/cmake/DD4hepBuild.cmake ) + dd4hep_configure_output() +ENDIF() + +dd4hep_set_compiler_flags() +dd4hep_use_python_executable() + +#========================================================================== +dd4hep_print("|++> OpticalTracker: ROOT version: ${ROOT_VERSION}") + +if(NOT ${ROOT_VERSION} VERSION_GREATER_EQUAL 6.18.00) + dd4hep_print("|++> Not building OpticalTracker test") + return() +endif() +dd4hep_print("|++> Building OpticalTracker test") + +#-------------------------------------------------------------------------- +dd4hep_configure_output() + +set(OpticalTracker_INSTALL ${CMAKE_INSTALL_PREFIX}/examples/OpticalTracker) +dd4hep_add_plugin(OpticalTrackerExample SOURCES src/*.cpp + USES DD4hep::DDCore DD4hep::DDCond ROOT::Core ROOT::Geom ROOT::GenVector ROOT::MathCore) +install(TARGETS OpticalTrackerExample LIBRARY DESTINATION lib) +install(DIRECTORY compact scripts DESTINATION ${OpticalTracker_INSTALL} ) +dd4hep_configure_scripts( OpticalTracker DEFAULT_SETUP WITH_TESTS) + +# ---Test: run simulation +dd4hep_add_test_reg( OpticalTracker_simulation + COMMAND "${CMAKE_INSTALL_PREFIX}/bin/run_test_OpticalTracker.sh" + EXEC_ARGS ${Python_EXECUTABLE} ${OpticalTracker_INSTALL}/scripts/richsim.py + --outputFile "${OpticalTracker_INSTALL}/sim.root" + REGEX_PASS "TEST: passed" + REGEX_FAIL "Exception;EXCEPTION;ERROR;Error;FATAL" + ) + +# ---Test: Number of raw photon hits +dd4hep_add_test_reg( OpticalTracker_number_of_hits + COMMAND "${CMAKE_INSTALL_PREFIX}/bin/run_test_OpticalTracker.sh" + EXEC_ARGS root.exe -b -x -n -q -l + "${OpticalTracker_INSTALL}/scripts/test_number_of_hits.C(\"${OpticalTracker_INSTALL}/sim.root\")" + REGEX_PASS "TEST: passed" + REGEX_FAIL "TEST: failed" + DEPENDS OpticalTracker_simulation + ) diff --git a/examples/OpticalTracker/README.md b/examples/OpticalTracker/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c07b25ab5c0e4051b2d8f0e49c0e50a2f6e61bab --- /dev/null +++ b/examples/OpticalTracker/README.md @@ -0,0 +1,55 @@ +Proximity Focusing RICH +======================= + +Example RICH demonstrating `Geant4OpticalTracker` Sensitive Detector plugin usage. + +This detector design has been pulled from [EPIC](https://github.com/eic/epic), and was originally developed in +[ATHENA](https://eicweb.phy.anl.gov/EIC/detectors/athena), for the Electron-Ion Collider. + + + +For testing, it is recommended to follow `../README.md` and use `ctest`. See below for guidance for +running this example standalone. + +To use `ctest`, run: +```bash +cd .. # `pwd` should now be `DD4hep/examples` +mkdir build +cd build +cmake -DDD4HEP_EXAMPLES="OpticalTracker" .. && make && make install +ctest --output-on-failure # or use `--verbose` to see all output +``` + +## Local Development +If you want to run this example standalone, without needing to run `ctest`, +make a standalone build. The following assumes that your current working +directory is `DD4hep/examples/OpticalTracker`. + +Build with `cmake`, for example: +```bash +cmake -B build -S . -D CMAKE_INSTALL_PREFIX=install +cmake --build build -- install +``` + +Run a test simulation: +```bash +install/bin/run_test_OpticalTracker.sh \ + python install/examples/OpticalTracker/scripts/richsim.py +``` +- See default settings in `scripts/richsim.py` +- Override these settings, or add additional settings by appending `ddsim` options + +Draw the hits, to show Cherenkov photon rings: +```bash +root sim.root +``` +```cpp +// In ROOT interpreter: +EVENT->Draw("PFRICHHits.position.Y():PFRICHHits.position.X()"); // hit positions +EVENT->Draw("@PFRICHHits.size()"); // raw number of hits per event +``` + +Test for the expected number of hits: +```bash +root -b -q -l scripts/test_number_of_hits.C +``` diff --git a/examples/OpticalTracker/compact/materials.xml b/examples/OpticalTracker/compact/materials.xml new file mode 100644 index 0000000000000000000000000000000000000000..97f37c61d6df622478bdd98d318ce6b38e8c43ad --- /dev/null +++ b/examples/OpticalTracker/compact/materials.xml @@ -0,0 +1,380 @@ +<lccdd> + <properties> + <matrix name="RINDEX__Vacuum" coldim="2" values=" + 1.0*eV 1.0 + 5.1*eV 1.0 + "/> + <matrix name="RINDEX__Air" coldim="2" values=" + 1.0*eV 1.00029 + 5.1*eV 1.00029 + "/> + <!-- PFRICH property tables from https://github.com/cisbani/dRICh/blob/main/share/source/g4dRIChOptics.hh --> + <matrix name="RINDEX__C4F10_PFRICH" coldim="2" values=" + 1.7712*eV 1.0013 + 1.92389*eV 1.0013 + 2.10539*eV 1.00131 + 2.3247*eV 1.00131 + 2.59502*eV 1.00132 + 2.93647*eV 1.00133 + 3.38139*eV 1.00134 + 3.98521*eV 1.00136 + 4.85156*eV 1.0014 + 6.19921*eV 1.00149 + "/> + <matrix name="ABSLENGTH__C4F10_PFRICH" coldim="2" values=" + 1.7712*eV 6.0*m + 1.92389*eV 6.0*m + 2.10539*eV 6.0*m + 2.3247*eV 6.0*m + 2.59502*eV 6.0*m + 2.93647*eV 6.0*m + 3.38139*eV 6.0*m + 3.98521*eV 6.0*m + 4.85156*eV 6.0*m + 6.19921*eV 6.0*m + "/> + <matrix name="RINDEX__Aerogel_PFRICH" coldim="2" values=" + 1.87855*eV 1.01852 + 1.96673*eV 1.01856 + 2.05490*eV 1.01861 + 2.14308*eV 1.01866 + 2.23126*eV 1.01871 + 2.31943*eV 1.01876 + 2.40761*eV 1.01881 + 2.49579*eV 1.01887 + 2.58396*eV 1.01893 + 2.67214*eV 1.01899 + 2.76032*eV 1.01905 + 2.84849*eV 1.01912 + 2.93667*eV 1.01919 + 3.02485*eV 1.01926 + 3.11302*eV 1.01933 + 3.20120*eV 1.01941 + 3.28938*eV 1.01948 + 3.37755*eV 1.01956 + 3.46573*eV 1.01965 + 3.55391*eV 1.01973 + 3.64208*eV 1.01982 + 3.73026*eV 1.01991 + 3.81844*eV 1.02001 + 3.90661*eV 1.02010 + 3.99479*eV 1.02020 + 4.08297*eV 1.02030 + 4.17114*eV 1.02041 + 4.25932*eV 1.02052 + 4.34750*eV 1.02063 + 4.43567*eV 1.02074 + 4.52385*eV 1.02086 + 4.61203*eV 1.02098 + 4.70020*eV 1.02111 + 4.78838*eV 1.02123 + 4.87656*eV 1.02136 + 4.96473*eV 1.02150 + 5.05291*eV 1.02164 + 5.14109*eV 1.02178 + 5.22927*eV 1.02193 + 5.31744*eV 1.02208 + 5.40562*eV 1.02223 + 5.49380*eV 1.02239 + 5.58197*eV 1.02255 + 5.67015*eV 1.02271 + 5.75833*eV 1.02288 + 5.84650*eV 1.02306 + 5.93468*eV 1.02324 + 6.02286*eV 1.02342 + 6.11103*eV 1.02361 + 6.19921*eV 1.02381 + "/> + <matrix name="ABSLENGTH__Aerogel_PFRICH" coldim="2" values=" + 1.87855*eV 140.000*mm + 1.96673*eV 141.973*mm + 2.05490*eV 143.776*mm + 2.14308*eV 145.431*mm + 2.23126*eV 146.955*mm + 2.31943*eV 148.364*mm + 2.40761*eV 149.669*mm + 2.49579*eV 150.882*mm + 2.58396*eV 152.012*mm + 2.67214*eV 153.067*mm + 2.76032*eV 154.055*mm + 2.84849*eV 154.982*mm + 2.93667*eV 155.854*mm + 3.02485*eV 156.674*mm + 3.11302*eV 157.448*mm + 3.20120*eV 158.180*mm + 3.28938*eV 158.872*mm + 3.37755*eV 159.528*mm + 3.46573*eV 160.150*mm + 3.55391*eV 160.742*mm + 3.64208*eV 147.916*mm + 3.73026*eV 128.139*mm + 3.81844*eV 111.378*mm + 3.90661*eV 97.121*mm + 3.99479*eV 84.948*mm + 4.08297*eV 74.518*mm + 4.17114*eV 65.552*mm + 4.25932*eV 57.819*mm + 4.34750*eV 51.130*mm + 4.43567*eV 45.327*mm + 4.52385*eV 40.278*mm + 4.61203*eV 35.873*mm + 4.70020*eV 32.019*mm + 4.78838*eV 28.641*mm + 4.87656*eV 25.670*mm + 4.96473*eV 23.054*mm + 5.05291*eV 20.742*mm + 5.14109*eV 18.698*mm + 5.22927*eV 16.884*mm + 5.31744*eV 15.272*mm + 5.40562*eV 13.837*mm + 5.49380*eV 12.557*mm + 5.58197*eV 11.413*mm + 5.67015*eV 10.389*mm + 5.75833*eV 9.470*mm + 5.84650*eV 8.645*mm + 5.93468*eV 7.902*mm + 6.02286*eV 7.233*mm + 6.11103*eV 6.629*mm + 6.19921*eV 6.082*mm + "/> + <matrix name="RAYLEIGH__Aerogel_PFRICH" coldim="2" values=" + 1.87855*eV 281.107*mm + 1.96673*eV 233.984*mm + 2.05490*eV 196.334*mm + 2.14308*eV 165.962*mm + 2.23126*eV 141.242*mm + 2.31943*eV 120.958*mm + 2.40761*eV 104.188*mm + 2.49579*eV 90.226*mm + 2.58396*eV 78.527*mm + 2.67214*eV 68.663*mm + 2.76032*eV 60.301*mm + 2.84849*eV 53.174*mm + 2.93667*eV 47.070*mm + 3.02485*eV 41.816*mm + 3.11302*eV 37.277*mm + 3.20120*eV 33.336*mm + 3.28938*eV 29.903*mm + 3.37755*eV 26.900*mm + 3.46573*eV 24.265*mm + 3.55391*eV 21.946*mm + 3.64208*eV 19.896*mm + 3.73026*eV 18.080*mm + 3.81844*eV 16.468*mm + 3.90661*eV 15.030*mm + 3.99479*eV 13.746*mm + 4.08297*eV 12.596*mm + 4.17114*eV 11.564*mm + 4.25932*eV 10.637*mm + 4.34750*eV 9.799*mm + 4.43567*eV 9.043*mm + 4.52385*eV 8.358*mm + 4.61203*eV 7.738*mm + 4.70020*eV 7.172*mm + 4.78838*eV 6.659*mm + 4.87656*eV 6.191*mm + 4.96473*eV 5.762*mm + 5.05291*eV 5.370*mm + 5.14109*eV 5.011*mm + 5.22927*eV 4.681*mm + 5.31744*eV 4.379*mm + 5.40562*eV 4.100*mm + 5.49380*eV 3.844*mm + 5.58197*eV 3.606*mm + 5.67015*eV 3.386*mm + 5.75833*eV 3.184*mm + 5.84650*eV 2.996*mm + 5.93468*eV 2.822*mm + 6.02286*eV 2.660*mm + 6.11103*eV 2.510*mm + 6.19921*eV 2.370*mm + "/> + <matrix name="RINDEX__Acrylic_PFRICH" coldim="2" values=" + 4.13281*eV 1.5017 + 4.22099*eV 1.5017 + 4.30916*eV 1.5017 + 4.39734*eV 1.5017 + 4.48552*eV 1.5017 + 4.57369*eV 1.5017 + 4.66187*eV 1.5017 + 4.75005*eV 1.5017 + 4.83822*eV 1.5017 + 4.9264*eV 1.5017 + 5.01458*eV 1.5017 + 5.10275*eV 1.5017 + 5.19093*eV 1.5017 + 5.27911*eV 1.5017 + 5.36728*eV 1.5017 + 5.45546*eV 1.5017 + 5.54364*eV 1.5017 + 5.63181*eV 1.5017 + 5.71999*eV 1.5017 + 5.80817*eV 1.5017 + 5.89634*eV 1.5017 + 5.98452*eV 1.5017 + 6.0727*eV 1.5017 + 6.16087*eV 1.5017 + 6.24905*eV 1.5017 + 6.33723*eV 1.5017 + 6.4254*eV 1.5017 + 6.51358*eV 1.5017 + 6.60176*eV 1.5017 + 6.68993*eV 1.5017 + 6.77811*eV 1.5017 + 6.86629*eV 1.5017 + 6.95446*eV 1.5017 + 7.04264*eV 1.5017 + 7.13082*eV 1.5017 + 7.21899*eV 1.5017 + 7.30717*eV 1.5017 + 7.39535*eV 1.5017 + 7.48353*eV 1.5017 + 7.5717*eV 1.5017 + 7.65988*eV 1.5017 + 7.74806*eV 1.5017 + 7.83623*eV 1.5017 + 7.92441*eV 1.5017 + 8.01259*eV 1.5017 + 8.10076*eV 1.5017 + 8.18894*eV 1.5017 + 8.27712*eV 1.5017 + 8.36529*eV 1.5017 + 8.45347*eV 1.5017 + "/> + <matrix name="ABSLENGTH__Acrylic_PFRICH" coldim="2" values=" + 4.13281*eV 82.0704*mm + 4.22099*eV 36.9138*mm + 4.30916*eV 13.3325*mm + 4.39734*eV 5.03627*mm + 4.48552*eV 2.3393*mm + 4.57369*eV 1.36177*mm + 4.66187*eV 0.933192*mm + 4.75005*eV 0.708268*mm + 4.83822*eV 0.573082*mm + 4.9264*eV 0.483641*mm + 5.01458*eV 0.420282*mm + 5.10275*eV 0.373102*mm + 5.19093*eV 0.33662*mm + 5.27911*eV 0.307572*mm + 5.36728*eV 0.283902*mm + 5.45546*eV 0.264235*mm + 5.54364*eV 0.247641*mm + 5.63181*eV 0.233453*mm + 5.71999*eV 0.221177*mm + 5.80817*eV 0.210456*mm + 5.89634*eV 0.201012*mm + 5.98452*eV 0.192627*mm + 6.0727*eV 0.185134*mm + 6.16087*eV 0.178399*mm + 6.24905*eV 0.172309*mm + 6.33723*eV 0.166779*mm + 6.4254*eV 0.166779*mm + 6.51358*eV 0.166779*mm + 6.60176*eV 0.166779*mm + 6.68993*eV 0.166779*mm + 6.77811*eV 0.166779*mm + 6.86629*eV 0.166779*mm + 6.95446*eV 0.166779*mm + 7.04264*eV 0.166779*mm + 7.13082*eV 0.166779*mm + 7.21899*eV 0.166779*mm + 7.30717*eV 0.166779*mm + 7.39535*eV 0.166779*mm + 7.48353*eV 0.166779*mm + 7.5717*eV 0.166779*mm + 7.65988*eV 0.166779*mm + 7.74806*eV 0.166779*mm + 7.83623*eV 0.166779*mm + 7.92441*eV 0.166779*mm + 8.01259*eV 0.166779*mm + 8.10076*eV 0.166779*mm + 8.18894*eV 0.166779*mm + 8.27712*eV 0.166779*mm + 8.36529*eV 0.166779*mm + 8.45347*eV 0.166779*mm + "/> + </properties> + + <materials> + <material name="Air"> + <D type="density" unit="g/cm3" value="0.0012"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + </material> + <material name="AirOptical"> + <D type="density" unit="g/cm3" value="0.0012"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + <property name="RINDEX" ref="RINDEX__Air"/> + <property name="ABSLENGTH" coldim="2" values="1*eV 200*m 5*eV 200*m"/> + </material> + <material name="Vacuum"> + <D type="density" unit="g/cm3" value="0.0000000001"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + </material> + <material name="VacuumOptical"> + <D type="density" unit="g/cm3" value="0.0000000001"/> + <fraction n="0.754" ref="N"/> + <fraction n="0.234" ref="O"/> + <fraction n="0.012" ref="Ar"/> + <property name="RINDEX" ref="RINDEX__Vacuum"/> + <property name="ABSLENGTH" coldim="2" values="1*eV 2000*m 5*eV 2000*m"/> + </material> + <material name="SiliconDioxide"> <!-- density from `G4_SILICON_DIOXIDE` (NIST DB) --> + <D type="density" value="2.32" unit="g/cm3"/> + <composite n="1" ref="Si"/> + <composite n="2" ref="O"/> + </material> + <material name="Plexiglass"> + <D type="density" value="1.19" unit="g/cm3"/> + <composite n="5" ref="C"/> + <composite n="8" ref="H"/> + <composite n="2" ref="O"/> + </material> + <material name="PolyvinylAcetate"> + <D type="density" value="1.19" unit="g/cm3"/> + <composite n="4" ref="C"/> + <composite n="6" ref="H"/> + <composite n="2" ref="O"/> + </material> + <material name="C4F10_PFRICH"> + <D type="density" value="0.009935" unit="g/cm3"/> + <composite n="4" ref="C"/> + <composite n="10" ref="F"/> + <property name="RINDEX" ref="RINDEX__C4F10_PFRICH"/> + <property name="ABSLENGTH" ref="ABSLENGTH__C4F10_PFRICH"/> + </material> + <material name="Aerogel_PFRICH"> + <D type="density" value="0.110" unit="g/cm3"/> + <comment> n_air = [dens(Si02)-dens(aerogel)] / [dens(Si02)-dens(Air) ] </comment> + <fraction n=" (2.32-0.11) / (2.32-0.0012)" ref="Air"/> + <fraction n="1 - (2.32-0.11) / (2.32-0.0012)" ref="SiliconDioxide"/> + <property name="RINDEX" ref="RINDEX__Aerogel_PFRICH"/> + <property name="ABSLENGTH" ref="ABSLENGTH__Aerogel_PFRICH"/> + <property name="RAYLEIGH" ref="RAYLEIGH__Aerogel_PFRICH"/> + </material> + <material name="Acrylic_PFRICH"> + <D type="density" value="1.19" unit="g/cm3"/> + <comment> TO BE IMPROVED </comment> + <fraction n="0.99" ref="Plexiglass"/> + <fraction n="0.01" ref="PolyvinylAcetate"/> + <property name="RINDEX" ref="RINDEX__Acrylic_PFRICH"/> + <property name="ABSLENGTH" ref="ABSLENGTH__Acrylic_PFRICH"/> + </material> + </materials> + + <surfaces> + <opticalsurface name="SensorSurface_PFRICH" model="glisur" finish="polished" type="dielectric_dielectric"> + <property name="EFFICIENCY" coldim="2" values=" + 1*eV 1 + 4*eV 1 + 7*eV 1 + "/> + </opticalsurface> + </surfaces> + +</lccdd> diff --git a/examples/OpticalTracker/compact/pfrich.xml b/examples/OpticalTracker/compact/pfrich.xml new file mode 100644 index 0000000000000000000000000000000000000000..9f3581575a79cb14371afcf46604b010385ac8fa --- /dev/null +++ b/examples/OpticalTracker/compact/pfrich.xml @@ -0,0 +1,209 @@ +<lccdd + xmlns:compact="http://www.lcsim.org/schemas/compact/1.0" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xs:noNamespaceSchemaLocation="http://www.lcsim.org/schemas/compact/1.0/compact.xsd" + > + +<info + name="PFRICH" + title="Proximity Focusing Ring Imaging Cherenkov Detector" + author="Christopher Dilks" + url="https://github.com/eic/epic" + status="development" + version="1.0" + > + <comment> + Example RICH Detector + </comment> +</info> + +<debug> + <type name="surface" value="0"/> + <type name="material" value="0"/> + <type name="readout" value="0"/> + <type name="segmentation" value="0"/> + <type name="limits" value="0"/> + <type name="region" value="0"/> + <type name="includes" value="0"/> +</debug> + +<includes> + <gdmlFile ref="${DD4hepINSTALL}/DDDetectors/compact/elements.xml"/> + <file ref="materials.xml"/> +</includes> + +<display> + <vis name="vessel_vis" r="102/256" g="102/256" b="102/256" alpha="1.0" showDaughters="true" visible="true" /> + <vis name="gas_vis" r="100/256" g="200/256" b="0/256" alpha="0.5" showDaughters="true" visible="true" /> + <vis name="aerogel_vis" r="0/256" g="161/256" b="156/256" alpha="1.0" showDaughters="true" visible="true" /> + <vis name="filter_vis" r="248/256" g="188/256" b="0/256" alpha="1.0" showDaughters="true" visible="true" /> + <vis name="sensor_vis" r="0/256" g="96/256" b="156/256" alpha="1.0" showDaughters="true" visible="true" /> + <vis name="service_vis" r="102/256" g="102/256" b="102/256" alpha="1.0" showDaughters="true" visible="true" /> + <vis name="no_vis" showDaughters="false" visible="false" /> +</display> + +<define> + <!-- global constants --> + <constant name="world_side" value="30*m"/> + <constant name="world_x" value="world_side"/> + <constant name="world_y" value="world_side"/> + <constant name="world_z" value="100*m"/> + <constant name="Pi" value="3.14159265359"/> + <constant name="mil" value="0.0254*mm"/> + <constant name="inch" value="2.54*cm"/> + <!-- RICH constants --> + <constant name="PFRICH_ID" value="1"/> <!-- unique ID for this detector --> + <constant name="PFRICH_Length" value="58.0*cm"/> <!-- vessel z-length --> + <constant name="PFRICH_zmin" value="-150*cm"/> <!-- vessel front --> + <constant name="PFRICH_zmax" value="PFRICH_zmin - PFRICH_Length"/> <!-- vessel back --> + <constant name="PFRICH_rmin0" value="5*cm"/> <!-- bore radius at vessel frontplane --> + <constant name="PFRICH_rmin1" value="7*cm"/> <!-- bore radius at vessel backplane --> + <constant name="PFRICH_rmax" value="93*cm"/> <!-- vessel backplane radius --> + <constant name="PFRICH_wall_thickness" value="0.5*cm"/> <!-- thickness of radial walls --> + <constant name="PFRICH_window_thickness" value="0.1*cm"/> <!-- thickness of entrance and exit walls --> + <constant name="PFRICH_aerogel_thickness" value="3.0*cm"/> <!-- aerogel thickness --> + <constant name="PFRICH_filter_thickness" value="0.3*mm"/> <!-- filter thickness (between aerogel and gas) --> + <constant name="PFRICH_sensor_active_size" value="24.0*mm"/> <!-- sensor side length (effective area) --> + <constant name="PFRICH_sensor_full_size" value="25.8*mm"/> <!-- sensor side length (full size, with enclosure) --> + <constant name="PFRICH_sensor_thickness" value="0.5*mm"/> <!-- sensor thickness --> + <constant name="PFRICH_sensor_dist" value="40*cm"/> <!-- distance between aerogel exit plane and sensor entrance plane --> + <constant name="PFRICH_num_px" value="8"/> <!-- number of pixels along one side of the sensor --> + <constant name="PFRICH_pixel_pitch" value="PFRICH_sensor_active_size / PFRICH_num_px"/> <!-- center-to-center distance between sensor pixels --> +</define> + +<detectors> + + <!-- /detectors/detector --> + <comment> + ### PFRICH: Proximity Focusing RICH + </comment> + <detector + id="PFRICH_ID" + name="PFRICH" + type="PFRICH" + readout="PFRICHHits" + gas="C4F10_PFRICH" + material="Aluminum" + vis_vessel="vessel_vis" + vis_gas="gas_vis" + > + + <!-- /detectors/detector/dimensions --> + <comment> + #### Vessel + - dimensions: + - `zmin`: z-position of vessel front plane + - `length`: overall z-length of the full vessel + - `rmin0` and `rmin1`: bore radius at front plane and back plane, respectively + - `rmax0` and `rmax1`: outer radius of vessel, at front plane and back plane, respectively + - `wall_thickness`: thickness of radial walls + - `window_thickness`: thickness of entrance and exit disks + </comment> + <dimensions + zmin="PFRICH_zmin" + zmax="PFRICH_zmax" + length="PFRICH_Length" + rmin0="PFRICH_rmin0" + rmin1="PFRICH_rmin1" + rmax0="PFRICH_rmax" + rmax1="PFRICH_rmax" + wall_thickness="PFRICH_wall_thickness" + window_thickness="PFRICH_window_thickness" + /> + + <!-- /detectors/detector/radiator --> + <comment> + #### Radiator + - `radiator` includes aerogel and a filter; the filter is applied to the back of the aerogel, so that it + separates the aerogel and gas radiators + - dimensions: + - `frontplane`: front of the aerogel, w.r.t. front plane of the vessel envelope + - `rmin` and `rmax`: inner and outer radius (at the front plane; radial bounds are conical) + - `thickness`: radiator thickness, defined separately for aerogel and filter + </comment> + <radiator + frontplane="-PFRICH_window_thickness" + rmin="PFRICH_rmin0 + PFRICH_wall_thickness + 0.2*cm" + rmax="(PFRICH_rmax/PFRICH_zmax)*PFRICH_zmin + 8.0*cm" + > + <aerogel material="Aerogel_PFRICH" vis="aerogel_vis" thickness="PFRICH_aerogel_thickness" /> + <filter material="Acrylic_PFRICH" vis="filter_vis" thickness="PFRICH_filter_thickness" /> + </radiator> + + <!-- /detectors/detector/sensors --> + <comment> + #### Sensors + </comment> + <sensors> + + <!-- /detectors/detector/sensors/module --> + <comment> + ##### Sensor module + - dimensions: + - `side`: side length of the square module + - `thickness`: thickness of the sensor module + - `gap`: provides room between the squares, to help prevent them from overlapping + - notes: + - the values of `side` and `gap` will determine how many sensors there are, since the + sensor placement algorithm will try to place as many as it can in the specified region + - the material is `AirOptical`, to resolve a technical issue with the refractive boundary + </comment> + <module + material="AirOptical" + surface="SensorSurface_PFRICH" + vis="sensor_vis" + side="PFRICH_sensor_active_size" + thickness="PFRICH_sensor_thickness" + gap="0.5*(PFRICH_sensor_full_size-PFRICH_sensor_active_size) + 0.5*mm" + /> + + <!-- /detectors/detector/sensors/plane --> + <comment> + ##### Sensor plane + - sensors will be placed on a plane + - plane dimensions: + - `sensordist`: distance between sensor plane active surface (e.g., photocathode) and aerogel backplane + - `rmin`: minimum radial position of a sensor's centroid + - `rmax`: maximum radial position of a sensor's centroid + </comment> + <plane + sensordist="PFRICH_sensor_dist" + rmin="PFRICH_rmin1 + 2*cm" + rmax="PFRICH_rmax - 4*cm" + /> + + <services> + <comment> + Material should be equivalent with 3x0.5cm Al, spread over the entire available distance. + </comment> + <component name="aluminum" thickness="5*mm" vis="service_vis" material="Aluminum"/> + <component name="air" thickness="40*mm" vis="no_vis" material="Air"/> + <component name="aluminum" thickness="5*mm" vis="service_vis" material="Aluminum"/> + <component name="air" thickness="40*mm" vis="no_vis" material="Air"/> + <component name="aluminum" thickness="5*mm" vis="service_vis" material="Aluminum"/> + </services> + + </sensors> + </detector> +</detectors> + +<comment> + #### Readout + - segmentation: square matrix of pixels + - `grid_size_x,y`: size of each sensor pixel + - `offset_x,y`: specified such that the `x` and `y` field values are unsigned +</comment> +<readouts> + <readout name="PFRICHHits"> + <segmentation + type="CartesianGridXY" + grid_size_x="PFRICH_pixel_pitch" + grid_size_y="PFRICH_pixel_pitch" + offset_x="-0.5*(PFRICH_num_px-1)*PFRICH_pixel_pitch" + offset_y="-0.5*(PFRICH_num_px-1)*PFRICH_pixel_pitch" + /> + <id>system:8,module:12,x:32:-16,y:-16</id> + </readout> +</readouts> + +</lccdd> diff --git a/examples/OpticalTracker/doc/geometry.png b/examples/OpticalTracker/doc/geometry.png new file mode 100644 index 0000000000000000000000000000000000000000..0b85d7c9a47118c0e33ae795e06bc6ca11162773 Binary files /dev/null and b/examples/OpticalTracker/doc/geometry.png differ diff --git a/examples/OpticalTracker/scripts/richsim.py b/examples/OpticalTracker/scripts/richsim.py new file mode 100755 index 0000000000000000000000000000000000000000..b371ce3a964c74fe58306fa30d01a92e4416e57e --- /dev/null +++ b/examples/OpticalTracker/scripts/richsim.py @@ -0,0 +1,92 @@ +""" +DD4hep simulation with some argument parsing +Based on M. Frank and F. Gaede runSim.py + @author A.Sailer + @version 0.1 + +Modified with settings for RICH simulation +""" +from __future__ import absolute_import, unicode_literals +import logging +import sys +import os + +from DDSim.DD4hepSimulation import DD4hepSimulation + + +if __name__ == "__main__": + logging.basicConfig( + format="%(name)-16s %(levelname)s %(message)s", + level=logging.INFO, + stream=sys.stdout, + ) + logger = logging.getLogger("DDSim") + + SIM = DD4hepSimulation() + + # Ensure that Cerenkov and optical physics are always loaded + def setupCerenkov(kernel): + from DDG4 import PhysicsList + + seq = kernel.physicsList() + cerenkov = PhysicsList(kernel, "Geant4CerenkovPhysics/CerenkovPhys") + cerenkov.MaxNumPhotonsPerStep = 10 + cerenkov.MaxBetaChangePerStep = 10.0 + cerenkov.TrackSecondariesFirst = False + cerenkov.VerboseLevel = 0 + cerenkov.enableUI() + seq.adopt(cerenkov) + ph = PhysicsList(kernel, "Geant4OpticalPhotonPhysics/OpticalGammaPhys") + ph.addParticleConstructor("G4OpticalPhoton") + ph.VerboseLevel = 0 + ph.enableUI() + seq.adopt(ph) + return None + + SIM.physics.setupUserPhysics(setupCerenkov) + + # Allow energy depositions to 0 energy in trackers (which include optical detectors) + SIM.filter.tracker = "edep0" + + # Some detectors are only sensitive to optical photons + SIM.filter.filters["opticalphotons"] = dict( + name="ParticleSelectFilter/OpticalPhotonSelector", + parameter={"particle": "opticalphoton"}, + ) + SIM.filter.mapDetFilter["PFRICH"] = "opticalphotons" + + # Use the optical tracker for the PFRICH + SIM.action.mapActions["PFRICH"] = "Geant4OpticalTrackerAction" + + # Disable user tracker particle handler, so hits can be associated to photons + SIM.part.userParticleHandler = "" + + # Particle gun settings: pions with fixed energy and theta, varying phi + SIM.numberOfEvents = 500 + SIM.enableGun = True + SIM.gun.energy = "40*GeV" + SIM.gun.particle = "pi+" + SIM.gun.thetaMin = "195.0*deg" + SIM.gun.thetaMax = "195.1*deg" + SIM.gun.distribution = "cos(theta)" + + # Installed compact file, otherwise assume the user passed `--compactFile` + install_prefix = os.environ.get("DD4hepExamplesINSTALL") + if install_prefix: + SIM.compactFile = install_prefix + "/examples/OpticalTracker/compact/pfrich.xml" + + # Output file (assuming CWD) + SIM.outputFile = "sim.root" + + # Override with user options + SIM.parseOptions() + + # Run the simulation + try: + SIM.run() + logger.info("TEST: passed") + except NameError as e: + logger.fatal("TEST: failed") + if "global name" in str(e): + globalToSet = str(e).split("'")[1] + logger.fatal("Unknown global variable, please add\nglobal %s\nto your steeringFile" % globalToSet) diff --git a/examples/OpticalTracker/scripts/test_number_of_hits.C b/examples/OpticalTracker/scripts/test_number_of_hits.C new file mode 100644 index 0000000000000000000000000000000000000000..f5bef5bd5034410f91c72868f68044c96670a1c2 --- /dev/null +++ b/examples/OpticalTracker/scripts/test_number_of_hits.C @@ -0,0 +1,21 @@ +void test_number_of_hits(TString sim_file_name="sim.root") { + + // test requirements + const Double_t expected_number_of_hits = 230.0; + const Double_t allowed_deviation = 15.0; + + // get average number of hits + auto sim_file = new TFile(sim_file_name); + auto t = (TTree*) sim_file->Get("EVENT"); + auto h = new TH1D("h","<hits>",500,0,1000); + t->Project("h","@PFRICHHits.size()"); + auto ave_hits = h->GetMean(); + + // check if this is the expected number of hits + bool pass_test = abs(ave_hits - expected_number_of_hits) < allowed_deviation; + std::cout << "TEST: " << (pass_test ? "passed" : "failed") + << " with average number of hits = " << ave_hits + << " (expected " << expected_number_of_hits + << "+/-" << allowed_deviation << ")" + << std::endl; +} diff --git a/examples/OpticalTracker/src/PFRICH_geo.cpp b/examples/OpticalTracker/src/PFRICH_geo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..779e2243dcad32da70a95a2c6b05c727d296093c --- /dev/null +++ b/examples/OpticalTracker/src/PFRICH_geo.cpp @@ -0,0 +1,270 @@ +//---------------------------------- +// pfRICH: Proximity Focusing RICH +// Author: C. Dilks +//---------------------------------- + +#include "DD4hep/DetFactoryHelper.h" +#include "DD4hep/OpticalSurfaces.h" +#include "DD4hep/Printout.h" +#include "DDRec/DetectorData.h" +#include <XML/Helper.h> + +using namespace dd4hep; +using namespace dd4hep::rec; + +// create the detector +static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetector sens) +{ + xml::DetElement detElem = handle; + std::string detName = detElem.nameStr(); + int detID = detElem.id(); + xml::Component dims = detElem.dimensions(); + OpticalSurfaceManager surfMgr = desc.surfaceManager(); + DetElement det(detName, detID); + + // constant attributes ----------------------------------------------------------- + // - vessel + double vesselLength = dims.attr<double>(_Unicode(length)); + double vesselZmin = dims.attr<double>(_Unicode(zmin)); + double vesselRmin0 = dims.attr<double>(_Unicode(rmin0)); + double vesselRmin1 = dims.attr<double>(_Unicode(rmin1)); + double vesselRmax0 = dims.attr<double>(_Unicode(rmax0)); + double vesselRmax1 = dims.attr<double>(_Unicode(rmax1)); + double wallThickness = dims.attr<double>(_Unicode(wall_thickness)); + double windowThickness = dims.attr<double>(_Unicode(window_thickness)); + auto vesselMat = desc.material(detElem.attr<std::string>(_Unicode(material))); + auto gasvolMat = desc.material(detElem.attr<std::string>(_Unicode(gas))); + auto vesselVis = desc.visAttributes(detElem.attr<std::string>(_Unicode(vis_vessel))); + auto gasvolVis = desc.visAttributes(detElem.attr<std::string>(_Unicode(vis_gas))); + // - radiator (applies to aerogel and filter) + auto radiatorElem = detElem.child(_Unicode(radiator)); + double radiatorRmin = radiatorElem.attr<double>(_Unicode(rmin)); + double radiatorRmax = radiatorElem.attr<double>(_Unicode(rmax)); + double radiatorFrontplane = radiatorElem.attr<double>(_Unicode(frontplane)); + // - aerogel + auto aerogelElem = radiatorElem.child(_Unicode(aerogel)); + auto aerogelMat = desc.material(aerogelElem.attr<std::string>(_Unicode(material))); + auto aerogelVis = desc.visAttributes(aerogelElem.attr<std::string>(_Unicode(vis))); + double aerogelThickness = aerogelElem.attr<double>(_Unicode(thickness)); + // - filter + auto filterElem = radiatorElem.child(_Unicode(filter)); + auto filterMat = desc.material(filterElem.attr<std::string>(_Unicode(material))); + auto filterVis = desc.visAttributes(filterElem.attr<std::string>(_Unicode(vis))); + double filterThickness = filterElem.attr<double>(_Unicode(thickness)); + // - sensor module + auto sensorElem = detElem.child(_Unicode(sensors)).child(_Unicode(module)); + auto sensorMat = desc.material(sensorElem.attr<std::string>(_Unicode(material))); + auto sensorVis = desc.visAttributes(sensorElem.attr<std::string>(_Unicode(vis))); + auto sensorSurf = surfMgr.opticalSurface(sensorElem.attr<std::string>(_Unicode(surface))); + double sensorSide = sensorElem.attr<double>(_Unicode(side)); + double sensorGap = sensorElem.attr<double>(_Unicode(gap)); + double sensorThickness = sensorElem.attr<double>(_Unicode(thickness)); + // - sensor plane + auto sensorPlaneElem = detElem.child(_Unicode(sensors)).child(_Unicode(plane)); + double sensorPlaneDist = sensorPlaneElem.attr<double>(_Unicode(sensordist)); + double sensorPlaneRmin = sensorPlaneElem.attr<double>(_Unicode(rmin)); + double sensorPlaneRmax = sensorPlaneElem.attr<double>(_Unicode(rmax)); + + // BUILD VESSEL ////////////////////////////////////// + /* - `vessel`: aluminum enclosure, the mother volume of the pfRICH + * - `gasvol`: gas volume, which fills `vessel`; all other volumes defined below + * are children of `gasvol` + */ + + // tank solids + double boreDelta = vesselRmin1 - vesselRmin0; + Cone vesselSolid( + vesselLength / 2.0, + vesselRmin1, + vesselRmax1, + vesselRmin0, + vesselRmax0 + ); + Cone gasvolSolid( + vesselLength / 2.0 - windowThickness, + vesselRmin1 + wallThickness, + vesselRmax1 - wallThickness, + vesselRmin0 + wallThickness, + vesselRmax0 - wallThickness + ); + + // volumes + Volume vesselVol(detName, vesselSolid, vesselMat); + Volume gasvolVol(detName+"_gas", gasvolSolid, gasvolMat); + vesselVol.setVisAttributes(vesselVis); + gasvolVol.setVisAttributes(gasvolVis); + + // reference positions + /* - the vessel is created such that the center of the cylindrical tank volume + * coincides with the origin; this is called the "origin position" of the vessel + * - when the vessel (and its children volumes) is placed, it is translated in + * the z-direction to be in the proper full-detector integration location + * - these reference positions are for the frontplane and backplane of the vessel, + * with respect to the vessel origin position + */ + auto originFront = Position(0., 0., vesselLength / 2.0); + + // sensitive detector type + sens.setType("tracker"); + + // BUILD RADIATOR ////////////////////////////////////// + + // attributes + double airGap = 0.01 * mm; // air gap between aerogel and filter (FIXME? actually it's currently a gas gap) + + // solid and volume: create aerogel and filter + Cone aerogelSolid( + aerogelThickness / 2, + radiatorRmin + boreDelta * aerogelThickness / vesselLength, // at backplane + radiatorRmax, + radiatorRmin, // at frontplane + radiatorRmax + ); + Cone filterSolid( + filterThickness / 2, + radiatorRmin + boreDelta * (aerogelThickness + airGap + filterThickness) / vesselLength, // at backplane + radiatorRmax, + radiatorRmin + boreDelta * (aerogelThickness + airGap) / vesselLength, // at frontplane + radiatorRmax + ); + Volume aerogelVol(detName + "_aerogel", aerogelSolid, aerogelMat); + Volume filterVol(detName + "_filter", filterSolid, filterMat); + aerogelVol.setVisAttributes(aerogelVis); + filterVol.setVisAttributes(filterVis); + + // aerogel placement and surface properties + // FIXME: define skin properties for aerogel and filter + auto radiatorPos = Position(0., 0., radiatorFrontplane - 0.5 * aerogelThickness) + originFront; + auto aerogelPV = gasvolVol.placeVolume( + aerogelVol, + Transform3D(Translation3D(radiatorPos.x(), radiatorPos.y(), radiatorPos.z())) // re-center to originFront + ); + DetElement aerogelDE(det, "aerogel_de", 0); + aerogelDE.setPlacement(aerogelPV); + + // filter placement and surface properties + auto filterPV = gasvolVol.placeVolume( + filterVol, + Transform3D( + Translation3D(0., 0., -airGap) // add an airgap (FIXME: actually a gas gap) + * + Translation3D(radiatorPos.x(), radiatorPos.y(), radiatorPos.z()) // re-center to originFront + * + Translation3D(0., 0., -(aerogelThickness + filterThickness) / 2.) // move to aerogel backplane + ) + ); + DetElement filterDE(det, "filter_de", 0); + filterDE.setPlacement(filterPV); + + // BUILD SENSORS /////////////////////// + + // solid and volume: single sensor module + Box sensorSolid(sensorSide / 2., sensorSide / 2., sensorThickness / 2.); + Volume sensorVol(detName + "_sensor", sensorSolid, sensorMat); + sensorVol.setVisAttributes(sensorVis); + + // sensitivity + sensorVol.setSensitiveDetector(sens); + + // sensor plane positioning: we want `sensorPlaneDist` to be the distance between the + // aerogel backplane (i.e., aerogel/filter boundary) and the sensor active surface (e.g, photocathode) + double sensorZpos = radiatorFrontplane - aerogelThickness - sensorPlaneDist - 0.5 * sensorThickness; + auto sensorPlanePos = Position(0., 0., sensorZpos) + originFront; // reference position + // miscellaneous + int imod = 0; // module number + double tBoxMax = vesselRmax1; // sensors will be tiled in tBox, within annular limits + + // SENSOR MODULE LOOP ------------------------ + /* cartesian tiling loop + * - start at (x=0,y=0), to center the grid + * - loop over positive-x positions; for each, place the corresponding negative-x sensor too + * - nested similar loop over y positions + */ + double sx, sy; + for (double usx = 0; usx <= tBoxMax; usx += sensorSide + sensorGap) { + for (int sgnx = 1; sgnx >= (usx > 0 ? -1 : 1); sgnx -= 2) { + for (double usy = 0; usy <= tBoxMax; usy += sensorSide + sensorGap) { + for (int sgny = 1; sgny >= (usy > 0 ? -1 : 1); sgny -= 2) { + + // sensor (x,y) center + sx = sgnx * usx; + sy = sgny * usy; + + // annular cut + if (std::hypot(sx, sy) < sensorPlaneRmin || std::hypot(sx, sy) > sensorPlaneRmax) + continue; + + // placement (note: transformations are in reverse order) + auto sensorPV = gasvolVol.placeVolume( + sensorVol, + Transform3D( + Translation3D(sensorPlanePos.x(), sensorPlanePos.y(), sensorPlanePos.z()) // move to reference position + * + Translation3D(sx, sy, 0.) // move to grid position + ) + ); + + // generate LUT for module number -> sensor position, for readout mapping tests + // printf("%d %f %f\n",imod,sensorPV.position().x(),sensorPV.position().y()); + + // properties + sensorPV.addPhysVolID("module", imod); + DetElement sensorDE(det, Form("sensor_de_%d", imod), imod); + sensorDE.setPlacement(sensorPV); + SkinSurface sensorSkin(desc, sensorDE, "sensor_optical_surface", sensorSurf, sensorVol); // FIXME: 3rd arg needs `imod`? + sensorSkin.isValid(); + + // increment sensor module number + imod++; + } + } + } + } + // END SENSOR MODULE LOOP ------------------------ + + // Add service material if desired (added by Sylvester Joosten) //////////////// + if (detElem.child("sensors").hasChild(_Unicode(services))) { + xml_comp_t x_service = detElem.child("sensors").child(_Unicode(services)); + Assembly service_vol("services"); + service_vol.setVisAttributes(desc, x_service.visStr()); + + // Compute service total thickness from components + double total_thickness = 0; + for (xml_coll_t ci(x_service, _Unicode(component)); ci; ++ci) { + total_thickness += xml_comp_t(ci).thickness(); + } + + int ncomponents = 0; + double thickness_sum = -total_thickness / 2.0; + for (xml_coll_t ci(x_service, _Unicode(component)); ci; ++ci, ncomponents++) { + xml_comp_t x_comp = ci; + double thickness = x_comp.thickness(); + Tube c_tube{sensorPlaneRmin, sensorPlaneRmax, thickness / 2}; + Volume c_vol{_toString(ncomponents, "component%d"), c_tube, desc.material(x_comp.materialStr())}; + c_vol.setVisAttributes(desc, x_comp.visStr()); + service_vol.placeVolume(c_vol, Position(0, 0, thickness_sum + thickness / 2.0)); + thickness_sum += thickness; + } + gasvolVol.placeVolume(service_vol, + Transform3D(Translation3D(sensorPlanePos.x(), sensorPlanePos.y(), + sensorPlanePos.z() - sensorThickness - total_thickness))); + } + + // VESSEL PLACEMENT ///////////////////////////////////////////////////////////// + + // place gas volume + PlacedVolume gasvolPV = vesselVol.placeVolume(gasvolVol, Position(0, 0, 0)); + DetElement gasvolDE(det, "gasvol_de", 0); + gasvolDE.setPlacement(gasvolPV); + + // place mother volume (vessel) + Volume motherVol = desc.pickMotherVolume(det); + PlacedVolume vesselPV = motherVol.placeVolume(vesselVol, Position(0, 0, vesselZmin) - originFront); + vesselPV.addPhysVolID("system", detID); + det.setPlacement(vesselPV); + + return det; +} + +// clang-format off +DECLARE_DETELEMENT(PFRICH, createDetector)