Exemple #1
0
class Point(SageObject):
    r"""
    A point on the complex plane with an associated differential operator.

    A point can be exact (a number field element) or inexact (a real or complex
    interval or ball). It can be classified as ordinary, regular singular, etc.
    The main reason for making the operator part of the definition of Points is
    that this gives a convenient place to cache information that depend on both,
    with an appropriate lifetime. Note however that the point is considered to
    lie on the complex plane, not on the Riemann surface of the operator.
    """
    def __init__(self, point, dop=None):
        """
        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: [Point(z, Dx)
            ....:  for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi,
            ....:  RDF(1), CDF(I), 0.5r, 0.5jr, 10r, QQbar(1), AA(1/3)]]
            [1, 1/2, I + 1, I, [0.333333333333333...], [0.333333333333333...],
            3.141592653589794?, 1.000000000000000, 1.000000000000000*I,
            0.5000000000000000, 0.5000000000000000*I, 10, 1, 1/3]
            sage: Point(sqrt(2), Dx).iv()
            [1.414...]
        """
        SageObject.__init__(self)

        from sage.rings.complex_double import ComplexDoubleField_class
        from sage.rings.complex_field import ComplexField_class
        from sage.rings.complex_interval_field import ComplexIntervalField_class
        from sage.rings.real_double import RealDoubleField_class
        from sage.rings.real_mpfi import RealIntervalField_class
        from sage.rings.real_mpfr import RealField_class

        point = sage.structure.coerce.py_scalar_to_element(point)
        try:
            parent = point.parent()
        except AttributeError:
            raise TypeError("unexpected value for point: " + repr(point))
        if isinstance(point, Point):
            self.value = point.value
        elif isinstance(
                parent,
            (number_field_base.NumberField, RealBallField, ComplexBallField)):
            self.value = point
        elif QQ.has_coerce_map_from(parent):
            self.value = QQ.coerce(point)
        # must come before QQbar, due to a bogus coerce map (#14485)
        elif parent is sage.symbolic.ring.SR:
            try:
                return self.__init__(point.pyobject(), dop)
            except TypeError:
                pass
            try:
                return self.__init__(QQbar(point), dop)
            except (TypeError, ValueError, NotImplementedError):
                pass
            try:
                self.value = RLF(point)
            except (TypeError, ValueError):
                self.value = CLF(point)
        elif QQbar.has_coerce_map_from(parent):
            alg = QQbar.coerce(point)
            NF, val, hom = alg.as_number_field_element()
            if NF is QQ:
                self.value = QQ.coerce(val)  # parent may be ZZ
            else:
                embNF = number_field.NumberField(NF.polynomial(),
                                                 NF.variable_name(),
                                                 embedding=hom(NF.gen()))
                self.value = val.polynomial()(embNF.gen())
        elif isinstance(
                parent,
            (RealField_class, RealDoubleField_class, RealIntervalField_class)):
            self.value = RealBallField(point.prec())(point)
        elif isinstance(parent, (ComplexField_class, ComplexDoubleField_class,
                                 ComplexIntervalField_class)):
            self.value = ComplexBallField(point.prec())(point)
        else:
            try:
                self.value = RLF.coerce(point)
            except TypeError:
                self.value = CLF.coerce(point)
        parent = self.value.parent()
        assert (isinstance(
            parent,
            (number_field_base.NumberField, RealBallField, ComplexBallField))
                or parent is RLF or parent is CLF)

        self.dop = dop or point.dop

        self.keep_value = False

    def _repr_(self):
        """
        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: Point(10**20, Dx)
            ~1.0000e20
        """
        try:
            len = (self.value.numerator().real().numerator().nbits() +
                   self.value.numerator().imag().numerator().nbits() +
                   self.value.denominator().nbits())
            if len > 50:
                return '~' + repr(self.value.n(digits=5))
        except AttributeError:
            pass
        return repr(self.value)

    # Numeric representations

    @cached_method
    def iv(self):
        """
        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.path import Point
        sage: Dops, x, Dx = DifferentialOperators()
        sage: [Point(z, Dx).iv()
        ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi]]
        [1.000000000000000,
        0.5000000000000000,
        1.000000000000000 + 1.000000000000000*I,
        1.000000000000000*I,
        [0.333333333333333 +/- 3.99e-16],
        [0.333333333333333 +/- 3.99e-16],
        [3.141592653589793 +/- 7.83e-16]]
        """
        return IC(self.value)

    def exact(self):
        r"""
        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.path import Point
        sage: Dops, x, Dx = DifferentialOperators()
        sage: QQi.<i> = QuadraticField(-1)
        sage: [Point(z, Dx).exact() for z in [1, 1/2, 1+i, QQbar(I)]]
        [1, 1/2, i + 1, I]
        sage: [Point(z, Dx).exact() for z in [RBF(3/4), RBF(1) + I]]
        [3/4, i + 1]
        sage: Point(RIF(1/3), Dx).exact()
        Traceback (most recent call last):
        ...
        ValueError
        """
        if self.is_exact():
            return self
        elif isinstance(self.value, RealBall) and self.value.is_exact():
            return Point(QQ(self.value), self.dop)
        elif isinstance(self.value, ComplexBall) and self.value.is_exact():
            value = QQi((QQ(self.value.real()), QQ(self.value.imag())))
            return Point(value, self.dop)
        raise ValueError

    def approx_abs_real(self, prec):
        r"""
        Compute an approximation with absolute error about 2^(-prec).
        """
        if isinstance(self.value.parent(), RealBallField):
            return self.value
        elif self.value.is_zero():
            return RealBallField(max(2, prec)).zero()
        elif self.is_real():
            expo = ZZ(IR(self.value).abs().log(2).upper().ceil())
            rel_prec = max(2, prec + expo + 10)
            val = RealBallField(rel_prec)(self.value)
            return val
        else:
            raise ValueError("point may not be real")

    def is_real(self):
        return is_real_parent(self.value.parent())

    def is_exact(self):
        # XXX: also include exact balls?
        return isinstance(
            self.value,
            (rings.Integer, rings.Rational, rings.NumberFieldElement))

    ### Methods that depend on dop

    @cached_method
    def is_ordinary(self):
        lc = self.dop.leading_coefficient()
        if self.is_exact():
            return bool(lc(self.value))
        elif not lc(self.iv()).contains_zero():
            return True
        else:
            raise ValueError("can't tell if inexact point is singular")

    def is_singular(self):
        return not is_ordinary(self)

    @cached_method
    def is_regular(self):
        try:
            if self.is_ordinary():
                return True
        except ValueError:
            # we could handle balls containing no irregular singular point...
            raise NotImplementedError("can't tell if inexact point is regular")
        assert self.is_exact()
        # Fuchs criterion
        Pols = self.dop.base_ring().change_ring(self.value.parent())

        def val(pol):
            return Pols(pol).valuation(Pols([self.value, -1]))

        ref = val(self.dop.leading_coefficient()) - self.dop.order()
        return all(val(coef) - k >= ref for k, coef in enumerate(self.dop))

    def is_regular_singular(self):
        return not self.is_ordinary() and self.is_regular()

    def is_irregular(self):
        return not is_regular(self)

    def singularity_type(self, short=False):
        r"""
        EXAMPLES::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()

            sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx
            sage: Point(1, dop).singularity_type()
            'ordinary point'
            sage: Point(i, dop).singularity_type()
            'regular singular point'
            sage: Point(0, x^2*Dx + 1).singularity_type()
            'irregular singular point'
            sage: Point(CIF(1/3), x^2*Dx + 1).singularity_type()
            'ordinary point'
            sage: Point(CIF(1/3)-1/3, x^2*Dx + 1).singularity_type()
            'point of unknown singularity type'
        """
        try:
            if self.is_ordinary():
                return "" if short else "ordinary point"
            elif self.is_regular():
                return "regular singular point"
            else:
                return "irregular singular point"
        except (ValueError, NotImplementedError):
            return "point of unknown singularity type"

    def descr(self):
        t = self.singularity_type(short=True)
        if t == "":
            return repr(self)
        else:
            return t + " " + repr(self)

    def dist_to_sing(self):
        """
        Distance of self to the singularities of self.dop *other than self*.

        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx
            sage: Point(1, dop).dist_to_sing()
            [1.41421356237309...]
            sage: Point(i, dop).dist_to_sing()
            2.00...
            sage: Point(1+i, dop).dist_to_sing()
            1.00...

        """
        # TODO - solve over CBF directly; perhaps with arb's own poly solver
        sing = dop_singularities(self.dop, CIF)
        sing = [IC(s) for s in sing]
        close, distant = split(lambda s: s.overlaps(self.iv()), sing)
        if (len(close) >= 2 or len(close) == 1
                and not self.dop.leading_coefficient()(self.value).is_zero()):
            raise NotImplementedError  # refine?
        dist = [(self.iv() - s).abs() for s in distant]
        min_dist = IR(rings.infinity).min(*dist)
        if min_dist.contains_zero():
            raise NotImplementedError  # refine???
        return IR(min_dist.lower())

    def local_diffop(self):  # ?
        r"""
        TESTS::

            sage: from ore_algebra import DifferentialOperators
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: Point(1, x*Dx - 1).local_diffop()
            (x + 1)*Dx - 1
            sage: Point(RBF(1/2), x*Dx - 1).local_diffop()
            (x + 1/2)*Dx - 1
        """
        Pols_dop = self.dop.base_ring()
        # NOTE: pushout(QQ[x], K) doesn't handle embeddings well, and creates
        # an L equal but not identical to K. But then other constructors like
        # PolynomialRing(L, x) sometimes return objects over K found in cache,
        # leading to endless headaches with slow coercions. But the version here
        # may be closer to what I really want in any case.
        # XXX: This seems to work in the usual trivial case where we are looking
        # for a scalar domain containing QQ and QQ[i], but probably won't be
        # enough if we really have two different number fields with embeddings
        ex = self.exact()
        Scalars = pushout.pushout(Pols_dop.base_ring(), ex.value.parent())
        Pols = Pols_dop.change_ring(Scalars)
        A, B = self.dop.base_ring().base_ring(), ex.value.parent()
        C = Pols.base_ring()
        assert C is A or C != A
        assert C is B or C != B
        dop_P = self.dop.change_ring(Pols)
        return dop_P.annihilator_of_composition(Pols([ex.value, 1]))

    def local_basis_structure(self):
        r"""
        EXAMPLES::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: Point(0, x*Dx^2 + Dx + x).local_basis_structure()
            [FundamentalSolution(leftmost=0, shift=0, log_power=1, value=None),
             FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None)]
            sage: Point(0, Dx^3 + x*Dx + x).local_basis_structure()
            [FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None),
             FundamentalSolution(leftmost=0, shift=1, log_power=0, value=None),
             FundamentalSolution(leftmost=0, shift=2, log_power=0, value=None)]
        """
        # TODO: provide a way to compute the first terms of the series. First
        # need a good way to share code with fundamental_matrix_regular. Or
        # perhaps modify generalized_series_solutions() to agree with our
        # definition of the basis?
        if self.is_ordinary():  # support inexact points in this case
            return [
                FundamentalSolution(QQbar.zero(), ZZ(expo), ZZ.zero(), None)
                for expo in range(self.dop.order())
            ]
        elif not self.is_regular():
            raise NotImplementedError("irregular singular point")
        sols = map_local_basis(self.local_diffop(), lambda ini, bwrec: None,
                               lambda leftmost, shift: {})
        sols.sort(key=sort_key_by_asympt)
        return sols
