Skip to content

Commit c743bab

Browse files
committed
Implement labels in the output. Tested.
1 parent d019ed3 commit c743bab

5 files changed

Lines changed: 96 additions & 116 deletions

File tree

src/attpc_engine/detector/simulator.py

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818

1919
@njit
20-
def dict_to_points(points: NumbaTypedDict[int, int]) -> np.ndarray:
20+
def dict_to_points(
21+
points: NumbaTypedDict[int, tuple[int, int]],
22+
) -> tuple[np.ndarray, np.ndarray]:
2123
"""
2224
Converts dictionary of N pad,tb keys with corresponding number of electrons
2325
to Nx3 array where each row is [pad, tb, e], now combined over pad/tb combos.
@@ -29,17 +31,19 @@ def dict_to_points(points: NumbaTypedDict[int, int]) -> np.ndarray:
2931
3032
Returns
3133
-------
32-
point_array: numpy.ndarray
33-
Array of points.
34+
tuple[numpy.ndarray, numpy.ndarray]
35+
Array of points and lables (in that order)
3436
"""
3537
point_array = np.empty((len(points), 3), dtype=float)
36-
for idx, point in enumerate(points.items()):
37-
tb, pad = unpair(point[0])
38+
label_array = np.empty(len(points), dtype=types.int64)
39+
for idx, (key, data) in enumerate(points.items()):
40+
tb, pad = unpair(key)
3841
point_array[idx, 0] = pad
3942
point_array[idx, 1] = tb
40-
point_array[idx, 2] = point[1]
43+
point_array[idx, 2] = data[0]
44+
label_array[idx] = data[1]
4145

42-
return point_array
46+
return point_array, label_array
4347

4448

4549
def simulate(
@@ -49,38 +53,31 @@ def simulate(
4953
mass_numbers: np.ndarray,
5054
config: Config,
5155
rng: Generator,
52-
indicies: list[int] | None,
53-
) -> np.ndarray:
54-
nuclei_to_sim = None
55-
if indicies is not None:
56-
nuclei_to_sim = indicies
57-
else:
58-
# default nuclei to sim, all final outgoing particles
59-
nuclei_to_sim = [idx for idx in range(2, len(proton_numbers), 2)]
60-
nuclei_to_sim.append(len(proton_numbers) - 1) # add the last
61-
62-
points = Dict.empty(key_type=types.int64, value_type=types.int64)
63-
64-
for idx in nuclei_to_sim:
56+
indicies: list[int],
57+
) -> tuple[np.ndarray, np.ndarray]:
58+
points = Dict.empty(
59+
key_type=types.int64, value_type=types.Tuple(types=[types.int64, types.int64])
60+
)
61+
for idx in indicies:
6562
if proton_numbers[idx] == 0:
6663
continue
6764
nucleus = nuclear_map.get_data(proton_numbers[idx], mass_numbers[idx])
6865
momentum = momenta[idx]
69-
generate_point_cloud(momentum, vertex, nucleus, config, rng, points)
66+
generate_point_cloud(momentum, vertex, nucleus, config, rng, points, idx)
7067

7168
# Convert to numpy array of [pad, tb, e], now combined over pad/tb combos
72-
point_array = dict_to_points(points)
69+
point_array, label_array = dict_to_points(points)
7370

7471
# Wiggle point TBs over interval [0.0, 1.0). This simulates effect of converting
7572
# the (in principle) int TBs to floats.
7673
point_array[:, 1] += rng.uniform(low=0.0, high=1.0, size=len(point_array))
7774

7875
# Remove points outside legal bounds in time. TODO check if this is needed
79-
point_array = point_array[
80-
np.logical_and(0 <= point_array[:, 1], point_array[:, 1] < NUM_TB)
81-
]
76+
mask = np.logical_and(0 <= point_array[:, 1], point_array[:, 1] < NUM_TB)
77+
point_array = point_array[mask]
78+
label_array = label_array[mask]
8279

83-
return point_array
80+
return point_array, label_array
8481

8582

