Exemplo n.º 1
0
def compute_series_truncations(f,x,y,alpha,T):
    """
    Computes the Puiseux series expansions at the `x`-point `x=a` with
    the necessary number of terms in order to compute the integral
    basis of the algebraic functions field corresponding to `f`. The
    Puiseux series is returned in parametric form for computational
    efficiency. (Sympy doesn't do as well with fractional exponents.)
    """
    # compute the first terms of the Puiseux series expansions
    p = puiseux(f,x,y,alpha,nterms=0,parametric=False)

    # compute the expansion bounds
    N = compute_expansion_bounds(p,x,alpha)
    Nmax = max(N)

    # compute Puiseux series and truncate using the expansion bounds.
    r = puiseux(f,x,y,alpha,degree_bound=Nmax)
    n = len(r)

    for i in xrange(n):
        terms = r[i].collect(x-alpha,evaluate=False)
        ri_trunc = sum( coeff*term for term,coeff in terms.iteritems()
                        if term.as_coeff_exponent(x)[1] <= N[i] )
        r[i] = ri_trunc

    return r
Exemplo n.º 2
0
def _multiplicity(g,u,v,u0,v0):
    """
    Returns the multiplicity of the place (alpha : beta : 1) from the
    Puiseux series P at the place.

    For each (parametric) Puiseux series
        
        Pj = { x = x(t)
             { y = y(t) 
    
    at (alpha : beta : 1) the contribution from Pj to the multiplicity
    is min( deg x(t), deg y(t) ).
    """
    # compute Puiseux expansions at u=u0 and filter out
    # only those with v(t=0) == v0
    P = puiseux(g,u,v,u0,nterms=0,parametric=_t)

    m = 0
    for X,Y in P:
        X = X - u0                      # Shift so no constant
        Y = Y - v0                      # term remains.
        ri = abs( X.leadterm(_t)[1] )   # Get order of lead term
        si = abs( Y.leadterm(_t)[1] )
        m += min(ri,si)

    return m
Exemplo n.º 3
0
    def test_extend_to_t(self):
        p = puiseux(self.f2, 0)
        pi = p[0]
        ti = 0.1

        # 1e-8
        pi.extend_to_t(ti, 1e-8)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt, yt))
        self.assertLess(error, 1e-8)

        # 1e-14
        pi.extend_to_t(ti, curve_tol=1e-14)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt, yt))
        self.assertLess(error, 1e-14)

        # 1e-20 (multi-precise)
        pi.extend_to_t(ti, curve_tol=1e-20)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt, yt))
        self.assertLess(error, 1e-20)
Exemplo n.º 4
0
 def get_PQ(self, f, a=0):
     p = puiseux(f, a)
     if p:
         series = [(P._xpart, P._ypart) for P in p]
     else:
         series = []
     return series
Exemplo n.º 5
0
    def centered_at_place(self, P, order=None):
        r"""Rewrite the differential in terms of the local coordinates at `P`.

        If `P` is a regular place, then returns `self` as a sympy
        expression. Otherwise, if `P` is a discriminant place
        :math:`P(t) = \{x(t), y(t)\}` then returns

        .. math::

            \omega |_P = q(x(t),y(t)) x'(t) / \partial_y f(x(t),y(t)).

        Parameters
        ----------
        P : Place
        order : int, optional
            Passed to :meth:`PuiseuxTSeries.eval_y`.

        Returns
        -------
        sympy.Expr
        """
        # by default, non-discriminant places do not store Pusieux series
        # expansions. this might change in the future
        if P.is_discriminant():
            p = P.puiseux_series
        else:
            p = puiseux(self.RS.f)[0]

        # substitute Puiseux series expansion into the differrential and expand
        # as a Laurent series
        xt = p.xpart
        yt = p.ypart.add_bigoh(p.order)
        dxdt = xt.derivative()
        omega = self.numer(xt,yt) * dxdt / self.denom(xt,yt)
        return omega
