Source code for vibe.analysis_validation_modes.svd.SVDPerformance_validation_mode

import basf2

try:
    import rawdata
    import tracking
    import svd
except ImportError:
    pass
import modularAnalysis as ma
from typing import List
import numpy as np

from vibe.core.validation_mode import ValidationModeBaseClass
from vibe.core.helper.histogram_tools import Histogram, HistComponent, HistVariable
from vibe.core.utils.misc import fancy_validation_mode_header
from vibe.core.helper.root_helper import makeROOTCompatible

__all__ = [
    "SVDPerformance",
]


[docs] @fancy_validation_mode_header class SVDPerformance(ValidationModeBaseClass): name = "SVDPerformance" # Configuration: Set to True to generate per-ladder histograms (A LOT of histograms!) # Set to False to generate only phi-integrated histograms # TODO: This could be made into a user-configurable option layer generate_per_ladder_histograms = False
[docs] def create_basf2_path(self): main_path = basf2.Path() main_path.add_module("Gearbox") main_path.add_module("Geometry", useDB=True) rawdata.add_unpackers(main_path) # now do reconstruction tracking.add_tracking_reconstruction( main_path, mcTrackFinding=False, trackFitHypotheses=[211], append_full_grid_cdc_eventt0=True, skip_full_grid_cdc_eventt0_if_svd_time_present=False, ) # Reconstruct strips svd.add_svd_create_recodigits(main_path) ma.fillParticleLists(decayStringsWithCuts=[("pi+:all", "")], path=main_path) svd_variables = [ # Cluster properties "SVDClusterCharge", "SVDClusterChargeNormTrkLength", "SVDClusterSNR", "SVDClusterSize", "SVDClusterTime", # For residuals and resolution "SVDResidual", "SVDTrackPrime", "SVDTrackPositionErrorUnbiased", "SVDTruePosition", # Geometric coordinates of a cluster "SVDLayer", "SVDLadder", "SVDSensor", "SVDSide", ] self.variables_to_validation_ntuple( decay_str="pi+:all", variables=svd_variables, path=main_path, ) return main_path
@property def analysis_validation_histograms(self) -> List[Histogram]: """Generate histograms for SVD performance validation. Generates two sets of histograms: 1. Phi-integrated (all ladders): L{layer}_S{sensor}_{side}_{variable} 2. Per-ladder: L{layer}_L{ladder}_S{sensor}_{side}_{variable} """ histograms = [] # Define layer-sensor-ladder mapping (from ROOT macro) layer_config = { 3: {"sensors": 2, "ladders": 7}, # Layer 3: 2 sensors, 7 ladders 4: {"sensors": 3, "ladders": 10}, # Layer 4: 3 sensors, 10 ladders 5: {"sensors": 4, "ladders": 12}, # Layer 5: 4 sensors, 12 ladders 6: {"sensors": 5, "ladders": 16}, # Layer 6: 5 sensors, 16 ladders } # Common track selection (matching ROOT macro) # TODO: Add these to the ntuple to activate the selection # common_selection = "abs(svdTrkz0) < 0.5 and abs(svdTrkd0) < 0.2" common_selection = "" # Define histogram configurations for each variable type histogram_configs = [ { "var_name": "ClCharge", "title": "Cluster Charge", "label": r"Cluster Charge", "unit": r"ke", "scope": (0, 120), "bins": 60, "base_variable": "SVDClusterCharge", }, { "var_name": "ClChargeNorm", "title": "Cluster Charge Normalized", "label": r"Cluster Charge Normalized", "unit": r"ke", "scope": (0, 120), "bins": 60, "base_variable": "SVDClusterChargeNormTrkLength", }, { "var_name": "ClSNR", "title": "Cluster SNR", "label": r"Cluster SNR", "unit": "", "scope": (0, 140), "bins": 50, "base_variable": "SVDClusterSNR", }, { "var_name": "ClSize", "title": "Cluster Size", "label": r"Cluster Size", "unit": "", "scope": (0, 15), "bins": 15, "base_variable": "SVDClusterSize", }, { "var_name": "ClTime", "title": "Cluster Time", "label": r"Cluster Time", "unit": "ns", "scope": (-100, 100), "bins": 200, "base_variable": "SVDClusterTime", }, ] # Loop over layers for layer in [3, 4, 5, 6]: max_sensor = layer_config[layer]["sensors"] max_ladder = layer_config[layer]["ladders"] # Loop over sensors for sensor in range(1, max_sensor + 1): # Loop over sides (0=V/N, 1=U/P) for side_idx, side_name in [(0, "V"), (1, "U")]: # 1. Phi-integrated histograms (all ladders) layer_sensor_side_sel = f"SVDLayer == {layer} and SVDSensor == {sensor} and SVDSide == {side_idx}" # Build full selection if common_selection: full_selection_phi_int = f"{common_selection} and {layer_sensor_side_sel}" else: full_selection_phi_int = layer_sensor_side_sel # Create phi-integrated histograms for each variable for config in histogram_configs: var_name = config["var_name"] formula = config["base_variable"] # Create histogram variable hist_var = HistVariable( df_label=makeROOTCompatible(variable=config["base_variable"]), label=config["label"], unit=config["unit"], scope=config["scope"], bins=config["bins"], ) # Create histogram component component = HistComponent( label=f"L{layer}-S{sensor}-{side_name}", additional_cut_str=full_selection_phi_int, variable=formula, ) # Create histogram histogram = Histogram( name=f"L{layer}_S{sensor}_{side_name}_{var_name}", title=f"Layer {layer}, Sensor {sensor}, {side_name} side", hist_variable=hist_var, hist_components=[component], particle_list="pi+:all", ) histograms.append(histogram) # 2. per-ladder histograms (optional) if self.generate_per_ladder_histograms: for ladder in range(1, max_ladder + 1): layer_ladder_sensor_side_sel = ( f"SVDLayer == {layer} and SVDLadder == {ladder} and " f"SVDSensor == {sensor} and SVDSide == {side_idx}" ) # Build full selection if common_selection: full_selection_per_ladder = f"{common_selection} and {layer_ladder_sensor_side_sel}" else: full_selection_per_ladder = layer_ladder_sensor_side_sel # Create per-ladder histograms for each variable for config in histogram_configs: var_name = config["var_name"] formula = config["base_variable"] # Create histogram variable hist_var = HistVariable( df_label=makeROOTCompatible(variable=config["base_variable"]), label=config["label"], unit=config["unit"], scope=config["scope"], bins=config["bins"], ) # Create histogram component component = HistComponent( label=f"L{layer}-L{ladder}-S{sensor}-{side_name}", additional_cut_str=full_selection_per_ladder, variable=formula, ) # Create histogram histogram = Histogram( name=f"L{layer}_L{ladder}_S{sensor}_{side_name}_{var_name}", title=f"Layer {layer}, Ladder {ladder}, Sensor {sensor}, {side_name} side", hist_variable=hist_var, hist_components=[component], particle_list="pi+:all", ) histograms.append(histogram) return histograms
[docs] def variables_to_validation_ntuple(self, decay_str, variables, path): path.add_module( "SVDVariablesToStorage", outputFileName=self.output_filename, containerName=self.get_tree_name(decay_str), particleListName=decay_str, variablesToNtuple=variables, ) last_module = path.modules()[-1] last_module.set_name(f"{last_module.type()}_{self.name}")
[docs] def get_ntuple_particle_lists(self, basf2_kwargs=None): if basf2_kwargs is None: basf2_kwargs = {} basf2_path = self.create_basf2_path(**basf2_kwargs) return { p.values for m in basf2_path.modules() if m.type() == "SVDVariablesToStorage" for p in m.available_params() if p.name == "particleListName" }
[docs] def get_ntuple_trees(self, basf2_kwargs=None): if basf2_kwargs is None: basf2_kwargs = {} basf2_path = self.create_basf2_path(**basf2_kwargs) return { p.values for m in basf2_path.modules() if m.type() == "SVDVariablesToStorage" for p in m.available_params() if p.name == "containerName" }
[docs] def offline_df_manipulation(self, df): # Convert cluster charge from electrons to kilo-electrons df["SVDClusterCharge"] = df["SVDClusterCharge"] / 1000.0 df["SVDClusterChargeNormTrkLength"] = df["SVDClusterChargeNormTrkLength"] / 1000.0 # Set this as they are not set by the SVDVariablesToStorage module and are required for plotting df[["__experiment__", "__run__", "__event__"]] = np.nan df["__weight__"] = 1.0 return df
@property def plotting_strategies(self) -> List[str]: return [ "per_dataset_root_renderer", "overlay_datasets_root_renderer", ]