HPS-MC
 
Loading...
Searching...
No Matches
_parameter.py
Go to the documentation of this file.
1"""! representation of alignment parameters"""
2
3import re
4
5
7 """!
8 Representation of a single alignment parameter
9
10 This class also contains helpful functions for operating on sets of alignment
11 parameters e.g. parsing the map file or pede res file
12
13 Attributes
14 ----------
15 id : int
16 pede ID number as written in compact.xml, pede steering, and map files
17 name : str
18 human-readable name as written in map file
19 half : int
20 1 is for top and 2 is for bottom
21 trans_rot : int
22 1 is for translation and 2 is for rotation
23 direction : int
24 1 is for 'u', 2 is for v, and 3 is for w
25 mp_layer_id : int
26 "layer" ID number in millepede (i.e. axial and stereo sensors are separated)
27 val : float
28 value of parameter (if loaded from res file)
29 error : float
30 error of parameter (if loaded from res file)
31 active : bool
32 true if parameter is floating, false otherwise
33 """
34
35 idn_str_pattern = re.compile('^[12][123][123][0-9][0-9]$')
36 layer_number_pattern = re.compile('^.*_L([0-9]).*$')
37
38 def __init__(self, idn, name, half, trans_rot, direction, mp_layer_id):
39 self._id = int(idn)
40 self._name = name
41 self._half = int(half) # 1 or 2
42 self._trans_rot = int(trans_rot)
43 self._direction = int(direction)
44 self._mp_layer_id = int(mp_layer_id)
45
46 self._val = 0.0
47 self._error = -1.0
48 self._active = False
49
50 def float(self, yes=True):
51 """!Set whether this parameter is floating/active or not"""
52 self._active = yes
53
54 def module(self):
55 """!Get the module number from the millepede layer number
56
57 We group sensors in pairs to form modules and the layer number
58 for millepede counts up from the furthest upstream sensor. Thus,
59 we do integer-division by two to get the module number.
60 """
61 if self._mp_layer_id < 9:
62 return self._mp_layer_id // 2
63 else:
64 return 4 + (self._mp_layer_id - 9) // 4
65
66 def layer(self):
67 """!Get the human layer number
68
69 Since, for the 2016 parameter mapping, a typo led to a dis-association between
70 the module number deduced from the ID number and the layer number, we have
71 to extract the layer number from the name of the parameter name as it appears
72 in the mapping. We look for '_L<digit>' and extract <digit> as the layer number.
73 """
74
75 m = Parameter.layer_number_pattern.match(self._name)
76 if m is None:
77 raise ValueError(f'Unable to deduce layer number from name {self.name}')
78
79 return int(m.group(1))
80
81 def id(self):
82 return self._id
83
84 def direction(self):
85 return self._direction
86
87 def individual(self):
88 """!Get whether this Parameter represents a single sensor (True)
89 or a structural component holding two or more sensors (False)
90 """
91 return self._mp_layer_id < 23
92
93 def translation(self):
94 """!True if Parameter represents a translation"""
95 return self._trans_rot == 1
96
97 def rotation(self):
98 """!True if Parameter represents a rotation"""
99 return self._trans_rot == 2
100
101 def top(self):
102 """!Does this parameter represent a component on the top half (True)
103 or bottom (False)
104 """
105 return (self._half == 1)
106
107 def bottom(self):
108 """!True if Parameter is in bottom half, False if in top half"""
109 return (self._half == 2)
110
111 def axial(self):
112 """!Get whether this Parameter represents a single axial sensor (True)
113 or something else (False)
114
115 We have to check the name to see if 'axial' is in it.
116 """
117 return self.individual() and ('axial' in self._name)
118
119 def stereo(self):
120 """!Get whether this Parameter represents a single stereo sensor (True)
121 or something else (False)
122
123 We have to check the name to see if 'stereo' is in it.
124 """
125 return self.individual() and ('stereo' in self._name)
126
127 def front(self):
128 """!True if Parameter is single sensor in front half, False otherwise"""
129 return self.individual() and (self._mp_layer_id < 9)
130
131 def back(self):
132 """!True if Parameter is single sensor in back half, False otherwise"""
133 return self.individual() and (self._mp_layer_id > 8)
134
135 def hole(self):
136 """!True if Parameter is a single sensor in back half on the hole side, Flase otherwise"""
137 return self.back() and (self._mp_layer_id % 4 == 1 or self._mp_layer_id % 4 == 2)
138
139 def slot(self):
140 """!True if Parameter is a single sensor in back half on the slot side, Flase otherwise"""
141 return self.back() and (self._mp_layer_id % 4 == 0 or self._mp_layer_id % 4 == 3)
142
143 def active(self):
144 """!True if Parameter is active (i.e. floating) and False if not"""
145 return self._active
146
148 """parse a line from the map file
149
150 we assume that the constructor's arguments are in the same
151 order as a line in the sensor map file
152 """
153 return Parameter(*line.split())
154
155 def from_idn(idn):
156 """! Deduce the categorical flags from the ID number
157
158 Each ID number is five digis.
159 In regex terms...
160
161 [12][12][123][0-9][0-9]
162 | |- last two digis are sensor ID number
163 | |------ direction 1==u, 2==v, 3==w
164 | |---------- transformation 1==translation, 2==rotation
165 |--------------- detector half 1==top, 2==bottom
166
167 So we just need to break it down by modulo and integer
168 division /OR/ do some str conversion nonsense in python.
169 """
170
171 idn = str(idn)
172 if len(idn) != 5:
173 raise ValueError(f'Bad ID Number: {idn} is not five digis')
174
175 if not Parameter.idn_str_pattern.match(idn):
176 raise ValueError(f'Bad ID Number: {idn} does not match the ID pattern')
177
178 # idn is good pattern, procede with str hackiness
179 digits = [*idn] # digits is list of characters in str
180 # convert digits into flag values
181 half = int(digits[0])
182 trans_rot = int(digits[1])
183 direction = int(digits[2])
184 mp_layer_id = int(digits[3]+digits[4])
185 return Parameter(int(idn), idn, half, trans_rot, direction, mp_layer_id)
186
187 def parse_map_file(map_filepath):
188 """! load the entire parameter set from a map file
189
190 Returns
191 -------
192 dict
193 map from ID number to a Parameter
194 """
195 parameters = {}
196 with open(map_filepath) as mf:
197 for line in mf:
198 # skip header line
199 if 'MilleParameter' in line:
200 continue
201 p = Parameter.from_map_file_line(line)
202 parameters[p.id()] = p
203 return parameters
204
205 def __from_res_file_line(self, line):
206 """! Assumes line is for the same parameter as stored in self
207
208 A line in the pede result file has either 3 or 5 columns.
209
210 1. ID
211 2. value
212 3. activity (0.0 if floating, -1.0 if not)
213 4. (if active) value
214 5. (if active) error in value
215
216 We ignore the first column and assume that we are only calling
217 this function if the line has already been deduced to correspond
218 to the parameter we represent.
219 """
220 elements = line.split()
221 # elements[0] is the ID number and we assume it is correct
222 self._val = float(elements[1])
223 self._active = (float(elements[2]) >= 0.0)
224 if len(elements) > 4:
225 self._val = float(elements[3])
226 self._error = float(elements[4])
227
228 def parse_pede_res(res_file, destination=None, skip_nonfloat=False):
229 """! parse a pede results file
230
231 Parse the results file into a dictionary. If no destination dictionary
232 is provided, a new dictionary is created with the ID numbers as keys
233 and Parameter instances as values. Since this mapping is created without
234 the sensor mapping, the rest of the Parameter attributes are assigned
235 non-sensical values.
236
237 Parameters
238 ----------
239 res_file : str
240 path to results file we are going to parse
241 destination : dict, optional
242 if provided, load the values from the file into parameters in this dict
243 skip_nonfloat : bool, optional
244 skip non-floating parameters
245 """
246 parameters = {}
247 with open(res_file) as rf:
248 for line in rf:
249 # skip header line
250 if 'Parameter' in line:
251 continue
252 idn = int(line.split()[0])
253 if destination is None:
254 p = Parameter.from_idn(idn)
255 p.__from_res_file_line(line)
256 if p.active() or not skip_nonfloat:
257 parameters[p.id()] = p
258 else:
259 if idn not in destination:
260 raise ValueError(f'Attempting to load parameter {idn} which is not in parameter map')
261 if destination[idn].active() or not skip_nonfloat:
262 destination[idn].__from_res_file_line(line)
263 return parameters if destination is None else None
264
265 def pede_format(self):
266 """! Print this parameter as it should appear in the pede steering file"""
267 return f'{self._id} {self._val} {0.0 if self._active else -1.0} {self._name}'
268
269 def compact_value(self):
270 """! Print the value of this parameter as it should be inserted into the compact
271
272 **including** the operator (either + or -)
273
274 This is where we handle whether the sign flips (translations) or doesn't (rotations)
275 """
276 # rotation, same sign as value
277 op = '+' if self._val > 0 else '-'
278 if self._trans_rot == 1:
279 # translation, flip operator
280 op = '-' if self._val > 0 else '+'
281
282 return f'{op} {abs(self._val)}'
283
284 def __repr__(self):
285 """! Representation of this parameter"""
286 return f'{self.__class__.__name__}({self._id})'
287
288 def __str__(self):
289 """! Human printing of this parameter"""
290 s = repr(self)
291 if self._trans_rot == 1:
292 # translation
293 # stored as mm, print as um
294 s += f' {self._val*1000} +- {self._error*1000} um'
295 else:
296 # rotation
297 # stored as rad, print as mrad
298 s += f' {self._val*1000} +- {self._error*1000} mrad'
299 return s
Representation of a single alignment parameter.
Definition _parameter.py:6
__from_res_file_line(self, line)
Assumes line is for the same parameter as stored in self.
float(self, yes=True)
Set whether this parameter is floating/active or not.
Definition _parameter.py:50
layer(self)
Get the human layer number.
Definition _parameter.py:66
active(self)
True if Parameter is active (i.e.
rotation(self)
True if Parameter represents a rotation.
Definition _parameter.py:97
individual(self)
Get whether this Parameter represents a single sensor (True) or a structural component holding two or...
Definition _parameter.py:87
parse_pede_res(res_file, destination=None, skip_nonfloat=False)
parse a pede results file
top(self)
Does this parameter represent a component on the top half (True) or bottom (False)
slot(self)
True if Parameter is a single sensor in back half on the slot side, Flase otherwise.
__repr__(self)
Representation of this parameter.
from_idn(idn)
Deduce the categorical flags from the ID number.
stereo(self)
Get whether this Parameter represents a single stereo sensor (True) or something else (False)
__init__(self, idn, name, half, trans_rot, direction, mp_layer_id)
Definition _parameter.py:38
pede_format(self)
Print this parameter as it should appear in the pede steering file.
back(self)
True if Parameter is single sensor in back half, False otherwise.
module(self)
Get the module number from the millepede layer number.
Definition _parameter.py:54
hole(self)
True if Parameter is a single sensor in back half on the hole side, Flase otherwise.
axial(self)
Get whether this Parameter represents a single axial sensor (True) or something else (False)
compact_value(self)
Print the value of this parameter as it should be inserted into the compact.
front(self)
True if Parameter is single sensor in front half, False otherwise.
parse_map_file(map_filepath)
load the entire parameter set from a map file
bottom(self)
True if Parameter is in bottom half, False if in top half.
translation(self)
True if Parameter represents a translation.
Definition _parameter.py:93