Exemplo n.º 6
0
 def get_PQ(self,f, a=0):
     p = puiseux(f,a)
     if p:
         series = [(P._xpart,P._ypart) for P in p]
     else:
         series = []
     return series
Exemplo n.º 7
0
    def centered_at_place(self, P, order=None):
        r"""Rewrite the differential in terms of the local coordinates at `P`.

        If `P` is a regular place, then returns `self` as a sympy
        expression. Otherwise, if `P` is a discriminant place
        :math:`P(t) = \{x(t), y(t)\}` then returns

        .. math::

            \omega |_P = q(x(t),y(t)) x'(t) / \partial_y f(x(t),y(t)).

        Parameters
        ----------
        P : Place
        order : int, optional
            Passed to :meth:`PuiseuxTSeries.eval_y`.

        Returns
        -------
        sympy.Expr
        """
        # by default, non-discriminant places do not store Pusieux series
        # expansions. this might change in the future
        if P.is_discriminant():
            p = P.puiseux_series
        else:
            p = puiseux(self.RS.f)[0]

        # substitute Puiseux series expansion into the differrential and expand
        # as a Laurent series
        xt = p.xpart
        yt = p.ypart.add_bigoh(p.order)
        dxdt = xt.derivative()
        omega = self.numer(xt, yt) * dxdt / self.denom(xt, yt)
        return omega
Exemplo n.º 8
0
    def test_extend_to_t(self):
        p = puiseux(self.f2,0)
        pi = p[0]
        ti = 0.1

        # 1e-8
        pi.extend_to_t(ti, 1e-8)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt,yt))
        self.assertLess(error, 1e-8)

        # 1e-14
        pi.extend_to_t(ti, curve_tol=1e-14)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt,yt))
        self.assertLess(error, 1e-14)

        # 1e-20 (multi-precise)
        pi.extend_to_t(ti, curve_tol=1e-20)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt,yt))
        self.assertLess(error, 1e-20)
Exemplo n.º 9
0
 def test_puiseux(self):
     self.assertEqual(puiseux(f1,x,y,0,4),
                      [(T**2, T**7/2 + T**6 + T**5 + T**4)])
     self.assertEqual(puiseux(f2,x,y,0,4),
                      [(T, 
                        -3*T**19/256 + 3*T**14/128 - T**9/16 + T**4/2), 
                       (-T**2/2, 
                         -T**18/16384 + 3*T**13/4096 - T**8/64 - T**3/2)])
     self.assertEqual(puiseux(f3,x,y,0,2),
                      [(-57**(1/2)*T/19, 136*57**(1/2)*T**2/1083 + T), 
                       (57**(1/2)*T/19, -136*57**(1/2)*T**2/1083 + T), 
                       (T, -11*3**(1/2)*T/12 - 3**(1/2)/2), 
                       (T, 11*3**(1/2)*T/12 + 3**(1/2)/2)])
     self.assertEqual(puiseux(f4,x,y,0,4),
                      [(-T, T**4/16 - T**3/8 + T**2/2 + T),
                       (T, -T**4/16 - T**3/8 - T**2/2 + T)])
Exemplo n.º 10
0
def singularities(f):
    r"""Returns the singularities of the curve `f` in projective space.

    Returns all of the projective singular points :math:`(x_k,y_k,z_k)` on the
    curve :math:`f(x,y) = 0`. For each singular point a three-tuple :math:`(m,
    \delta, r)` is given representing the multiplicity, delta invariant, and
    branching number of the singularity.

    This information is used to resolve singularities for the purposes of
    computing a Riemann surface. The singularities are resolved using Puiseux
    series.

    Parameters
    ----------
    f : polynomial
        A plane algebraic curve.

    Returns
    -------
    list
        A list of the singularities, both finite and infinite, along with their
        multiplicity, delta invariant, and branching number information.

    """
    S = singular_points_finite(f)
    S_oo = singular_points_infinite(f)
    S.extend(S_oo)

    info = []
    for singular_pt in S:
        # perform a projective transformation of the curve so it's almost
        # centered at the singular point.
        g, u0, v0 = _transform(f, singular_pt)
        P = puiseux(g, u0, v0)

        # filter out any places with infinite v-part: they are being handled by
        # other centerings / transformations
        def has_finite_v(Pi):
            # make sure the order of the y-part is positive
            while Pi.order <= 0:
                Pi.add_term()
            n, alpha = zip(*Pi.terms)

            # if there are still no terms then they are positive exponent
            if n == []:
                return True
            elif min(n) >= 0:
                return True
            return False

        P = filter(has_finite_v, P)

        # P now consists of the places that project to (u0,v0) on the curve
        m = _multiplicity(P)
        delta = _delta_invariant(P)
        r = _branching_number(P)
        info.append((m, delta, r))

    return zip(S, info)
