gstlal  0.8.1
 All Classes Namespaces Files Functions Variables Pages
cmp_nxydumps.py
1 #!/usr/bin/env python
2 
3 
4 import itertools
5 
6 
7 from glue import iterutils
8 from glue import segments
9 from lal import LIGOTimeGPS
10 
11 
12 default_timestamp_fuzz = 1e-9 # seconds
13 default_sample_fuzz = 1e-15 # relative
14 
15 
16 #
17 # flags
18 #
19 
20 
21 # when comparing time series, require gap intervals to be identical
22 COMPARE_FLAGS_EXACT_GAPS = 1
23 # consider samples that are all 0 also to be gaps
24 COMPARE_FLAGS_ZERO_IS_GAP = 2
25 # don't require the two time series to start and stop at the same time
26 COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN = 4
27 
28 # the default flags for comparing time series
29 COMPARE_FLAGS_DEFAULT = 0
30 
31 
32 #
33 # tools
34 #
35 
36 
37 def load_file(fobj, transients = (0.0, 0.0)):
38  stream = (line.strip() for line in fobj)
39  stream = (line.split() for line in stream if line and not line.startswith("#"))
40  lines = [(LIGOTimeGPS(line[0]),) + tuple(map(float, line[1:])) for line in stream]
41  assert lines, "no data"
42  channel_count_plus_1 = len(lines[0])
43  assert all(len(line) == channel_count_plus_1 for line in lines), "not all lines have the same channel count"
44  for t1, t2 in itertools.izip((line[0] for line in lines), (line[0] for line in lines[1:])):
45  assert t2 > t1, "timestamps not in order @ t = %s s" % str(t2)
46  start = lines[0][0] + transients[0]
47  stop = lines[-1][0] - transients[-1]
48  iterutils.inplace_filter(lambda line: start <= line[0] <= stop, lines)
49  assert lines, "transients remove all data"
50  return lines
51 
52 
53 def identify_gaps(lines, timestamp_fuzz = default_timestamp_fuzz, sample_fuzz = default_sample_fuzz, flags = COMPARE_FLAGS_DEFAULT):
54  # assume the smallest interval bewteen samples indicates the true
55  # sample rate, and correct for possible round-off by assuming true
56  # sample rate is an integer number of Hertz
57  dt = min(float(line1[0] - line0[0]) for line0, line1 in itertools.izip(lines, lines[1:]))
58  dt = 1.0 / round(1.0 / dt)
59 
60  # convert to absolute fuzz (but don't waste time with this if we
61  # don't need it)
62  if flags & COMPARE_FLAGS_ZERO_IS_GAP:
63  sample_fuzz *= max(max(abs(x) for x in line[1:]) for line in lines)
64 
65  gaps = segments.segmentlist()
66  for i, line in enumerate(lines):
67  if i and (line[0] - lines[i - 1][0]) - dt > timestamp_fuzz * 2:
68  gaps.append(segments.segment((lines[i - 1][0] + dt, line[0])))
69  if flags & COMPARE_FLAGS_ZERO_IS_GAP and all(abs(x) <= sample_fuzz for x in line[1:]):
70  # all samples are 0
71  gaps.append(segments.segment((line[0], lines[i + 1][0] if i + 1 < len(lines) else line[0] + (line[0] - lines[i - 1][0]))))
72  return gaps.coalesce()
73 
74 
75 def compare_fobjs(fobj1, fobj2, transients = (0.0, 0.0), timestamp_fuzz = default_timestamp_fuzz, sample_fuzz = default_sample_fuzz, flags = COMPARE_FLAGS_DEFAULT):
76  timestamp_fuzz = LIGOTimeGPS(timestamp_fuzz)
77 
78  # load dump files with transients removed
79  lines1 = load_file(fobj1, transients = transients)
80  lines2 = load_file(fobj2, transients = transients)
81  assert len(lines1[0]) == len(lines2[0]), "files do not have same channel count"
82 
83  # trim lead-in and lead-out if requested
84  if flags & COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN:
85  lines1 = [line for line in lines1 if lines2[0][0] <= line[0] <= lines2[-1][0]]
86  assert lines1, "time intervals do not overlap"
87  lines2 = [line for line in lines2 if lines1[0][0] <= line[0] <= lines1[-1][0]]
88  assert lines2, "time intervals do not overlap"
89 
90  # construct segment lists indicating gap intervals
91  gaps1 = identify_gaps(lines1, timestamp_fuzz = timestamp_fuzz, sample_fuzz = sample_fuzz, flags = flags)
92  gaps2 = identify_gaps(lines2, timestamp_fuzz = timestamp_fuzz, sample_fuzz = sample_fuzz, flags = flags)
93  if flags & COMPARE_FLAGS_EXACT_GAPS:
94  difference = gaps1 ^ gaps2
95  iterutils.inplace_filter(lambda seg: abs(seg) > timestamp_fuzz, difference)
96  assert not difference, "gap discrepancy: 1 ^ 2 = %s" % str(difference)
97 
98  # convert relative sample fuzz to absolute
99  sample_fuzz *= max(max(abs(x) for x in line[1:]) for line in itertools.chain(lines1, lines2))
100 
101  lines1 = iter(lines1)
102  lines2 = iter(lines2)
103  # guaranteeed to be at least 1 line in both lists
104  line1 = lines1.next()
105  line2 = lines2.next()
106  while True:
107  try:
108  if abs(line1[0] - line2[0]) <= timestamp_fuzz:
109  for val1, val2 in zip(line1[1:], line2[1:]):
110  assert abs(val1 - val2) <= sample_fuzz, "values disagree @ t = %s s" % str(line1[0])
111  line1 = lines1.next()
112  line2 = lines2.next()
113  elif line1[0] < line2[0] and line1[0] in gaps2:
114  line1 = lines1.next()
115  elif line2[0] < line1[0] and line2[0] in gaps1:
116  line2 = lines2.next()
117  else:
118  raise AssertionError("timestamp misalignment @ %s s and %s s" % (str(line1[0]), str(line2[0])))
119  except StopIteration:
120  break
121  # FIXME: should check that we're at the end of both series
122 
123 
124 def compare(filename1, filename2, *args, **kwargs):
125  try:
126  compare_fobjs(open(filename1), open(filename2), *args, **kwargs)
127  except AssertionError as e:
128  raise AssertionError("%s <--> %s: %s" % (filename1, filename2, str(e)))
129 
130 
131 #
132 # main()
133 #
134 
135 
136 if __name__ == "__main__":
137  from optparse import OptionParser
138  parser = OptionParser()
139  parser.add_option("--compare-exact-gaps", action = "store_const", const = COMPARE_FLAGS_EXACT_GAPS, default = 0)
140  parser.add_option("--compare-zero-is-gap", action = "store_const", const = COMPARE_FLAGS_ZERO_IS_GAP, default = 0)
141  parser.add_option("--compare-allow-startstop-misalign", action = "store_const", const = COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN, default = 0)
142  parser.add_option("--timestamp-fuzz", metavar = "seconds", type = "float", default = default_timestamp_fuzz)
143  parser.add_option("--sample-fuzz", metavar = "fraction", type = "float", default = default_sample_fuzz)
144  options, (filename1, filename2) = parser.parse_args()
145  compare(filename1, filename2, timestamp_fuzz = options.timestamp_fuzz, sample_fuzz = options.sample_fuzz, flags = options.compare_exact_gaps | options.compare_zero_is_gap | options.compare_allow_startstop_misalign)