Complex.py 9.27 KB
Newer Older
Guido van Rossum's avatar
Guido van Rossum committed
1
# Complex numbers
2
# ---------------
Guido van Rossum's avatar
Guido van Rossum committed
3

4 5 6
# [Now that Python has a complex data type built-in, this is not very
# useful, but it's still a nice example class]

7 8 9 10 11 12 13 14 15 16 17 18
# This module represents complex numbers as instances of the class Complex.
# A Complex instance z has two data attribues, z.re (the real part) and z.im
# (the imaginary part).  In fact, z.re and z.im can have any value -- all
# arithmetic operators work regardless of the type of z.re and z.im (as long
# as they support numerical operations).
#
# The following functions exist (Complex is actually a class):
# Complex([re [,im]) -> creates a complex number from a real and an imaginary part
# IsComplex(z) -> true iff z is a complex number (== has .re and .im attributes)
# ToComplex(z) -> a complex number equal to z; z itself if IsComplex(z) is true
#                 if z is a tuple(re, im) it will also be converted
# PolarToComplex([r [,phi [,fullcircle]]]) ->
19 20
#       the complex number z for which r == z.radius() and phi == z.angle(fullcircle)
#       (r and phi default to 0)
21
# exp(z) -> returns the complex exponential of z. Equivalent to pow(math.e,z).
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
#
# Complex numbers have the following methods:
# z.abs() -> absolute value of z
# z.radius() == z.abs()
# z.angle([fullcircle]) -> angle from positive X axis; fullcircle gives units
# z.phi([fullcircle]) == z.angle(fullcircle)
#
# These standard functions and unary operators accept complex arguments:
# abs(z)
# -z
# +z
# not z
# repr(z) == `z`
# str(z)
# hash(z) -> a combination of hash(z.re) and hash(z.im) such that if z.im is zero
#            the result equals hash(z.re)
# Note that hex(z) and oct(z) are not defined.
#
# These conversions accept complex arguments only if their imaginary part is zero:
# int(z)
# float(z)
#
# The following operators accept two complex numbers, or one complex number
# and one real number (int, long or float):
# z1 + z2
# z1 - z2
# z1 * z2
# z1 / z2
# pow(z1, z2)
# cmp(z1, z2)
# Note that z1 % z2 and divmod(z1, z2) are not defined,
# nor are shift and mask operations.
#
# The standard module math does not support complex numbers.
56
# The cmath modules should be used instead.
57 58 59 60 61 62
#
# Idea:
# add a class Polar(r, phi) and mixed-mode arithmetic which
# chooses the most appropriate type for the result:
# Complex for +,-,cmp
# Polar   for *,/,pow
Guido van Rossum's avatar
Guido van Rossum committed
63

64
import math
65
import sys
Guido van Rossum's avatar
Guido van Rossum committed
66

67 68
twopi = math.pi*2.0
halfpi = math.pi/2.0
Guido van Rossum's avatar
Guido van Rossum committed
69

70
def IsComplex(obj):
71
    return hasattr(obj, 're') and hasattr(obj, 'im')
72

73
def ToComplex(obj):
74 75
    if IsComplex(obj):
        return obj
76 77
    elif isinstance(obj, tuple):
        return Complex(*obj)
78 79
    else:
        return Complex(obj)
80 81

def PolarToComplex(r = 0, phi = 0, fullcircle = twopi):
82 83
    phi = phi * (twopi / fullcircle)
    return Complex(math.cos(phi)*r, math.sin(phi)*r)
84 85

def Re(obj):
86 87
    if IsComplex(obj):
        return obj.re
88
    return obj
89 90

def Im(obj):
91 92
    if IsComplex(obj):
        return obj.im
93
    return 0
94 95 96

class Complex:

97
    def __init__(self, re=0, im=0):
98 99
        _re = 0
        _im = 0
100
        if IsComplex(re):
101 102 103 104
            _re = re.re
            _im = re.im
        else:
            _re = re
105
        if IsComplex(im):
106 107 108 109 110 111 112 113
            _re = _re - im.im
            _im = _im + im.re
        else:
            _im = _im + im
        # this class is immutable, so setting self.re directly is
        # not possible.
        self.__dict__['re'] = _re
        self.__dict__['im'] = _im
114 115

    def __setattr__(self, name, value):
116
        raise TypeError('Complex numbers are immutable')
117 118

    def __hash__(self):
119 120 121
        if not self.im:
            return hash(self.re)
        return hash((self.re, self.im))
122 123 124

    def __repr__(self):
        if not self.im:
125
            return 'Complex(%r)' % (self.re,)
126
        else:
127
            return 'Complex(%r, %r)' % (self.re, self.im)
128 129 130

    def __str__(self):
        if not self.im:
131
            return repr(self.re)
132
        else:
133
            return 'Complex(%r, %r)' % (self.re, self.im)
134 135 136 137 138 139 140 141

    def __neg__(self):
        return Complex(-self.re, -self.im)

    def __pos__(self):
        return self

    def __abs__(self):
142
        return math.hypot(self.re, self.im)
143 144 145

    def __int__(self):
        if self.im:
146
            raise ValueError("can't convert Complex with nonzero im to int")
147 148 149 150
        return int(self.re)

    def __float__(self):
        if self.im:
151
            raise ValueError("can't convert Complex with nonzero im to float")
152 153
        return float(self.re)

154
    def __eq__(self, other):
155
        other = ToComplex(other)
156
        return (self.re, self.im) == (other.re, other.im)
157

