6from math
import exp, sqrt, log, ceil
8import matplotlib.pyplot
as plt
10from ROOT.TMath
import Landau, Gaus, Poisson
11from ShipGeoConfig
import ConfigRegistry
23is_sipm_hor_reversed =
True
24is_sipm_vert_reversed =
True
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)
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
87random_width_persent = 0.01
99energy_range = 0.18, 0.477
102CDF_integral = 0.0185640424
106ly_linear_params = 332.882, -58.7085
109k_cdfs_corr = 0.993076766938
113sigma_in_percent = 0.01
116sigma_from_width = 1 / 4.
127 lambda ly, *p: exp(p[0] + p[1]*sqrt(ly))
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]
138ly_CDF_landau_params = {
140 (0.001038, -0.000378, 3.53e-05),
141 lambda ly, *p: p[0] + p[1]*ly + p[2]*ly**2
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
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
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]
160 (158, 1.035, 0.24, 217),
161 lambda cdf, *p: p[0] * log(sqrt(cdf)+p[1]) + p[2]*exp(p[3]*cdf)
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]
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])
178design2018 = {
'dy': 10.,
'dv': 6,
'ds': 9,
'nud': 3,
'caloDesign': 3,
'strawDesign': 10}
182nud = design2018[
'nud']
183caloDesign = design2018[
'caloDesign']
184strawDesign = design2018[
'strawDesign']
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)
191n_hor_planes = ship_geo.NuTauTT.n_hor_planes
192n_vert_planes = ship_geo.NuTauTT.n_vert_planes
193scifimat_hor = ship_geo.NuTauTT.scifimat_hor
194scifimat_vert = ship_geo.NuTauTT.scifimat_vert
195scifimat_width = ship_geo.NuTauTT.scifimat_width
196scifimat_z = ship_geo.NuTauTT.scifimat_z
197n_tt_stations = ship_geo.NuTauTT.n
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):
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.
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)
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
226 return gaps_map.get((locpos + hw) // charr,
False)
228def channel_to_cm(channelpos, sipm_map=sipm_map, reverse=False, pitch=pitch):
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.
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)
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
251 It returns a type of a scifi module.
252 1 - vertical scifi assembly
253 0 - horizontal scifi assembly
256 if DetID < 1000:
return False
257 return (DetID % 1000) // 100.
263 It returns an id (number) of a scifi module. In current version one plane have 7 vertical
264 and 11 horisontal scifi assemblies.
267 return int(DetID % 1000 % 100)
272 It returns a length of a scifi mat. The values 'scifimat_vert', 'scifimat_hor' are set
273 in the FairShip geometry file.
276 global scifimat_vert, scifimat_hor
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.
290 global n_vert_planes, n_hor_planes
300 It returns an id of a plane. In current the detector have 19 TT stations and 5 HPT stations.
303 return int(hit.GetDetectorID() // 1000.)
308 It returns the local coordinates in one scifi assembly frame from global coordinates.
311 global scifimat_width
315 return globalpos + ((nmats-1)/2. - matnum + 1) * scifimat_width
320 It returns the global coordinates from the local coordinates in one scifi assembly frame.
323 global scifimat_width
327 return localpos + (-nmats/2. + matnum - 1/2.) * scifimat_width
332 It return the light yield depending on the distance to SiPMs
335 A1, k1, A2, k2 = params
336 return A1 * exp(k1 * distance / 100.) + A2 * exp(k2 * distance / 100.)
341 It return the light yield losses in percent depending on the distance to SiPMs
350 It return a mean cluster width depending on the distance to SiPMs
354 return exp(A + B*distance) + C
359 It return a standard deviation of the mean cluster width depending on the distance to SiPMs
363 return A + B*distance
366 cluster_width_min=cluster_width_min, cluster_width_max=cluster_width_max):
369 It generates a cluster. The cluster have 'ly' photoelectrons.
370 The cluster width depends on the distance to SiPM
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
380 while random_width < 1
or ly < random_width:
381 random_width = int(round(random.gauss(mean - 1, sigma))) + 1
388 This universal function substitutes the parameters to the function.
389 The parameters and the function are in the dictionary
392 for (left, right), (params, func)
in approx_data.items():
393 if left <= var <= right:
394 return func(var, *params)
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):
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
406 The linear converting range 0.18 MeV < dE < 0.477 MeV corresponds 4.5 < LY < 104 ph.e.
408 If energy more then 0.477 MeV the light yield calculated randomly (uniformly in the range)
409 according to the distribution
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)
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
420 elif e_max <= energy:
421 cdf_raw = CDF_integral * np.random.uniform(0., 1.0)
424 cdf_random = random.gauss(cdf_raw, sigma_in_percent * CDF_integral)
427 while cdf_random < 0
or cdf_random > CDF_integral:
428 cdf_random = random.gauss(cdf_raw, sigma_in_percent * CDF_integral)
433 chpos_min=chpos_min, chpos_max=chpos_max):
436 It generates an event cluster with given weighted mean position in channels, width and
439 If right side of the cluster can be out of range, the maximum of the right side will be
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.
448 if int(ceil(wmp + 0.5 + cluster_width_max)) < chpos_max:
449 max_fired_ch = int(ceil(wmp + 0.5 + cluster_width_max))
451 max_fired_ch = int(ceil(chpos_max))
452 cluster = [0
for _
in range(max_fired_ch)]
457 elif wmp == chpos_max:
458 fired_channel = int(chpos_max)
460 fired_channel = int(wmp + 0.5)
461 cluster[fired_channel] += amplitude
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
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.
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:
489 The final function for creating a signal cluster
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
507 Calculate the weighted mean position of the cluster
515 for channel, value
in enumerate(cluster):
517 sumup += value * channel
520 wmp = sumup / sumdown - 0.5
569 first_coord = self.
Xraw
570 second_coord = self.
Yraw
574 first_coord = self.
Yraw
575 second_coord = self.
Xraw
589 channelpos =
cm_to_channel(localpos, reverse=is_sipm_reversed)
600 recovery_localpos =
channel_to_cm(wmp_of_cluster, reverse=is_sipm_reversed)
606 if abs(self.
delta) > 1:
625if __name__ ==
'__main__':
637 muonfile = ROOT.TFile(
"$PWD/ship.conical.PG_13-TGeant4.root")
638 tree = muonfile.Get(
"cbmsim")
640 for index, event
in enumerate(tree):
641 for hit
in event.TTPoint:
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)
648 if pnt.is_created
is False:
continue
657 pnt.recovery_globalpos,
671 hit.GetEnergyLoss() * 1.0e03,
678 tt_raw = np.array(tt_raw)
679 tt_points = np.array(tt_points)
682 for index, event
in enumerate(tree):
683 for hit
in event.HptPoint:
684 pnt =
TTCluster(hit.GetDetectorID(), hit.GetX(), hit.GetY(), hit.GetEnergyLoss())
686 if pnt.is_created
is False:
continue
690 pnt.station + n_tt_stations,
695 pnt.recovery_globalpos,
709 hit.GetEnergyLoss() * 1.0e03,
717 hpt_raw = np.array(hpt_raw)
718 hpt_points = np.array(hpt_points)
721 tt_raw = np.vstack((tt_raw, hpt_raw))
722 tt_points = np.vstack((tt_points, hpt_points))
727 fig, axs = plt.subplots(2,2)
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")
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")
748 for index, event
in enumerate(tt_points):
749 if tt_points[index, 7] == 1:
754 elif tt_points[index, 7] == 0:
759 xz_points = np.array(xz_points)
760 yz_points = np.array(yz_points)
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)
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)
779 fig, axs = plt.subplots(1,1)
782 plt.hist(tt_points[:,8], bins=100, label=
"delta")
CLASS OF TT AND HPT EVENTS ######################################################.
SetSipmIsReversed(self, is_sipm_hor_reversed, is_sipm_vert_reversed)
SetLYRange(self, ly_min, ly_max)
__init__(self, DetID, Xraw, Yraw, Edep)
SetSipmPosition(self, sipm_hor_pos, sipm_vert_pos)
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 #####################################################.
cluster_width_sigma(distance, params=cluster_width_sigma_params)
global_to_local(DetID, globalpos)
cluster_generator(amplitude, width, wmp, cluster_width_max=cluster_width_max, chpos_min=chpos_min, chpos_max=chpos_max)
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)
local_to_global(DetID, localpos)
ly_loss_mean(distance, params=ly_loss_params)
is_realistic(cluster, width)
cluster_width_mean(distance, params=cluster_width_mean_params)
channel_to_cm(channelpos, sipm_map=sipm_map, reverse=False, pitch=pitch)
cluster_width_random(distance, ly, persent=random_width_persent, cluster_width_min=cluster_width_min, cluster_width_max=cluster_width_max)
create_cluster(amplitude, width, wmp)
approx_function(var, approx_data)
weigthed_mean_pos(cluster)