|
1 | 1 | #!/usr/bin/env python |
2 | 2 | import argparse |
3 | 3 | import json |
| 4 | +import sys |
4 | 5 | import itk |
5 | 6 | from itk import PCT as pct |
6 | 7 | import numpy as np |
@@ -72,8 +73,95 @@ def build_parser(): |
72 | 73 | "--psout", help="Name of tree in output phase space", default="PhaseSpace" |
73 | 74 | ) |
74 | 75 |
|
| 76 | + data_noise_group = parser.add_argument_group("Data noise") |
| 77 | + data_noise_group.add_argument('--noise-measurements', help="Standard deviation of the Gaussian noise on the measurements (energy or time)", type=float) |
| 78 | + data_noise_group.add_argument('--noise-position', help="Standard deviation of the Gaussian noise on the position", type=float) |
| 79 | + data_noise_group.add_argument('--tracker-distance', help="Distance between the two trackers of the upstream and downstream detectors, used to calculate noise on directions", type=float) |
| 80 | + data_noise_group.add_argument('--seed', help="Random number generator seed", type=int) |
| 81 | + |
75 | 82 | return parser |
76 | 83 |
|
| 84 | +def add_noise(pairs, measurement_column, noise_measurements, noise_position, tracker_distance, seed): |
| 85 | + |
| 86 | + rng = np.random.default_rng(seed) |
| 87 | + |
| 88 | + if noise_measurements is not None: |
| 89 | + measurement_in = pairs[measurement_column + "_in"] |
| 90 | + measurement_out = pairs[measurement_column + "_out"] |
| 91 | + measurement_in += rng.normal(scale=noise_measurements, size=len(measurement_in)) |
| 92 | + measurement_out += rng.normal(scale=noise_measurements, size=len(measurement_out)) |
| 93 | + |
| 94 | + if noise_position is not None: |
| 95 | + |
| 96 | + u_in = pairs["u_in"] |
| 97 | + u_out = pairs["u_out"] |
| 98 | + v_in = pairs["v_in"] |
| 99 | + v_out = pairs["v_out"] |
| 100 | + |
| 101 | + noise_u_in = u_in + rng.normal(scale=noise_position, size=len(u_in)) |
| 102 | + noise_u_out = u_out + rng.normal(scale=noise_position, size=len(u_out)) |
| 103 | + noise_v_in = v_in + rng.normal(scale=noise_position, size=len(v_in)) |
| 104 | + noise_v_out = v_out + rng.normal(scale=noise_position, size=len(v_out)) |
| 105 | + |
| 106 | + if tracker_distance is None: |
| 107 | + print("Warning: noise on position was provided, but tracker distance is unspecified. No noise on directions will be applied.", file=sys.stderr) |
| 108 | + else: |
| 109 | + # Recover the point on the second tracker in each detector |
| 110 | + w_in = pairs["w_in"] |
| 111 | + w_out = pairs["w_out"] |
| 112 | + du_in = pairs["du_in"] |
| 113 | + du_out = pairs["du_out"] |
| 114 | + dv_in = pairs["dv_in"] |
| 115 | + dv_out = pairs["dv_out"] |
| 116 | + dw_in = pairs["dw_in"] |
| 117 | + dw_out = pairs["dw_out"] |
| 118 | + |
| 119 | + w_in_2 = w_in - tracker_distance |
| 120 | + slope_in = (w_in_2 - w_in) / dw_in |
| 121 | + u_in_2 = u_in + slope_in * du_in |
| 122 | + v_in_2 = v_in + slope_in * dv_in |
| 123 | + |
| 124 | + w_out_2 = w_out + tracker_distance |
| 125 | + slope_out = (w_out_2 - w_out) / dw_out |
| 126 | + u_out_2 = u_out + slope_out * du_out |
| 127 | + v_out_2 = v_out + slope_out * dv_out |
| 128 | + |
| 129 | + # Add noise to the point on the second tracker |
| 130 | + noise_u_in_2 = u_in_2 + rng.normal(scale=noise_position, size=len(u_in_2)) |
| 131 | + noise_v_in_2 = v_in_2 + rng.normal(scale=noise_position, size=len(v_in_2)) |
| 132 | + noise_u_out_2 = u_out_2 + rng.normal(scale=noise_position, size=len(u_out_2)) |
| 133 | + noise_v_out_2 = v_out_2 + rng.normal(scale=noise_position, size=len(v_out_2)) |
| 134 | + |
| 135 | + # Build a direction from these noisy points |
| 136 | + noise_du_in = noise_u_in - noise_u_in_2 |
| 137 | + noise_dv_in = noise_v_in - noise_v_in_2 |
| 138 | + noise_dw_in = tracker_distance |
| 139 | + noise_du_out = noise_u_out_2 - noise_u_out |
| 140 | + noise_dv_out = noise_v_out_2 - noise_v_out |
| 141 | + noise_dw_out = tracker_distance |
| 142 | + |
| 143 | + norm_in = np.sqrt(noise_du_in**2 + noise_dv_in**2 + noise_dw_in**2) |
| 144 | + norm_out = np.sqrt(noise_du_out**2 + noise_dv_out**2 + noise_dw_out**2) |
| 145 | + |
| 146 | + noise_du_in /= norm_in |
| 147 | + noise_dv_in /= norm_in |
| 148 | + noise_dw_in /= norm_in |
| 149 | + noise_du_out /= norm_out |
| 150 | + noise_dv_out /= norm_out |
| 151 | + noise_dw_out /= norm_out |
| 152 | + |
| 153 | + pairs["du_in"] = noise_du_in |
| 154 | + pairs["du_out"] = noise_du_out |
| 155 | + pairs["dv_in"] = noise_dv_in |
| 156 | + pairs["dv_out"] = noise_dv_out |
| 157 | + pairs["dw_in"] = noise_dw_in |
| 158 | + pairs["dw_out"] = noise_dw_out |
| 159 | + |
| 160 | + pairs["u_in"] = noise_u_in |
| 161 | + pairs["v_in"] = noise_v_in |
| 162 | + pairs["u_out"] = noise_u_out |
| 163 | + pairs["v_out"] = noise_v_out |
| 164 | + |
77 | 165 |
|
78 | 166 | def process(args_info: argparse.Namespace): |
79 | 167 | import uproot |
@@ -186,6 +274,10 @@ def load_tree_as_df(root_file, tree_name): |
186 | 274 | np.recarray.sort(pairs, order=["RunID", "EventID", "TrackID_in", "TrackID_out"]) |
187 | 275 | verbose("Merged input and output phase spaces.") |
188 | 276 |
|
| 277 | + if args_info.noise_measurements is not None or args_info.noise_position is not None: |
| 278 | + verbose("Adding noise…") |
| 279 | + add_noise(pairs, measurement_column, args_info.noise_measurements, args_info.noise_position, args_info.tracker_distance, args_info.seed) |
| 280 | + |
189 | 281 | if args_info.fit is not None: |
190 | 282 | verbose("Converting energy loss or TOF to WEPL…") |
191 | 283 | with open(args_info.fit, encoding="utf-8") as f: |
|
0 commit comments