def test_path(self): CPF = ComplexPathFactory(self.f1, base_point=-2, kappa=1) b = CPF.discriminant_points[0] R = CPF.radius(b) self.assertAlmostEqual(R, 1.0) # straight line path from (-2,1) to (1,-2). this intersects the circle # of radius 1 about the origin at (-1,0) goes around to (0,-1). # # this test goes below the circle z0 = -2 + 1.j z1 = 1 - 2.j gamma = CPF.path(z0, z1) segments = gamma.segments self.assertEqual(segments[0], ComplexLine(z0, -1)) self.assertEqual(segments[1], ComplexArc(1, 0, -pi, pi / 2)) self.assertEqual(segments[2], ComplexLine(-1.j, z1)) # straight line path from (-1,2) to (2,-1). this intersects the circle # of radius 1 about the origin at (0,1) goes around to (1,0). # # this test goes below the circle z0 = -1 + 2.j z1 = 2 - 1.j gamma = CPF.path(z0, z1) segments = gamma.segments self.assertEqual(segments[0], ComplexLine(z0, 1.j)) self.assertEqual(segments[1], ComplexArc(1, 0, pi / 2, -pi / 2)) self.assertEqual(segments[2], ComplexLine(1, z1))
def test_arc_derivative(self): # arc from theta=0 to theta=pi/2 on the unit circle gamma = ComplexArc(1, 0, 0, pi/2) scale = 1.j*pi/2 self.assertAlmostEqual(gamma.derivative(0), scale) self.assertAlmostEqual(gamma.derivative(0.5), scale*exp(1.j*pi/4)) self.assertAlmostEqual(gamma.derivative(0.75), scale*exp(1.j*3*pi/8)) self.assertAlmostEqual(gamma.derivative(1), scale*exp(1.j*pi/2))
def test_single_arc(self): gamma = ComplexArc(1,0,pi/5,3*pi/5) gamma_rev = gamma.reverse() self.assertAlmostEqual(gamma(0.0), gamma_rev(1.0)) self.assertAlmostEqual(gamma(0.1), gamma_rev(0.9)) self.assertAlmostEqual(gamma(0.25), gamma_rev(0.75)) self.assertAlmostEqual(gamma(0.50), gamma_rev(0.50)) self.assertAlmostEqual(gamma(0.75), gamma_rev(0.25)) self.assertAlmostEqual(gamma(1.0), gamma_rev(0.0))
def test_single_arc(self): gamma = ComplexArc(1, 0, pi / 5, 3 * pi / 5) gamma_rev = gamma.reverse() self.assertAlmostEqual(gamma(0.0), gamma_rev(1.0)) self.assertAlmostEqual(gamma(0.1), gamma_rev(0.9)) self.assertAlmostEqual(gamma(0.25), gamma_rev(0.75)) self.assertAlmostEqual(gamma(0.50), gamma_rev(0.50)) self.assertAlmostEqual(gamma(0.75), gamma_rev(0.25)) self.assertAlmostEqual(gamma(1.0), gamma_rev(0.0))
def test_equality(self): gamma0 = ComplexLine(-1, 0) gamma1 = ComplexLine(-1, 0) self.assertEqual(gamma0, gamma1) gamma0 = ComplexArc(1, 0, 0, pi) gamma1 = ComplexArc(1, 0, 0, pi) self.assertEqual(gamma0, gamma1) gamma0 = ComplexRay(-1) gamma1 = ComplexRay(-1) self.assertEqual(gamma0, gamma1)
def monodromy_path_infinity(self, nrots=1): """Returns the complex path starting at the base point, going around infinity `nrots` times, and returning to the base point. This path is sure to not only encircle all of the discriminant points but also stay sufficiently outside the bounding circles of the points. Parameters ---------- nrots : integer, (default `1`) The number of rotations around infinity. Returns ------- RiemannSurfacePath The complex path encircling infinity. """ path = [] # determine the radius R of the circle, centered at the origin, # encircling all of the discriminant points and the bounding circles b = self.discriminant_points R = numpy.abs(self.base_point) for bi in b: radius = self.radius(bi) Ri = numpy.abs(bi) + 2*radius # to be safely away R = Ri if Ri > R else R # the path begins with a line starting at the base point and ending at # the point -R (where the circle will begin) path = ComplexLine(self.base_point, -R) # the positive direction around infinity is equal to the # negative direction around the origin dtheta = -numpy.pi if nrots > 0 else numpy.pi for _ in range(abs(nrots)): path += ComplexArc(R, 0, numpy.pi, dtheta) path += ComplexArc(R, 0, 0, dtheta) # return to the base point path += ComplexLine(-R, self.base_point) # determine if the circle actually touches the base point. this occurs # when the base point is further away from the origin than the bounding # circles of discriminant points. in this case, the path only consists # of the arcs defining the circle if abs(self.base_point + R) < 1e-15: path = ComplexPath(path.segments[1:-1]) return path
def test_arc(self): # arc from theta=0 to theta=pi/2 on the unit circle gamma = ComplexArc(1, 0, 0, pi / 2) self.assertAlmostEqual(gamma(0), 1) self.assertAlmostEqual(gamma(0.5), exp(1.j * pi / 4)) self.assertAlmostEqual(gamma(0.75), exp(1.j * 3 * pi / 8)) self.assertAlmostEqual(gamma(1), exp(1.j * pi / 2))
def test_analytic_continuation_X1(self): gammax = ComplexLine(1, 0) y0 = [-1, 1] gamma = RiemannSurfacePathPuiseux(self.X1, gammax, y0) y = gamma.get_y(0) self.assertAlmostEqual(y[0], -1) self.assertAlmostEqual(y[1], 1) y = gamma.get_y(0.5) self.assertAlmostEqual(y[0], -sqrt(complex(0.5))) self.assertAlmostEqual(y[1], sqrt(complex(0.5))) y = gamma.get_y(0.75) self.assertAlmostEqual(y[0], -sqrt(complex(0.25))) self.assertAlmostEqual(y[1], sqrt(complex(0.25))) y = gamma.get_y(1) self.assertAlmostEqual(y[0], 0) self.assertAlmostEqual(y[1], 0) gammax = ComplexArc(2, 2, 0, pi) y0 = [-2, 2] gamma = RiemannSurfacePathPuiseux(self.X1, gammax, y0) y = gamma.get_y(0) self.assertAlmostEqual(y[0], -2) self.assertAlmostEqual(y[1], 2) y = gamma.get_y(1) self.assertAlmostEqual(y[0], 0) self.assertAlmostEqual(y[1], 0)
def test_simple_arc(self): gammax = ComplexArc(1, 0, 0, pi) gamma = RiemannSurfacePathSmale(self.X1, gammax, [-1, 1]) nu = lambda x, y: y nu_gamma = gamma.parameterize(nu) val = nu_gamma(0.0) test = gammax.derivative(0.0) * (-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5) test = gammax.derivative(0.5) * (-sqrt(1.j)) self.assertAlmostEqual(val, test) val = nu_gamma(1.0) test = gammax.derivative(1.0) * (-1.j) self.assertAlmostEqual(val, test)
def test_simple_arc(self): gammax = ComplexArc(1,0,0,pi) gamma = RiemannSurfacePathSmale(self.X1, gammax, [-1,1]) nu = lambda x,y: y nu_gamma = gamma.parameterize(nu) val = nu_gamma(0.0) test = gammax.derivative(0.0)*(-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5) test = gammax.derivative(0.5)*(-sqrt(1.j)) self.assertAlmostEqual(val, test) val = nu_gamma(1.0) test = gammax.derivative(1.0)*(-1.j) self.assertAlmostEqual(val, test)
def test_primitive_arc_smale(self): PF = RiemannSurfacePathFactory(self.X1, base_point=-1, base_sheets=[-1.j,1.j]) gamma_x = ComplexArc(1, 0, pi, pi) gamma = PF.RiemannSurfacePath_from_complex_path(gamma_x) self.assertAlmostEqual(gamma.get_x(1.0), 1) self.assertAlmostEqual(gamma.get_y(1.0)[0], 1) self.assertAlmostEqual(gamma.get_y(1.0)[1], -1) # swap the base sheets PF = RiemannSurfacePathFactory(self.X1, base_point=-1, base_sheets=[1.j,-1.j]) gamma_x = ComplexArc(1, 0, pi, pi) gamma = PF.RiemannSurfacePath_from_complex_path(gamma_x) self.assertAlmostEqual(gamma.get_x(1.0), 1) self.assertAlmostEqual(gamma.get_y(1.0)[0], -1) self.assertAlmostEqual(gamma.get_y(1.0)[1], 1)
def test_indexing(self): gamma0 = ComplexLine(-1, 0) gamma1 = ComplexLine(0, 1.j) gamma2 = ComplexArc(1, 0, pi / 2, -pi / 2) gamma = gamma0 + gamma1 + gamma2 self.assertEqual(gamma.segments, [gamma0, gamma1, gamma2]) self.assertEqual(gamma[0], gamma0) self.assertEqual(gamma[1], gamma1) self.assertEqual(gamma[2], gamma2)
def test_composite(self): x1 = (sqrt(2) + sqrt(2) * 1.j) / 2 gamma = ComplexLine(0, x1) + ComplexArc(1, 0, pi / 4, 3 * pi / 5) gamma_rev = gamma.reverse() self.assertAlmostEqual(gamma(0.0), gamma_rev(1.0)) self.assertAlmostEqual(gamma(0.1), gamma_rev(0.9)) self.assertAlmostEqual(gamma(0.25), gamma_rev(0.75)) self.assertAlmostEqual(gamma(0.50), gamma_rev(0.50)) self.assertAlmostEqual(gamma(0.75), gamma_rev(0.25)) self.assertAlmostEqual(gamma(1.0), gamma_rev(0.0))
def monodromy_path(self, bi, nrots=1): """Returns the complex path starting from the base point, going around the discriminant point `bi` `nrots` times, and returning to the base x-point. The sign of `nrots` indicates the sign of the direction. Parameters ---------- bi : complex A discriminant point. nrots : integer (default `1`) A number of rotations around this discriminant point. Returns ------- path : ComplexPath A complex path representing the monodromy path with `nrots` rotations about the discriminant point `bi`. """ if bi in [infinity, numpy.Infinity, 'oo']: return self.monodromy_path_infinity(nrots=nrots) path_to_bi = self.path_to_discriminant_point(bi) # determine the rotational path around the discriminant point z = path_to_bi(1.0) bi = complex(bi) Ri = self.radius(bi) theta = angle(z - bi) dtheta = numpy.pi if nrots > 0 else -numpy.pi circle = ComplexArc(Ri, bi, theta, dtheta) + \ ComplexArc(Ri, bi, theta + dtheta, dtheta) path_around_bi = circle for _ in range(abs(nrots)-1): path_around_bi += circle # the monodromy path is the sum of the path to the point, the # rotational part, and the return path to the base point path = path_to_bi + path_around_bi + path_to_bi.reverse() return path
def tests_monodromy(self): gammax = ComplexArc(1, 0, 0, 2 * pi) y0 = [-1, 1] gamma = RiemannSurfacePathSmale(self.X1, gammax, y0) y = gamma.get_y(0.0) self.assertAlmostEqual(y[0], -1) self.assertAlmostEqual(y[1], 1) y = gamma.get_y(1.0) self.assertAlmostEqual(y[0], 1) self.assertAlmostEqual(y[1], -1)
def test_simple_composite(self): gammax1 = ComplexLine(4,1) gamma1 = RiemannSurfacePathSmale(self.X1, gammax1, [-2,2]) gammax2 = ComplexArc(1,0,0,pi) gamma2 = RiemannSurfacePathSmale(self.X1, gammax2, [-1,1]) gamma = gamma1 + gamma2 nu = lambda x,y: y nu_gamma = gamma.parameterize(nu) val = nu_gamma(0.0) test = gammax1.derivative(0.0)*(-2) self.assertAlmostEqual(val, test) val = nu_gamma(0.25) test = gammax1.derivative(0.5)*(-sqrt(2.5)) self.assertAlmostEqual(val, test) eps = 1e-12 val = nu_gamma(0.5-eps) test = gammax1.derivative(1.0-eps/2)*(-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5) test = gammax2.derivative(0.0)*(-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5+eps) test = gammax2.derivative(eps/2)*(-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.75) test = gammax2.derivative(0.5)*(-sqrt(1.j)) self.assertAlmostEqual(val, test) val = nu_gamma(1.0) test = gammax2.derivative(1.0)*(-1.j) self.assertAlmostEqual(val, test)
def test_iteration_reverse(self): gamma0 = ComplexLine(-1, 0) gamma1 = ComplexLine(0, 1.j) gamma2 = ComplexArc(1, 0, pi / 2, -pi / 2) gamma = gamma0 + gamma1 + gamma2 index = 0 for segment in gamma[::-1]: if index == 0: self.assertEqual(segment, gamma2) elif index == 1: self.assertEqual(segment, gamma1) elif index == 2: self.assertEqual(segment, gamma0) index += 1
def test_arc_derivative(self): # arc from theta=0 to theta=pi/2 on the unit circle gamma = ComplexArc(1, 0, 0, pi / 2) scale = 1.j * pi / 2 self.assertAlmostEqual(gamma.derivative(0), scale) self.assertAlmostEqual(gamma.derivative(0.5), scale * exp(1.j * pi / 4)) self.assertAlmostEqual(gamma.derivative(0.75), scale * exp(1.j * 3 * pi / 8)) self.assertAlmostEqual(gamma.derivative(1), scale * exp(1.j * pi / 2))
def test_simple_composite(self): gammax1 = ComplexLine(4, 1) gamma1 = RiemannSurfacePathSmale(self.X1, gammax1, [-2, 2]) gammax2 = ComplexArc(1, 0, 0, pi) gamma2 = RiemannSurfacePathSmale(self.X1, gammax2, [-1, 1]) gamma = gamma1 + gamma2 nu = lambda x, y: y nu_gamma = gamma.parameterize(nu) val = nu_gamma(0.0) test = gammax1.derivative(0.0) * (-2) self.assertAlmostEqual(val, test) val = nu_gamma(0.25) test = gammax1.derivative(0.5) * (-sqrt(2.5)) self.assertAlmostEqual(val, test) eps = 1e-12 val = nu_gamma(0.5 - eps) test = gammax1.derivative(1.0 - eps / 2) * (-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5) test = gammax2.derivative(0.0) * (-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.5 + eps) test = gammax2.derivative(eps / 2) * (-1) self.assertAlmostEqual(val, test) val = nu_gamma(0.75) test = gammax2.derivative(0.5) * (-sqrt(1.j)) self.assertAlmostEqual(val, test) val = nu_gamma(1.0) test = gammax2.derivative(1.0) * (-1.j) self.assertAlmostEqual(val, test)
def test_arc(self): gamma = ComplexArc(1, 0, pi, -pi) self.assertEqual(gamma.R, 1) self.assertEqual(gamma.w, 0) self.assertEqual(gamma.theta, pi) self.assertEqual(gamma.dtheta, -pi)
def avoiding_arc(self, w0, w1, b, R, orientation=None): """Returns the arc `(radius, center, starting_theta, dtheta)`, from the points `w0` and `w1` on the bounding circle around `bi`. The arc is constructed in such a way so that the monodromy properties of the path are conserved. Parameters ---------- w0 : complex The starting point of the arc on the bounding circle of `bi`. w1 : complex The ending point of the arc on the bounding circle of `bi`. b : complex The discriminant point to avoid. R : double The radius of the bounding circle. Returns ------- arc : ComplexArc An arc from `w0` to `w1` around `bi`. """ w0 = complex(w0) w1 = complex(w1) b = complex(b) R = double(R) # ASSUMPTION: Re(w0) < Re(w1) if w0.real >= w1.real: raise ValueError('Cannot construct avoiding arc: all paths must ' 'travel from left to right unless "reversed".') # ASSERTION: w0 and w1 lie on the circle of radius Ri centered at bi R0 = abs(w0 - b) R1 = abs(w1 - b) if abs(R0 - R) > 1e-13 or abs(R1 - R) > 1e-13: raise ValueError('Cannot construct avoiding arc: ' '%s and %s must lie on the bounding circle of ' 'radius %s centered at %s'%(w0,w1,R,b)) # degenerate case: w0, bi, w1 are co-linear # # if no orientation is provided then go above. otherwise, adhere to the # orientation: orientation = +1/-1 means the path goes above/below phi_w0_w1 = numpy.angle(w1-w0) phi_w0_b = numpy.angle(b-w0) if abs(phi_w0_w1 - phi_w0_b) < 1e-13: theta0 = numpy.angle(w0-b) dtheta = -numpy.pi # default above if not orientation is None: dtheta *= orientation return ComplexArc(R, b, theta0, dtheta) # otherwise: w0, bi, w1 are not co-linear # # first determine if the line form w0 to w1 is above or below the # branch point bi. this will determine if dtheta is negative or # positive, respectively if phi_w0_b <= phi_w0_w1: dtheta_sign = -1 else: dtheta_sign = 1 # now determine the angle between w0 and w1 on the circle. since w0, # bi, and w1 are not colinear this angle must be normalized to be in # the interval (-pi,pi) theta0 = numpy.angle(w0 - b) theta1 = numpy.angle(w1 - b) dtheta = theta1 - theta0 if dtheta > numpy.pi: dtheta = 2*numpy.pi - dtheta elif dtheta < -numpy.pi: dtheta = 2*numpy.pi + dtheta # sanity check: |dtheta| should be less than pi if abs(dtheta) >= numpy.pi: raise ValueError('Cannot construct avoiding arc: ' '|dtheta| must be less than pi.') dtheta = dtheta_sign * abs(dtheta) # finally, take orentation into account. orientation is a stronger # condition than the above computations. # # in the case when the signs of the orientation and the dtheta are # opposite then do nothing since: orentation = +1/-1 implies go # above/below implies dtheta negative/positive. # # when the signs are same then make adjustments: if not orientation is None: if orientation == 1 and dtheta > 0: dtheta = dtheta - 2*numpy.pi elif orientation == -1 and dtheta < 0: dtheta = 2*numpy.pi + dtheta # add the path from z0 to w1 going around bi arc = ComplexArc(R, b, theta0, dtheta) return arc