Belle II Software development
crystal_shapes_per_cellid.py
1
8
9"""
10Map every ECL cellID (1..8736) to its crystal shape, volume and mass.
11
12Everything is taken from the plain text tables in ecl/data, no geometry is built:
13
14 cellID -> (thetaID, phiID) ECLGeometryPar mapping tables
15 -> crystal placement index as in ECLGeometryPar::read()
16 -> shape number (nshape) crystal_placement_<part>.dat
17 -> volume and mass of that shape crystal_shape_<part>.dat
18
19The Volume(cc) column of crystal_shape_<part>.dat is reproduced by the Geant4
20BelleCrystal solids used in the simulation (see crystal_volumes_masses.py), so
21these numbers are consistent with the simulated geometry.
22"""
23
24import basf2
25
26
27N_CRYSTALS = 8736
28
29
30N_THETA_RINGS = 69
31
32
33N_PLACEMENTS = 224
34
35# The three tables below are copied from Mapping_t in ecl/geometry/src/ECLGeometryPar.cc.
36
37
38RING_START_CORRECTION = ([0, -5, -10, -14, -18, -22, -24, -26, -28, -30, -32, -34, -33]
39 + list(range(-32, 14))
40 + [14, 15, 16, 14, 12, 10, 8, 6, 2, -2])
41
42
43SHAPES_PER_RING = [3, 3, 4, 4, 4, 6, 6, 6, 6, 6, 6, 9, 9] + [2] * 46 + [9, 9, 6, 6, 6, 6, 6, 4, 4, 4]
44
45
46RING_PLACEMENT_OFFSET = ([0, 3, 6, 10, 14, 18, 24, 30, 36, 42, 48, 54, 63]
47 + list(range(132, 224, 2))
48 + [72, 81, 90, 96, 102, 108, 114, 120, 124, 128])
49
50
51def data_rows(filename):
52 """Yield the whitespace-separated fields of every non-empty, non-comment line."""
53 for line in open(basf2.find_file(f"ecl/data/{filename}")):
54 fields = line.split("#")[0].split()
55 if fields:
56 yield fields
57
58
59def part_placements(part):
60 """Return the (part, shape number) of every crystal placement of an ECL part.
61
62 Same selection as load_placements(): the entries with nshape >= 1000 are the
63 global transformations of the part, not crystals, and are skipped.
64 """
65 return [(part, int(fields[0])) for fields in data_rows(f"crystal_placement_{part}.dat")
66 if len(fields) == 7 and int(fields[0]) < 1000]
67
68
69
71placements = part_placements("forward") + part_placements("backward") + part_placements("barrel")
72assert len(placements) == N_PLACEMENTS
73
74
75shape_volume_mass = {(part, int(fields[0])): (float(fields[-2]), float(fields[-1]))
76 for part in ("forward", "barrel", "backward")
77 for fields in data_rows(f"crystal_shape_{part}.dat")}
78
79
80def crystal_info(cell_id):
81 """Return (thetaID, phiID, part, shape number, volume [cc], mass [kg]) of a cellID."""
82 index = cell_id - 1
83 theta_id = max(i for i in range(N_THETA_RINGS) if RING_START_CORRECTION[i] * 16 + i * 128 <= index)
84 phi_id = index - RING_START_CORRECTION[theta_id] * 16 - theta_id * 128
85 part, shape_number = placements[RING_PLACEMENT_OFFSET[theta_id] + phi_id % SHAPES_PER_RING[theta_id]]
86 volume, mass = shape_volume_mass[(part, shape_number)]
87 return theta_id, phi_id, part, shape_number, volume, mass
88
89
90if __name__ == "__main__":
91 print(f"{'cellID':>6s} {'thetaID':>7s} {'phiID':>5s} {'part':8s} {'shape':>5s} "
92 f"{'V [cc]':>8s} {'m [kg]':>6s}")
93 for cell_id in range(1, N_CRYSTALS + 1):
94 theta_id, phi_id, part, shape_number, volume, mass = crystal_info(cell_id)
95 print(f"{cell_id:6d} {theta_id:7d} {phi_id:5d} {part:8s} {shape_number:5d} "
96 f"{volume:8.2f} {mass:6.3f}")