8683
def run_simulation(
@@ -107,9 +104,18 @@ def run_simulation(
107104
print(f"Applying detector effects to kinematics from file: {input_path}")
108105
input = h5.File(input_path, "r")
109106
input_data_group: h5.Group = input["data"] # type: ignore
110-
proton_numbers = input_data_group.attrs["proton_numbers"]
107+
proton_numbers: np.ndarray = input_data_group.attrs["proton_numbers"] # type: ignore
111108
mass_numbers = input_data_group.attrs["mass_numbers"]
112109

110+
# Decide which nuclei to sim, either by user input or all reaction final products
111+
nuclei_to_sim = None
112+
if indicies is not None:
113+
nuclei_to_sim = indicies
114+
else:
115+
# default nuclei to sim, all final outgoing particles
116+
nuclei_to_sim = [idx for idx in range(2, len(proton_numbers), 2)]
117+
nuclei_to_sim.append(len(proton_numbers) - 1) # add the last
118+
113119
n_events: int = input_data_group.attrs["n_events"] # type: ignore
114120
miniters = int(0.01 * n_events)
115121
n_chunks: int = input_data_group.attrs["n_chunks"] # type: ignore
@@ -138,7 +144,7 @@ def run_simulation(
138144
dataset: h5.Dataset = input_data_group[f"chunk_{chunk}"][ # type: ignore
139145
f"event_{event_number}"
140146
] # type: ignore
141-
cloud = simulate(
147+
cloud, labels = simulate(
142148
dataset[:].copy(), # type: ignore
143149
np.array(
144150
[
@@ -151,13 +157,13 @@ def run_simulation(
151157
mass_numbers, # type: ignore
152158
config,
153159
rng,
154-
indicies,
160+
nuclei_to_sim,
155161
)
156162

157163
if len(cloud) == 0:
158164
continue
159165

160-
writer.write(cloud, config, event_number)
166+
writer.write(cloud, labels, config, event_number)
161167
writer.close()
162168
print("Done.")
163169
print("----------------------------------------")

src/attpc_engine/detector/solver.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ def generate_point_cloud(
346346
config: Config,
347347
rng: Generator,
348348
points: NumbaTypedDict,
349+
label: int,
349350
) -> None:
350351
"""Create the point cloud
351352
@@ -391,4 +392,5 @@ def generate_point_cloud(
391392
track,
392393
electrons,
393394
points,
395+
label,
394396
)

src/attpc_engine/detector/transporter.py

Lines changed: 28 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ def point_transport(
124124
time: float,
125125
center: tuple[float, float],
126126
electrons: int,
127-
points: NumbaTypedDict[int, int],
127+
points: NumbaTypedDict[int, tuple[int, int]],
128+
label: int,
128129
):
129130
"""
130131
Transports all electrons created at a point in a simulated nucleus' track
@@ -157,9 +158,9 @@ def point_transport(
157158
if pad != -1 and pad not in BEAM_PADS_ARRAY:
158159
tb = int(time) # Convert from absolute time bucket to discretized
159160
id = pair(tb, pad)
160-
points[id] = (
161-
points.get(id, 0) + electrons
162-
) # The get returns 0 if the key doesn't exist
161+
charge, _ = points.get(id, (0, 0)) # The get returns 0 if the key doesn't exist
162+
charge += electrons
163+
points[id] = (charge, label)
163164

164165

165166
@njit
@@ -170,7 +171,8 @@ def transverse_transport(
170171
center: tuple[float, float],
171172
electrons: int,
172173
sigma_t: float,
173-
points: NumbaTypedDict[int, int],
174+
points: NumbaTypedDict[int, tuple[int, int]],
175+
label: int,
174176
):
175177
"""
176178
Transports all electrons created at a point in a simulated nucleus'
@@ -233,59 +235,9 @@ def transverse_transport(
233235
* electrons
234236
)
235237
)
236-
points[id] = (
237-
points.get(id, 0) + pixel_electrons
238-
) # The get returns 0 if the key doesn't exist
239-
240-
241-
@njit
242-
def find_pads_hit(
243-
pad_grid: np.ndarray,
244-
grid_edges: np.ndarray,
245-
time: float,
246-
center: tuple[float, float],
247-
electrons: int,
248-
sigma_t: float,
249-
points: NumbaTypedDict[int, int],
250-
):
251-
"""
252-
Finds the pads hit by transporting the electrons created at a point in
253-
the nucleus' trajectory to the pad plane and applies transverse diffusion, if selected.
254-
255-
Parameters
256-
----------
257-
pad_grid: numpy.ndarray
258-
Grid of pad id for a given index, where index is calculated from x-y position
259-
grid_edges: numpy.ndarray
260-
Edges of the pad grid in mm, as well as the step size of the grid in mm
261-
Allows conversion of position to grid index. 3 element array [low_edge, hi_edge, step]
262-
time: float
263-
Time of point being transported.
264-
center: tuple[float, float]
265-
(x,y) position of point being transported.
266-
electrons: int
267-
Number of electrons made at point being transported.
268-
sigma_t: float
269-
Standard deviation of transverse diffusion at point
270-
being transported.
271-
points: numba.typed.Dict[int, int]
272-
A dictionary mapping a unique pad,tb key to the number of electrons, which
273-
will be filled by this function
274-
"""
275-
# Point transport
276-
if sigma_t == 0.0:
277-
point_transport(pad_grid, grid_edges, time, center, electrons, points)
278-
# Transverse diffusion transport
279-
else:
280-
transverse_transport(
281-
pad_grid,
282-
grid_edges,
283-
time,
284-
center,
285-
electrons,
286-
sigma_t,
287-
points,
288-
)
238+
charge, _ = points.get(id, (0, 0))
239+
charge += pixel_electrons
240+
points[id] = (charge, label) # The get returns 0 if the key doesn't exist
289241

290242

291243
@njit
@@ -297,7 +249,8 @@ def transport_track(
297249
dv: float,
298250
track: np.ndarray,
299251
electrons: np.ndarray,
300-
points: NumbaTypedDict[int, int],
252+
points: NumbaTypedDict[int, tuple[int, int]],
253+
label: int,
301254
):
302255
"""
303256
High-level function that transports each point in a nucleus' trajectory
@@ -333,6 +286,19 @@ def transport_track(
333286
center = (row[0], row[1])
334287
point_electrons = electrons[idx]
335288
sigma_t = np.sqrt(2.0 * diffusion * dv * time / efield)
336-
find_pads_hit(
337-
pad_grid, grid_edges, time, center, point_electrons, sigma_t, points
338-
)
289+
if sigma_t == 0.0:
290+
point_transport(
291+
pad_grid, grid_edges, time, center, point_electrons, points, label
292+
)
293+
# Transverse diffusion transport
294+
else:
295+
transverse_transport(
296+
pad_grid,
297+
grid_edges,
298+
time,
299+
center,
300+
point_electrons,
301+
sigma_t,
302+
points,
303+
label,
304+
)

src/attpc_engine/detector/writer.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ class SimulationWriter(Protocol):
2323
Closes the writer.
2424
"""
2525

26-
def write(self, data: np.ndarray, config: Config, event_number: int) -> None:
26+
def write(
27+
self, data: np.ndarray, labels: np.ndarray, config: Config, event_number: int
28+
) -> None:
2729
"""
2830
Writes a simulated point cloud to the point cloud file.
2931
@@ -83,8 +85,6 @@ def convert_to_spyral(
8385
(x, y) coordinates of each pad's center on the pad plane in mm.
8486
pad_sizes: np.ndarray
8587
Contains size of each pad.
86-
adc_threshold: int
87-
Minimum ADC signal amplitude a point must have in the point cloud.
8888
8989
Returns
9090
-------
@@ -107,11 +107,7 @@ def convert_to_spyral(
107107
storage[idx, 6] = point[1]
108108
storage[idx, 7] = pad_sizes[int(point[0])]
109109

110-
if adc_threshold >= 4095:
111-
raise ValueError(
112-
"adc_threshold cannot be equal to or greater than the max GET ADC value!"
113-
)
114-
return storage[storage[:, 3] > adc_threshold]
110+
return storage
115111

116112

117113
class SpyralWriter:
@@ -191,7 +187,9 @@ def create_next_file(self) -> None:
191187
self.file = h5.File(path, "w")
192188
self.cloud_group: h5.Group = self.file.create_group("cloud")
193189

194-
def write(self, data: np.ndarray, config: Config, event_number: int) -> None:
190+
def write(
191+
self, data: np.ndarray, labels: np.ndarray, config: Config, event_number: int
192+
) -> None:
195193
"""
196194
Writes a simulated point cloud to the point cloud file.
197195
@@ -224,21 +222,27 @@ def write(self, data: np.ndarray, config: Config, event_number: int) -> None:
224222
config.pad_sizes,
225223
config.elec_params.adc_threshold,
226224
)
225+
# apply ADC threshold
226+
mask = spyral_format[:, 3] > config.elec_params.adc_threshold
227+
spyral_format = spyral_format[mask]
228+
labels = labels[mask]
227229
# Make sure we're still sorted in z
228230
indicies = np.argsort(spyral_format[:, 2])
229231
spyral_format = spyral_format[indicies]
232+
labels = labels[indicies]
230233

231-
dset = self.cloud_group.create_dataset(
234+
pc_dset = self.cloud_group.create_dataset(
232235
f"cloud_{event_number}", data=spyral_format
233236
)
234-
235-
dset.attrs["orig_run"] = self.run_number
236-
dset.attrs["orig_event"] = event_number
237+
pc_dset.attrs["orig_run"] = self.run_number
238+
pc_dset.attrs["orig_event"] = event_number
237239
# No ic stuff from simulation
238-
dset.attrs["ic_amplitude"] = -1.0
239-
dset.attrs["ic_multiplicity"] = -1.0
240-
dset.attrs["ic_integral"] = -1.0
241-
dset.attrs["ic_centroid"] = -1.0
240+
pc_dset.attrs["ic_amplitude"] = -1.0
241+
pc_dset.attrs["ic_multiplicity"] = -1.0
242+
pc_dset.attrs["ic_integral"] = -1.0
243+
pc_dset.attrs["ic_centroid"] = -1.0
244+
245+
_ = self.cloud_group.create_dataset(f"labels_{event_number}", data=labels)
242246

243247
# We wrote an event
244248
self.events_written += 1

tests/test_detector.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
PadParams,
55
Config,
66
)
7-
from attpc_engine.detector.simulator import SimEvent
7+
from attpc_engine.detector.simulator import simulate
88
from attpc_engine import nuclear_map
99

1010
from spyral_utils.nuclear.target import GasTarget
@@ -45,17 +45,19 @@ def test_simulation_event():
4545
# all protons bby
4646
fake_data = np.array(
4747
[
48-
[0.0, 0.0, 0.0, 938.0],
49-
[0.0, 0.0, 0.0, 938.0],
50-
[0.0, 0.0, 0.0, 938.0],
51-
[0.0, 0.0, 0.0, 938.0],
48+
[0.0, 0.0, 10.0, 938.0],
49+
[0.0, 0.0, 10.0, 938.0],
50+
[0.0, 0.0, 10.0, 938.0],
51+
[0.0, 0.0, 10.0, 938.0],
5252
]
5353
)
5454

5555
proton_numbers = np.array([1, 1, 1, 1])
5656
mass_numbers = np.array([1, 1, 1, 1])
5757
vertex = np.array([1.0, 1.0, 1.0])
58+
config = Config(detector, electronics, pads)
59+
rng = np.random.default_rng()
5860

59-
event = SimEvent(fake_data, vertex, proton_numbers, mass_numbers)
61+
event = simulate(fake_data, vertex, proton_numbers, mass_numbers, config, rng, [0])
6062

61-
assert len(event.nuclei) == 2
63+
assert len(event) == 2

0 commit comments

Comments
 (0)