158
    def __bool__(self):
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
        return not (self.re == self.im == 0)

    abs = radius = __abs__

    def angle(self, fullcircle = twopi):
        return (fullcircle/twopi) * ((halfpi - math.atan2(self.re, self.im)) % twopi)

    phi = angle

    def __add__(self, other):
        other = ToComplex(other)
        return Complex(self.re + other.re, self.im + other.im)

    __radd__ = __add__

    def __sub__(self, other):
        other = ToComplex(other)
        return Complex(self.re - other.re, self.im - other.im)

    def __rsub__(self, other):
        other = ToComplex(other)
        return other - self

    def __mul__(self, other):
        other = ToComplex(other)
        return Complex(self.re*other.re - self.im*other.im,
                       self.re*other.im + self.im*other.re)

    __rmul__ = __mul__

189
    def __truediv__(self, other):
190 191
        other = ToComplex(other)
        d = float(other.re*other.re + other.im*other.im)
192
        if not d: raise ZeroDivisionError('Complex division')
193 194 195
        return Complex((self.re*other.re + self.im*other.im) / d,
                       (self.im*other.re - self.re*other.im) / d)

196
    def __rtruediv__(self, other):
197 198 199 200 201
        other = ToComplex(other)
        return other / self

    def __pow__(self, n, z=None):
        if z is not None:
202
            raise TypeError('Complex does not support ternary pow()')
203 204
        if IsComplex(n):
            if n.im:
205
                if self.im: raise TypeError('Complex to the Complex power')
206 207 208 209 210 211 212 213 214 215
                else: return exp(math.log(self.re)*n)
            n = n.re
        r = pow(self.abs(), n)
        phi = n*self.angle()
        return Complex(math.cos(phi)*r, math.sin(phi)*r)

    def __rpow__(self, base):
        base = ToComplex(base)
        return pow(base, self)

216
def exp(z):
217 218
    r = math.exp(z.re)
    return Complex(math.cos(z.im)*r,math.sin(z.im)*r)
219 220 221


def checkop(expr, a, b, value, fuzz = 1e-6):
222
    print('       ', a, 'and', b, end=' ')
223 224
    try:
        result = eval(expr)
225 226 227
    except Exception as e:
        print('!!\t!!\t!! error: {}'.format(e))
        return
228
    print('->', result)
229 230
    if isinstance(result, str) or isinstance(value, str):
        ok = (result == value)
231 232 233
    else:
        ok = abs(result - value) <= fuzz
    if not ok:
234
        print('!!\t!!\t!! should be', value, 'diff', abs(result - value))
Guido van Rossum's avatar
Guido van Rossum committed
235 236

def test():
237
    print('test constructors')
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    constructor_test = (
        # "expect" is an array [re,im] "got" the Complex.
            ( (0,0), Complex() ),
            ( (0,0), Complex() ),
            ( (1,0), Complex(1) ),
            ( (0,1), Complex(0,1) ),
            ( (1,2), Complex(Complex(1,2)) ),
            ( (1,3), Complex(Complex(1,2),1) ),
            ( (0,0), Complex(0,Complex(0,0)) ),
            ( (3,4), Complex(3,Complex(4)) ),
            ( (-1,3), Complex(1,Complex(3,2)) ),
            ( (-7,6), Complex(Complex(1,2),Complex(4,8)) ) )
    cnt = [0,0]
    for t in constructor_test:
        cnt[0] += 1
        if ((t[0][0]!=t[1].re)or(t[0][1]!=t[1].im)):
254
            print("        expected", t[0], "got", t[1])
255
            cnt[1] += 1
256
    print("  ", cnt[1], "of", cnt[0], "tests failed")
257
    # test operators
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    testsuite = {
            'a+b': [
                    (1, 10, 11),
                    (1, Complex(0,10), Complex(1,10)),
                    (Complex(0,10), 1, Complex(1,10)),
                    (Complex(0,10), Complex(1), Complex(1,10)),
                    (Complex(1), Complex(0,10), Complex(1,10)),
            ],
            'a-b': [
                    (1, 10, -9),
                    (1, Complex(0,10), Complex(1,-10)),
                    (Complex(0,10), 1, Complex(-1,10)),
                    (Complex(0,10), Complex(1), Complex(-1,10)),
                    (Complex(1), Complex(0,10), Complex(1,-10)),
            ],
            'a*b': [
                    (1, 10, 10),
                    (1, Complex(0,10), Complex(0, 10)),
                    (Complex(0,10), 1, Complex(0,10)),
                    (Complex(0,10), Complex(1), Complex(0,10)),
                    (Complex(1), Complex(0,10), Complex(0,10)),
            ],
            'a/b': [
                    (1., 10, 0.1),
                    (1, Complex(0,10), Complex(0, -0.1)),
                    (Complex(0, 10), 1, Complex(0, 10)),
                    (Complex(0, 10), Complex(1), Complex(0, 10)),
                    (Complex(1), Complex(0,10), Complex(0, -0.1)),
            ],
            'pow(a,b)': [
                    (1, 10, 1),
                    (1, Complex(0,10), 1),
                    (Complex(0,10), 1, Complex(0,10)),
                    (Complex(0,10), Complex(1), Complex(0,10)),
                    (Complex(1), Complex(0,10), 1),
                    (2, Complex(4,0), 16),
            ],
    }
296
    for expr in sorted(testsuite):
297
        print(expr + ':')
298 299
        t = (expr,)
        for item in testsuite[expr]:
300
            checkop(*(t+item))
301

302 303

if __name__ == '__main__':
304
    test()