gstlal 1.13.0
Loading...
Searching...
No Matches
cmp_nxydumps.py
1#
2# Copyright (C) 2013--2015 Kipp Cannon
3#
4# This program is free software; you can redistribute it and/or modify it
5# under the terms of the GNU General Public License as published by the
6# Free Software Foundation; either version 2 of the License, or (at your
7# option) any later version.
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12# Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18
19import itertools
20
21
22from ligo import segments
23from lal import iterutils
24from lal import LIGOTimeGPS
25
26
27default_timestamp_fuzz = 1e-9 # seconds
28default_sample_fuzz = 1e-15 # relative
29
30
31#
32# flags
33#
34
35
36# when comparing time series, require gap intervals to be identical
37COMPARE_FLAGS_EXACT_GAPS = 1
38# consider samples that are all 0 also to be gaps
39COMPARE_FLAGS_ZERO_IS_GAP = 2
40# don't require the two time series to start and stop at the same time
41COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN = 4
42
43# the default flags for comparing time series
44COMPARE_FLAGS_DEFAULT = 0
45
46
47#
48# tools
49#
50
51
52def load_file(fobj, transients = (0.0, 0.0)):
53 stream = (line.strip() for line in fobj)
54 stream = (line.split() for line in stream if line and not line.startswith("#"))
55 lines = [(LIGOTimeGPS(line[0]),) + tuple(map(float, line[1:])) for line in stream]
56 assert lines, "no data"
57 channel_count_plus_1 = len(lines[0])
58 assert all(len(line) == channel_count_plus_1 for line in lines), "not all lines have the same channel count"
59 for t1, t2 in zip((line[0] for line in lines), (line[0] for line in lines[1:])):
60 assert t2 > t1, "timestamps not in order @ t = %s s" % str(t2)
61 start = lines[0][0] + transients[0]
62 stop = lines[-1][0] - transients[-1]
63 iterutils.inplace_filter(lambda line: start <= line[0] <= stop, lines)
64 assert lines, "transients remove all data"
65 return lines
66
67
68def max_abs_sample(lines):
69 # return the largest of the absolute values of the samples
70 return max(max(abs(x) for x in line[1:]) for line in lines)
71
72
73def identify_gaps(lines, timestamp_fuzz = default_timestamp_fuzz, sample_fuzz = default_sample_fuzz, flags = COMPARE_FLAGS_DEFAULT):
74 # assume the smallest interval bewteen samples indicates the true
75 # sample rate, and correct for possible round-off by assuming true
76 # sample rate is an integer number of Hertz
77 dt = min(float(line1[0] - line0[0]) for line0, line1 in zip(lines, lines[1:]))
78 dt = 1.0 / round(1.0 / dt)
79
80 # convert to absolute fuzz (but don't waste time with this if we
81 # don't need it)
82 if flags & COMPARE_FLAGS_ZERO_IS_GAP:
83 sample_fuzz *= max_abs_sample(lines)
84
85 gaps = segments.segmentlist()
86 for i, line in enumerate(lines):
87 if i and (line[0] - lines[i - 1][0]) - dt > timestamp_fuzz * 2:
88 # clock skip. interpret missing timestamps as a
89 # gap
90 gaps.append(segments.segment((lines[i - 1][0] + dt, line[0])))
91 if flags & COMPARE_FLAGS_ZERO_IS_GAP and all(abs(x) <= sample_fuzz for x in line[1:]):
92 # all samples are "0". the current sample is a gap
93 gaps.append(segments.segment((line[0], lines[i + 1][0] if i + 1 < len(lines) else line[0] + dt)))
94 return gaps.protract(timestamp_fuzz).coalesce()
95
96
97def compare_fobjs(fobj1, fobj2, transients = (0.0, 0.0), timestamp_fuzz = default_timestamp_fuzz, sample_fuzz = default_sample_fuzz, flags = COMPARE_FLAGS_DEFAULT):
98 timestamp_fuzz = LIGOTimeGPS(timestamp_fuzz)
99
100 # load dump files with transients removed
101 lines1 = load_file(fobj1, transients = transients)
102 lines2 = load_file(fobj2, transients = transients)
103 assert len(lines1[0]) == len(lines2[0]), "files do not have same channel count"
104
105 # trim lead-in and lead-out if requested
106 if flags & COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN:
107 lines1 = [line for line in lines1 if lines2[0][0] <= line[0] <= lines2[-1][0]]
108 assert lines1, "time intervals do not overlap"
109 lines2 = [line for line in lines2 if lines1[0][0] <= line[0] <= lines1[-1][0]]
110 assert lines2, "time intervals do not overlap"
111
112 # construct segment lists indicating gap intervals
113 gaps1 = identify_gaps(lines1, timestamp_fuzz = timestamp_fuzz, sample_fuzz = sample_fuzz, flags = flags)
114 gaps2 = identify_gaps(lines2, timestamp_fuzz = timestamp_fuzz, sample_fuzz = sample_fuzz, flags = flags)
115 if flags & COMPARE_FLAGS_EXACT_GAPS:
116 difference = gaps1 ^ gaps2
117 iterutils.inplace_filter(lambda seg: abs(seg) > timestamp_fuzz, difference)
118 assert not difference, "gap discrepancy: 1 ^ 2 = %s" % str(difference)
119
120 # convert relative sample fuzz to absolute
121 sample_fuzz *= max_abs_sample(itertools.chain(lines1, lines2))
122
123 lines1 = iter(lines1)
124 lines2 = iter(lines2)
125 # guaranteeed to be at least 1 line in both lists
126 line1 = next(lines1)
127 line2 = next(lines2)
128 while True:
129 try:
130 if abs(line1[0] - line2[0]) <= timestamp_fuzz:
131 for val1, val2 in zip(line1[1:], line2[1:]):
132 assert abs(val1 - val2) <= sample_fuzz, "values disagree @ t = %s s" % str(line1[0])
133 line1 = next(lines1)
134 line2 = next(lines2)
135 elif line1[0] < line2[0] and line1[0] in gaps2:
136 line1 = next(lines1)
137 elif line2[0] < line1[0] and line2[0] in gaps1:
138 line2 = next(lines2)
139 else:
140 raise AssertionError("timestamp misalignment @ %s s and %s s" % (str(line1[0]), str(line2[0])))
141 except StopIteration:
142 break
143 # FIXME: should check that we're at the end of both series
144
145
146def compare(filename1, filename2, *args, **kwargs):
147 try:
148 compare_fobjs(open(filename1), open(filename2), *args, **kwargs)
149 except AssertionError as e:
150 raise AssertionError("%s <--> %s: %s" % (filename1, filename2, str(e)))
151
152
153#
154# main()
155#
156
157
158if __name__ == "__main__":
159 from optparse import OptionParser
160 parser = OptionParser()
161 parser.add_option("--compare-exact-gaps", action = "store_const", const = COMPARE_FLAGS_EXACT_GAPS, default = 0)
162 parser.add_option("--compare-zero-is-gap", action = "store_const", const = COMPARE_FLAGS_ZERO_IS_GAP, default = 0)
163 parser.add_option("--compare-allow-startstop-misalign", action = "store_const", const = COMPARE_FLAGS_ALLOW_STARTSTOP_MISALIGN, default = 0)
164 parser.add_option("--timestamp-fuzz", metavar = "seconds", type = "float", default = default_timestamp_fuzz)
165 parser.add_option("--sample-fuzz", metavar = "fraction", type = "float", default = default_sample_fuzz)
166 options, (filename1, filename2) = parser.parse_args()
167 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)