Exemplo n.º 11
0
def singularities(f):
    r"""Returns the singularities of the curve `f` in projective space.

    Returns all of the projective singular points :math:`(x_k,y_k,z_k)` on the
    curve :math:`f(x,y) = 0`. For each singular point a three-tuple :math:`(m,
    \delta, r)` is given representing the multiplicity, delta invariant, and
    branching number of the singularity.

    This information is used to resolve singularities for the purposes of
    computing a Riemann surface. The singularities are resolved using Puiseux
    series.

    Parameters
    ----------
    f : polynomial
        A plane algebraic curve.

    Returns
    -------
    list
        A list of the singularities, both finite and infinite, along with their
        multiplicity, delta invariant, and branching number information.

    """
    S = singular_points_finite(f)
    S_oo = singular_points_infinite(f)
    S.extend(S_oo)

    info = []
    for singular_pt in S:
        # perform a projective transformation of the curve so it's almost
        # centered at the singular point.
        g, u0, v0 = _transform(f, singular_pt)
        P = puiseux(g, u0, v0)

        # filter out any places with infinite v-part: they are being handled by
        # other centerings / transformations
        def has_finite_v(Pi):
            # make sure the order of the y-part is positive
            while Pi.order <= 0:
                Pi.add_term()
            n, alpha = zip(*Pi.terms)

            # if there are still no terms then they are positive exponent
            if n == []:
                return True
            elif min(n) >= 0:
                return True
            return False

        P = filter(has_finite_v, P)

        # P now consists of the places that project to (u0,v0) on the curve
        m = _multiplicity(P)
        delta = _delta_invariant(P)
        r = _branching_number(P)
        info.append((m, delta, r))

    return zip(S, info)
Exemplo n.º 12
0
    def test_puiseux(self):
        self.assertEqual(puiseux(f1,x,y,0,4,parametric=T),
            [(T**2, T**7/2 + T**6 + T**5 + T**4)])

        self.assertEqual(puiseux(f2,x,y,0,4,parametric=T),
            [(T,-3*T**19/256 + 3*T**14/128 - T**9/16 + T**4/2), 
             (-T**2/2,-T**18/16384 + 3*T**13/4096 - T**8/64 - T**3/2)])

        half = Rational(1,2)
        self.assertEqual(puiseux(f3,x,y,0,0,parametric=T),
            [(-57**half*T/19, 136*57**half*T**2/1083 + T), 
             (57**half*T/19, -136*57**half*T**2/1083 + T), 
             (T, -11*3**half*T/12 - 3**half/2), 
             (T, 11*3**half*T/12 + 3**half/2)])

        self.assertEqual(puiseux(f4,x,y,0,4,parametric=T),
                         [(-T, T**4/16 - T**3/8 + T**2/2 + T),
                          (T, -T**4/16 - T**3/8 - T**2/2 + T)])
    def test_example_puiseux(self):
        p = puiseux(self.f1, 0)[0]
        px = p.xseries()
        R = px[0].parent()
        x = R.gen()
        half = QQ(1)/2
        self.assertEqual(px[0].truncate(1), -x**half)
        self.assertEqual(px[1].truncate(1), x**half)

        p = puiseux(self.f2, 0)[0]
        px = p.xseries()
        R = px[0].parent()
        x = R.gen()
        third = QQ(1)/3
        S = QQ['t']; t = S.gen()
        alpha,beta,gamma = (t**3 - 1).roots(ring=QQbar, multiplicities=False)
        self.assertEqual(px[0].truncate(1), alpha*x**third)
        self.assertEqual(px[1].truncate(1), gamma*x**third)
        self.assertEqual(px[2].truncate(1), beta*x**third)
