Microphysics Simulation
Now that you learned how to run a simulation with fuse from a geant4 file up to raw_records we will take a look the the data that fuse can produce in the intermediate simulation steps focusing on the microphysics simulation. This part of the XENONnT simulation chain was formerly done using the epix software.
Imports & Simulation Context
Just like in the Getting_Started notebook we will first do our necessary imports and then set up the full chain simulation context.
[ ]:
import fuse
[ ]:
st = fuse.context.xenonnt_fuse_full_chain_simulation(
output_folder="./fuse_data",
simulation_config="sr0_dev",
)
st.set_config(
{
"path": "/project2/lgrandi/xenonnt/simulations/testing",
"file_name": "pmt_neutrons_100.root",
"entry_stop": 10,
}
)
run_number = "00000"
Running the Simulation
Just like in the Getting_Started notebook we will run the simulation using the st.make(run_number, "target") function from strax. This time we will tell fuse explicitly to save the intermediate simulation output.
Reading the root file and assigning the cluster index
Before we do some simulation and start to calculate stuff, we need to read in the root file from Geant4 and convert it into a data format that can be handled by strax. This step is done in the ChunkInput plugin and the result can be accessed using target geant4_interactions. This plugin used uproot to open the root file and then converts it into numpy arrays. Each event is then assigned a time based on the config option source_rate. The plugin will cut delayed interaction and divide
the data into chunks if necessary.
In the next simulation step we will give each interaction a cluster index. This is done by the FindCluster plugin. First, all interactions are grouped by time and then a DBSCAN algorithm is used to spacially cluster the interactions. The output of this plugin can be accessed using target cluster_index and can be loaded along with the geant4_interactions.
[ ]:
st.make(run_number, "geant4_interactions")
st.make(run_number, "cluster_index")
geant4_interactions = st.get_df(run_number, ["geant4_interactions", "cluster_index"])
Lets take a look at the data:
[ ]:
geant4_interactions.head(10)
Clustering and Volume Cuts
In the next step, all interactions with the same cluster_ids are merged. The energy of the interactions is summed up and the position and time is calculated as the weighted average of the positions of the individual interactions. The interaction type of the cluster is determined either by the interaction with the highest energy or by the first interaction in the cluster. The interaction type is later used to choose the correct emmision model.
Following the clustering, the VolumeProperties plugin is used to assign physical properties to different detector regions, like the xenon density and the ability to create S2s. The plugin also assigns a vol_id to each interaction based on its volume. The volume definitions are stored in a dictionary in fuse.common.
After the volume properties have been assigned, the VolumeSelection plugin is used to select only interactions in the detector regions of interest. By default, these are the TPC and the region below the cathode.
The next step is the SelectionMerger plugin. This plugin merges all interactions that pass the selection into a new data set, the interactions_in_roi. By default, the only selection applied is the volume selection, as defined in the DefaultSimulation plugin. However, more complex selections can be defined, for example using the EnergyCut plugin to select only events with a certain energy range. The selection logic can be used by registring - as an example - the
LowEnergySimulation plugin, which inherits from SelectionMerger and applies both the volume selection and an energy cut defined in the EnergyCut plugin.
[ ]:
st.make(run_number, "clustered_interactions")
st.make(run_number, "volume_properties")
st.make(run_number, "interactions_in_roi")
Electric Field and Emission Model
The aim of this simulation part is to model the scintillation and ionization processes at the interaction site. First we need to estimate the electric field strength at the interaction position. This is done in the ElectricField plugin using a simulated field map. The field values can be accessed using the target electric_field_values. Next we can estimate the number of produced electrons and photons using an emission model. The default implementation of fuse uses the NestYields
plugin where nestpy is used. fuse also provides alternative plugins where the yields are calculated using BBF or a beta response model. These plugins should only be used if you know what you are doing. The result of the emission model can be accessed using the target quanta.
[ ]:
st.make(run_number, "electric_field_values")
st.make(run_number, "quanta")
Finally we can collect the simulation results of the last few steps using the MicroPhysicsSummary plugin. This plugin is a strax.MergeOnlyPlugin and does not do any calculations. It just merges the results of the previous plugins and can be accessed using the target microphysics_summary.
[ ]:
st.make(run_number, "microphysics_summary")
microphysics_summary = st.get_df(run_number, ["microphysics_summary"])
[ ]:
microphysics_summary.head()