def monodromy_group(self):
        """Returns the monodromy group of the algebraic curve defining the
        Riemann surface.

        Returns
        -------
        list : list
            A list containing the base point, base sheets, branch points, and
            corresponding monodromy group elements.

        """
        x0 = self.base_point
        y0 = self.base_sheets

        # compute the monodromy element for each discriminant point. if
        # it's the identity permutation, don't add to monodromy group
        branch_points = []
        permutations = []
        for bi in self.discriminant_points:
            gamma = self.monodromy_path(bi)

            # compute the end fibre and the corresponding permutation
            yend = array(gamma.get_y(1.0), dtype=complex)
            phi = matching_permutation(y0, yend)

            # add the point to the monodromy group if the permutation is
            # not the identity.
            if not phi.is_identity():
                branch_points.append(bi)
                permutations.append(phi)

        # compute the monodromy element of the point at infinity
        gamma_x = self.complex_path_factory.monodromy_path_infinity()
        gamma = self.RiemannSurfacePath_from_complex_path(gamma_x, x0, y0)
        yend = array(gamma.get_y(1.0), dtype=complex)
        phi_oo = matching_permutation(y0, yend)

        # sanity check: the product of the finite branch point permutations
        # should be equal to the inverse of the permutation at infinity
        phi_prod_all = phi_oo
        for sigma in permutations:
            phi_prod_all = sigma * phi_prod_all
        if not phi_prod_all.is_identity():
            raise ValueError('Contradictory permutation at infinity.')

        # add infinity if it's a branch point
        if not phi_oo.is_identity():
            branch_points.append(infinity)
            permutations.append(phi_oo)

        return branch_points, permutations
    def monodromy_group(self):
        """Returns the monodromy group of the algebraic curve defining the
        Riemann surface.

        Returns
        -------
        list : list
            A list containing the base point, base sheets, branch points, and
            corresponding monodromy group elements.

        """
        x0 = self.base_point
        y0 = self.base_sheets

        # compute the monodromy element for each discriminant point. if
        # it's the identity permutation, don't add to monodromy group
        branch_points = []
        permutations = []
        for bi in self.discriminant_points:
            gamma = self.monodromy_path(bi)

            # compute the end fibre and the corresponding permutation
            yend = array(gamma.get_y(1.0), dtype=complex)
            phi = matching_permutation(y0, yend)

            # add the point to the monodromy group if the permutation is
            # not the identity.
            if not phi.is_identity():
                branch_points.append(bi)
                permutations.append(phi)

        # compute the monodromy element of the point at infinity
        gamma_x = self.complex_path_factory.monodromy_path_infinity()
        gamma = self.RiemannSurfacePath_from_complex_path(gamma_x, x0, y0)
        yend = array(gamma.get_y(1.0), dtype=complex)
        phi_oo = matching_permutation(y0, yend)

        # sanity check: the product of the finite branch point permutations
        # should be equal to the inverse of the permutation at infinity
        phi_prod = reduce(lambda phi1,phi2: phi2*phi1, permutations)
        phi_prod_all = phi_prod * phi_oo
        if not phi_prod_all.is_identity():
            raise ValueError('Contradictory permutation at infinity.')

        # add infinity if it's a branch point
        if not phi_oo.is_identity():
            branch_points.append(infinity)
            permutations.append(phi_oo)

        return branch_points, permutations
Exemple #3
0
    def _find_permutation_path(self, fineness=10):
        def _norm(x, scale=1, center=self.cp.cut_point):
            x = np.complex(x)
            r = np.complex(x - center)
            return np.complex(center + scale*r/np.abs(r))

        # radius of a circle encompassing all branch points
        R = 1.5*max(np.abs(x.val - self.cp.cut_point) for x in self.cp.branch_points if x.is_finite)
        # angle of this branch point
        self.angle = np.angle(self.val - self.cp.cut_point)

        base_point = _norm(self.cp.monodromy_point, scale=R)
        if self.is_finite:
            # half the distance to closest problematic point
            r = np.abs(np.complex(self.val - self.cp.cut_point))
            for x in self.cp.surface.discriminant_points:
                if x.n() != self.val:
                    r = min(r, 0.5*np.abs(np.complex(self.val - np.complex(x))))

            outter = _norm(self.val, scale=R)
            circle_start = _norm(self.val, scale=np.abs(self.val - self.cp.cut_point) + r)
            ang = np.angle(outter - self.cp.cut_point) - np.angle(base_point - self.cp.cut_point)
            if self.imag < imag_part(self.cp.cut_point):
                ang = 2*np.pi + ang

            points_to_outter = [self.cp.monodromy_point] + [np.complex(self.cp.cut_point) + np.complex(base_point-self.cp.cut_point)*np.exp(1j*ang*float(i)/fineness) for i in range(fineness+1)]
            around = [self.val + (circle_start-self.val)*np.exp(2j*np.pi*i/fineness) for i in range(fineness+1)]
            points = points_to_outter + around + points_to_outter[::-1]
        else:
            points = [self.cp.monodromy_point] + [np.complex(self.cp.cut_point) + np.complex(base_point-self.cp.cut_point)*np.exp(-2j*np.pi*i/fineness) for i in range(fineness+1)] + [self.cp.monodromy_point]

        # self.cp.ax.scatter([np.real(x) for x in points], [np.imag(x) for x in points])
        # return
        self.permutation_path = CyclePainterPath(points, 0, self.cp, color_sheets=False)
        self.permutation = matching_permutation(self.permutation_path.get_y(0), self.permutation_path.get_y(1))
