1 """Interval class
2
3 Robert Clewley, June 2005
4
5 Interval objects have attributes:
6 name: str (variable label, which DOES NOT have to be unique)
7 typestr: 'int' or 'float' (immutable) - doesn't handle 'complex' yet
8 type: type
9 _loval (low endpoint val): numeric
10 _hival (hi endpoint val): numeric
11 _abseps: numeric
12 issingleton: boolean
13 _intervalstr: str
14 """
15
16
17
18
19
20 from utils import *
21 from common import *
22 from errors import *
23
24
25 from numpy import Inf, NaN, isfinite, isinf, isnan, array, sign, linspace, arange
26 import re, math
27 import copy
28
29 MIN_EXP = -15
30
31
32 re_number = '([-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?)'
33
34 c=1
35 n=0
36 u=-1
37
38
39 __all__ = ['notcontained', 'contained', 'uncertain', 'Interval',
40 'isinterval', 'issingleton']
41
42
44 """Numeric Interval membership type."""
45
47 val = arg[0]
48 if val == -1:
49 self.valstr = 'uncertain'
50 if val == 0:
51 self.valstr = 'notcontained'
52 if val == 1:
53 self.valstr = 'contained'
54 if self.__int__() not in [-1, 0, 1]:
55 raise ValueError('Invalid value for numeric Interval membership type')
56
59
60 __str__ = __repr__
61
63 """Interval `logical AND` for checking interval containment.
64 e.g. if both endpoints are contained then the whole interval is."""
65 sv = self.__int__()
66 ov = v.__int__()
67 if sv == n or ov == n:
68 return notcontained
69 elif sv == u or ov == u:
70 return uncertain
71 else:
72 return contained
73
76
78 """Interval `logical OR` for checking interval intersection.
79 e.g. if at least one endpoint is contained then there is intersection."""
80 sv = self.__int__()
81 ov = v.__int__()
82 if sv == c or ov == c:
83 return contained
84 elif sv == u and ov == u:
85 return uncertain
86 else:
87 return notcontained
88
91
92
93 global notcontained, contained, uncertain
94 notcontained = IntervalMembership(False)
95 contained = IntervalMembership(True)
96 uncertain = IntervalMembership(-1)
97
98
99
101 """Numeric Interval class.
102
103 Numeric Interval implementation for integer and float types.
104
105 If the interval is not specified fully on initialisation then operations
106 on the object are limited to set().
107 """
108
109 - def __init__(self, name, intervaltype, intervalspec=None, abseps=None):
110
111
112 self.name = name
113 try:
114 self.type = _num_equivtype[intervaltype]
115 except KeyError:
116 raise PyDSTool_TypeError('Incorrect type specified for interval')
117 self.typestr = _num_type2name[self.type]
118 if compareNumTypes(self.type, _int_types):
119 abseps = 0
120 self.isdiscrete = True
121 else:
122 if abseps is None:
123 abseps = 1e-13
124 else:
125 assert abseps >= 0, "abseps argument must be non-negative"
126 self.isdiscrete = False
127 self._abseps = abseps
128 self.defined = False
129 self._maxexp = None
130 if intervalspec is not None:
131 self.set(intervalspec)
132 else:
133
134 self._loval = None
135 self._hival = None
136
137
138 - def info(self, verboselevel=0):
139 if verboselevel > 0:
140
141 info(self.__dict__, "Interval " + self.name,
142 recurseDepthLimit=1+verboselevel)
143 else:
144 print self.__repr__()
145
146
147
149 if self.defined:
150 return (self.name, self.type, self.typestr,\
151 (self._loval, self._hival))
152 else:
153 raise PyDSTool_ExistError('Interval undefined')
154
155
157 assert isinstance(other, Interval)
158 return self.defined == other.defined and \
159 self.type == other.type and \
160 self._loval == other._loval and \
161 self._hival == other._hival and \
162 self._abseps == other._abseps
163
164
166 return not self == other
167
169 if isinstance(other, Interval):
170 raise NotImplementedError
171 elif isinstance(other, _seq_types):
172 return [o <= self._loval + self._abseps for o in other]
173 else:
174 return other <= self._loval + self._abseps
175
177 if isinstance(other, Interval):
178 raise NotImplementedError
179 elif isinstance(other, _seq_types):
180 return [o >= self._hival - self._abseps for o in other]
181 else:
182 return other >= self._hival - self._abseps
183
185 if isinstance(other, Interval):
186 return other._loval - other._abseps > self._hival + self._abseps
187 elif isinstance(other, _seq_types):
188 return [o > self._hival + self._abseps for o in other]
189 else:
190 return other > self._hival + self._abseps
191
193 if isinstance(other, Interval):
194 return self._loval - self._abseps > other._hival + other._abseps
195 elif isinstance(other, _seq_types):
196 return [o < self._loval - self._abseps for o in other]
197 else:
198 return other < self._loval - self._abseps
199
201 c = copy.copy(self)
202 c.set((self._loval+val,self._hival+val))
203 return c
204
206 c = copy.copy(self)
207 c.set((self._loval+val,self._hival+val))
208 return c
209
211 c = copy.copy(self)
212 c.set((self._loval-val,self._hival-val))
213 return c
214
216 c = copy.copy(self)
217
218 c.set((val-self._hival,val-self._loval))
219 return c
220
222 c = copy.copy(self)
223 c.set((self._loval*val,self._hival*val))
224 return c
225
227 c = copy.copy(self)
228 c.set((self._loval*val,self._hival*val))
229 return c
230
232 c = copy.copy(self)
233 c.set((self._loval/val,self._hival/val))
234 return c
235
237 c = copy.copy(self)
238
239 if isfinite(self._hival):
240 if self._hival==0:
241 new_lo = sign(val)*Inf
242 else:
243 new_lo = val/self._hival
244 else:
245 new_lo = val/self._hival
246 if isfinite(self._loval):
247 if self._loval==0:
248 new_hi = sign(val)*Inf
249 else:
250 new_hi = val/self._loval
251 else:
252 new_hi = val/self._loval
253 if new_hi < new_lo:
254
255 c.set((new_hi,new_lo))
256 else:
257 c.set((new_lo,new_hi))
258 return c
259
261 c = copy.copy(self)
262
263 c.set((-self._hival,-self._loval))
264 return c
265
275
277 """Report membership of val in the interval,
278 returning type IntervalMembership."""
279 if isinstance(val, _seq_types):
280 return [self.contains(v) for v in val]
281 try:
282 if not self.defined:
283 raise PyDSTool_ExistError('Interval undefined')
284 if self._maxexp is None and not self.issingleton:
285 try:
286 loexp = math.log(abs(self._loval), 10)
287 except (OverflowError, ValueError):
288 loexp = 0
289 try:
290 hiexp = math.log(abs(self._hival), 10)
291 except (OverflowError, ValueError):
292 hiexp = 0
293 self._maxexp = max(loexp, hiexp)
294 if isinstance(val, _num_name2equivtypes[self.typestr]):
295 compval = val
296 if compareNumTypes(self.type, _int_types):
297 eps = 0
298 else:
299 eps = self._abseps
300 else:
301 if isinstance(val, _all_int):
302 compval = float(val)
303 eps = self._abseps
304 elif isinstance(val, _all_float):
305
306
307 if isinf(val):
308 compval = val
309 eps = 0
310 elif int(val) == val:
311 compval = int(val)
312 eps = 0
313 else:
314 raise PyDSTool_TypeError('Incorrect type of query value')
315 elif not val.issingleton:
316
317 if not val.defined:
318 raise PyDSTool_ExistError('Input interval undefined')
319 if not compareNumTypes(val.type, self.type) and \
320 compareNumTypes(val.type, _all_float):
321
322
323 raise PyDSTool_TypeError('Interval type mismatch')
324 if compareNumTypes(val.type, self.type) and \
325 compareNumTypes(self.type, _all_int):
326 eps = 0
327 else:
328 eps = max(self._abseps, val._abseps)
329 try:
330 minexpallowed = math.ceil(-MIN_EXP - self._maxexp)
331 except TypeError:
332
333 minexpallowed = Inf
334 if eps > 0 and -math.log(eps,10) > minexpallowed:
335 eps = math.pow(10,-minexpallowed)
336 if isfinite(val._loval) or isfinite(self._loval):
337 tempIlo = val._loval >= (self._loval + eps)
338 else:
339 tempIlo = False
340 if isfinite(val._hival) or isfinite(self._hival):
341 tempIhi = val._hival <= (self._hival - eps)
342 else:
343 tempIhi = False
344 if tempIlo and tempIhi:
345 return contained
346 elif eps == 0:
347 return notcontained
348 else:
349
350
351 if isfinite(val._loval) or isfinite(self._loval):
352 tempUlo = val._loval > (self._loval - eps)
353 tempElo = val._loval <= (self._loval - eps)
354 else:
355 tempUlo = val._loval == self._loval
356 tempElo = False
357 if isfinite(val._hival) or isfinite(self._hival):
358 tempUhi = val._hival < (self._hival + eps)
359 tempEhi = val._hival >= (self._hival + eps)
360 else:
361 tempUhi = val._hival == self._hival
362 tempEhi = False
363 if ((tempUlo and not tempEhi) or (tempUhi and \
364 not tempElo)) \
365 and not self.isdiscrete:
366 return uncertain
367 else:
368
369 return notcontained
370 else:
371
372
373
374 if compareNumTypes(val.type, self.type):
375 compval = val.get()
376 if compareNumTypes(self.type, _all_int):
377 eps = 0
378 else:
379 eps = max(self._abseps, val._abseps)
380 try:
381 loexp = math.log(abs(self._loval), 10)
382 except (OverflowError, ValueError):
383 loexp = 0
384 try:
385 hiexp = math.log(abs(self._hival), 10)
386 except (OverflowError, ValueError):
387 hiexp = 0
388 minexpallowed = math.ceil(-MIN_EXP - max(loexp,
389 hiexp))
390 if eps > 0 and -math.log(eps,10) > minexpallowed:
391 eps = math.pow(10,-minexpallowed)
392 else:
393 if compareNumTypes(val.type, _all_int):
394 compval = val.get()
395 eps = self._abseps
396 elif compareNumTypes(val.type, _all_float):
397
398
399 if int(val._loval) == val._loval and \
400 int(val._hival) == val._hival:
401 compval = (int(val[0]), int(val[1]))
402 eps = 0
403 else:
404 raise PyDSTool_TypeError('Invalid numeric type '
405 'of query value')
406 else:
407 raise PyDSTool_TypeError('Invalid numeric type of '
408 'query value')
409 except AttributeError:
410 raise PyDSTool_TypeError('Expected a numeric type or a singleton '
411 'interval. Got type '+str(type(val)))
412 else:
413 tempIlo = compval >= (self._loval + eps)
414 tempIhi = compval <= (self._hival - eps)
415 if tempIlo and tempIhi:
416 return contained
417 elif eps == 0:
418 return notcontained
419 else:
420
421
422 tempUlo = compval > (self._loval - eps)
423 tempUhi = compval < (self._hival + eps)
424 tempElo = compval <= (self._loval - eps)
425 tempEhi = compval >= (self._hival + eps)
426 if ((tempUlo and not tempEhi) or (tempUhi and not tempElo)) and \
427 not self.isdiscrete:
428 return uncertain
429
430 else:
431 return notcontained
432
433
435 if not isinstance(other, Interval):
436 raise PyDSTool_TypeError("Can only intersect with other Interval "
437 "types")
438 result = None
439 if self.type != other.type:
440 raise PyDSTool_TypeError("Can only intersect with other Intervals "
441 "having same numeric type")
442 if compareNumTypes(self.type, _all_complex) or \
443 compareNumTypes(other.type, _all_complex):
444 raise TypeError("Complex intervals not supported")
445 if self.contains(other):
446 result = other
447 elif other.contains(self):
448 result = self
449 else:
450
451 if other.contains(self._hival) is contained:
452
453 result = Interval('__result__', self.type, [other._loval,
454 self._hival])
455 elif other.contains(self._loval) is contained:
456
457 result = Interval('__result__', self.type, [self._loval,
458 other._hival])
459 return result
460
461
463 """val, bdcode -> Bool
464
465 Determines whether val is at the endpoint specified by bdcode,
466 to the precision of the interval's _abseps tolerance.
467 bdcode can be one of 'lo', 'low', 0, 'hi', 'high', 1"""
468
469 assert self.defined, 'Interval undefined'
470 assert isinstance(val, (_int_types, _float_types)), \
471 'Invalid value type'
472 assert isfinite(val), "Can only test finite argument values"
473 if bdcode in ['lo', 'low', 0]:
474 if self.isdiscrete:
475 return val == self._loval
476 else:
477 return abs(val - self._loval) < self._abseps
478 elif bdcode in ['hi', 'high', 1]:
479 if self.isdiscrete:
480 return val == self._hival
481 else:
482 return abs(val - self._hival) < self._abseps
483 else:
484 raise ValueError('Invalid boundary spec code')
485
486 - def sample(self, dt, strict=False, avoidendpoints=False):
487 """Sample the interval, returning a list.
488
489 Arguments:
490
491 dt : sample step
492
493 strict : (Boolean) This option forces dt to be used throughout the interval,
494 with a final step of < dt if not commensurate. Default of False
495 is used for auto-selection of sample rate to fit interval
496 (choice based on dt argument).
497
498 avoidendpoints : (Boolean, default False). When True, ensures that the first and
499 last independent variable ("t") values are not included, offset by
500 an amount given by self._abseps (the endpoint tolerance).
501 """
502 assert self.defined
503 intervalsize = self._hival - self._loval
504 assert isfinite(intervalsize), "Interval must be finite"
505 if dt > intervalsize:
506 print "Interval size = %f, dt = %f"%(intervalsize, dt)
507 raise ValueError('dt must be smaller than size of interval')
508 if dt <= 0:
509 raise ValueError('Must pass dt >= 0')
510 if compareNumTypes(self.type, _all_float):
511 if strict:
512
513 samplelist = list(arange(self._loval, self._hival, dt,
514 dtype=float))
515 if self._hival not in samplelist:
516 samplelist.append(self._hival)
517 else:
518 n = max(round(intervalsize/dt),2)
519 dt = intervalsize/n
520 samplelist = list(linspace(self._loval, self._hival, n))
521 if avoidendpoints:
522 samplelist[-1] = self._hival - 1.1*self._abseps
523 samplelist[0] = self._loval + 1.1*self._abseps
524 elif compareNumTypes(self.type, _all_int):
525 if not isinstance(dt, _int_types):
526 raise ValueError("dt must be an integer for integer "
527 "intervals")
528 if strict:
529 print "Warning: 'strict' option is invalid for integer " + \
530 "interval types"
531 if avoidendpoints:
532 loval = self._loval+1
533 hival = self._hival-1
534 assert loval <= hival, ('There are no points to return with '
535 'these options!')
536 else:
537 loval = self._loval
538 hival = self._hival
539 samplelist = range(loval, hival+1, dt)
540
541 else:
542 raise TypeError("Unsupported value type")
543
544 return samplelist
545
546
547 uniformSample = sample
548
549
550 - def set(self, arg):
551 """Define interval in an Interval object"""
552 if isinstance(arg, _seq_types) and len(arg)==2:
553 if arg[0] == arg[1]:
554
555 self.set(arg[0])
556 else:
557 self.issingleton = False
558 loval = arg[0]
559 hival = arg[1]
560
561
562 if not loval < hival:
563 print "set() was passed loval = ", loval, \
564 " and hival = ", hival
565 raise PyDSTool_ValueError('Interval endpoints must be '
566 'given in order of increasing size')
567 self._intervalstr = '['+str(loval)+',' \
568 +str(hival)+']'
569 if compareNumTypes(type(loval), self.type):
570 self._loval = loval
571 elif compareNumTypes(self.type, _float_types):
572 self._loval = float(loval)
573 elif isinf(loval):
574
575 self._loval = loval
576 else:
577 raise TypeError("Invalid interval endpoint type")
578 if compareNumTypes(type(hival), self.type):
579 self._hival = hival
580 elif compareNumTypes(self.type, _float_types):
581 self._hival = float(hival)
582 elif isinf(hival):
583
584 self._hival = hival
585 else:
586 raise TypeError("Invalid interval endpoint type")
587 self.defined = True
588 elif isinstance(arg, (_int_types, _float_types)):
589 assert isfinite(arg), \
590 "Singleton interval domain value must be finite"
591 if self.isdiscrete:
592
593 if not int(arg)==arg:
594 raise TypeError("Invalid interval singleton type")
595 else:
596 arg = float(arg)
597 self.issingleton = True
598 self._intervalstr = str(arg)
599 self._loval = arg
600 self._hival = arg
601 self.defined = True
602 else:
603 print "Error in argument: ", arg, "of type", type(arg)
604 raise PyDSTool_TypeError('Interval spec must be a numeric or '
605 'a length-2 sequence type')
606
608 if ix == 0:
609 self.set((val, self._hival))
610 elif ix == 1:
611 self.set((self._loval, val))
612 else:
613 raise PyDSTool_TypeError('Invalid endpoint')
614
616 if self.defined:
617 if ix == 0:
618 return self._loval
619 elif ix == 1:
620 return self._hival
621 else:
622 raise PyDSTool_ValueError("Invalid endpoint")
623 else:
624 raise PyDSTool_ExistError('Interval undefined')
625
634
635 - def get(self, ix=None):
636 """Get the interval as a tuple or a number (for singletons),
637 or an endpoint if ix is not None"""
638 if self.defined:
639 if ix == 0:
640 return self._loval
641 elif ix == 1:
642 return self._hival
643 elif ix is None:
644 if self.issingleton:
645 return self._loval
646 else:
647 return [self._loval, self._hival]
648 else:
649 raise PyDSTool_TypeError('Invalid return form specified')
650 else:
651 raise PyDSTool_ExistError('Interval undefined')
652
653
655 """Get info on a known interval definition."""
656
657 if verbose > 0:
658 infostr = "Interval "+self.name+"\n"
659 if self.defined:
660 infostr += ' ' + self.typestr+': '+\
661 self.name+' = '+self._intervalstr+ \
662 ' @ eps = '+str(self._abseps)
663 else:
664 infostr += ' ' + self.typestr+': '+self.name+' @ eps = '+ \
665 str(self._abseps) + " (not fully defined)"
666 else:
667 infostr = "Interval "+self.name
668 return infostr
669
670
673
674
675 __str__ = __repr__
676
677
678 - def info(self, verboselevel=1):
680
681
683 pickledself = pickle.dumps(self)
684 return pickle.loads(pickledself)
685
686
688 d = copy.copy(self.__dict__)
689
690 d['type'] = None
691 return d
692
693
698
699
700
701
702
703
705 """Determines whether the given obj is a Interval object."""
706 return isinstance(obj, Interval)
707
708
709
715