WCSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
kin_converter.py
Go to the documentation of this file.
1 #!/bin/env python3
2 
3 import argparse
4 import sys
5 from datetime import datetime
6 
7 ns_conversion = {'ns':1,
8  'us':1E3,
9  'ms':1E6,
10  's':1E9}
11 
12 parser = argparse.ArgumentParser(description='KinConverter: convert kin files that have multiple vertices into a single event (or multiple overlapping events)')
13 parser.add_argument('--input-filename','-i',required=True,type=str,help='Input .kin filename. Output filename(s) will be the same as the input filename & path, with [0-9].merge suffix(es) added')
14 parser.add_argument('--input-time-unit',required=True,choices=ns_conversion.keys(),help='The time unit of the input file')
15 parser.add_argument('--dark-noise-start', type=int,required=True,help='When to start the simulation (in ns)')
16 parser.add_argument('--dark-noise-end',type=int,required=True,help='When to end the simulation (in ns)')
17 parser.add_argument('--event-overlap',type=int,required=True,help='How long (in ns) to overlap')
18 parser.add_argument('--verbose','--v',type=int,default=0,help='Verbosity level')
19 #Use either this
20 parser.add_argument('--fixed-duration',type=int,default=None,help='A fixed duration (in ns) for each event')
21 #or these - dark rate, ntubes, NHits per MeV, max allowed hits
22 #TODO add option for windows that can change size depending on how many physics hits are expected
23 args = parser.parse_args()
24 
25 ToNS = ns_conversion[args.input_time_unit]
26 
27 def PrintNS(time):
28  for x in ['ns', 'us', 'ms', 's']:
29  if time < 1000.0:
30  return "%f %s" % (time, x)
31  time /= 1000.0
32 
33 DummyVertex = """$ nuance 0
34 $ vertex 0 0 0 0
35 $ track -12 0.00000 0.00000 0.00000 1.00000 -1
36 $ track 2212 938.27231 0.00000 0.00000 1.00000 -1
37 $ info 0 0 0
38 $ track -11 0.511 0 0 0 0
39 """
40 
41 #read a vertex at a time
42 def GetVertex(seq, group_by, exclude=['']):
43  data = []
44  for line in seq:
45  if isinstance(line, bytes):
46  line = line.decode()
47  if line.startswith(group_by):
48  if data:
49  yield data
50  data = []
51  if line.strip() in exclude:
52  continue
53  data.append(line)
54 
55  if data:
56  yield data
57 
58 #get the time from a vertex
59 def GetTime(vertex):
60  for line in vertex:
61  if 'vertex' in line:
62  return float(line.split()[-1]) * ToNS
63 
64 #check that the file is time ordered
65 def IsTimeOrdered(filename):
66  with open(filename, 'r') as fin:
67  for i, vertex in enumerate(GetVertex(fin, "$ begin")):
68  #skip the header
69  if vertex[0].startswith('#'):
70  continue
71  try:
72  last_time = this_time
73  except UnboundLocalError:
74  last_time = float(GetTime(vertex))
75  this_time = float(GetTime(vertex))
76  if this_time < last_time:
77  print(PrintNS(this_time), "comes before", PrintNS(last_time))
78  return False
79  print("Is time ordered")
80  return True
81 
82 #Sort the initial file by time
83 def SortByTime(filename):
84  outfilename = args.input_filename + '.temp'
85  with open(filename, 'r') as fin, open(outfilename, 'w') as fout:
86  print('TODO SortByTime() not yet implemented')
87  sys.exit(-1)
88 
89 #Get the header
90 def GetHeader(filename, args):
91  with open(filename, 'r') as fin:
92  for vertex in GetVertex(fin, "$ begin"):
93  #if there's no header, return blank
94  if vertex[0].startswith("$ begin"):
95  return ''
96  #return the header as a single string
97  header = ''.join(vertex) + ''\
98  '# Split by kin_converter ' + str(datetime.now()) + '\n'\
99  '# --fixed-duration ' + str(args.fixed_duration) + '\n'\
100  '# --event-overlap ' + str(args.event_overlap) + '\n'
101  return header
102 
103 #See if the file is time ordered
104 if not IsTimeOrdered(args.input_filename):
105  #If not, sort it
106  SortByTime(args.input_filename)
107 
108 header = GetHeader(args.input_filename, args)
109 print(header)
110 
111 
112 #loop over kin file start/stop times
113 event_start = args.dark_noise_start
114 last_event_end = args.dark_noise_end
115 ievent = 0
116 file_position = 0
117 while event_start < last_event_end:
118  event_end = event_start + args.fixed_duration
119  next_event_start = event_start + args.fixed_duration - args.event_overlap
120  print("Event", ievent, "corresponds to range", PrintNS(event_start), PrintNS(event_end))
121  with open(args.input_filename, 'rb') as fin, open(args.input_filename + '.%09d' % ievent, 'w') as fout:
122  #write the original header
123  fout.write(header)
124  #write the dark noise range
125  fout.write('# Event ' + str(ievent) + '\n')
126  fout.write('# /DarkRate/SetDarkLow ' + str(event_start) + '\n')
127  fout.write('# /DarkRate/SetDarkHigh ' + str(event_end) + '\n')
128  #and the event start
129  fout.write('$ begin\n')
130  nvertices = 0
131  #skip forward in the file a bit
132  if args.verbose:
133  print('Skipping to position in file', file_position)
134  fin.seek(file_position)
135  #loop over the input file
136  for i, vertex in enumerate(GetVertex(fin, "$ begin", ["$ begin", "$ end"])):
137  #skip the header and any partial vertices we've found from using seek()
138  if not vertex[0].startswith('$ nuance'):
139  continue
140  #get the event time
141  time = GetTime(vertex)
142  if args.verbose > 1:
143  print(PrintNS(time))
144  if args.verbose > 2:
145  print("Vertex #{}".format(i))
146  print("".join(vertex))
147  if time > event_end:
148  break
149  if time >= event_start:
150  fout.write(''.join(vertex))
151  nvertices += 1
152  #save the current file position if it is earlier than required for the next event
153  # At most, the position will be the '$ begin' line of the first vertex in the next event
154  if time < next_event_start:
155  file_position = fin.tell()
156  #need to add a dummy vertex, else WCSim/Geant4 will complain
157  if not nvertices:
158  fout.write(DummyVertex)
159  #and close the event/file
160  fout.write('$ end\n')
161  fout.write('$ stop\n')
162  print('contains', nvertices, 'vertices')
163  #increment for next event
164  event_start = next_event_start
165  ievent += 1