SND@LHC Software
Loading...
Searching...
No Matches
TTCluster.py
Go to the documentation of this file.
1#!/usr/bin/python
2
3import ROOT
4import numpy as np
5import math
6from math import exp, sqrt, log, ceil
7import random
8import matplotlib.pyplot as plt
9
10from ROOT.TMath import Landau, Gaus, Poisson
11from ShipGeoConfig import ConfigRegistry
12
13
16
17# Position of SiPMs in the detector (left/right and up/down)
18sipm_hor_pos = +1 # at X = +len_vert / 2
19sipm_vert_pos = -1 # at Y = -len_hor / 2
20
21# If reversed is true the channels counted in reverse way
22# (0->511 => 511<-0). It connected with how the modules are placed
23is_sipm_hor_reversed = True
24is_sipm_vert_reversed = True
25
26# SiPM geometry
27# hw - half width, pitch - one channel width, charr - array of 64 channels without gaps,
28# edge - from left and right sides of the SiPM, gap - between two charr,
29# array - 128 channels with the gap, biggap - gap between two arrays,
30# ch_max_num - max channel in the SiPM (0-511),
31# n_solid_sipms - number of "charr" arrays
32
33hw = 13.06 / 2.
34pitch = 0.025
35charr = 1.6
36edge = 0.017
37gap = 0.025
38array = gap + 2.*charr
39biggap = 0.042
40ch_max_num = 511
41n_solid_sipms = 8
42
43# SiPM geometry. If a point is in the solid part then the position will be counted.
44# Else false value returns. When
45sipm_map = (
46 (-hw, -hw+edge, False),
47 (-hw+edge, -hw+edge+charr, (-0.5, 63.5)),
48 (-hw+edge+charr, -hw+edge+charr+gap, False),
49 (-hw+edge+charr+gap, -hw+edge+array, (63.5, 127.5)),
50 (-hw+edge+array, -hw+edge+array+biggap, False),
51 (-hw+edge+array+biggap, -hw+edge+array+biggap+charr, (127.5, 191.5)),
52 (-hw+edge+array+biggap+charr, -hw+edge+array+biggap+charr+gap, False),
53 (-hw+edge+array+biggap+charr+gap, -hw+edge+array+biggap+array, (191.5, 255.5)),
54 (-hw+edge+array+biggap+array, biggap/2., False),
55 (biggap/2., biggap/2.+charr, (255.5, 319.5)),
56 (biggap/2.+charr, biggap/2.+charr+gap, False),
57 (biggap/2.+charr+gap, biggap/2.+array, (319.5, 383.5)),
58 (biggap/2.+array, biggap/2.+array+biggap, False),
59 (biggap/2.+array+biggap, biggap/2.+array+biggap+charr, (383.5, 447.5)),
60 (biggap/2.+array+biggap+charr, biggap/2.+array+biggap+charr+gap, False),
61 (biggap/2.+array+biggap+charr+gap, biggap/2.+array+biggap+charr+array, (447.5, 511.5)),
62 (biggap/2.+array+biggap+charr+array, biggap/2.+array+biggap+charr+array+edge, False)
63)
64
65# If a point is in the dead space then the following channel value will be selected
66gaps_map = {
67 0.: -0.5,
68 1.: 63.5,
69 2.: 127.5,
70 3.: 191.5,
71 4.: 255.5,
72 5.: 319.5,
73 6.: 383.5,
74 7.: 447.5,
75 8.: 511.5
76}
77
78# Light yield attenuation A*exp(B*x) + C*exp(D*x)
79# Sigma depends on the distance from a SiPM the also. The following parameters
80ly_loss_params = 20.78, -0.26, 7.89, -3.06
81ly_loss_sigma_params = 6.8482, -0.5757, 3.4458
82cluster_width_mean_params = 0.6661, -0.006955, 2.163
83cluster_width_sigma_params = 1.103, -0.0005751
84
85# The random width in the range is manually set as 1% of events
86# It covers cases where the width is 5-10
87random_width_persent = 0.01
88
89# Possible values of LY, channel position and cluser width
90ly_min = 4.5
91ly_max = 104
92chpos_min = -0.5
93chpos_max = 511.5
94cluster_width_min = 1
95cluster_width_max = 10
96
97# Energy deposit converts in the light yield range linearly. Energy less then 0.18 MeV gives doesn't registered.
98# Energy more then 0.477 MeV converts randomly with approximation distribution.
99energy_range = 0.18, 0.477
100
101# The maximum value of the CDF integral
102CDF_integral = 0.0185640424
103
104# Parameters that are used for linear conversion of deposit energy to the range
105# equal to the light yield range
106ly_linear_params = 332.882, -58.7085
107
108# The coefficient between the maximums of CDF(E) and CDF(LY), a little differs
109k_cdfs_corr = 0.993076766938
110
111# The addition of a little randomness in the converting from the energy distibution to
112# the light yield distibution
113sigma_in_percent = 0.01
114
115# 4 sigma range includes 95% of events. It needs for creating a cluster
116sigma_from_width = 1 / 4.
117
118# The following parameters will be used in the converting from the energy distibution to
119# the light yield distibution.
120# Dictionaries format is following - (x_min, x_max) : (parameters), approximating function.
121
122# ly_CDF_params - approximation of CDF of the light yield distribution.
123# It is doesn't used already.
124ly_CDF_params = {
125 (4.5, 13): (
126 (-13.2, 1.976),
127 lambda ly, *p: exp(p[0] + p[1]*sqrt(ly))
128 ),
129 (13, 104): (
130 (0.0108183, -0.179752, -19.2, 0.00772965),
131 lambda ly, *p: p[0]*(1 - exp(p[1]*(ly+p[2]))) / (1 + exp(p[1]*(ly+p[2]))) + p[3]
132 )
133}
134
135
136# Get a CDF value from LY (actually from an energy deposit which preliminarily linearly
137# converted to the range of the light yield (4.5 - 104 ph.e.)
138ly_CDF_landau_params = {
139 (4.5, 15): (
140 (0.001038, -0.000378, 3.53e-05),
141 lambda ly, *p: p[0] + p[1]*ly + p[2]*ly**2
142 ),
143 (15, 40): (
144 (-0.001986, -0.0003014, 7.031e-05, -2.067e-06, 1.892e-08),
145 lambda ly, *p: p[0] + p[1]*ly + p[2]*ly**2 + p[3]*ly**3 + p[4]*ly**4
146 ),
147 (40, 104): (
148 (-0.007149, 0.001056, -1.779e-05, 1.41e-07, -4.29e-10),
149 lambda ly, *p: p[0] + p[1]*ly + p[2]*ly**2 + p[3]*ly**3 + p[4]*ly**4
150 )
151}
152
153# Get a light yild value from a CDF values
154CDF_ly_params = {
155 (0., 0.0006): (
156 (89., 4.152, 0.0001574, -1.326e+04, 4.3),
157 lambda cdf, *p: p[0] * sqrt(p[1]*(cdf+p[2])) * (1-exp(p[3]*cdf)) + p[4]
158 ),
159 (0.0006, 0.012): (
160 (158, 1.035, 0.24, 217),
161 lambda cdf, *p: p[0] * log(sqrt(cdf)+p[1]) + p[2]*exp(p[3]*cdf)
162 ),
163 (0.012, 0.018561405): (
164 (9.36, 335.984, -18100, -400, 15),
165 lambda cdf, *p: p[0] * log((p[1]-p[2]*cdf)/(p[1]+p[2]*cdf)) + p[3]*cdf + p[4]
166 ),
167 (0.018561405, 0.0185640424): (
168 (9.36, 335.984, -18100, -400, 15),
169 lambda cdf, *p: (p[0] * log((p[1]-p[2]*0.018561405)/(p[1]+p[2]*0.018561405))
170 + p[3]*0.018561405 + p[4])
171 )
172}
173
174
177
178design2018 = {'dy': 10.,'dv': 6,'ds': 9,'nud': 3,'caloDesign': 3,'strawDesign': 10}
179dy = design2018['dy']
180dv = design2018['dv']
181ds = design2018['ds']
182nud = design2018['nud']
183caloDesign = design2018['caloDesign']
184strawDesign = design2018['strawDesign']
185geofile = None
186
187ship_geo = ConfigRegistry.loadpy("$FAIRSHIP/geometry/geometry_config.py", Yheight=dy,
188 tankDesign=dv, muShieldDesign=ds, nuTauTargetDesign=nud, CaloDesign=caloDesign,
189 strawDesign=strawDesign, muShieldGeo=geofile)
190
191n_hor_planes = ship_geo.NuTauTT.n_hor_planes # 11
192n_vert_planes = ship_geo.NuTauTT.n_vert_planes # 7
193scifimat_hor = ship_geo.NuTauTT.scifimat_hor
194scifimat_vert = ship_geo.NuTauTT.scifimat_vert
195scifimat_width = ship_geo.NuTauTT.scifimat_width # 13.06 cm
196scifimat_z = ship_geo.NuTauTT.scifimat_z # 0.145 cm
197n_tt_stations = ship_geo.NuTauTT.n # 19
198
199
202
203def cm_to_channel(locpos, sipm_map=sipm_map, gaps_map=gaps_map, pitch=pitch, charr=charr,
204 reverse=False, ch_max_num=ch_max_num, n_solid_sipms=n_solid_sipms):
205
206 """
207 It converts a particle position (an event) measured in cm to a position measured
208 in channels. The SiPM map is used. The position is in the scifi modul frame.
209 """
210
211 if reverse is True:
212 for left, right, ch_range in sipm_map:
213 if left <= locpos <= right:
214 if not ch_range is False:
215 ch_start = ch_range[0]
216 return ch_max_num - ((locpos-left) / pitch + ch_start)
217 else:
218 return gaps_map.get((n_solid_sipms - (locpos + hw) // charr), False)
219 elif reverse is False:
220 for left, right, ch_range in sipm_map:
221 if left <= locpos <= right:
222 if not ch_range is False:
223 ch_start = ch_range[0]
224 return (locpos-left) / pitch + ch_start
225 else:
226 return gaps_map.get((locpos + hw) // charr, False)
227
228def channel_to_cm(channelpos, sipm_map=sipm_map, reverse=False, pitch=pitch):
229
230 """
231 It converts a particle position measured channels to a position measured
232 in cm. The SiPM map is used. The position is in the scifi modul frame.
233 """
234
235 if reverse is True:
236 for left, _, ch_range in sipm_map:
237 if not ch_range is False:
238 ch_start, ch_end = ch_range
239 if ch_start <= channelpos <= ch_end:
240 return -(left + (channelpos - ch_start) * pitch)
241 if reverse is False:
242 for left, _, ch_range in sipm_map:
243 if not ch_range is False:
244 ch_start, ch_end = ch_range
245 if ch_start <= channelpos <= ch_end:
246 return left + (channelpos - ch_start) * pitch
247
248def GetMatType(DetID):
249
250 """
251 It returns a type of a scifi module.
252 1 - vertical scifi assembly
253 0 - horizontal scifi assembly
254 """
255
256 if DetID < 1000: return False
257 return (DetID % 1000) // 100.
258
259
260def GetMatNum(DetID):
261
262 """
263 It returns an id (number) of a scifi module. In current version one plane have 7 vertical
264 and 11 horisontal scifi assemblies.
265 """
266
267 return int(DetID % 1000 % 100)
268
269def GetMatLength(DetID):
270
271 """
272 It returns a length of a scifi mat. The values 'scifimat_vert', 'scifimat_hor' are set
273 in the FairShip geometry file.
274 """
275
276 global scifimat_vert, scifimat_hor
277
278 if GetMatType(DetID) == 1:
279 return scifimat_vert
280 elif GetMatType(DetID) == 0:
281 return scifimat_hor
282
283def GetMatQty(DetID):
284
285 """
286 It returns a number of scifi mats in a plane. In current version it is 7 for vertical
287 and 11 for horisontal scifi assemblies.
288 """
289
290 global n_vert_planes, n_hor_planes
291
292 if GetMatType(DetID) == 1:
293 return n_vert_planes
294 elif GetMatType(DetID) == 0:
295 return n_hor_planes
296
297def GetStationNum(DetID):
298
299 """
300 It returns an id of a plane. In current the detector have 19 TT stations and 5 HPT stations.
301 """
302
303 return int(hit.GetDetectorID() // 1000.)
304
305def global_to_local(DetID, globalpos):
306
307 """
308 It returns the local coordinates in one scifi assembly frame from global coordinates.
309 """
310
311 global scifimat_width
312
313 matnum = GetMatNum(DetID)
314 nmats = GetMatQty(DetID)
315 return globalpos + ((nmats-1)/2. - matnum + 1) * scifimat_width
316
317def local_to_global(DetID, localpos):
318
319 """
320 It returns the global coordinates from the local coordinates in one scifi assembly frame.
321 """
322
323 global scifimat_width
324
325 matnum = GetMatNum(DetID)
326 nmats = GetMatQty(DetID)
327 return localpos + (-nmats/2. + matnum - 1/2.) * scifimat_width
328
329def ly_loss_mean(distance, params=ly_loss_params):
330
331 """
332 It return the light yield depending on the distance to SiPMs
333 """
334
335 A1, k1, A2, k2 = params
336 return A1 * exp(k1 * distance / 100.) + A2 * exp(k2 * distance / 100.)
337
338def ly_attenuation(distance):
339
340 """
341 It return the light yield losses in percent depending on the distance to SiPMs
342 """
343
344 res_ly_loss = ly_loss_mean(distance) / ly_loss_mean(0.)
345 return res_ly_loss
346
347def cluster_width_mean(distance, params=cluster_width_mean_params):
348
349 """
350 It return a mean cluster width depending on the distance to SiPMs
351 """
352
353 A, B, C = params
354 return exp(A + B*distance) + C
355
356def cluster_width_sigma(distance, params=cluster_width_sigma_params):
357
358 """
359 It return a standard deviation of the mean cluster width depending on the distance to SiPMs
360 """
361
362 A, B = params
363 return A + B*distance
364
365def cluster_width_random(distance, ly, persent=random_width_persent,
366 cluster_width_min=cluster_width_min, cluster_width_max=cluster_width_max):
367
368 """
369 It generates a cluster. The cluster have 'ly' photoelectrons.
370 The cluster width depends on the distance to SiPM
371 """
372
373 mean = cluster_width_mean(distance)
374 sigma = cluster_width_sigma(distance)
375 if random.random() <= persent:
376 return random.randint(cluster_width_min, cluster_width_max)
377 random_width = int(round(random.gauss(mean - 1, sigma))) + 1
378
379 # Generate again if the width < minimal width and the light yield < the width
380 while random_width < 1 or ly < random_width:
381 random_width = int(round(random.gauss(mean - 1, sigma))) + 1
382
383 return random_width
384
385def approx_function(var, approx_data):
386
387 """
388 This universal function substitutes the parameters to the function.
389 The parameters and the function are in the dictionary
390 """
391
392 for (left, right), (params, func) in approx_data.items():
393 if left <= var <= right:
394 return func(var, *params)
395 return False
396
397def edep_to_ly(energy, CDF_integral=CDF_integral, energy_range=energy_range,
398 ly_linear_params=ly_linear_params, k_cdfs_corr=k_cdfs_corr, sigma_in_percent=sigma_in_percent,
399 ly_CDF_params=ly_CDF_params, CDF_ly_params=CDF_ly_params, ly_CDF_landau_params=ly_CDF_landau_params):
400
401 """
402 It returns the light yield calculated from the energy deposit. The calculations are based
403 on the equality of the cumulative distribution functions (CDF) :
404 energy => CDF(energy) => CDF(light yield) => ly
405
406 The linear converting range 0.18 MeV < dE < 0.477 MeV corresponds 4.5 < LY < 104 ph.e.
407
408 If energy more then 0.477 MeV the light yield calculated randomly (uniformly in the range)
409 according to the distribution
410
411 Also a little randomness is added to the CDF value with a normal distribution and
412 standard deviation with 'sigma_in_percent' (in percent of the whole range 0 - max CDF)
413 """
414
415 e_min, e_max = energy_range
416 A, C = ly_linear_params
417 if e_min < energy < e_max:
418 ly_lin = A * energy + C
419 cdf_raw = approx_function(ly_lin, ly_CDF_landau_params) * k_cdfs_corr
420 elif e_max <= energy:
421 cdf_raw = CDF_integral * np.random.uniform(0., 1.0)
422 else:
423 return 0.
424 cdf_random = random.gauss(cdf_raw, sigma_in_percent * CDF_integral)
425
426 # Generate again while the CDF value is not in the range
427 while cdf_random < 0 or cdf_random > CDF_integral:
428 cdf_random = random.gauss(cdf_raw, sigma_in_percent * CDF_integral)
429
430 return approx_function(cdf_random, CDF_ly_params)
431
432def cluster_generator(amplitude, width, wmp, cluster_width_max=cluster_width_max,
433 chpos_min=chpos_min, chpos_max=chpos_max):
434
435 """
436 It generates an event cluster with given weighted mean position in channels, width and
437 amplitude.
438
439 If right side of the cluster can be out of range, the maximum of the right side will be
440 right channel.
441
442 At first an array [0, 0, 0, ... ] is generated which corresponds to the channels.
443 Next the cluster generated in the array.
444 Final array will be like [0, 0, ..., 1, 2, 5, 1, 0, ...],
445 [0, 17, 0, ...] or etc.
446 """
447
448 if int(ceil(wmp + 0.5 + cluster_width_max)) < chpos_max:
449 max_fired_ch = int(ceil(wmp + 0.5 + cluster_width_max))
450 else:
451 max_fired_ch = int(ceil(chpos_max))
452 cluster = [0 for _ in range(max_fired_ch)]
453 mean = wmp
454 if width == 1:
455 if wmp == chpos_min:
456 fired_channel = 0
457 elif wmp == chpos_max:
458 fired_channel = int(chpos_max)
459 else:
460 fired_channel = int(wmp + 0.5)
461 cluster[fired_channel] += amplitude
462 else:
463 sigma = width * sigma_from_width
464 for _ in range(amplitude):
465 fired_channel = int(round(random.gauss(mean, sigma)))
466 while not 0 <= fired_channel < len(cluster):
467 fired_channel = int(round(random.gauss(mean, sigma)))
468 cluster[fired_channel] += 1
469 return cluster
470
471def is_realistic(cluster, width):
472
473 """
474 It returns TRUE if cluster is realistic: it doesn't have a gap between numders, like
475 [..., 0, 1, 2, 0, 0, 5, 6, ...], and it doens't have the light yield less then width.
476 """
477
478 cluster_only_values = [(channel, value) for channel, value in enumerate(cluster) if value > 0]
479 first_channel, _ = cluster_only_values[0]
480 last_channel, _ = cluster_only_values[-1]
481 if len(cluster_only_values) != width or (last_channel - first_channel + 1) != width:
482 return False
483 else:
484 return True
485
486def create_cluster(amplitude, width, wmp):
487
488 """
489 The final function for creating a signal cluster
490
491 """
492 if not chpos_min < wmp < chpos_max: return False
493 if wmp is False: return False
494 if not ly_min <= amplitude >= width: return False
495 shifted_wmp = wmp + 0.5 # For right counting
496 cluster = cluster_generator(amplitude, width, shifted_wmp)
497
498 # Generate again if it doesn't look like real cluster
499 while is_realistic(cluster, width) is False:
500 cluster = cluster_generator(amplitude, width, shifted_wmp)
501
502 return cluster
503
504def weigthed_mean_pos(cluster):
505
506 """
507 Calculate the weighted mean position of the cluster
508 """
509
510 if cluster is False:
511 return False
512 sumup = 0.
513 sumdown = 0.
514 wmp = 0.
515 for channel, value in enumerate(cluster):
516 if value > 0:
517 sumup += value * channel
518 sumdown += value
519 if sumdown != 0:
520 wmp = sumup / sumdown - 0.5
521 return wmp
522
523
526
528 # Constructor
529 def __init__(self, DetID, Xraw, Yraw, Edep):
530 self.DetID = DetID
531 self.Xraw = Xraw
532 self.Yraw = Yraw
533 self.Edep = Edep * 1000 # to MeV
534 self.ly = 0.
535 self.cluster = []
536 self.station = 0
537 self.matnum = 0
538 self.Xrec = 0.
539 self.Yrec = 0.
540 self.is_created = False
541 # Only to check the edge events
543 self.delta = -13
544 # The default values should be set
545 self.sipm_hor_pos = +1 # +1 at X = +len_vert / 2
546 self.sipm_vert_pos = +1 #-1 at Y = -len_hor / 2
547 self.is_sipm_hor_reversed = False # Channels and axis are co-directional or not
549 self.ly_min = 4.5
550 self.ly_max = 104
551
552 def SetLYRange(self, ly_min, ly_max):
553 self.ly_min = ly_min
554 self.ly_max = ly_max
555
556 def SetSipmPosition(self, sipm_hor_pos, sipm_vert_pos):
557 self.sipm_hor_pos = sipm_hor_pos
558 self.sipm_vert_pos = sipm_vert_pos
559
560 def SetSipmIsReversed(self, is_sipm_hor_reversed, is_sipm_vert_reversed):
561 self.is_sipm_hor_reversed = is_sipm_hor_reversed
562 self.is_sipm_vert_reversed = is_sipm_vert_reversed
563
564 # Cluster generator from the raw data. It returns False if it fails.
565 def ClusterGen(self):
566 if GetMatType(self.DetID) is False:
567 return False
568 elif GetMatType(self.DetID) == 1: # vert
569 first_coord = self.Xraw
570 second_coord = self.Yraw
571 sipm_pos = self.sipm_vert_pos
572 is_sipm_reversed = self.is_sipm_hor_reversed
573 elif GetMatType(self.DetID) == 0: # hor
574 first_coord = self.Yraw
575 second_coord = self.Xraw
576 sipm_pos = self.sipm_hor_pos
577 is_sipm_reversed = self.is_sipm_vert_reversed
578 self.matnum = GetMatNum(self.DetID)
579 nmats = GetMatQty(self.DetID)
580 matlen = GetMatLength(self.DetID)
581 self.station = GetStationNum(self.DetID)
582 localpos = global_to_local(self.DetID, first_coord)
583
584 if sipm_pos == +1:
585 distance = GetMatLength(self.DetID)/2. - second_coord
586 elif sipm_pos == -1:
587 distance = GetMatLength(self.DetID)/2. + second_coord
588
589 channelpos = cm_to_channel(localpos, reverse=is_sipm_reversed)
590 self.ly = int(round(
591 edep_to_ly(self.Edep) * ly_attenuation(distance)
592 ))
593
594 if not self.ly_min < self.ly < self.ly_max:
595 return False
596
597 cluster_width = int(round(cluster_width_random(distance, ly=self.ly)))
598 cluster = create_cluster(self.ly, cluster_width, channelpos)
599 wmp_of_cluster = weigthed_mean_pos(cluster)
600 recovery_localpos = channel_to_cm(wmp_of_cluster, reverse=is_sipm_reversed)
601
602 self.recovery_globalpos = local_to_global(self.DetID, recovery_localpos)
603 self.delta = first_coord - self.recovery_globalpos
604
605 # Some edge events may be reconstructed incorrectly
606 if abs(self.delta) > 1:
607 return False
608
609 if GetMatType(self.DetID) == 1: # vert
610 self.Xrec = self.recovery_globalpos
611 elif GetMatType(self.DetID) == 0: # hor
612 self.Yrec = self.recovery_globalpos
613 self.is_created = True
614 return cluster
615
616 def GetXYZ(self):
617 if self.is_created:
618 return self.Xrec, self.Yrec, self.station
619 else:
620 return 0, 0, 0
621
622
623
624
625if __name__ == '__main__':
626
627
630
631 tt_points = []
632 hpt_points = []
633 tt_raw = []
634 hpt_raw = []
635
636 # Set the path to the file
637 muonfile = ROOT.TFile("$PWD/ship.conical.PG_13-TGeant4.root")
638 tree = muonfile.Get("cbmsim")
639
640 for index, event in enumerate(tree):
641 for hit in event.TTPoint:
642
643 pnt = TTCluster(hit.GetDetectorID(), hit.GetX(), hit.GetY(), hit.GetEnergyLoss())
644 pnt.SetLYRange(ly_min, ly_max)
645 pnt.SetSipmPosition(sipm_hor_pos, sipm_vert_pos)
646 pnt.SetSipmIsReversed(is_sipm_hor_reversed, is_sipm_vert_reversed)
647 pnt.ClusterGen()
648 if pnt.is_created is False: continue
649
650 tt_points.append([
651 hit.GetTime(), # 0; ns
652 pnt.station, # 1
653 pnt.matnum, # 2 ;~100-vert, ~0-hor
654 False, # 3 ; in one mat
655 pnt.ly, #ly signal, # 4
656 False, # 5
657 pnt.recovery_globalpos, # 6
658 GetMatType(pnt.DetID), # 7 # 0-vert (X), 1-hor (Y)
659 pnt.delta, # 8
660 ])
661
662 tt_raw.append([
663 hit.GetTrackID(), # 0
664 hit.GetTime(), # 1 ; ns
665 hit.PdgCode(), # 2
666 hit.GetEventID(), # 3
667 hit.GetDetectorID(), # 4
668 hit.GetX(), # 5 ; cm
669 hit.GetY(), # 6 ; cm
670 hit.GetZ(), # 7 ; cm
671 hit.GetEnergyLoss() * 1.0e03, # 8 ; MeV
672 hit.GetPx(), # 9
673 hit.GetPy(), # 10
674 hit.GetPz(), # 11
675 hit.GetLength() # 12
676 ])
677 # Convert to numpy array
678 tt_raw = np.array(tt_raw)
679 tt_points = np.array(tt_points)
680
681 # HPT
682 for index, event in enumerate(tree):
683 for hit in event.HptPoint:
684 pnt = TTCluster(hit.GetDetectorID(), hit.GetX(), hit.GetY(), hit.GetEnergyLoss())
685 pnt.ClusterGen()
686 if pnt.is_created is False: continue
687
688 hpt_points.append([
689 hit.GetTime(), # 0; ns
690 pnt.station + n_tt_stations, # 1
691 pnt.matnum, # 2 ;~100-vert, ~0-hor
692 False, # 3 ; in one mat
693 pnt.ly, #ly signal, # 4
694 False, # 5
695 pnt.recovery_globalpos, # 6
696 GetMatType(pnt.DetID), # 7 # 1-vert (X), 0-hor (Y)
697 pnt.delta, # 8
698 ])
699
700 hpt_raw.append([
701 hit.GetTrackID(), # 0
702 hit.GetTime(), # 1 ; ns
703 hit.PdgCode(), # 2
704 hit.GetEventID(), # 3
705 hit.GetDetectorID(), # 4
706 hit.GetX(), # 5 ; cm
707 hit.GetY(), # 6 ; cm
708 hit.GetZ(), # 7 ; cm
709 hit.GetEnergyLoss() * 1.0e03, # 8 ; MeV
710 hit.GetPx(), # 9
711 hit.GetPy(), # 10
712 hit.GetPz(), # 11
713 hit.GetLength() # 12
714 ])
715
716 # Convert to numpy array
717 hpt_raw = np.array(hpt_raw)
718 hpt_points = np.array(hpt_points)
719
720 # Merge the arrays
721 tt_raw = np.vstack((tt_raw, hpt_raw))
722 tt_points = np.vstack((tt_points, hpt_points))
723 # ----------------------------------------------
724
725
726 # ---- 3. SHOW THE DATA --------------------------
727 fig, axs = plt.subplots(2,2)
728 plt.subplot(2, 2, 1)
729 axs = plt.gca()
730 ly_bins = np.linspace(4,104,100)
731 plt.hist(tt_points[:,4], bins=ly_bins, label="Light yield")
732 axs.set_xlabel("LY, [ph.e.]")
733 axs.set_ylabel("Events")
734 plt.legend(loc="upper right")
735
736 plt.subplot(2, 2, 2)
737 axs = plt.gca()
738 nbins = 24
739 plt.hist(tt_points[:,1], bins=nbins, label="TT station")
740 axs.set_xlabel("Station number")
741 axs.set_ylabel("Events")
742 plt.legend(loc="upper right")
743
744 # Print 'X-Z' and 'Y-Z' as output data.
745 # 'X-Y-Z' graphics need additional analysis (linking by time).
746 xz_points = []
747 yz_points = []
748 for index, event in enumerate(tt_points):
749 if tt_points[index, 7] == 1: # vertical
750 xz_points.append([
751 tt_raw[index, 7], # Z
752 tt_points[index, 6]
753 ])
754 elif tt_points[index, 7] == 0: # horizontal
755 yz_points.append([
756 tt_raw[index, 7], # Z
757 tt_points[index, 6]
758 ])
759 xz_points = np.array(xz_points)
760 yz_points = np.array(yz_points)
761
762 plt.subplot(2, 2, 3)
763 axs = plt.gca()
764 axs.scatter(xz_points[:,0], xz_points[:, 1])
765 axs.set_xlabel("Z, [cm]")
766 axs.set_ylabel("X, [cm]")
767 axs.set_ylim(-50, +50) # +-73.2475
768
769 plt.subplot(2, 2, 4)
770 axs = plt.gca()
771 axs.scatter(yz_points[:,0], yz_points[:, 1])
772 axs.set_xlabel("Z, [cm]")
773 axs.set_ylabel("Y, [cm]")
774 axs.set_ylim(-80, +80) # +-47.1575
775
776 plt.show()
777 # ----------------------------------------------
778
779 fig, axs = plt.subplots(1,1)
780 plt.subplot(1, 1, 1)
781 axs = plt.gca()
782 plt.hist(tt_points[:,8], bins=100, label="delta")
783 plt.show()
784
CLASS OF TT AND HPT EVENTS ######################################################.
Definition TTCluster.py:527
SetSipmIsReversed(self, is_sipm_hor_reversed, is_sipm_vert_reversed)
Definition TTCluster.py:560
SetLYRange(self, ly_min, ly_max)
Definition TTCluster.py:552
__init__(self, DetID, Xraw, Yraw, Edep)
Definition TTCluster.py:529
SetSipmPosition(self, sipm_hor_pos, sipm_vert_pos)
Definition TTCluster.py:556
cm_to_channel(locpos, sipm_map=sipm_map, gaps_map=gaps_map, pitch=pitch, charr=charr, reverse=False, ch_max_num=ch_max_num, n_solid_sipms=n_solid_sipms)
FUNCTIONS FOR TTCLASS #####################################################.
Definition TTCluster.py:204
cluster_width_sigma(distance, params=cluster_width_sigma_params)
Definition TTCluster.py:356
global_to_local(DetID, globalpos)
Definition TTCluster.py:305
cluster_generator(amplitude, width, wmp, cluster_width_max=cluster_width_max, chpos_min=chpos_min, chpos_max=chpos_max)
Definition TTCluster.py:433
GetMatNum(DetID)
Definition TTCluster.py:260
edep_to_ly(energy, CDF_integral=CDF_integral, energy_range=energy_range, ly_linear_params=ly_linear_params, k_cdfs_corr=k_cdfs_corr, sigma_in_percent=sigma_in_percent, ly_CDF_params=ly_CDF_params, CDF_ly_params=CDF_ly_params, ly_CDF_landau_params=ly_CDF_landau_params)
Definition TTCluster.py:399
GetMatType(DetID)
Definition TTCluster.py:248
GetMatQty(DetID)
Definition TTCluster.py:283
GetStationNum(DetID)
Definition TTCluster.py:297
GetMatLength(DetID)
Definition TTCluster.py:269
ly_attenuation(distance)
Definition TTCluster.py:338
local_to_global(DetID, localpos)
Definition TTCluster.py:317
ly_loss_mean(distance, params=ly_loss_params)
Definition TTCluster.py:329
is_realistic(cluster, width)
Definition TTCluster.py:471
cluster_width_mean(distance, params=cluster_width_mean_params)
Definition TTCluster.py:347
channel_to_cm(channelpos, sipm_map=sipm_map, reverse=False, pitch=pitch)
Definition TTCluster.py:228
cluster_width_random(distance, ly, persent=random_width_persent, cluster_width_min=cluster_width_min, cluster_width_max=cluster_width_max)
Definition TTCluster.py:366
create_cluster(amplitude, width, wmp)
Definition TTCluster.py:486
approx_function(var, approx_data)
Definition TTCluster.py:385
weigthed_mean_pos(cluster)
Definition TTCluster.py:504