#!/usr/bin/python # -*- coding: utf-8 -*- """Interval arithmetic in Python for a Mandelbrot set rendering example, which is in intervalmand.py. This interval arithmetic package is NOT SUITABLE FOR RELIABLE COMPUTATION. Reliable computation is a use of interval arithmetic to avoid being fooled by errors introduced by the limited precision of floating-point calculations, and implementing it requires control over the floating-point processor's rounding mode. I don't know how to control floating-point rounding behavior in Python, so this module is not useful for that. Arithmetic on complex-number intervals with this package is about 100× slower than with native Python complex numbers. There are other implementations of interval arithmetic for Python, which I haven't evaluated: "An interval object consists of a finite union of closed, possibly unbound, mathematical intervals [on the extended real number set]." "a small and simple library written in Python for performing interval and discontinous range arithmetic." "Maximum Accuracy Interval Arithmetic, a mathematical tool for the solution of problems related to numerical errors. ...Only Real Intervals are available. No Complex Intervals, no Interval Vectors and Matrixes, and no extensions of basic functions. These will be our next work." Dead since 2009. """ import math class Interval(object): def __init__(self, min, max): self.min = min self.max = max def __iter__(self): yield self.min yield self.max def __add__(self, other): if not isinstance(other, Interval): other = interval(other) return Interval(self.min + other.min, self.max + other.max) def __iadd__(self, other): if not isinstance(other, Interval): other = interval(other) self.min += other.min self.max += other.max return self def __isub__(self, other): if not isinstance(other, Interval): other = interval(other) self.min -= other.max self.max -= other.min return self def __neg__(self): return Interval(-self.max, -self.min) def __sub__(self, other): return self + -interval(other) def __radd__(self, other): return self + other def __rsub__(self, other): return interval(other) - self def __mul__(self, other): if not isinstance(other, Interval): other = interval(other) a = self.min * other.min b = self.max * other.min c = self.min * other.max d = self.max * other.max return Interval(min(a, b, c, d), max(a, b, c, d)) def __rmul__(self, other): return self * other def __div__(self, other): if 0 in other: return infinite_interval a = self.min / other.min b = self.max / other.min c = self.min / other.max d = self.max / other.max return Interval(min(a, b, c, d), max(a, b, c, d)) def __rdiv__(self, other): return interval(other) / self def __contains__(self, other): return self.min <= other <= self.max def __abs__(self): if 0 in self: return Interval(0, max(abs(self.min), abs(self.max))) elif self.max < 0: return -self else: return self def log(self): return Interval(math.log(self.min) if self.min != 0 else -inf, math.log(self.max) if self.max != 0 else -inf) def exp(self): try: max = math.exp(self.max) except OverflowError: print "overflow in exp", self.max max = inf return Interval(math.exp(self.min), max) def __pow__(self, other): "Handle some special cases of exponentiation that work on the real line." other = interval(other) if self.min >= 0: return (self.log() * other).exp() elif other.min != other.max or int(other.min) != other.min: # Some special cases can be handled on the real line, but # taking a negative number to a fractional power is not # among them. raise ValueError(self) try: a = self.min ** other.min except OverflowError: a = inf try: b = self.max ** other.min except OverflowError: b = inf if other.min % 2 != 0: # Odd integral powers increase monotonically, so this is easy: return Interval(a, b) elif 0 in self: # Even powers have separate positive, negative, and # zero-spanning cases; the positive case is handled above. # Zero-spanning includes zero: return Interval(0, max(a, b)) else: # Finally, the negative case; decreasing, so backwards: return Interval(b, a) def span(self): return self.max - self.min def copy(self): return Interval(self.min, self.max) inf = 1e1000 infinite_interval = Interval(-inf, inf) def interval(object): "Coerce an object to an interval." if isinstance(object, Interval): return object else: return Interval(object, object) class ComplexInterval(object): def __init__(self, re, im): self.re = re self.im = im def __iter__(self): yield self.re yield self.im def __add__(self, other): other = complex_interval(other) return ComplexInterval(self.re + other.re, self.im + other.im) def __neg__(self): return ComplexInterval(-self.re, -self.im) def __sub__(self, other): return self + -complex_interval(other) def __radd__(self, other): return self + other def __rsub__(self, other): return complex_interval(other) - self def __mul__(self, other): if not isinstance(other, ComplexInterval): other = complex_interval(other) return ComplexInterval(self.re * other.re - self.im * other.im, self.im * other.re + self.re * other.im) def __imul__(self, other): if not isinstance(other, ComplexInterval): other = complex_interval(other) other_re = other.re other_im = other.im self_re = self.re self_im = self.im # self.re, self.im = (self_re * other_re - self_im * other_im, # self_im * other_re + self_re * other_im) imsneg = self_im * other_im reim = self_re * other_im self.re *= other_re self.re -= imsneg self.im *= other_re self.im += reim return self def __iadd__(self, other): if not isinstance(other, ComplexInterval): other = complex_interval(other) self.re += other.re self.im += other.im return self def __rmul__(self, other): return self * other # I forget how complex division works but I'm not using it right now, # so I'm leaving it unimplemented. def __rdiv__(self, other): return complex_interval(other) / self def __contains__(self, other): other = complex(other) return other.real in self.re and other.imag in self.im def __abs__(self): rv = self.abs_sq()**0.5 assert type(rv) is Interval, rv return rv def abs_sq(self): return self.re*self.re + self.im*self.im def copy(self): return ComplexInterval(self.re.copy(), self.im.copy()) def complex_interval(object): "Coerce an object to a ComplexInterval." if isinstance(object, ComplexInterval): return object elif isinstance(object, Interval): return ComplexInterval(object, interval(0)) elif isinstance(object, complex): return ComplexInterval(interval(object.real), interval(object.imag)) elif isinstance(object, int) or isinstance(object, long) or isinstance(object, float): return ComplexInterval(interval(object), interval(0)) else: raise TypeError(object) def mandelbrot_escape_time(z0, maxiter=63): """Compute the escape time for the Mandelbrot set for an interval. This computes a conservative approximation to the number of iterations needed for points in the given complex interval to escape given the orbit of the Mandelbrot set. It's guaranteed that the (real) interval it returns will be a superset of the number of iterations any number inside the (complex) interval given as argument z0 needs to escape the usual cutoff radius of 2 from the origin. However, it may be overbroad. """ z = complex_interval(z0) c = z.copy() minimum = None for ii in range(maxiter): abs_sq_z = z.abs_sq() # print "absz, minimum", abs_z, minimum if minimum is None and abs_sq_z.max > 4: minimum = ii if abs_sq_z.min > 4: return Interval(minimum, ii) z *= z z += c # If we have reached maxiter, we might either be entirely inside # the (approximate) Mandelbrot set, or just partly. If we're # entirely inside, minimum is still None. return Interval(minimum if minimum is not None else maxiter, maxiter) def main(): "Simple test function." # print 0, interval(0) # print "log 0", interval(0).log() # print "2 log 0", 2 * interval(0).log() # print "exp(2 log 0)", (2 * interval(0).log()).exp() print "0²", interval(0)**2 print abs(-Interval(3, 4) - Interval(0, 0.1))**2 print abs(complex_interval(1j) * complex_interval(1j)) print mandelbrot_escape_time(ComplexInterval(Interval(-2., 2.), Interval(-2., 2.))) print mandelbrot_escape_time(ComplexInterval(Interval(0., .25), Interval(0., .25))) if __name__ == "__main__": import cgitb cgitb.enable(format='text') main()