class Point(SageObject):
    r"""
    A point on the complex plane with an associated differential operator.

    A point can be exact (a number field element) or inexact (a real or complex
    interval or ball). It can be classified as ordinary, regular singular, etc.
    The main reason for making the operator part of the definition of Points is
    that this gives a convenient place to cache information that depend on both,
    with an appropriate lifetime. Note however that the point is considered to
    lie on the complex plane, not on the Riemann surface of the operator.
    """
    def __init__(self, point, dop=None, singular=None, **kwds):
        """
        INPUT:

        - ``singular``: can be set to True to force this point to be considered
          a singular point, even if this cannot be checked (e.g. because we only
          have an enclosure)

        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: [Point(z, Dx)
            ....:  for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi,
            ....:  RDF(1), CDF(I), 0.5r, 0.5jr, 10r, QQbar(1), AA(1/3)]]
            [1, 1/2, I + 1, I, [0.333333333333333...], [0.333333333333333...],
            3.141592653589794?, 1.000000000000000, 1.000000000000000*I,
            0.5000000000000000, 0.5000000000000000*I, 10, 1, 1/3]
            sage: Point(sqrt(2), Dx).iv()
            [1.414...]
            sage: Point(RBF(0), (x-1)*x*Dx, singular=True).dist_to_sing()
            1.000000000000000
        """
        SageObject.__init__(self)

        from sage.rings.complex_double import ComplexDoubleField_class
        from sage.rings.complex_field import ComplexField_class
        from sage.rings.complex_interval_field import ComplexIntervalField_class
        from sage.rings.real_double import RealDoubleField_class
        from sage.rings.real_mpfi import RealIntervalField_class
        from sage.rings.real_mpfr import RealField_class

        point = sage.structure.coerce.py_scalar_to_element(point)
        try:
            parent = point.parent()
        except AttributeError:
            raise TypeError("unexpected value for point: " + repr(point))
        if isinstance(point, Point):
            self.value = point.value
        elif isinstance(
                parent,
            (number_field_base.NumberField, RealBallField, ComplexBallField)):
            self.value = point
        elif QQ.has_coerce_map_from(parent):
            self.value = QQ.coerce(point)
        # must come before QQbar, due to a bogus coerce map (#14485)
        elif parent is sage.symbolic.ring.SR:
            try:
                return self.__init__(point.pyobject(), dop)
            except TypeError:
                pass
            try:
                return self.__init__(QQbar(point), dop)
            except (TypeError, ValueError, NotImplementedError):
                pass
            try:
                self.value = RLF(point)
            except (TypeError, ValueError):
                self.value = CLF(point)
        elif QQbar.has_coerce_map_from(parent):
            alg = QQbar.coerce(point)
            NF, val, hom = alg.as_number_field_element()
            if NF is QQ:
                self.value = QQ.coerce(val)  # parent may be ZZ
            else:
                embNF = number_field.NumberField(NF.polynomial(),
                                                 NF.variable_name(),
                                                 embedding=hom(NF.gen()))
                self.value = val.polynomial()(embNF.gen())
        elif isinstance(
                parent,
            (RealField_class, RealDoubleField_class, RealIntervalField_class)):
            self.value = RealBallField(point.prec())(point)
        elif isinstance(parent, (ComplexField_class, ComplexDoubleField_class,
                                 ComplexIntervalField_class)):
            self.value = ComplexBallField(point.prec())(point)
        else:
            try:
                self.value = RLF.coerce(point)
            except TypeError:
                self.value = CLF.coerce(point)
        parent = self.value.parent()
        assert (isinstance(
            parent,
            (number_field_base.NumberField, RealBallField, ComplexBallField))
                or parent is RLF or parent is CLF)

        if dop is None:  # TBI
            if isinstance(point, Point):
                self.dop = point.dop
        else:
            self.dop = DifferentialOperator(dop.numerator())
        self._force_singular = bool(singular)
        self.options = kwds

    def _repr_(self):
        """
        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: Point(10**20, Dx)
            ~1.0000e20
        """
        try:
            len = (self.value.numerator().real().numerator().nbits() +
                   self.value.numerator().imag().numerator().nbits() +
                   self.value.denominator().nbits())
            if len > 50:
                return '~' + repr(self.value.n(digits=5))
        except AttributeError:
            pass
        return repr(self.value)

    # Numeric representations

    @cached_method
    def iv(self):
        """
        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.path import Point
        sage: Dops, x, Dx = DifferentialOperators()
        sage: [Point(z, Dx).iv()
        ....: for z in [1, 1/2, 1+I, QQbar(I), RIF(1/3), CIF(1/3), pi]]
        [1.000000000000000,
        0.5000000000000000,
        1.000000000000000 + 1.000000000000000*I,
        1.000000000000000*I,
        [0.333333333333333 +/- 3.99e-16],
        [0.333333333333333 +/- 3.99e-16],
        [3.141592653589793 +/- 7.83e-16]]
        """
        return IC(self.value)

    def exact(self):
        r"""
        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.path import Point
        sage: Dops, x, Dx = DifferentialOperators()
        sage: QQi.<i> = QuadraticField(-1)
        sage: [Point(z, Dx).exact() for z in [1, 1/2, 1+i, QQbar(I)]]
        [1, 1/2, i + 1, I]
        sage: [Point(z, Dx).exact() for z in [RBF(3/4), RBF(1) + I]]
        [3/4, i + 1]
        sage: Point(RIF(1/3), Dx).exact()
        Traceback (most recent call last):
        ...
        ValueError
        """
        if self.value.parent().is_exact():
            return self
        elif isinstance(self.value, RealBall) and self.value.is_exact():
            return Point(QQ(self.value), self.dop)
        elif isinstance(self.value, ComplexBall) and self.value.is_exact():
            value = QQi((QQ(self.value.real()), QQ(self.value.imag())))
            return Point(value, self.dop)
        raise ValueError

    def approx_abs_real(self, prec):
        r"""
        Compute an approximation with absolute error about 2^(-prec).
        """
        if isinstance(self.value.parent(), RealBallField):
            return self.value
        elif self.value.is_zero():
            return RealBallField(max(2, prec)).zero()
        elif self.is_real():
            expo = ZZ(IR(self.value).abs().log(2).upper().ceil())
            rel_prec = max(2, prec + expo + 10)
            val = RealBallField(rel_prec)(self.value)
            return val
        else:
            raise ValueError("point may not be real")

    def is_real(self):
        return is_real_parent(self.value.parent())

    def is_exact(self):
        return (isinstance(
            self.value,
            (rings.Integer, rings.Rational, rings.NumberFieldElement)) or
                isinstance(self.value,
                           (RealBall, ComplexBall)) and self.value.is_exact())

    def rationalize(self):
        a = self.iv()
        lc = self.dop.leading_coefficient()
        if lc(a).contains_zero():
            raise PathPrecisionError
        else:
            return Point(_rationalize(a), self.dop)

    # Point equality is identity

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return id(self)

    ### Methods that depend on dop

    @cached_method
    def is_ordinary(self):
        if self._force_singular:
            return False
        lc = self.dop.leading_coefficient()
        if not lc(self.iv()).contains_zero():
            return True
        if self.is_exact():
            try:
                val = lc(self.value)
            except TypeError:  # work around coercion weaknesses
                val = lc.change_ring(QQbar)(QQbar.coerce(self.value))
            return not val.is_zero()
        else:
            raise ValueError("can't tell if inexact point is singular")

    def is_singular(self):
        return not self.is_ordinary()

    @cached_method
    def is_regular(self):
        try:
            if self.is_ordinary():
                return True
        except ValueError:
            # we could handle balls containing no irregular singular point...
            raise NotImplementedError("can't tell if inexact point is regular")
        assert self.is_exact()
        # Fuchs criterion
        Pols = self.dop.base_ring().change_ring(self.value.parent())

        def val(pol):
            return Pols(pol).valuation(Pols([self.value, -1]))

        ref = val(self.dop.leading_coefficient()) - self.dop.order()
        return all(val(coef) - k >= ref for k, coef in enumerate(self.dop))

    def is_regular_singular(self):
        return not self.is_ordinary() and self.is_regular()

    def is_irregular(self):
        return not is_regular(self)

    def singularity_type(self, short=False):
        r"""
        EXAMPLES::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()

            sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx
            sage: Point(1, dop).singularity_type()
            'ordinary point'
            sage: Point(i, dop).singularity_type()
            'regular singular point'
            sage: Point(0, x^2*Dx + 1).singularity_type()
            'irregular singular point'
            sage: Point(CIF(1/3), x^2*Dx + 1).singularity_type()
            'ordinary point'
            sage: Point(CIF(1/3)-1/3, x^2*Dx + 1).singularity_type()
            'point of unknown singularity type'
        """
        try:
            if self.is_ordinary():
                return "" if short else "ordinary point"
            elif self.is_regular():
                return "regular singular point"
            else:
                return "irregular singular point"
        except (ValueError, NotImplementedError):
            return "point of unknown singularity type"

    def descr(self):
        t = self.singularity_type(short=True)
        if t == "":
            return repr(self)
        else:
            return t + " " + repr(self)

    def dist_to_sing(self):
        """
        Distance of self to the singularities of self.dop *other than self*.

        TESTS::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: dop = (x^2 + 1)*Dx^2 + 2*x*Dx
            sage: Point(1, dop).dist_to_sing()
            [1.41421356237309...]
            sage: Point(i, dop).dist_to_sing()
            2.00...
            sage: Point(1+i, dop).dist_to_sing()
            1.00...

        """
        sing = self.dop._singularities(IC)
        close, distant = split(lambda s: s.overlaps(self.iv()), sing)
        if (len(close) >= 2 or len(close) == 1 and not self.is_singular()):
            raise NotImplementedError  # refine?
        dist = [(self.iv() - s).abs() for s in distant]
        min_dist = IR(rings.infinity).min(*dist)
        if min_dist.contains_zero():
            raise NotImplementedError  # refine???
        return IR(min_dist.lower())

    def local_basis_structure(self):
        r"""
        EXAMPLES::

            sage: from ore_algebra import *
            sage: from ore_algebra.analytic.path import Point
            sage: Dops, x, Dx = DifferentialOperators()
            sage: Point(0, x*Dx^2 + Dx + x).local_basis_structure()
            [FundamentalSolution(leftmost=0, shift=0, log_power=1, value=None),
             FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None)]
            sage: Point(0, Dx^3 + x*Dx + x).local_basis_structure()
            [FundamentalSolution(leftmost=0, shift=0, log_power=0, value=None),
             FundamentalSolution(leftmost=0, shift=1, log_power=0, value=None),
             FundamentalSolution(leftmost=0, shift=2, log_power=0, value=None)]
        """
        # TODO: provide a way to compute the first terms of the series. First
        # need a good way to share code with fundamental_matrix_regular. Or
        # perhaps modify generalized_series_solutions() to agree with our
        # definition of the basis?
        if self.is_ordinary():  # support inexact points in this case
            return [
                FundamentalSolution(QQbar.zero(), ZZ(expo), ZZ.zero(), None)
                for expo in range(self.dop.order())
            ]
        elif not self.is_regular():
            raise NotImplementedError("irregular singular point")
        sols = LocalBasisMapper().run(self.dop.shift(self))
        sols.sort(key=sort_key_by_asympt)
        return sols