Package glue :: Module offsetvector
[hide private]
[frames] | no frames]

Source Code for Module glue.offsetvector

  1  # Copyright (C) 2010--2013,2015,2016  Kipp Cannon 
  2  # 
  3  # This program is free software; you can redistribute it and/or modify it 
  4  # under the terms of the GNU General Public License as published by the 
  5  # Free Software Foundation; either version 2 of the License, or (at your 
  6  # option) any later version. 
  7  # 
  8  # This program is distributed in the hope that it will be useful, but 
  9  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General 
 11  # Public License for more details. 
 12  # 
 13  # You should have received a copy of the GNU General Public License along 
 14  # with this program; if not, write to the Free Software Foundation, Inc., 
 15  # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. 
 16   
 17   
 18  # 
 19  # ============================================================================= 
 20  # 
 21  #                                   Preamble 
 22  # 
 23  # ============================================================================= 
 24  # 
 25   
 26   
 27  import itertools 
 28   
 29   
 30  from glue import git_version 
 31   
 32   
 33  __author__ = "Kipp Cannon <kipp.cannon@ligo.org>" 
 34  __version__ = "git id %s" % git_version.id 
 35  __date__ = git_version.date 
36 37 38 # 39 # ============================================================================= 40 # 41 # Offset Vector 42 # 43 # ============================================================================= 44 # 45 46 47 -class offsetvector(dict):
48 """ 49 Subclass of the dict built-in type for storing mappings of 50 instrument to time offset. 51 52 Examples: 53 54 >>> x = offsetvector({"H1": 0, "L1": 10, "V1": 20}) 55 >>> x["H1"] 56 0 57 >>> not any(x.values()) # check for "zero-lag" 58 False 59 60 The motivation for introducing this class, instead of using 61 dictionaries, is that it provides a number of tools for comparing 62 offset vectors besides strict value-for-value equality. For 63 example the Python cmp() operation compares two offset vectors by 64 the relative offsets between instruments rather than their absolute 65 offsets, whereas the == operation compares two offset vectors by 66 demanding strict equality. There is also the ability to check if 67 one offset vector is a subset of another one. 68 """ 69 @property
70 - def refkey(self):
71 """ 72 = min(self) 73 74 Raises ValueError if the offsetvector is empty. 75 """ 76 # min() emits ValueError when the list is empty, but it 77 # might also emit a ValueError if the comparison operations 78 # inside it fail, so we can't simply wrap it in a 79 # try/except pair or we might mask genuine failures 80 if not self: 81 raise ValueError("offsetvector is empty") 82 return min(self)
83 84 @property
85 - def deltas(self):
86 """ 87 Dictionary of relative offsets. The keys in the result are 88 pairs of keys from the offset vector, (a, b), and the 89 values are the relative offsets, (offset[b] - offset[a]). 90 Raises ValueError if the offsetvector is empty (WARNING: 91 this behaviour might change in the future). 92 93 Example: 94 95 >>> x = offsetvector({"H1": 0, "L1": 10, "V1": 20}) 96 >>> x.deltas 97 {('H1', 'L1'): 10, ('H1', 'V1'): 20, ('H1', 'H1'): 0} 98 >>> y = offsetvector({'H1': 100, 'L1': 110, 'V1': 120}) 99 >>> y.deltas == x.deltas 100 True 101 102 Note that the result always includes a "dummy" entry, 103 giving the relative offset of self.refkey with respect to 104 itself, which is always 0. 105 106 See also .fromdeltas(). 107 108 BUGS: I think the keys in each tuple should be reversed. 109 I can't remember why I put them in the way they are. 110 Expect them to change in the future. 111 """ 112 # FIXME: instead of raising ValueError when the 113 # offsetvector is empty this should return an empty 114 # dictionary. the inverse, .fromdeltas() accepts 115 # empty dictionaries 116 # NOTE: the arithmetic used to construct the offsets 117 # *must* match the arithmetic used by 118 # time_slide_component_vectors() so that the results of the 119 # two functions can be compared to each other without worry 120 # of floating-point round off confusing things. 121 refkey = self.refkey 122 refoffset = self[refkey] 123 return dict(((refkey, key), self[key] - refoffset) for key in self)
124
125 - def __str__(self, compact = False):
126 """ 127 Return a human-readable string representation of an offset 128 vector. 129 130 Example: 131 132 >>> a = offsetvector({"H1": -10.1234567, "L1": 0.125}) 133 >>> str(a) 134 'H1 = -10.1234567 s, L1 = +0.125 s' 135 >>> a.__str__(compact = True) 136 'H1=-10.123,L1=0.125' 137 """ 138 if compact: 139 return ",".join(("%s=%.5g" % x) for x in sorted(self.items())) 140 return ", ".join(("%s = %+.16g s" % x) for x in sorted(self.items()))
141
142 - def __repr__(self):
143 """ 144 Return a string representation of the offset vector. 145 Running eval() on the result reconstructs the offsetvector. 146 147 Example: 148 149 >>> a = offsetvector({"H1": -10.1234567, "L1": 0.1}) 150 >>> repr(a) 151 "offsetvector({'H1': -10.1234567, 'L1': 0.1})" 152 >>> b = eval(repr(a)) 153 >>> b 154 offsetvector({'H1': -10.1234567, 'L1': 0.1}) 155 >>> b == a 156 True 157 >>> b is a 158 False 159 """ 160 return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))
161
162 - def __abs__(self):
163 """ 164 Returns max(offset) - min(offset). 165 166 Example: 167 168 >>> abs(offsetvector({"H1": 0.0, "H2": 0.0, "L1": 0.0})) 169 0.0 170 >>> abs(offsetvector({"H1": 10.0, "H2": 10.0, "L1": 10.0})) 171 0.0 172 >>> abs(offsetvector({'H1': 10.0, 'L1': 0.0, 'V1': -10.0})) 173 20.0 174 """ 175 return max(self.values()) - min(self.values())
176
177 - def __cmp__(self, other):
178 """ 179 Compare two offset vectors by their relative offsets. The 180 return value is 0 if the relative offsets are all equal, 181 nonzero otherwise. 182 183 Example: 184 185 >>> a = offsetvector({"H1": 0.0, "H2": 0.0, "L1": 0.0}) 186 >>> b = offsetvector({"H1": 10.0, "H2": 10.0, "L1": 10.0}) 187 >>> cmp(a, b) 188 0 189 >>> a == b 190 False 191 192 Note that cmp() and testing for equality are different 193 tests! The equality test returns False because the offset 194 vectors are not identical, however the cmp() function 195 returns 0 because the relative offsets are all equal. 196 """ 197 return cmp(self.deltas, other.deltas)
198
199 - def contains(self, other):
200 """ 201 Returns True if offset vector @other can be found in @self, 202 False otherwise. An offset vector is "found in" another 203 offset vector if the latter contains all of the former's 204 instruments and the relative offsets among those 205 instruments are equal (the absolute offsets need not be). 206 207 Example: 208 209 >>> a = offsetvector({"H1": 10, "L1": 20, "V1": 30}) 210 >>> b = offsetvector({"H1": 20, "V1": 40}) 211 >>> a.contains(b) 212 True 213 214 Note the distinction between this and the "in" operator: 215 216 >>> "H1" in a 217 True 218 """ 219 return offsetvector((key, offset) for key, offset in self.items() if key in other).deltas == other.deltas
220
221 - def normalize(self, **kwargs):
222 """ 223 Adjust the offsetvector so that a particular instrument has 224 the desired offset. All other instruments have their 225 offsets adjusted so that the relative offsets are 226 preserved. The instrument to noramlize, and the offset one 227 wishes it to have, are provided as a key-word argument. 228 The return value is the time slide dictionary, which is 229 modified in place. 230 231 If more than one key-word argument is provided the keys are 232 sorted and considered in order until a key is found that is 233 in the offset vector. The offset vector is normalized to 234 that value. This function is a no-op if no key-word 235 argument is found that applies. 236 237 Example: 238 239 >>> a = offsetvector({"H1": -10, "H2": -10, "L1": -10}) 240 >>> a.normalize(L1 = 0) 241 offsetvector({'H2': 0, 'H1': 0, 'L1': 0}) 242 >>> a = offsetvector({"H1": -10, "H2": -10}) 243 >>> a.normalize(L1 = 0, H2 = 5) 244 offsetvector({'H2': 5, 'H1': 5}) 245 """ 246 # FIXME: should it be performed in place? if it should 247 # be, the should there be no return value? 248 for key, offset in sorted(kwargs.items()): 249 if key in self: 250 delta = offset - self[key] 251 for key in self.keys(): 252 self[key] += delta 253 break 254 return self
255 256 @classmethod
257 - def fromdeltas(cls, deltas):
258 """ 259 Construct an offsetvector from a dictionary of offset 260 deltas as returned by the .deltas attribute. 261 262 Example: 263 264 >>> x = offsetvector({"H1": 0, "L1": 10, "V1": 20}) 265 >>> y = offsetvector.fromdeltas(x.deltas) 266 >>> y 267 offsetvector({'V1': 20, 'H1': 0, 'L1': 10}) 268 >>> y == x 269 True 270 271 See also .deltas, .fromkeys() 272 """ 273 return cls((key, value) for (refkey, key), value in deltas.items())
274
275 276 # 277 # ============================================================================= 278 # 279 # Utilities 280 # 281 # ============================================================================= 282 # 283 284 285 -def component_offsetvectors(offsetvectors, n):
286 """ 287 Given an iterable of offset vectors, return the shortest list of 288 the unique n-instrument offset vectors from which all the vectors 289 in the input iterable can be constructed. This can be used to 290 determine the minimal set of n-instrument coincs required to 291 construct all of the coincs for all of the requested instrument and 292 offset combinations in a set of offset vectors. 293 294 It is assumed that the coincs for the vector {"H1": 0, "H2": 10, 295 "L1": 20} can be constructed from the coincs for the vectors {"H1": 296 0, "H2": 10} and {"H2": 0, "L1": 10}, that is only the relative 297 offsets are significant in determining if two events are 298 coincident, not the absolute offsets. 299 """ 300 # 301 # collect unique instrument set / deltas combinations 302 # 303 304 delta_sets = {} 305 for vect in offsetvectors: 306 for instruments in itertools.combinations(sorted(vect), n): 307 # NOTE: the arithmetic used to construct the 308 # offsets *must* match the arithmetic used by 309 # offsetvector.deltas so that the results of the 310 # two can be compared to each other without worry 311 # of floating-point round off confusing things. 312 delta_sets.setdefault(instruments, set()).add(tuple(vect[instrument] - vect[instruments[0]] for instrument in instruments)) 313 314 # 315 # translate into a list of normalized n-instrument offset vectors 316 # 317 318 return [offsetvector(zip(instruments, deltas)) for instruments, delta_set in delta_sets.items() for deltas in delta_set]
319