#mobius.py

import cmath, numpy

class circle_involution:
    def __init__(self, center, radius):
        self.center = center
        self.radius = radius
    def transform(self):
        c = self.center
        r = self.radius
        return translation(conj(c))*self.scaling(r)*involution*self.inv_scaling(1.0/r)*self.inv_translation(-c)
    def of(self, z):
        "Apply to homogenous coordinate z."
        w = self.transform()*z
        return conj(w)
    def of_circle(self, center, radius):
        "Apply to circle involution."
        if radius == 0:
            return 0, 0
        # We find the involution of center, radius
        c = self.center
        r = self.radius
        # We conjugate it with our own involution
        C = translation(conj(c))*scaling(r)*involution*scaling(1.0/r)*translation(-c)
        D = translation(center)*scaling(radius)*involution*scaling(1.0/radius)*translation(conj(-center))
        E = translation(conj(c))*scaling(r)*involution*scaling(1.0/r)*translation(-c)
        image_transform = C*D*E
        # The result is itself an involution whose center/radius we return
        a = image_transform.item(0)
        b = image_transform.item(1)
        c = image_transform.item(2)
        d = image_transform.item(3)
        a /= c
        b /= c
        d /= c
        c = 1.0
        new_center = -d
        new_radius = abs(b + (new_center*conj(new_center)))**.5
        return new_center, new_radius

def translation(t):
    return numpy.matrix([[1.0, t], [0.0, 1.0]])

def scaling(r):
    return numpy.matrix([[r, 0.0], [0.0, 1.0]])

def conj(z):
    return z - 2j*z.imag

def normalize(z):
    "Normalize homogenous coordinates so last is 1.0"
    a = z.item(0)
    b = z.item(1)
    if b != 0.0 + 0j:
        return numpy.matrix([[a/b], [1.0 + 0j]])
    else:
        return numpy.matrix([[1.0 + 0j], [0.0 + 0j]])

involution = numpy.matrix([[0.0, 1.0], [1.0, 0.0]])