def ordered_puiseux_series(riemann_surface, complex_path, y0, target_point):
    r"""Returns an ordered list of Puiseux series such that each Puiseux series
    matches with the corresponding y-fibre element above the starting point of
    `complex_path`.

    In order to analytically continue from the regular places at the beginning
    of the path :math:`x=a` to the discriminant places at the end of the path
    :math:`x=b`we need to compute all of the `PuiseuxXSeries` at :math:`x=b`.
    There are two steps to this calculation:

    * compute enough terms of the Puiseux series centered at :math:`x=b` in
      order to accurately capture the y-roots at :math:`x=a`.

    * permute the series accordingly to match up with the y-roots at
      :math:`x=a`.

    Parameters
    ----------
    riemann_surface : RiemannSurface
        The riemann surface on which all of this lives.
    complex_path : ComplexPath
        The path or path segment starting at a regular point and ending at a
        discriminant point.
    y0 : list of complex
        The starting fibre lying above the starting point of `complex_path`.
        The first component of the list indicates the starting sheet.
    target_point : complex
        The point to analytically continue to. Usually a discriminant point.

    Methods
    -------
    .. autosummary::

      analytically_continue

    Returns
    -------
    list, Place : a list of Puiseux series and a Place
        A list of ordered Puiseux series corresponding to each branch above
        :math:`x=a` as well as the place that the first y-fibre element
        analytically continues to.
    """
    # obtain all puiseux series above the target place
    f = riemann_surface.f
    x0 = CC(complex_path(0))  # XXX - need to coerce input to CC
    y0 = numpy.array(y0, dtype=complex)
    P = puiseux(f, target_point)

    # extend the Puiseux series to enough terms to accurately captue the
    # y-fibre above x=a (the starting point of the complex path)
    for Pi in P:
        Pi.extend_to_x(x0)

    # compute the corresponding x-series representations of the Puiseux series
    alpha = 0 if target_point == infinity else target_point
    px = [Pi.xseries() for Pi in P]
    p = [pxi for sublist in px for pxi in sublist]
    ramification_indices = [Pi.ramification_index for Pi in P]

    # reorder them according to the ordering of the y-fibre above x=x0
    p_evals_above_x0 = [pj(x0 - alpha) for pj in p]
    p_evals_above_x0 = numpy.array(p_evals_above_x0, dtype=complex)
    sigma = matching_permutation(p_evals_above_x0, y0)
    p = sigma.action(p)

    # also return the place that the first y-fibre element ends up analytically
    # continuing to
    px_idx = sigma[0]  # index of target x-series in unsorted list
    place_idx = -1  # index of place corresponding to this x-series
    while px_idx >= 0:
        place_idx += 1
        px_idx -= abs(ramification_indices[place_idx])
    target_place = DiscriminantPlace(riemann_surface, P[place_idx])
    return p, target_place
def ordered_puiseux_series(riemann_surface, complex_path, y0, target_point):
    r"""Returns an ordered list of Puiseux series such that each Puiseux series
    matches with the corresponding y-fibre element above the starting point of
    `complex_path`.

    In order to analytically continue from the regular places at the beginning
    of the path :math:`x=a` to the discriminant places at the end of the path
    :math:`x=b`we need to compute all of the `PuiseuxXSeries` at :math:`x=b`.
    There are two steps to this calculation:

    * compute enough terms of the Puiseux series centered at :math:`x=b` in
      order to accurately capture the y-roots at :math:`x=a`.

    * permute the series accordingly to match up with the y-roots at
      :math:`x=a`.

    Parameters
    ----------
    riemann_surface : RiemannSurface
        The riemann surface on which all of this lives.
    complex_path : ComplexPath
        The path or path segment starting at a regular point and ending at a
        discriminant point.
    y0 : list of complex
        The starting fibre lying above the starting point of `complex_path`.
        The first component of the list indicates the starting sheet.
    target_point : complex
        The point to analytically continue to. Usually a discriminant point.

    Methods
    -------
    .. autosummary::

      analytically_continue

    Returns
    -------
    list, Place : a list of Puiseux series and a Place
        A list of ordered Puiseux series corresponding to each branch above
        :math:`x=a` as well as the place that the first y-fibre element
        analytically continues to.
    """
    # obtain all puiseux series above the target place
    f = riemann_surface.f
    x0 = CC(complex_path(0)) # XXX - need to coerce input to CC
    y0 = numpy.array(y0, dtype=complex)
    P = puiseux(f, target_point)

    # extend the Puiseux series to enough terms to accurately captue the
    # y-fibre above x=a (the starting point of the complex path)
    for Pi in P:
        Pi.extend_to_x(x0)

    # compute the corresponding x-series representations of the Puiseux series
    alpha = 0 if target_point == infinity else target_point
    px = [Pi.xseries() for Pi in P]
    p = [pxi for sublist in px for pxi in sublist]
    ramification_indices = [Pi.ramification_index for Pi in P]

    # reorder them according to the ordering of the y-fibre above x=x0
    p_evals_above_x0 = [pj(x0-alpha) for pj in p]
    p_evals_above_x0 = numpy.array(p_evals_above_x0, dtype=complex)
    sigma = matching_permutation(p_evals_above_x0, y0)
    p = sigma.action(p)

    # also return the place that the first y-fibre element ends up analytically
    # continuing to
    px_idx = sigma[0] # index of target x-series in unsorted list
    place_idx = -1    # index of place corresponding to this x-series
    while px_idx >= 0:
        place_idx += 1
        px_idx -= abs(ramification_indices[place_idx])
    target_place = DiscriminantPlace(riemann_surface,P[place_idx])
    return p, target_place