0001"""
0002base class for points of the complex plane
0003Much from the PyPy complex number implementation - but now mutable
0004"""
0005from __future__ import division
0006
0007import string
0008from pygeo.base.analytics.pygeomath import arctan2,absolute,array
0009from pygeo.base.support.pygeoopts import EPS
0010
0011
0012class CPosition(object):
0013    """The pypy complex object code, made mutable by removing the properties declarations
0014    for real and imag, and with additional attributes and methods used by Pygeo"""
0015
0016    PREC_REPR = 17
0017    PREC_STR = 12
0018    __slots__ = ['real', 'imag']
0019    # XXX this class is not well tested
0020    # provide __new__to prevent the default which has no parameters
0021    def __new__(typ, *args,**kws):
0022
0023        if args:
0024            if isinstance(args[0],CPosition):
0025                real=args[0].real
0026                imag=args[0].imag
0027            elif isinstance(args[0],complex):
0028                real=args[0].real
0029                imag=args[0].imag
0030            elif isinstance(args[0],(float,int)):
0031                real=args[0]
0032                try:
0033                    if isinstance(args[1],(float,int)):
0034                        imag=args[1]
0035                except IndexError:
0036                    imag=0
0037            else:
0038                real=0
0039                imag=0
0040        else:
0041            real=0
0042            imag=0
0043        ret = object.__new__(typ)
0044        ret._init(real, imag)
0045        return ret
0046
0047    def __getnewargs__(self):
0048        return (CPosition(self.real, self.imag),)
0049
0050    def __reduce__(self):
0051        return (self.__class__, (self.real, self.imag),
0052                getattr(self, '__dict__', None))
0053
0054    def _init(self, re, im):
0055        real_slot.__set__(self, re)
0056        imag_slot.__set__(self, im)
0057
0058    def __getitem__(self, index):
0059        return self.vector[index]
0060
0061    def get_name(self):
0062        return self.__class__.__name__
0063
0064    def set_x(self,value):
0065        self.real=value
0066
0067    def set_y(self,value):
0068        self.imag=value
0069
0070    def get_x(self):
0071        return self.real
0072
0073    def get_y(self):
0074        return self.imag
0075
0076    def get_z(self):
0077        return 0.
0078
0079    def get_pos(self):
0080        return self.real,self.imag
0081
0082    pos= property(get_pos,None,None,"The 3 (z=0) coordinate drawing position vector")
0083
0084    x = property(get_x,set_x,None,"The real coordinate ")
0085    y = property(get_y,set_y,None,"The imag coordinate ")
0086    z = property(get_z,None,None,"The complex plane z position")
0087
0088    def __description(self, precision):
0089        if self.real != 0.:
0090            return self.__class__.__name__ + "(%.*g%+.*gj)"%(precision, self.real, precision, self.imag)
0091        else:
0092            return "%.*gj"%(precision, self.imag)
0093
0094    def __repr__(self):
0095        return self.__description(self.PREC_REPR)
0096
0097    def __str__(self):
0098        return self.__description(self.PREC_STR)
0099
0100    def __hash__(self):
0101        hashreal = hash(self.real)
0102        hashimag = hash(self.imag)
0103        # Note:  if the imaginary part is 0, hashimag is 0 now,
0104        # so the following returns hashreal unchanged.  This is
0105        # important because numbers of different types that
0106        # compare equal must have the same hash value, so that
0107        # hash(x + 0*j) must equal hash(x).
0108        return hashreal + 1000003 * hashimag
0109
0110    def __add__(self, other):
0111        result = self.__coerce__(other)
0112        if result is NotImplemented:
0113            return result
0114        self, other = result
0115        real = self.real + other.real
0116        imag = self.imag + other.imag
0117        return CPosition(real, imag)
0118
0119    __radd__ = __add__
0120
0121    def __sub__(self, other):
0122        result = self.__coerce__(other)
0123        if result is NotImplemented:
0124            return result
0125        self, other = result
0126        real = self.real - other.real
0127        imag = self.imag - other.imag
0128        return CPosition(real, imag)
0129
0130    def __rsub__(self, other):
0131        result = self.__coerce__(other)
0132        if result is NotImplemented:
0133            return result
0134        self, other = result
0135        return other.__sub__(self)
0136
0137    def __mul__(self, other):
0138        result = self.__coerce__(other)
0139        if result is NotImplemented:
0140            return result
0141        self, other = result
0142        real = self.real*other.real - self.imag*other.imag
0143        imag = self.real*other.imag + self.imag*other.real
0144        return CPosition(real, imag)
0145
0146    __rmul__ = __mul__
0147
0148    def __div__(self, other):
0149        result = self.__coerce__(other)
0150        if result is NotImplemented:
0151            return result
0152        self, other = result
0153        if absolute(other.real) >= absolute(other.imag):
0154            # divide tops and bottom by other.real
0155            try:
0156                ratio = other.imag / other.real
0157            except ZeroDivisionError:
0158                raise ZeroDivisionError, "Complex division"
0159            denom = other.real + other.imag * ratio
0160            real = (self.real + self.imag * ratio) / denom
0161            imag = (self.imag - self.real * ratio) / denom
0162        else:
0163            # divide tops and bottom by other.imag
0164            assert other.imag != 0.0
0165
0166            ratio = other.real / other.imag
0167            denom = other.real * ratio + other.imag
0168            real = (self.real * ratio + self.imag) / denom
0169            imag = (self.imag * ratio - self.real) / denom
0170        return CPosition(real, imag)
0171
0172    def __rdiv__(self, other):
0173        result = self.__coerce__(other)
0174        if result is NotImplemented:
0175            return result
0176        self, other = result
0177        return other.__div__(self)
0178
0179    def __floordiv__(self, other):
0180        result = self.__divmod__(other)
0181        if result is NotImplemented:
0182            return result
0183        div, mod = result
0184        return div
0185
0186    def __rfloordiv__(self, other):
0187        result = self.__coerce__(other)
0188        if result is NotImplemented:
0189            return result
0190        self, other = result
0191        return other.__floordiv__(self)
0192
0193    __truediv__ = __div__
0194
0195    __rtruediv__ = __rdiv__
0196
0197    def __mod__(self, other):
0198        result = self.__divmod__(other)
0199        if result is NotImplemented:
0200            return result
0201        div, mod = result
0202        return mod
0203
0204    def __rmod__(self, other):
0205        result = self.__coerce__(other)
0206        if result is NotImplemented:
0207            return result
0208        self, other = result
0209        return other.__mod__(self)
0210
0211    def __divmod__(self, other):
0212        result = self.__coerce__(other)
0213        if result is NotImplemented:
0214            return result
0215        self, other = result
0216        import warnings, math
0217        warnings.warn("complex divmod(), // and % are deprecated", DeprecationWarning)
0218        try:
0219            div = self/other # The raw divisor value.
0220        except ZeroDivisionError:
0221            raise ZeroDivisionError, "Complex remainder"
0222        div = CPosition(math.floor(div.real), 0.0)
0223        mod = self - div*other
0224        return div, mod
0225
0226    def __rdivmod__(self, other):
0227        result = self.__coerce__(other)
0228        if result is NotImplemented:
0229            return result
0230        self, other = result
0231        return other.__divmod__(self)
0232
0233    def __pow__(self, other, mod=None):
0234        if mod is not None:
0235            raise ValueError("Complex modulo")
0236        result = self.__coerce__(other)
0237        if result is NotImplemented:
0238            return result
0239        a, b = result
0240        import math
0241        if b.real == 0. and b.imag == 0.:
0242            real = 1.
0243            imag = 0.
0244        elif a.real == 0. and a.imag == 0.:
0245            if b.imag != 0. or b.real < 0.:
0246                raise ZeroDivisionError, "0.0 to a negative or Complex power"
0247            real = 0.
0248            imag = 0.
0249        else:
0250            vabs = math.hypot(a.real,a.imag)
0251            len = math.pow(vabs,b.real)
0252            at = math.atan2(a.imag, a.real)
0253            phase = at*b.real
0254            if b.imag != 0.0:
0255                len /= math.exp(at*b.imag)
0256                phase += b.imag*math.log(vabs)
0257            real = len*math.cos(phase)
0258            imag = len*math.sin(phase)
0259        result = CPosition(real, imag)
0260        return result
0261
0262    def __rpow__(self, other, mod=None):
0263        result = self.__coerce__(other)
0264        if result is NotImplemented:
0265            return result
0266        self, other = result
0267        return other.__pow__(self, mod)
0268
0269    def __neg__(self):
0270        return CPosition(-self.real, -self.imag)
0271
0272    def __pos__(self):
0273        return CPosition(self.real, self.imag)
0274
0275    def __abs__(self):
0276        import math
0277        result = math.hypot(self.real, self.imag)
0278        return float(result)
0279
0280    def __nonzero__(self):
0281        return self.real != 0.0 or self.imag != 0.0
0282
0283    def __coerce__(self, other):
0284        if isinstance(other, complex):
0285            return self, CPosition(other)
0286        if isinstance(other, (int, long, float)):
0287            return self, CPosition(other)
0288        if isinstance(other,CPosition):
0289            return self,other
0290        return NotImplemented
0291
0292    def conjugate(self):
0293        return CPosition(self.real, -self.imag)
0294
0295    def __eq__(self, other):
0296        result = self.__coerce__(other)
0297        if result is NotImplemented:
0298            return id(self) == id(other)
0299        self, other = result
0300        return ((absolute(self.real-other.real)) < EPS
0301                 and absolute((self.imag-other.imag)) < EPS)
0302
0303    def __ne__(self, other):
0304        result = self.__coerce__(other)
0305        if result is NotImplemented:
0306            return result
0307        self, other = result
0308        return ((self.real-other.real) >= EPS
0309                 or (self.imag-other.imag) >= EPS)
0310    # unsupported operations
0311
0312    def __lt__(self, other):
0313        result = self.__coerce__(other)
0314        if result is NotImplemented:
0315            return result
0316        raise TypeError, "cannot compare Complex numbers using <, <=, >, >="
0317
0318    __le__ = __gt__ = __ge__ = __lt__
0319
0320    def __int__(self):
0321        raise TypeError, "can't convert Complex to int; use e.g. int(absolute(z))"
0322
0323    def __long__(self):
0324        raise TypeError, "can't convert Complex to long; use e.g. long(absolute(z))"
0325
0326    def __float__(self):
0327        raise TypeError, "can't convert Complex to float; use e.g. float(absolute(z))"
0328
0329
0330    ##begins PyGeo's additional methods
0331
0332    def arg(self):
0333        return arctan2(self.imag,self.real)
0334
0335    def mod2(self):
0336        return self.real**2+self.imag**2
0337
0338    def mod(self):
0339        return absolute(self)
0340
0341    def distance(self,other):
0342        import math
0343        real=self.real-other.real
0344        imag=self.imag-other.imag
0345        result = math.hypot(real, imag)
0346        return float(result)
0347
0348    def distanceSquared(self,other):
0349        return self.distance(other)**2
0350
0351    def homogenous(self):
0352        return array([CPosition(self.real,self.imag),CPosition(1,0)])
0353
0354    def set(self,other):
0355        CPosition.real.__set__(self, other.real)
0356        CPosition.imag.__set__(self, other.imag)
0357
0358
0359    def toComplex(self):
0360        return complex(self.real,self.imag)
0361
0362real_slot = CPosition.real
0363
0364imag_slot = CPosition.imag