xboa
Hit.py
Go to the documentation of this file.
1 #This file is a part of xboa
2 #
3 #xboa is free software: you can redistribute it and/or modify
4 #it under the terms of the GNU General Public License as published by
5 #the Free Software Foundation, either version 3 of the License, or
6 #(at your option) any later version.
7 #
8 #xboa is distributed in the hope that it will be useful,
9 #but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 #GNU General Public License for more details.
12 #
13 #You should have received a copy of the GNU General Public License
14 #along with xboa in the doc folder. If not, see
15 #<http://www.gnu.org/licenses/>.
16 #
17 
18 import copy
19 import Common
20 import math
21 import gzip
22 import warnings
23 import array
24 try:
25  import json
26 except ImportError:
27  pass
28 
29 import xboa.core.Hitcore
30 from xboa.core.Hitcore import Hitcore
31 try:
32  import numpy
33  from numpy import matrix
34 except ImportError:
35  pass #safety provided by calls to Common.has_numpy whenever I use this library
36 
37 class BadEventError(IOError):
38  """
39  BadEventError is raised if Hit reads a bad
40  """
41 
42  def __init__(self, value):
43  self.value = value
44 
45  def __str__(self):
46  return repr(self.value)
47 
48 class Hit(object):
49  """
50  Represents a particle at a point in some phase space. Hit contains functions for i/o,
51  as well as accessors for rectangular or cylindrical coordinate systems and functions to
52  perform translations, abelian transformations etc.
53 
54  Hit has the following variables (stored for real in C struct Hitcore)
55 
56  - **x** transverse horizontal position
57  - **y** transverse vertical position
58  - **z** longitudinalposition
59  - **t** time
60  - **px** transverse horizontal component of momentum
61  - **py** transverse vertical component of momentum
62  - **pz** longitudinal component of momentum
63  - **energy** total energy
64  - **local_weight** local statistical weight (for a particular hit)
65  - **mass** particle mass
66  - **bx** horizontal component of magnetic field
67  - **by** vertical component of magnetic field
68  - **bz** longitudinal component of magnetic field
69  - **ex** x component of electric field
70  - **ey** y component of electric field
71  - **ez** z component of electric field
72  - **sx** x component of spin vector
73  - **sy** y component of spin vector
74  - **sz** z component of spin vector
75  - **path_length** total distance traversed by a particle
76  - **proper_time** proper time of the particle
77  - **e_dep** energy deposited, as registered by a Monte Carlo code
78  - **charge** particle charge, in units of electron charge
79  - **station** output plane index
80  - **pid** PDG particle ID (am I an electron? am I a proton?)
81  - **status** is the particle track okay? (code dependent)
82  - **spill** indexes the spill (for MICE)
83  - **event_number** indexes the event
84  - **particle_number** indexes the particle track within the event
85 
86  Additionally,
87 
88  - global_weight is a global statistical weight (for a particular particle).
89  All hits with the same (spill, event_number, particle_number) will register
90  the same global weight.
91  """
92  #this lets python define a global vtable rather than one for each instance
93  __slots__ = ['__hitcore']
94 
95  def __init__(self):
96  """Initialise to an empty event. Alternatively use static initialisers defined below - I prefer static initialisers"""
97  self.__hitcore = Hitcore()
98 
99  def __repr__(self):
100  """Formatting for print command"""
101  return 'Hit.new_from_dict('+repr(self.dict_from_hit())+')'
102 
103  def __copy__(self):
104  """Shallow copy i.e. copy as reference"""
105  hitCopy = self
106  return hitCopy
107 
108  def __deepcopy__(self, target):
109  """Deep copy i.e. copy as data"""
110  target = Hit.new_from_dict(self.dict_from_hit())
111  return target
112 
113  def __eq__(self, target, float_tolerance=Common.float_tolerance):
114  """Test for equality of data values between self and target"""
115  if type(self) != type(target): return False
116  for key in self.__hitcore.get_variables():
117  if abs(self.__hitcore.get(key) - target.__hitcore.get(key)) > float_tolerance: return False
118  return True
119 
120  def __ne__(self, target, float_tolerance=Common.float_tolerance):
121  """Test for inequality of data values between self and target"""
122  return not self.__eq__(target, float_tolerance)
123 
124  def __getitem__(self, variable):
125  """Mimic some aspects of dict"""
126  return self.get(variable)
127 
128  def __setitem__(self, variable, value):
129  """Mimic some aspects of dict"""
130  return self.set(variable, value)
131 
132  def __del__(self):
133  """Clean up"""
134  del(self.__hitcore)
135 
136  # static initialisers #############
137  def new_from_dict(set_dict, mass_shell_string=''):
138  """
139  Static function returns a new hit object, setting data using string:value dict. Then forces E^2=p^2+m^2 by changing mass_shell_string.
140 
141  - set_dict = dict of string:value pairs where strings are from set_variables()
142  - mass_shell_string = string from list mass_shell_variables that references the value that will be changed to force E^2=p^2+m^2
143 
144  e.g. myHit = Hit.new_from_dict({'x':5, 'y':0, 'z':100, 'px':0, 'py':5, 'pz':200, 'pid':-13}, 'energy' )
145  """
146  my_hit = Hit()
147  for k,v in set_dict.iteritems():
148  my_hit.set(k, v)
149  if(mass_shell_string != ''):
150  my_hit.mass_shell_condition(mass_shell_string)
151  return my_hit
152  new_from_dict = staticmethod(new_from_dict)
153 
154  def new_from_read_builtin(format, filehandle):
155  """
156  Static function returns a new hit object, read from filehandle with a built-in format
158  - format = string from the list file_types() that defines the format of the input
159  - filehandle = filehandle from which the hit object will be read
160 
161  e.g. myHit = Hit.new_from_read_builtin('zgoubi', myfile)
162  Note that this will read one event, typically corresponding to one line in <filehandle>
163  """
164  hit = Hit()
165  if format == 'opal_loss':
166  opal_read = hit.__read_opal_loss_file(filehandle)
167  else:
168  hit.read_builtin_formatted(format, filehandle)
169  return hit
170  new_from_read_builtin = staticmethod(new_from_read_builtin)
171 
172  def new_from_read_user(format_list, format_units_dict, filehandle):
173  """
174  Static function returns a new hit object sets data using string/value pairs from set_dict and forces E^2=p^2+m^2 by changing mass_shell_string
175 
176  - format_list = ordered list of variables from get_variables() that contains the particle variables on each line of your input file
177  - format_units_dict = dict of variables:units that are used to transform to internal system of units
178  - filehandle = file handle, created using e.g. filehandle = open('for009.dat')
179 
180  e.g. myHit = Hit.new_from_read_user(['x','y','z','px','py','pz'], {'x':'mm', 'y':'mm', 'z':'mm', 'px':'MeV/c', 'py':'MeV/c', 'pz':'MeV/c'}, my_input_file)
181  """
182  hit = Hit()
183  hit.read_user_formatted(format_list, format_units_dict, filehandle)
184  return hit
185  new_from_read_user = staticmethod(new_from_read_user)
186 
187  def new_list_from_maus_root_spill(maus_types, root_spill, spill_number):
188  """
189  Static function returns a list of hit objects found in the spill
190 
191  - maus_types = list of types to take from the maus file. Types which are not maus_root_types are ignored
192  - root_spill = maus spill data
193  - spill_number = maus spill number (used as the event number)
194 
195  Returns a list of hit objects. Station number will be taken from the relevant maus_type. event_number will be given by the spill_number. track_number will be given by
196  the index on mc_events or recon_events
197  """
198  hit_list = []
199  spill = root_spill.GetSpill()
200  for i in range(spill.GetMCEventSize()):
201  for maus_root_type, getter in Hit.__maus_root_mc_types.iteritems():
202  if maus_root_type in maus_types:
203  new_hits = getter(spill.GetAnMCEvent(i), i)
204  for hit in new_hits:
205  hit['event_number'] = i
206  hit_list += new_hits
207  for i in range(spill.GetReconEventSize()):
208  for maus_root_type, getter in Hit.__maus_root_recon_types.iteritems():
209  if maus_root_type in maus_types:
210  new_hits = getter(spill.GetAReconEvent(i), i)
211  for hit in new_hits:
212  hit['event_number'] = i
213  hit_list += new_hits
214  for hit in hit_list:
215  hit['spill'] = spill_number
216  if hit['pid'] == 0:
217  print 'AARGH - pid = found'
218  return hit_list
219  new_list_from_maus_root_spill = staticmethod(new_list_from_maus_root_spill)
220 
221  def new_from_maus_object(type_name, maus_dict, event_number):
222  """
223  Convert a dict from a maus hit to an xboa hit
224 
225  - type_name = name of the maus type
226  - maus_dict = dict containing maus data
227  - event_number = indexes the originating spill
228  """
229  xboa_dict = {}
230  three_vec_conversions = Hit.__maus_three_vec_conversions[type_name]
231  conversion_dict = Hit.__maus_variable_conversions[type_name]
232  for maus_name, xboa_suffix in three_vec_conversions.iteritems():
233  for maus_xyz, value in maus_dict[maus_name].iteritems():
234  xboa_dict[xboa_suffix+maus_xyz] = value
235  for maus_key, xboa_key in conversion_dict.iteritems():
236  xboa_dict[xboa_key] = maus_dict[maus_key]
237  xboa_dict['event_number'] = event_number
238  for key, value in Hit._Hit__file_units[type_name]:
239  xboa_dict[key] /= Hit._Hit__file_units[type_name][key]
240  if 'mass' not in xboa_dict.keys():
241  xboa_dict['mass'] = Common.pdg_pid_to_mass[abs(xboa_dict['pid'])]
242  if 'charge' not in xboa_dict.keys():
243  xboa_dict['charge'] = Common.pdg_pid_to_charge[xboa_dict['pid']]
244  return Hit.new_from_dict(xboa_dict, Hit.__file_mass_shell[type_name])
245  new_from_maus_object = staticmethod(new_from_maus_object)
246 
247  def copy(self):
248  """Return a shallow copy of self (copying data as references)"""
249  return self.__copy__()
250 
251  def deepcopy(self):
252  """Return a deep copy of target (deep copying target's data to self as well)"""
253  target = Hit()
254  target = copy.deepcopy(self)
255  return target
256 
257  # Transformations #################
258 
259  def get_vector(self, get_variable_list, origin_dict={}):
260  """
261  Return a numpy vector of data values taking data from get_variable_list, relative to some origin
262 
263  - get_variable_list = list of variable strings from get_variables()
264  - origin_dict = dict of variable strings to origin value; if not set, assumes 0
266  e.g. transverse_vector = myHit.get_vector(['x', 'y', 'px', 'py'])
267  """
268  Common.has_numpy()
269  my_list = []
270  for key in get_variable_list:
271  if key in origin_dict: origin = origin_dict[key]
272  else: origin = 0.
273  my_list.append(self.get(key) - origin)
274  return matrix(numpy.array(my_list))
275 
276  def translate(self, translation_dict, mass_shell_string):
277  """
278  Iterate over translation_dict and add the value in the dict to the value stored in Hit. Then force E^2 = p^2 + m^2
279 
280  - translation_dict = dict of strings from list set_variables() to floats
281  - mass_shell_string = string from list mass_shell_variables()
282  """
283  for k,v in translation_dict.iteritems():
284  self.set(k, v+self.get(k))
285  self.mass_shell_condition(mass_shell_string)
287  def abelian_transformation(self, rotation_list, rotation_matrix, translation_dict={}, origin_dict={}, mass_shell_variable=''):
288  """
289  Perform an abelian transformation about the origin, i.e. V_out - O = R*(V_in-O) + T.
290  Then force E^2 = p^2 + m^2
291 
292  - rotation_list = list of variables to be rotated
293  - rotation_matrix = matrix R
294  - translation_dict = dict of strings from set_variables() to floats. Becomes O
295  - mass_shell_variable = string from list mass_shell_variables()
296  - origin_dict = dict of strings from set_variables() to floats. Becomes t
297 
298  e.g. hit.abelian_transformation(['x','px'], array[[1,0.5],[0,1]],{'x':10},'energy') will
299  look like a drift space plus a translation
300  """
301  vector = (self.get_vector(rotation_list)).transpose()
302  origin = copy.deepcopy(origin_dict)
303  trans = copy.deepcopy(translation_dict)
304  for key in rotation_list:
305  if not key in origin: origin[key] = 0
306  if not key in trans: trans [key] = 0
307  for i in range( len(rotation_list) ):
308  vector[i,0] -= origin[rotation_list[i]]
309  vector = rotation_matrix*vector
310  for i in range( len(rotation_list) ):
311  self.set(rotation_list[i], float(vector[i,0]+trans[rotation_list[i]]+origin[rotation_list[i]]))
312  self.mass_shell_condition(mass_shell_variable)
313  return self
314 
315  def mass_shell_condition(self, variable_string, float_tolerance = 1.e-6):
316  """
317  Change variable represented by variable_string to force E^2 = p^2 + m^2
318 
319  - variable_string = string which should be one of the list mass_shell_variables().
320  """
321  if(variable_string == ''):
322  return
323  px = self.get('px')
324  py = self.get('py')
325  pz = self.get('pz')
326  e = self.get('energy')
327  m = self.get('mass')
328  if(variable_string == 'p'):
329  self.set('p', ( (e-m)*(e+m) )**0.5 )
330  elif(variable_string == 'px'):#get direction right!
331  val = (e*e-m*m-py*py-pz*pz)
332  if val>float_tolerance: self.set('px', abs(val)**1.5/val )
333  else: self.set('px', 0.)
334  elif(variable_string == 'py'):
335  val = (e*e-m*m-px*px-pz*pz)
336  if val>float_tolerance: self.set('py', abs(val)**1.5/val )
337  else: self.set('py', 0.)
338  elif(variable_string == 'pz'):
339  val = (e*e-m*m-px*px-py*py)
340  if val>float_tolerance: self.set('pz', abs(val)**1.5/val )
341  else: self.set('pz', 0.)
342  elif(variable_string == 'energy'):
343  self.set('energy', (m*m+px*px+py*py+pz*pz) **0.5 )
344  else:
345  raise IndexError('mass_shell_condition did not recognise \''+str(variable_string)+'\'. Options are '+str(self.__mass_shell_variables))
346 
347  # Manipulators ################
348 
349  def get(self, key):
350  """
351  Return the value referenced by key
352 
353  - key = string which should be one of the list get_variables()
354  """
355  try:
356  return self.__hitcore.get(key)
357  except Exception:
358  pass
359  if(key in self.__get_variables):
360  return self.__get_variables[key](self)
361  else:
362  raise IndexError('Key \''+str(key)+'\' could not be found for Hit.get() - should be one of '+str(Hit.get_variables()))
363 
364  def set(self, key, value):
365  """
366  Set the value referenced by key
367 
368  - key = string which should be one of the list get_variables()
369  - value = float
370  """
371  try:
372  self.__hitcore.set(key, value)
373  return
374  except: pass
375  if(key in self.__set_variables):
376  self.__set_variables[key](self, value)
377  else:
378  raise IndexError('Key \''+str(key)+'\' could not be found for Hit.set() - should be one of '+str(Hit.set_variables()))
379 
380  def check(self, tolerance_float=1e-3):
381  """Return True if mass shell condition is obeyed and pid is correct for the mass else return False"""
382  pid = self.get('pid')
383  if (not abs(pid) in Common.pdg_pid_to_mass) and (not pid in Hit.__bad_pids) and (not pid == 0):
384  print 'pid not recognised',self.get('pid')
385  return False
386  if abs(pid) in Common.pdg_pid_to_mass.keys():
387  if abs(self.get('mass')-Common.pdg_pid_to_mass[abs(pid)]) > tolerance_float:
388  print 'Mass',self.get('mass'),'does not match pid',self.get('pid')
389  return False
390  if abs(round(self.get('p')**2 + self.get('mass')**2) - round(self.get('energy')**2)) > tolerance_float :
391  return False
392  return True
393 
394  def dict_from_hit(self):
395  """
396  Return a dict that uniquely defines the hit, so that new_from_dict(dict_from_hit(hit)) returns a copy of hit
397  """
398  my_dict = {}
399  for key in self.__hitcore.set_variables():
400  my_dict[key] = self.__hitcore.get(key)
401  return my_dict
402 
403  # IO ###################
404 
405  def write_builtin_formatted(self, format, file_handle):
406  """
407  Write to a file formatted according to built-in file_type format
408 
409  - format = string from file_types
410  - file_handle = file handle made using e.g. open() command
411 
412  e.g. aHit.write_builtin_formatted('icool_for009', for009_dat) would write aHit in icool_for009 format to for009_dat
413  """
414  if( format.find('maus') > -1 ):
415  raise IOError("Can't write single maus hits, only lists of hits")
416  if( format.find('muon1_csv') > -1 ):
417  raise IOError("muon1 is only available for reading - sorry")
418  if( format.find('icool') > -1 ):
419  self.set('pid', Common.pdg_pid_to_icool[self.get('pid')])
420  if( format.find('mars') > -1 ):
421  self.set('pid', Common.pdg_pid_to_mars [self.get('pid')])
422  self.__write_formatted(self.__file_formats[format], self.__file_units[format], file_handle)
423  if( format.find('icool') > -1 ):
424  self.set('pid', Common.icool_pid_to_pdg[self.get('pid')])
425  if( format.find('mars') > -1 ):
426  self.set('pid', Common.mars_pid_to_pdg [self.get('pid')])
427 
428  def write_list_builtin_formatted(list_of_hits, file_type_string, file_name, user_comment=None):
429  """
430  Write a list of hits to a file formatted according to built-in file_type format
431 
432  - format = string from file_types
433  - file_handle = file handle made using e.g. open() command
434  - user_comment = comment included in some output formats (e.g. problem title, etc)
435 
436  e.g. aHit.write_builtin_formatted('icool_for009', for009_dat) would write aHit in icool_for009 format to for009_dat
437  """
438  if( file_type_string.find('maus_root') > -1 ):
439  raise IOError("Can't write maus_root formats")
440  filehandle = Hit.open_filehandle_for_writing(file_type_string, file_name, user_comment)
441  if( file_type_string.find('maus') > -1 ):
442  Common.has_json()
443  if file_type_string in Hit.__force_unique_particle_number:
444  list_of_hits = Hit.force_unique_particle_number(list_of_hits)
445  maus_tree = Hit.get_maus_tree(list_of_hits, file_type_string)
446  for item in maus_tree:
447  print >>filehandle,json.dumps(item)
448  return
449  if file_type_string.find('g4mice') > -1:
450  comptor = (Hit.__event_cmp)
451  else:
452  comptor = (Hit.__station_cmp)
453  list_of_hits.sort(comptor)
454  old_hit = None
455  current_hits = []
456  for hit_in in list_of_hits:
457  if old_hit == None or comptor(hit_in, old_hit) == 0:
458  current_hits.append(hit_in)
459  else:
460  if file_type_string == 'g4mice_special_hit':
461  filehandle.write(str(len(current_hits))+' SpecialHits\n')
462  if file_type_string == 'g4mice_virtual_hit':
463  filehandle.write(str(len(current_hits))+' VirtualHits\n')
464  for hit_out in current_hits:
465  try: hit_out.write_builtin_formatted(file_type_string, filehandle)
466  except: pass
467  if file_type_string.find( 'g4mice' ) > -1: filehandle.write('-1 STOP\n')
468  current_hits = [hit_in]
469  old_hit = hit_in
470  if file_type_string == 'g4mice_special_hit':
471  filehandle.write(str(len(current_hits))+' SpecialHits\n')
472  if file_type_string == 'g4mice_virtual_hit':
473  filehandle.write(str(len(current_hits))+' VirtualHits\n')
474  for hit_out in current_hits:
475  try:
476  hit_out.write_builtin_formatted(file_type_string, filehandle)
477  except:
478  print 'Warning - failed to write ',hit_out
479  if file_type_string.find( 'g4mice' ) > -1: filehandle.write('-1 STOP\n')
480  filehandle.close()
481  return
482  write_list_builtin_formatted = staticmethod(write_list_builtin_formatted)
483 
484  def open_filehandle_for_writing(file_type_string, file_name, user_comment=None):
485  """
486  Open a file handle of the specified type for writing. Some filehandles need special care, e.g. some are gzipped etc
487 
488  - file_type_string = open filehandle for this file type
489  - file_name = string name of the file
490  """
491  filehandle = None
492  if file_type_string.find('g4mice') > -1: filehandle = gzip.GzipFile(file_name, 'w')
493  else: filehandle = open(file_name, 'w')
494  filehandle.write(Hit.file_header(file_type_string, user_comment))
495  return filehandle
496  open_filehandle_for_writing = staticmethod(open_filehandle_for_writing)
497 
498  def file_header(file_type_string, user_comment=None):
499  """
500  Return the file_header for the given file_type. Optionally, can add a user comment
501 
502  - file_type_string = header returned for this file type. Select from file_types()
503  - user_comment = add a user comment - default is 'File generated by xboa'
504 
505  e.g. Hit.file_header('icool_for009', 'This is my for009 file') would set 'This is my for009 file'
506  as a user comment and return the header string
507  """
508  if user_comment == None: file_header = Hit.__file_headers[file_type_string].replace(str('<user>'), str(Hit.__default_user_string))
509  else: file_header = Hit.__file_headers[file_type_string].replace(str('<user>'), str(user_comment))
510  return file_header
511  file_header = staticmethod(file_header)
512 
513  def read_builtin_formatted(self, format, filehandle):
514  """
515  Read a single event (typically a single line) from a file formatted according to built-in file_type format
516 
517  - format = string from file_types
518  - file_handle = file handle made using e.g. open() command
520  e.g. aHit.read_builtin_formatted('icool_for009', for009_dat) would read aHit in icool_for009 format from for009_dat
521  """
522  if( format.find('maus') > -1 ):
523  raise IOError("Can't read single maus hits, only bunches")
524  if (format == 'muon1_csv'):
525  self.__read_muon1_csv(filehandle)
526  return
527  self.__read_formatted(self.__file_formats[format], self.__file_units[format], filehandle)
528  if( format.find('icool') > -1 ):
529  self.set('pid', Common.icool_pid_to_pdg[self.get('pid')])
530  if( format.find('mars') > -1 ):
531  self.set('pid', Common.mars_pid_to_pdg[self.get('pid')])
532  try:
533  self.set('mass', Common.pdg_pid_to_mass[abs(self.get('pid'))])
534  self.mass_shell_condition(self.__file_mass_shell[format])
535  except KeyError:
536  self.set('mass', 0.)
538  if self.get('pid') not in self.__bad_pids:
539  print 'Warning - could not resolve PID ',self.get('pid'),' setting mass to 0.'
540  self.__bad_pids.append(self.get('pid'))
541  if 'charge' not in self.__file_formats[format]:
542  try:
543  self.set('charge', Common.pdg_pid_to_charge[self.get('pid')])
544  except KeyError:
545  if self.get('pid') not in self.__bad_pids:
546  print 'Warning - could not resolve PID ',self.get('pid'),' setting charge to 0.'
547  self.__bad_pids.append(self.get('pid'))
548 
549  def write_user_formatted(self, format_list, format_units_dict, file_handle, separator=' '):
550  """
551  Write to a file formatted according to built-in file_type format
553  - format_list = ordered list of strings from get_variables()
554  - format_units_dict = dict of formats from format_list to units
555  - file_handle = file handle made using e.g. open() command
556 
557  e.g. aHit.write_user_formatted(['x','px','y','py'], ['x':'m','y':'m','px':'MeV/c','py':'MeV/c'], some_file, '@') would make output like
558  0.001@0.002@0.001@0.002 in some_file
559  """
560  self.__write_formatted(format_list, format_units_dict, file_handle, separator)
561 
562 
563  def read_user_formatted(self, format_list, format_units_dict, file_handle):
564  """
565  Read to a file formatted according to built-in file_type format
566 
567  - format_list = ordered list of strings from get_variables()
568  - format_units_dict = dict of formats from format_list to units
569  - file_handle = file handle made using e.g. open() command
570 
571  e.g. aHit.write_user_formatted('icool_for009', for009_dat) would write aHit in icool_for009 format to for009_dat
572  """
573  self.__read_formatted(format_list, format_units_dict, file_handle)
574  self.set( 'mass', Common.pdg_pid_to_mass[abs(self.get('pid'))] );
575 
576  def file_types():
577  """Static function returns a list of available file types"""
578  return Hit.__file_types
579  file_types = staticmethod(file_types)
580 
581  def force_unique_particle_number(list_of_hits):
582  """
583  Force all hits in the list to have unique particle numbers
584 
585  - list_of_hits = list of hits to check for particle numbers
586 
587  Returns list_of_hits with particle numbers updated to ensure they are unique
588  Starts adding new particle numbers indexed from 0
589  """
590  # not efficient
591  new_hits = ['']*len(list_of_hits) # need to force a copy in case we have duplicate references in the list
592  for i, hit in enumerate(list_of_hits):
593  new_hits[i] = copy.deepcopy(hit)
594  particle_numbers = []
595  lowest_unused_int = 0
596  for i, hit in enumerate(new_hits):
597  while lowest_unused_int in particle_numbers:
598  lowest_unused_int += 1
599  if new_hits[i]['particle_number'] in particle_numbers:
600  new_hits[i]['particle_number'] = lowest_unused_int
601  particle_numbers.append(new_hits[i]['particle_number'])
602  return new_hits
603  force_unique_particle_number = staticmethod(force_unique_particle_number)
604 
605  def get_maus_tree(list_of_hits, type_name):
606  """
607  Convert from list of hits to a tree of maus objects
608 
609  - list_of_hits = list of hits to be converted
610  - type_name = maus type, used to define position in the maus tree
612  Return value is a list of maus spills (each of which is a data tree)
613  """
614  # tried to be fairly general here; this should work for any tree that has all hit data
615  # stored either directly at the same level or in three vectors at the same level
616  # if we need to put pid here, momentum there, etc... then we need to think again
617  return Hit.__get_maus_tree_recursive(list_of_hits, [["event_number"]]+Hit.__maus_paths[type_name], type_name)
618  get_maus_tree = staticmethod(get_maus_tree)
619 
620  def get_maus_dict(self, type_name):
621  """
622  Convert from hit to a maus dict for MAUS IO
623 
624  - type_name = name of the maus type to generate
625 
626  Returns a tuple of (maus_dict, spill_number)
627  """
628  maus_dict = {}
629  three_vec_conversions = Hit.__maus_three_vec_conversions[type_name]
630  conversion_dict = Hit.__maus_variable_conversions[type_name]
631  for maus_name, xboa_suffix in three_vec_conversions.iteritems():
632  maus_dict[maus_name] = {}
633  for xyz in ['x','y','z']:
634  maus_dict[maus_name][xyz] = self[xboa_suffix+xyz]
635  for maus_key, xboa_key in conversion_dict.iteritems():
636  maus_dict[maus_key] = self[xboa_key]
637  for key, value in Hit._Hit__file_units[type_name]:
638  xboa_dict[key] *= Hit._Hit__file_units[type_name][key]
639  return (maus_dict, self['event_number'])
640 
641  # static data that describe the class #################
642 
643  def mass_shell_variables():
644  """Static function returns a list of variables suitable for mass_shell_condition calls"""
645  return Hit.__mass_shell_variables
646  mass_shell_variables = staticmethod(mass_shell_variables)
647 
648  def get_variables():
649  """Static function returns a list of variable suitable for get calls"""
650  return Hit.__get_keys
651  get_variables = staticmethod(get_variables)
652 
653  def set_variables():
654  """Static function returns a list of variable suitable for set calls"""
655  return Hit.__set_keys
656  set_variables = staticmethod(set_variables)
657 
658  def get_bad_pids():
659  """
660  If Hit fails to read a pid, it is stored in list bad_pids for later reference. Returns the list
661  """
662  return Hit.__bad_pids
663  get_bad_pids = staticmethod(get_bad_pids)
665  def set_bad_pids(bad_pid_list):
666  """
667  If Hit fails to read a pid, it is stored in list bad_pids for later reference. Set the list
668  """
669  Hit.__bad_pids = bad_pid_list
670  set_bad_pids = staticmethod(set_bad_pids)
671 
672  def get_maus_paths():
673  """
674  Returns a dict of <maus_type>:<path> where <maus_type> is a string type name and <path> is a list
675  that tells how data is stored in maus json dicts.
676  """
677  return Hit.__maus_paths
678  get_maus_paths = staticmethod(get_maus_paths)
679 
680 
681  def set_opal_pid(pid):
682  """
683  Set up opal loss file pid
685  - pid = (int) pdg particle id of particles in the opal_loss file
686  """
687  Hit.__opal_pid = pid
688  set_opal_pid = staticmethod(set_opal_pid)
689 
691  """
692  Get opal loss file pid
693  """
694  return Hit.__opal_pid
695  get_opal_pid = staticmethod(get_opal_pid)
697  def set_opal_probes(probes):
698  """
699  Set up opal probe name to station mapping
700 
701  - probes = Dict mapping probe name to station number. Stations should be
702  numbered sequentially from 1. Station number will be calculated
703  like hit['station'] = probe_index+n_probes*turn_number. In any
704  case, xboa will add any unrecognised probe commands as a new
705  station...
706  """
707  Hit.__opal_probes = probes
708  set_opal_probes = staticmethod(set_opal_probes)
709 
710  # get and set variables ####################
711 
712  def get_p (self):
713  """Returns total momentum of the hit"""
714  return (self.get('px')**2+self.get('py')**2+self.get('pz')**2)**0.5
715 
716  def get_r (self):
717  """Returns transverse distance (i.e. in x,y space) from 0,0"""
718  return (self.get('x')**2+self.get('y')**2)**0.5
719 
720  def get_phi (self):
721  """Returns transverse angle (i.e. in x,y space) in range (-pi, pi); phi = 0. is positive y and phi = pi/2 is positive x"""
722  return math.atan2(self['y'], self['x'])
723 
724  def get_pt (self):
725  """Returns transverse momentum of the hit"""
726  return (self['px']**2+self['py']**2)**0.5
727 
728  def get_pphi(self):
729  """Returns transverse angle of momentum (i.e. in px,py space) in range (-pi, pi); phi = 0. is positive py and phi = pi/2 is positive px"""
730  return math.atan2(self['py'], self['px'])
731 
732  def get_r_squared(self):
733  """Returns x divergence i.e. px/pz of the hit"""
734  return (self['x']**2+self['y']**2)
735 
736  def get_xP (self):
737  """Returns x divergence i.e. px/pz of the hit"""
738  return (self['px']/self['pz'])
739 
740  def get_yP (self):
741  """Returns y divergence i.e. py/pz of the hit"""
742  return (self['py']/self['pz'])
743 
744  def get_tP (self):
745  """Returns t \'divergence\' i.e. E/pz of the hit"""
746  return (-self['energy']/self['pz'])
747 
748  def get_rP (self):
749  """Returns dr/dz = pt/pz of the hit"""
750  return (self.get('pt')/self['pz'])
751 
752  def get_spin(self):
753  """Returns absolute value of the spin"""
754  return (self.get('sx')**2+self.get('sy')**2+self.get('sz')**2)**0.5
755 
756  def get_ct (self):
757  """Returns speed_of_light*t of the hit"""
758  return (self['t']*Common.constants['c_light'])
759 
760  def get_ek (self):
761  """Returns total energy - mass, ie kinetic energy of the hit"""
762  return self['energy'] - self.get('mass')
763 
764  def set_ek (self, value_float):
765  """Sets kinetic energy = total energy - mass of the hit"""
766  self['energy'] = value_float + self.get('mass')
767 
768  def get_l_kin(self):
769  """Returns kinetic angular momentum about the z-axis.
770  To use a different axis, you will have to perform your own transformation"""
771  return self['x']*self['py'] - self['y']*self['px']
772 
773  def set_ct (self, value_float):
774  """Sets t = value_float/c_light"""
775  self['t'] = value_float/Common.constants['c_light']
776 
777  def set_p(self, value_float):
778  """Set p to value_float keeping momentum direction constant"""
779  p = self.get_p()
780  if(p == 0):
781  self.set('pz', 1.)
782  scale = value_float/self.get_p()
783  self['px'] *= scale
784  self['py'] *= scale
785  self['pz'] *= scale
786 
787  def set_xP (self, value_float):
788  """Set x\' to value_float keeping pz constant"""
789  if(math.fabs(self['pz']) < 1e-9): raise FloatingPointError('Cant set x\' while pz is 0')
790  self['px'] = value_float*self['pz']
791 
792  def set_yP (self, value_float):
793  """Set y\' to value_float keeping pz constant"""
794  if(math.fabs(self['pz']) < 1e-9): raise FloatingPointError('Cant set y\' while pz is 0')
795  self['py'] = value_float*self['pz']
796 
797  def set_tP (self, value_float):
798  """Set t\' (dt/dz=-E/pz) to value_float keeping pz constant; note sign of pz may change"""
799  if(math.fabs(self['pz']) < 1e-9): raise FloatingPointError('Cant set t\' while pz is 0')
800  self['energy'] = -value_float*self['pz']
801  if self['pz'] < 0.:
802  self['energy'] *= -1.
803  self['pz'] *= -1.
804  if self['energy'] < self.get('mass'): raise FloatingPointError('Energy less than muon mass')
805 
806  def get_weight(self):
807  """Returns total weight for this Hit"""
808  return self.get('global_weight')*self.get('local_weight')
809 
811  """Set all global weights to 1"""
812  Hitcore.clear_global_weights()
813  clear_global_weights = staticmethod(clear_global_weights)
814 
816  """Clear memory allocated to global weights - also resets global weights to 1"""
817  raise NotImplementedError("delete_global_weights is deprecated - please use clear_global_weights")
818  delete_global_weights = staticmethod(delete_global_weights)
819 
820  def get_local_weight(self):
821  """Returns local weight for this Hit"""
822  return self.get('local_weight')
823 
824  def set_local_weight(self, value):
825  """Set local weight for this Hit to be value"""
826  self.set('local_weight', value)
827 
828  def get_global_weight(self):
829  """Returns global weight for this Hit"""
830  return self.get('global_weight')
831 
832  def set_global_weight(self, value):
833  """Set global weight for this Hit to be value"""
834  self.set('global_weight', value)
835 
836  def set_g4bl_unit(unit):
837  """
838  g4beamline_track_file can take different units for length - set the unit here
839 
840  - unit = string that is a unit of length
842  e.g. set_g4bl_unit('m') would set the length unit to metres
843  """
844  Hit.__file_units['g4beamline_bl_track_file']['x'] = unit
845  Hit.__file_units['g4beamline_bl_track_file']['y'] = unit
846  Hit.__file_units['g4beamline_bl_track_file']['z'] = unit
847  set_g4bl_unit = staticmethod(set_g4bl_unit)
848 
849 # privates #######################
850 
851  #BEGIN PRIVATE DATA AND FUNCTIONS - DONT USE THEM!
852  #read formatted input - don't touch mass or pid beyond reading them
853  def __read_formatted(self, format_list, format_units_dict, file_handle):
854  try:
855  line = file_handle.next()
856  except StopIteration:
857  raise EOFError("End of file reached")
858  words = line.split()
859  words.reverse()
860  if not(len(words) == len(format_list)):
861  raise IOError("Read operation failed with line "+str(line))
862  for key in format_list:
863  if key != '':
864  value = words.pop()
865  try:
866  value = Hit.__default_var_types[key](value)
867  except ValueError: #I ran into problems with int > 1000000 written in scientific notation by Geant4 (urk)
868  value = float(value)
869  value = Hit.__default_var_types[key](value)
870  if Hit.__default_var_types[key] == float:
871  value *= Common.units[format_units_dict[key]]
872  self.set(key, value)
873  else: words.pop()
875  # read beam loss hit
876  # probes is the list of probes discovered - appended to dynamically
877  def __read_opal_loss_file(self, file_handle):
878  probes = Hit.__opal_probes
879  pid = Hit.__opal_pid
880  if pid == 0:
881  raise RuntimeError("Error - need to set pid using Hit.set_opal_pid()")
882  try:
883  line = file_handle.next()
884  except StopIteration:
885  raise EOFError("End of file reached")
886  words = line.split()
887  if words[0] in Hit.__opal_ignore_probes:
888  raise BadEventError("Failed to parse event")
889  if not len(words) == 10:
890  raise BadEventError("Failed to parse event")
891  if words[0] not in probes.keys():
892  probes[words[0]] = len(probes.keys())
893  hit_dict = dict( (key, float(words[i+1]) ) for i, key in enumerate(['x', 'y', 'z', 'px', 'py', 'pz', 'event_number', 'station', 't']))
894  hit_dict['pid'] = pid
895  hit_dict['mass'] = Common.pdg_pid_to_mass[abs(pid)]
896  hit_dict['charge'] = Common.pdg_pid_to_charge[pid]
897  hit_dict['station'] = probes[words[0]]+len(probes)*(int(words[8])-1)
898  for item in ['px', 'py', 'pz']:
899  hit_dict[item] *= hit_dict['mass']
900  for key, value in hit_dict.iteritems():
901  self.set(key, value)
902  self.mass_shell_condition('energy')
903 
904  def __read_muon1_csv(self, filehandle):
905  # BUG for some reason muon1 puts the run header in the middle of the file...
906  try:
907  line = filehandle.next()
908  except StopIteration:
909  raise EOFError("End of file reached")
910 
911  words = line.split(',')
912  try:
913  float(words[0])
914  except ValueError:
915  try:
916  line = filehandle.next()
917  words = line.split(',')
918  float(words[0])
919  except StopIteration:
920  raise EOFError("End of file reached")
921  except ValueError:
922  raise
923 
924  hit_dict = dict( (key, float(words[i]) ) for i, key in enumerate(['x', 'y', 't', 'px', 'py', 'pz']))
925 
926  pid = Common.muon1_pid_to_pdg[words[-2]]
927  mass = Common.pdg_pid_to_mass[abs(pid)]
928  mom_units = mass/(Common.constants['c_light']/Common.units['m']*Common.units['s'])
929  units = {'x':Common.units['m'], 'y':Common.units['m'], 't':Common.units['s'], 'px':mom_units, 'py':mom_units, 'pz':mom_units}
930  for key, value in hit_dict.iteritems():
931  hit_dict[key] *= units[key]
932  hit_dict['pid'] = pid
933  hit_dict['mass'] = mass
934  hit_dict['charge'] = Common.pdg_pid_to_charge[hit_dict['pid']]
935  hit_dict['station'] = 0
936  for key, value in hit_dict.iteritems():
937  self.set(key, value)
938  self.mass_shell_condition('energy')
939 
940  #write formatted output - don't touch mass or pid beyond reading them
941  def __write_formatted(self, format_list, format_units_dict, file_handle, separator=' '):
942  for key in format_list:
943  if key == '': value = 0
944  else: value = Hit.__default_var_types[key](self.get(key)/Common.units[ format_units_dict[key] ])
945  file_handle.write( str( value )+separator)
946  file_handle.write('\n')
947 
948  # extract virtual hits from the root tree
949  def __get_maus_root_virtual_hits(mc_event, track_number):
950  hit_list = []
951  for i in range(mc_event.GetVirtualHitsSize()):
952  maus_hit = mc_event.GetAVirtualHit(i)
953  pid = maus_hit.GetParticleId()
954  hit_dict = {'pid':pid, 't':maus_hit.GetTime(), 'charge':maus_hit.GetCharge(), 'proper_time':maus_hit.GetProperTime(),
955  'path_length':maus_hit.GetPathLength(), 'station':maus_hit.GetStationId(),
956  'x':maus_hit.GetPosition().x(), 'y':maus_hit.GetPosition().y(), 'z':maus_hit.GetPosition().z(),
957  'px':maus_hit.GetMomentum().x(), 'py':maus_hit.GetMomentum().y(), 'pz':maus_hit.GetMomentum().z(),
958  'bx':maus_hit.GetBField().x(), 'by':maus_hit.GetBField().y(), 'bz':maus_hit.GetBField().z(),
959  'ex':maus_hit.GetEField().x(), 'ey':maus_hit.GetEField().y(), 'ez':maus_hit.GetEField().z(),
960  'particle_number':maus_hit.GetTrackId()}
961  hit_dict['mass'] = Common.pdg_pid_to_mass[abs(pid)]
962  if hit_dict['pid'] != 0:
963  hit_list.append(Hit.new_from_dict(hit_dict, 'energy'))
964  else:
965  print 'Warning - pid 0 detected in maus_root_virtual_hit; hit will not be loaded'
966  return hit_list
967  __get_maus_root_virtual_hits = staticmethod(__get_maus_root_virtual_hits)
968 
969  def __get_maus_root_primary_hits(mc_event, track_number):
970  maus_hit = mc_event.GetPrimary()
971  pid = maus_hit.GetParticleId()
972  hit_dict = {'pid':pid, 'energy':maus_hit.GetEnergy(), 't':maus_hit.GetTime(),
973  'x':maus_hit.GetPosition().x(), 'y':maus_hit.GetPosition().y(), 'z':maus_hit.GetPosition().z(),
974  'px':maus_hit.GetMomentum().x(), 'py':maus_hit.GetMomentum().y(), 'pz':maus_hit.GetMomentum().z()}
975  hit_dict['particle_number'] = 0
976  hit_dict['charge'] = Common.pdg_pid_to_charge[abs(pid)]
977  hit_dict['mass'] = Common.pdg_pid_to_mass[abs(pid)]
978  return [Hit.new_from_dict(hit_dict, 'energy')]
979  __get_maus_root_primary_hits = staticmethod(__get_maus_root_primary_hits)
980 
981  # split a list into sub lists where items in sublist have item1[sort_value] == item2[sort_value]
982  def __split_list_by_equality(a_list, sort_attribute):
983  a_dict = {}
984  for item in a_list:
985  value = item[sort_attribute]
986  if not value in a_dict: a_dict[value] = []
987  a_dict[value].append(item)
988  return a_dict.values()
989  __split_list_by_equality = staticmethod(__split_list_by_equality)
990 
991  # recursively reconstruct the maus_path
992  def __get_maus_tree_recursive(list_of_hits, maus_path, format):
993  if len(maus_path) == 1:
994  if type(maus_path[0]) == type(""):
995  if len(list_of_hits) > 1:
996  raise IOError("More than one hit for maus key")
997  return {maus_path[0]:list_of_hits[0].get_maus_dict(format)[0]}
998  if type(maus_path[0]) == type([]):
999  return [hit.get_maus_dict(format)[0] for hit in list_of_hits]
1000  if type(maus_path[0]) == type([]):
1001  list_of_hits_new = Hit.__split_list_by_equality(list_of_hits, maus_path[0][0])
1002  my_output = [Hit.__get_maus_tree_recursive(x, maus_path[1:], format) for x in list_of_hits_new]
1003  if len(maus_path) == 1:
1004  output = []
1005  for out in my_output: output += out
1006  else: output = my_output
1007  return my_output
1008  if type(maus_path[0]) == type(""):
1009  return {maus_path[0]:Hit.__get_maus_tree_recursive(list_of_hits, maus_path[1:], format)}
1010  __get_maus_tree_recursive = staticmethod(__get_maus_tree_recursive)
1011 
1012  def __return_one(self, value=''):
1013  return 1.
1014 
1015  def __do_nothing(self, value = ''):
1016  pass
1017 
1018  #internal data
1019  #information on available types
1020  __file_types = ['icool_for009', 'icool_for003', 'g4beamline_bl_track_file', 'g4mice_special_hit', 'g4mice_virtual_hit','mars_1', 'muon1_csv'] #'opal_loss'
1021  try:
1022  Common.has_json()
1023  __file_types += ['maus_virtual_hit', 'maus_primary']
1024  except ImportError:
1025  pass
1026  try:
1027  Common.has_maus()
1028  __file_types += ['maus_root_virtual_hit', 'maus_root_primary']
1029  except ImportError:
1030  pass
1031 
1032  __mass_shell_variables = ['', 'p', 'px', 'py', 'pz', 'energy']
1033  __get_variables = {'p':get_p,'r':get_r,'phi':get_phi,'pt':get_pt,'pphi':get_pphi,'x\'':get_xP,'y\'':get_yP,'t\'':get_tP, 'ct\'':get_tP,'r\'':get_rP,'spin':get_spin,
1034  'weight':get_weight,'ct':get_ct,'r_squared':get_r_squared,'z\'':__return_one,'kinetic_energy':get_ek,
1035  'l_kin':get_l_kin,'':__do_nothing}
1036  __set_variables = {'p':set_p,'x\'':set_xP,'y\'':set_yP,'t\'':set_tP,'ct':set_ct,'kinetic_energy':set_ek,'':__do_nothing}
1037  __default_var_types = {'x':float,'y':float,'z':float,'t':float,'px':float,'py':float,'pz':float,'energy':float,'bx':float,'by':float,'bz':float,
1038  'ex':float,'ey':float,'ez':float,'eventNumber':int, 'event_number':int, 'particleNumber':int, 'particle_number':int, 'pid':int,'status':int,'station':int,'local_weight':float,
1039  'sx':float,'sy':float,'sz':float,'mass':float,'path_length':float,'proper_time':float,'e_dep':float, 'charge':float}
1040 
1041  __get_keys = []
1042  __set_keys = []
1043  for key in Hitcore.get_variables():
1044  __get_keys.append(key)
1045  for key in Hitcore.set_variables():
1046  __set_keys.append(key)
1047  for key, value in __get_variables.iteritems():
1048  __get_keys.append(key)
1049  for key, value in __set_variables.iteritems():
1050  __set_keys.append(key)
1051  for key, value in __get_variables.iteritems():
1052  if not key in __default_var_types:
1053  __default_var_types[key] = float #assume everything else is a float
1054 
1055  #formatting information
1056  __file_formats = {
1057  'icool_for009' : ['eventNumber', 'particleNumber', 'pid', 'status', 'station', 't', 'x', 'y', 'z', 'px', 'py', 'pz', 'bx', 'by', 'bz', 'local_weight',
1058  'ex', 'ey', 'ez', '', 'sx', 'sy', 'sz'],
1059  'icool_for003' : ['eventNumber', 'particleNumber', 'pid', 'status', 't', 'local_weight', 'x', 'y', 'z', 'px', 'py', 'pz', 'sx', 'sy', 'sz'],
1060  'g4beamline_bl_track_file' : ['x','y','z','px','py','pz','t','pid','eventNumber','particleNumber', '','local_weight'],
1061  'ZGoubi' : [],
1062  'Turtle' : [],
1063  'MadX' : [],
1064  'g4mice_special_hit': ['', 'station','','','','','particleNumber','','pid','mass','x','y','z','t','px','py','pz','energy','e_dep','bx','by','bz','ex','ey','ez',
1065  '','','', '','','', '','','', '','','','path_length','proper_time'],
1066  'g4mice_virtual_hit': ['', 'station','particleNumber','pid','mass','','x','y','z','t','px','py','pz','energy','bx','by','bz','ex','ey','ez','',''],
1067  'mars_1' : ['eventNumber','pid','x','y','z','px','py','pz','energy','ct','local_weight']
1068  }
1069  __file_units = {
1070  'icool_for009' : {'eventNumber':'', 'particleNumber':'', 'pid':'', 'status':'', 'station':'', 't':'s', 'x':'m', 'y':'m', 'z':'m', 'px':'GeV/c', 'py':'GeV/c',
1071  'pz':'GeV/c', 'bx':'T', 'by':'T', 'bz':'T', 'local_weight':'',
1072  'ex':'GV/m', 'ey':'GV/m', 'ez':'GV/m', 'sx':'', 'sy':'', 'sz':'', '':''},
1073  'icool_for003' : {'eventNumber':'', 'particleNumber':'', 'pid':'', 'status':'', 't':'s', 'local_weight':'', 'x':'m', 'y':'m', 'z':'m', 'px':'GeV/c', 'py':'GeV/c', 'pz':'GeV/c', 'sx':'', 'sy':'', 'sz':''},
1074  'g4beamline_bl_track_file' : {'x':'mm','y':'mm','z':'mm','px':'MeV/c','py':'MeV/c','pz':'MeV/c','t':'ns','pid':'','eventNumber':'','station':'','local_weight':'', 'particleNumber':''},
1075  'ZGoubi' : {},
1076  'Turtle' : {},
1077  'MadX' : {},
1078  'g4mice_special_hit': {'':'', 'station':'','':'','':'','':'','':'','particleNumber':'','':'','pid':'','mass':'MeV/c2','x':'mm','y':'mm','z':'mm','t':'ns','px':'MeV/c','py':'MeV/c','pz':'MeV/c','energy':'MeV','e_dep':'MeV','bx':'kT','by':'kT','bz':'kT','ex':'GV','ey':'GV','ez':'GV',
1079  '':'','':'','':'', '':'','':'','':'', '':'','':'','':'', '':'','':'','':'','path_length':'mm','proper_time':'ns'},
1080  'g4mice_virtual_hit': {'':'','station':'','particleNumber':'','':'','pid':'','mass':'MeV/c2','x':'mm','y':'mm','z':'mm','t':'ns','px':'MeV/c','py':'MeV/c','pz':'MeV/c','energy':'MeV','':'','bx':'kT','by':'kT','bz':'kT','ex':'GV','ey':'GV','ez':'GV'},
1081  'mars_1' : {'eventNumber':'','pid':'','x':'mm','y':'mm','z':'mm','px':'GeV/c','py':'GeV/c','pz':'GeV/c','energy':'GeV','ct':'cm','local_weight':''},
1082  'maus_virtual_hit': {},
1083  'maus_primary': {},
1084  }
1085 
1086  __file_headers = {
1087  'icool_for003':'<user>\n0. 0. 0. 0. 0. 0. 0. 0.\n',
1088  'icool_for009':'#<user>\n# units = [s] [m] [GeV/c] [T] [V/m]\nevt par typ flg reg time x y z Px Py Pz Bx By Bz wt Ex Ey Ez arclength polX polY polZ\n',
1089  'g4beamline_bl_track_file':'#BLTrackFile <user>\n#x y z Px Py Pz t PDGid EvNum TrkId Parent weight\n',
1090  'ZGoubi':'',
1091  'Turtle':'',
1092  'MadX':'',
1093  'g4mice_special_hit':'',
1094  'g4mice_virtual_hit':'',
1095  'mars_1':'',
1096  'maus_virtual_hit':'',
1097  'maus_primary':'',
1098  }
1099  __default_user_string = 'File generated by X_BOA'
1100 
1101  def __event_cmp(lhs, rhs):
1102  return cmp(lhs.get('eventNumber'), rhs.get('eventNumber'))
1103  __event_cmp = staticmethod(__event_cmp)
1104 
1105  def __station_cmp(lhs, rhs):
1106  return cmp(lhs.get('station'), rhs.get('station'))
1107  __station_cmp = staticmethod(__station_cmp)
1108 
1109  __file_mass_shell = {'icool_for009':'energy', 'icool_for003':'energy', 'g4beamline_bl_track_file':'energy', 'zgoubi':'', 'Turtle':'', 'MadX':'',
1110  'g4mice_special_hit':'', 'g4mice_virtual_hit':'','mars_1':'energy', 'maus_virtual_hit':'energy', "maus_primary":"p",
1111  'opal_loss':'', 'muon1_csv':''}
1112 
1113  __hit_sort_comparator = {
1114  'icool_for009': __event_cmp,
1115  'icool_for003': __event_cmp,
1116  'g4beamline_bl_track_file': __event_cmp,
1117  'ZGoubi': __event_cmp,
1118  'Turtle': __event_cmp,
1119  'MadX': __event_cmp,
1120  'g4mice_special_hit': __station_cmp,
1121  'g4mice_virtual_hit': __station_cmp,
1122  'maus_virtual_hit': __event_cmp,
1123  'maus_primary': __event_cmp,
1124  'mars_1': __event_cmp,
1125  'muon1_csv': __event_cmp,
1126  }
1127 
1128  __force_unique_particle_number = ['maus_primary']
1129 
1130  __angular_momentum_centroid = (0.,0.,0.)
1131  __angular_momentum_vector = (0.,0.,1.)
1132  __bad_pids = []
1133 
1134  __maus_three_vec_conversions = { # maus three vectors are sub-dicts of virtual_hit
1135  "maus_virtual_hit":{"position":"", "momentum":"p", "b_field":"b", "e_field":"e"},
1136  "maus_primary":{"position":"", "momentum":"p"}
1137  }
1138 
1139  __maus_variable_conversions = {
1140  "maus_virtual_hit":{"station_id":"station", "particle_id":"pid", "track_id":"particle_number", "time":"t", "mass":"mass", "charge":"charge", "proper_time":"proper_time", "path_length":"path_length"},
1141  "maus_primary":{"particle_id":"pid", "time":"t", "energy":"energy"} # we also force "mass" from "pid"
1142  }
1144  __maus_paths = {
1145  "maus_virtual_hit":["mc_events", ["particle_number"], "virtual_hits", ["station"]],
1146  "maus_primary":["mc_events", ["particle_number"], "primary"],
1147  }
1148 
1149  __maus_root_mc_types = {
1150  "maus_root_virtual_hit": lambda x, y: Hit.__get_maus_root_virtual_hits(x, y),
1151  "maus_root_primary": lambda x, y: Hit.__get_maus_root_primary_hits(x, y)
1152  }
1153  __maus_root_recon_types = {}
1154 
1155  __opal_ignore_probes = ["RING"]
1156  __opal_probes = {}
1157  __opal_pid = 0
1158 
1159  def hit_overview_doc(verbose = False):
1160  """Creates some summary documentation for the Hit class. If verbose is True then will also print any functions or data not included in summary"""
1161  name_list = ['initialise', 'get', 'set', 'transform', 'io', 'ancillary']
1162  function_list = {
1163  'initialise' : ['new_from_dict', 'new_from_read_builtin', 'new_from_read_user', 'new_from_maus_object', 'copy', 'deepcopy'],
1164  'get' : ['get', 'get_ct', 'get_ek', 'get_global_weight', 'get_l_kin', 'get_local_weight', 'get_p', 'get_phi', 'get_pphi', 'get_pt', 'get_r', 'get_rP', 'get_r_squared', 'get_spin', 'get_tP', 'get_vector', 'get_weight', 'get_xP', 'get_yP'],
1165  'set' : ['set', 'set_ct', 'set_ek', 'set_local_weight', 'set_p', 'set_tP', 'set_variables', 'set_xP', 'set_yP', 'set_global_weight'],
1166  'transform' : ['abelian_transformation', 'translate', 'mass_shell_condition'],
1167  'io' : ['file_header', 'file_types', 'set_g4bl_unit', 'write_builtin_formatted', 'write_list_builtin_formatted', 'write_user_formatted', 'open_filehandle_for_writing', 'read_builtin_formatted', 'read_user_formatted', 'get_maus_dict', 'get_maus_paths', 'get_maus_tree'],
1168  'ancillary' : ['check','clear_global_weights', 'delete_global_weights', 'get_bad_pids', 'set_bad_pids', 'dict_from_hit', 'mass_shell_variables', 'get_variables']
1169  }
1170  function_doc = {
1171  'initialise':'Functions that can be used to initialise a Hit in various different ways:',
1172  'get' :'Functions to get Hit data',
1173  'set' :'Functions to set Hit data',
1174  'transform' :'Functions that transform a Hit in some way:',
1175  'io' :'Output and some input helper functions:',
1176  'ellipse':'Functions to calculate beam ellipses based on Twiss parameters etc:',
1177  'ancillary':'Some other useful functions'
1178  }
1179  hit_doc = '\nHit class stores all data for a Hit on e.g. a detector - so for example, (x,y,z) and (px,py,pz) data. Mimics a string-indexed dict; full documentation for internal variables is given below under Hitcore. In brief, gettable variables are\n'+str(Hit.get_variables())+'\n and settable variables are\n'+str(Hit.set_variables())+'.\n Call using e.g. my_hit[\'x\'] = 3. Also has IO functions and a few other useful functions.\n'
1180  dir_hit = dir(Hit)
1181  if verbose:
1182  print 'The following functions and data are in Bunch but not in overview_doc:'
1183  for func in dir_hit:
1184  found = False
1185  for func_sublist in function_list.values():
1186  if func in func_sublist: found = True
1187  if not found: print func,
1188  print '\n'
1189 
1190  print 'The following functions and data are in bunch_overview_doc but not in Bunch:'
1191  for func_sublist in function_list.values():
1192  for func in func_sublist:
1193  if func not in dir_hit:
1194  print func,
1195  print
1196 
1197  doc = hit_doc
1198  for key in name_list:
1199  doc = doc + function_doc[key]+'\n'
1200  for item in function_list[key]:
1201  doc = doc+' '+item+'\n'
1202  return doc
1203  hit_overview_doc = staticmethod(hit_overview_doc)
1205 __doc__ = Hit.hit_overview_doc()