Exemplo n.º 14
0
    def test_example_puiseux(self):
        p = puiseux(self.f1, 0)[0]
        px = p.xseries()
        R = px[0].parent()
        x = R.gen()
        half = QQ(1) / 2
        self.assertEqual(px[0].truncate(1), -x**half)
        self.assertEqual(px[1].truncate(1), x**half)

        p = puiseux(self.f2, 0)[0]
        px = p.xseries()
        R = px[0].parent()
        x = R.gen()
        third = QQ(1) / 3
        S = QQ['t']
        t = S.gen()
        alpha, beta, gamma = (t**3 - 1).roots(ring=QQbar, multiplicities=False)
        self.assertEqual(px[0].truncate(1), alpha * x**third)
        self.assertEqual(px[1].truncate(1), gamma * x**third)
        self.assertEqual(px[2].truncate(1), beta * x**third)
Exemplo n.º 15
0
def _delta_invariant(g,u,v,u0,v0):
    """
    Returns the delta invariant corresponding to the singular point
    `singular_pt` = [alpha, beta, gamma] on the plane algebraic curve
    f(x,y) = 0.
    """
    # compute Puiseux expansions at u=u0 and filter out only those
    # with v(t=0) == v0. We only chose one y=y(x) Puiseux series for
    # each place as a representative to prevent over-counting by using
    # the "grouped=True" flag in Puiseux
    P = puiseux(g,u,v,u0,nterms=0,parametric=_t,grouped=True)
    P_x = puiseux(g,u,v,u0,nterms=0,parametric=False,grouped=True)
    P_x_v0 = []
    for i in range(len(P)):
        X,Y = P[i]
        p = P_x[i][0]
        if Y.subs(_t,0).simplify() == v0:
            # store the index as well so we know which parametric form
            # corresponds to this puiseux series.
            P_x_v0.append((p,i))

    # now obtain ungrouped series
    P_x = puiseux(g,u,v,u0,nterms=0,parametric=False)
    
    # for each place compute its contribution to the delta invariant
    delta = sympy.Rational(0,1)
    for i in range(len(P_x_v0)):
        yhat, place_index = P_x_v0[i]
        j = P_x.index(yhat)
        IntPj = Int(j,P_x,u,u0)

        # obtain the ramification index by retreiving the
        # corresponding parametric form. By definition, this
        # parametric series satisfies Y(t=0) = v0
        X,Y = P[place_index]
        rj = (X-u0).as_coeff_exponent(_t)[1]
        delta += sympy.Rational(rj * IntPj - rj + 1, 2)

    return sympy.numer(delta)
Exemplo n.º 16
0
def _branching_number(g,u,v,u0,v0):
    """
    Returns the branching number of the place [alpha : beta : 1]
    from the Puiseux series P at the place.
        
    The braching number is simply the number of distinct branches
    (i.e. non-interacting branches) at the place. In parametric form,
    this is simply the number of Puiseux series at the place.
    """
    # compute Puiseux expansions at u=u0 and filter out
    # only those with v(t=0) == v0
    P = puiseux(g,u,v,u0,nterms=1,parametric=_t)
    P_v0 = [(X,Y) for X,Y in P if Y.subs(_t,0) == v0]

    return sympy.S(len(P_v0))
