HPS-MC
_parameter.py
Go to the documentation of this file.
1 """! representation of alignment parameters"""
2 
3 import re
4 
5 
6 class Parameter:
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_id = int(idn)
40  self._name_name = name
41  self._half_half = int(half) # 1 or 2
42  self._trans_rot_trans_rot = int(trans_rot)
43  self._direction_direction = int(direction)
44  self._mp_layer_id_mp_layer_id = int(mp_layer_id)
45 
46  self._val_val = 0.0
47  self._error_error = -1.0
48  self._active_active = False
49 
50  def float(self, yes=True):
51  """!Set whether this parameter is floating/active or not"""
52  self._active_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_mp_layer_id < 9:
62  return self._mp_layer_id_mp_layer_id // 2
63  else:
64  return 4 + (self._mp_layer_id_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_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_id
83 
84  def direction(self):
85  return self._direction_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_mp_layer_id < 23
92 
93  def translation(self):
94  """!True if Parameter represents a translation"""
95  return self._trans_rot_trans_rot == 1
96 
97  def rotation(self):
98  """!True if Parameter represents a rotation"""
99  return self._trans_rot_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_half == 1)
106 
107  def bottom(self):
108  """!True if Parameter is in bottom half, False if in top half"""
109  return (self._half_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.individualindividual() and ('axial' in self._name_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.individualindividual() and ('stereo' in self._name_name)
126 
127  def front(self):
128  """!True if Parameter is single sensor in front half, False otherwise"""
129  return self.individualindividual() and (self._mp_layer_id_mp_layer_id < 9)
130 
131  def back(self):
132  """!True if Parameter is single sensor in back half, False otherwise"""
133  return self.individualindividual() and (self._mp_layer_id_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.backback() and (self._mp_layer_id_mp_layer_id % 4 == 1 or self._mp_layer_id_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.backback() and (self._mp_layer_id_mp_layer_id % 4 == 0 or self._mp_layer_id_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_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_val = float(elements[1])
223  self._active_active = (float(elements[2]) >= 0.0)
224  if len(elements) > 4:
225  self._val_val = float(elements[3])
226  self._error_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_val > 0 else '-'
278  if self._trans_rot_trans_rot == 1:
279  # translation, flip operator
280  op = '-' if self._val_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_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
def parse_map_file(map_filepath)
load the entire parameter set from a map file
Definition: _parameter.py:187
def compact_value(self)
Print the value of this parameter as it should be inserted into the compact.
Definition: _parameter.py:269
def active(self)
True if Parameter is active (i.e.
Definition: _parameter.py:143
def __str__(self)
Human printing of this parameter.
Definition: _parameter.py:288
def translation(self)
True if Parameter represents a translation.
Definition: _parameter.py:93
def layer(self)
Get the human layer number.
Definition: _parameter.py:66
def bottom(self)
True if Parameter is in bottom half, False if in top half.
Definition: _parameter.py:107
def __from_res_file_line(self, line)
Assumes line is for the same parameter as stored in self.
Definition: _parameter.py:205
def slot(self)
True if Parameter is a single sensor in back half on the slot side, Flase otherwise.
Definition: _parameter.py:139
def back(self)
True if Parameter is single sensor in back half, False otherwise.
Definition: _parameter.py:131
def axial(self)
Get whether this Parameter represents a single axial sensor (True) or something else (False)
Definition: _parameter.py:111
def top(self)
Does this parameter represent a component on the top half (True) or bottom (False)
Definition: _parameter.py:101
def parse_pede_res(res_file, destination=None, skip_nonfloat=False)
parse a pede results file
Definition: _parameter.py:228
def rotation(self)
True if Parameter represents a rotation.
Definition: _parameter.py:97
def from_idn(idn)
Deduce the categorical flags from the ID number.
Definition: _parameter.py:155
def float(self, yes=True)
Set whether this parameter is floating/active or not.
Definition: _parameter.py:50
def module(self)
Get the module number from the millepede layer number.
Definition: _parameter.py:54
def __repr__(self)
Representation of this parameter.
Definition: _parameter.py:284
def front(self)
True if Parameter is single sensor in front half, False otherwise.
Definition: _parameter.py:127
def stereo(self)
Get whether this Parameter represents a single stereo sensor (True) or something else (False)
Definition: _parameter.py:119
def individual(self)
Get whether this Parameter represents a single sensor (True) or a structural component holding two or...
Definition: _parameter.py:87
def pede_format(self)
Print this parameter as it should appear in the pede steering file.
Definition: _parameter.py:265
def __init__(self, idn, name, half, trans_rot, direction, mp_layer_id)
Definition: _parameter.py:38
def hole(self)
True if Parameter is a single sensor in back half on the hole side, Flase otherwise.
Definition: _parameter.py:135