Exemplo n.º 17
0
    def test_extend_to_t_oo(self):
        p = puiseux(self.f2,'oo')
        pi = p[0]
        ti = 100

        # 1e-8
        pi.extend_to_t(ti, 1e-8)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt,yt))
        self.assertLess(error, 1e-8)

        # 1e-12
        pi.extend_to_t(ti, curve_tol=1e-12)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt,yt))
        self.assertLess(error, 1e-12)
Exemplo n.º 18
0
    def test_extend_to_t_oo(self):
        p = puiseux(self.f2, 'oo')
        pi = p[0]
        ti = 100

        # 1e-8
        pi.extend_to_t(ti, 1e-8)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt, yt))
        self.assertLess(error, 1e-8)

        # 1e-12
        pi.extend_to_t(ti, curve_tol=1e-12)
        xt = pi.eval_x(ti)
        yt = pi.eval_y(ti)
        error = abs(self.f2(xt, yt))
        self.assertLess(error, 1e-12)
Exemplo n.º 19
0
def compute_series_truncations(f, alpha):
    r"""Computes Puiseux series at :math:`x=\alpha` with necessary terms.

    The Puiseux series expansions of :math:`f = f(x,y)` centered at
    :math:`\alpha` are computed up to the number of terms needed for the
    integral basis algorithm to be successful. The expansion degree bounds are
    determined by :func:`compute_expansion_bounds`.

    Parameters
    ----------
    f : polynomial
    alpha : complex

    Returns
    -------
    list : PuiseuxXSeries
        A list of Puiseux series expansions centered at :math:`x = \alpha` with
        enough terms to compute integral bases as SymPy expressions.

    """
    # compute the parametric Puiseix series with the minimal number of terms
    # needed to distinguish them.
    pt = puiseux(f, alpha)
    px = [p for P in pt for p in P.xseries()]

    # compute the orders necessary for the integral basis algorithm. the orders
    # are on the Puiseux x-series (non-parametric) so scale by the ramification
    # index of each series
    N = compute_expansion_bounds(px)
    for i in range(len(N)):
        e = px[i].ramification_index
        N[i] = ceil(N[i] * e)

    order = max(N) + 1
    for pti in pt:
        pti.extend(order=order)

    # recompute the corresponding x-series with the extened terms
    px = [p for P in pt for p in P.xseries()]
    return px
Exemplo n.º 20
0
def compute_series_truncations(f, alpha):
    r"""Computes Puiseux series at :math:`x=\alpha` with necessary terms.

    The Puiseux series expansions of :math:`f = f(x,y)` centered at
    :math:`\alpha` are computed up to the number of terms needed for the
    integral basis algorithm to be successful. The expansion degree bounds are
    determined by :func:`compute_expansion_bounds`.

    Parameters
    ----------
    f : polynomial
    alpha : complex

    Returns
    -------
    list : PuiseuxXSeries
        A list of Puiseux series expansions centered at :math:`x = \alpha` with
        enough terms to compute integral bases as SymPy expressions.

    """
    # compute the parametric Puiseix series with the minimal number of terms
    # needed to distinguish them.
    pt = puiseux(f,alpha)
    px = [p for P in pt for p in P.xseries()]

    # compute the orders necessary for the integral basis algorithm. the orders
    # are on the Puiseux x-series (non-parametric) so scale by the ramification
    # index of each series
    N = compute_expansion_bounds(px)
    for i in range(len(N)):
        e = px[i].ramification_index
        N[i] = ceil(N[i]*e)

    order = max(N) + 1
    for pti in pt:
        pti.extend(order=order)

    # recompute the corresponding x-series with the extened terms
    px = [p for P in pt for p in P.xseries()]
    return px
Exemplo n.º 21
0
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
Exemplo n.º 22
0
    def places(self, alpha, beta=None):
        r"""Returns a place or places on the Riemann surface with the given x-projection
        and, optionally, given y-projection.

        Parameters
        ----------
        alpha : complex
            The x-projection of the place.
        beta : complex
            If provided, will only return places with the given y-projection.
            There may be multiple places on the surface with the same x- and
            y-projections.

        Returns
        -------
        places : Place or list of Places
            Returns all places on the Riemann surface with x-projection `alpha`
            or x,y-projection `(alpha, beta)`, if `beta` is provided.

        """
        # alpha = infinity case
        infinities = [infinity, 'oo', numpy.Inf]
        if alpha in infinities:
            alpha = infinity
            p = puiseux(self.f, alpha)
            places = [DiscriminantPlace(self, pi) for pi in p]
            return places

        # if alpha is epsilon close to a discriminant point then set it exactly
        # equal to that discriminant point. there is usually no reason to
        # compute a puiseux series so close to a discriminant point
        try:
            alpha = QQbar(alpha)
            exact = True
        except TypeError:
            alpha = numpy.complex(alpha)
            exact = False
        b = self.path_factory.closest_discriminant_point(alpha,exact=exact)

        # if alpha is equal to or close to a discriminant point then return a
        # discriminant place
        if abs(alpha - b) < 1e-12:
            p = puiseux(self.f, b, beta)
            places = [DiscriminantPlace(self, pi) for pi in p]
            return places

        # otherwise, return a regular place if far enough away
        if not beta is None:
            curve_eval = self.f(alpha, beta)
            if abs(curve_eval) > 1e-8:
                raise ValueError('The place (%s, %s) does not lie on the curve '
                                 '/ surface.' % (alpha, beta))
            place = RegularPlace(self, alpha, beta)
            return place

        # if a beta (y-coordinate) is not specified then return all places
        # lying above x=alpha
        R = self.f.parent()
        x,y = R.gens()
        falpha = self.f(alpha,y).univariate_polynomial()
        yroots = falpha.roots(ring=falpha.base_ring(), multiplicities=False)
        places = [RegularPlace(self, alpha, beta) for beta in yroots]
        return places
Exemplo n.º 23
0
    def places(self, alpha, beta=None):
        r"""Returns a place or places on the Riemann surface with the given x-projection
        and, optionally, given y-projection.

        Parameters
        ----------
        alpha : complex
            The x-projection of the place.
        beta : complex
            If provided, will only return places with the given y-projection.
            There may be multiple places on the surface with the same x- and
            y-projections.

        Returns
        -------
        places : Place or list of Places
            Returns all places on the Riemann surface with x-projection `alpha`
            or x,y-projection `(alpha, beta)`, if `beta` is provided.

        """
        # alpha = infinity case
        infinities = [infinity, 'oo', numpy.Inf]
        if alpha in infinities:
            alpha = infinity
            p = puiseux(self.f, alpha)
            places = [DiscriminantPlace(self, pi) for pi in p]
            return places

        # if alpha is epsilon close to a discriminant point then set it exactly
        # equal to that discriminant point. there is usually no reason to
        # compute a puiseux series so close to a discriminant point
        try:
            alpha = QQbar(alpha)
            exact = True
        except TypeError:
            alpha = numpy.complex(alpha)
            exact = False
        b = self.path_factory.closest_discriminant_point(alpha, exact=exact)

        # if alpha is equal to or close to a discriminant point then return a
        # discriminant place
        if abs(alpha - b) < 1e-12:
            p = puiseux(self.f, b, beta)
            places = [DiscriminantPlace(self, pi) for pi in p]
            return places

        # otherwise, return a regular place if far enough away
        if not beta is None:
            curve_eval = self.f(alpha, beta)
            if abs(curve_eval) > 1e-8:
                raise ValueError(
                    'The place (%s, %s) does not lie on the curve '
                    '/ surface.' % (alpha, beta))
            place = RegularPlace(self, alpha, beta)
            return place

        # if a beta (y-coordinate) is not specified then return all places
        # lying above x=alpha
        R = self.f.parent()
        x, y = R.gens()
        falpha = self.f(alpha, y).univariate_polynomial()
        yroots = falpha.roots(ring=falpha.base_ring(), multiplicities=False)
        places = [RegularPlace(self, alpha, beta) for beta in yroots]
        return places
Exemplo n.º 24
0
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