Elliptic-curve morphisms

This class serves as a common parent for various specializations of morphisms between elliptic curves, with the aim of providing a common interface regardless of implementation details.

Current implementations of elliptic-curve morphisms (child classes):

AUTHORS:

class sage.schemes.elliptic_curves.hom.EllipticCurveHom(*args, **kwds)[source]

Bases: Morphism

Base class for elliptic-curve morphisms.

as_morphism()[source]

Return self as a morphism of projective schemes.

EXAMPLES:

sage: k = GF(11)
sage: E = EllipticCurve(k, [1,1])
sage: Q = E(6,5)
sage: phi = E.isogeny(Q)
sage: mor = phi.as_morphism()
sage: mor.domain() == E
True
sage: mor.codomain() == phi.codomain()
True
sage: mor(Q) == phi(Q)
True
>>> from sage.all import *
>>> k = GF(Integer(11))
>>> E = EllipticCurve(k, [Integer(1),Integer(1)])
>>> Q = E(Integer(6),Integer(5))
>>> phi = E.isogeny(Q)
>>> mor = phi.as_morphism()
>>> mor.domain() == E
True
>>> mor.codomain() == phi.codomain()
True
>>> mor(Q) == phi(Q)
True
characteristic_polynomial()[source]

Return the characteristic polynomial of this elliptic-curve morphism, which must be an endomorphism.

See also

EXAMPLES:

sage: E = EllipticCurve(QQ, [42, 42])
sage: m5 = E.scalar_multiplication(5)
sage: m5.characteristic_polynomial()
x^2 - 10*x + 25
>>> from sage.all import *
>>> E = EllipticCurve(QQ, [Integer(42), Integer(42)])
>>> m5 = E.scalar_multiplication(Integer(5))
>>> m5.characteristic_polynomial()
x^2 - 10*x + 25

sage: E = EllipticCurve(GF(71), [42, 42])
sage: pi = E.frobenius_endomorphism()
sage: pi.characteristic_polynomial()
x^2 - 8*x + 71
sage: E.frobenius().charpoly()
x^2 - 8*x + 71
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(71)), [Integer(42), Integer(42)])
>>> pi = E.frobenius_endomorphism()
>>> pi.characteristic_polynomial()
x^2 - 8*x + 71
>>> E.frobenius().charpoly()
x^2 - 8*x + 71
degree()[source]

Return the degree of this elliptic-curve morphism.

EXAMPLES:

sage: E = EllipticCurve(QQ, [0,0,0,1,0])
sage: phi = EllipticCurveIsogeny(E, E((0,0)))
sage: phi.degree()
2
sage: phi = EllipticCurveIsogeny(E, [0,1,0,1])
sage: phi.degree()
4

sage: E = EllipticCurve(GF(31), [1,0,0,1,2])
sage: phi = EllipticCurveIsogeny(E, [17, 1])
sage: phi.degree()
3
>>> from sage.all import *
>>> E = EllipticCurve(QQ, [Integer(0),Integer(0),Integer(0),Integer(1),Integer(0)])
>>> phi = EllipticCurveIsogeny(E, E((Integer(0),Integer(0))))
>>> phi.degree()
2
>>> phi = EllipticCurveIsogeny(E, [Integer(0),Integer(1),Integer(0),Integer(1)])
>>> phi.degree()
4

>>> E = EllipticCurve(GF(Integer(31)), [Integer(1),Integer(0),Integer(0),Integer(1),Integer(2)])
>>> phi = EllipticCurveIsogeny(E, [Integer(17), Integer(1)])
>>> phi.degree()
3

Degrees are multiplicative, so the degree of a composite isogeny is the product of the degrees of the individual factors:

sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
sage: E = EllipticCurve(GF(419), [1,0])
sage: P, = E.gens()
sage: phi = EllipticCurveHom_composite(E, P+P)
sage: phi.degree()
210
sage: phi.degree() == prod(f.degree() for f in phi.factors())
True
[Python]
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
>>> E = EllipticCurve(GF(Integer(419)), [Integer(1),Integer(0)])
>>> P, = E.gens()
>>> phi = EllipticCurveHom_composite(E, P+P)
>>> phi.degree()
210
>>> phi.degree() == prod(f.degree() for f in phi.factors())
True

Isomorphisms always have degree \(1\) by definition:

sage: E1 = EllipticCurve([1,2,3,4,5])
sage: E2 = EllipticCurve_from_j(E1.j_invariant())
sage: E1.isomorphism_to(E2).degree()
1
>>> from sage.all import *
>>> E1 = EllipticCurve([Integer(1),Integer(2),Integer(3),Integer(4),Integer(5)])
>>> E2 = EllipticCurve_from_j(E1.j_invariant())
>>> E1.isomorphism_to(E2).degree()
1
divide_left(psi)[source]

Return an isogeny \(\chi\) such that \(\psi\circ\chi = \varphi\), where \(\varphi\) is this isogeny, if such a \(\chi\) exists.

EXAMPLES:

sage: E = EllipticCurve('54.b2')
sage: K = next(T for T in E.torsion_points() if T.order() == 9)
sage: phi, psi = E.isogeny(K).factors()
sage: chain = psi * phi; chain
Composite morphism of degree 9 = 3^2:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
sage: chain.divide_right(phi)
Fractional elliptic-curve morphism of degree 3:
  Numerator:   Composite morphism of degree 27 = 3^3:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 + 106*x - 323 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
  Denominator: 3
sage: chain.divide_right(phi) == psi
True
>>> from sage.all import *
>>> E = EllipticCurve('54.b2')
>>> K = next(T for T in E.torsion_points() if T.order() == Integer(9))
>>> phi, psi = E.isogeny(K).factors()
>>> chain = psi * phi; chain
Composite morphism of degree 9 = 3^2:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
>>> chain.divide_right(phi)
Fractional elliptic-curve morphism of degree 3:
  Numerator:   Composite morphism of degree 27 = 3^3:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 + 106*x - 323 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
  Denominator: 3
>>> chain.divide_right(phi) == psi
True
divide_right(psi)[source]

Return an isogeny \(\chi\) such that \(\chi\circ\psi = \varphi\), where \(\varphi\) is this isogeny, if such a \(\chi\) exists.

EXAMPLES:

sage: E = EllipticCurve('54.b2')
sage: K = next(T for T in E.torsion_points() if T.order() == 9)
sage: phi, psi = E.isogeny(K).factors()
sage: chain = psi * phi; chain
Composite morphism of degree 9 = 3^2:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
sage: chain.divide_left(psi)
Fractional elliptic-curve morphism of degree 3:
  Numerator:   Composite morphism of degree 27 = 3^3:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 + 106*x - 323 over Rational Field
  Denominator: 3
sage: chain.divide_left(psi) == phi
True
>>> from sage.all import *
>>> E = EllipticCurve('54.b2')
>>> K = next(T for T in E.torsion_points() if T.order() == Integer(9))
>>> phi, psi = E.isogeny(K).factors()
>>> chain = psi * phi; chain
Composite morphism of degree 9 = 3^2:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 2324*x - 43091 over Rational Field
>>> chain.divide_left(psi)
Fractional elliptic-curve morphism of degree 3:
  Numerator:   Composite morphism of degree 27 = 3^3:
  From: Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 - 14*x + 29 over Rational Field
  To:   Elliptic Curve defined by y^2 + x*y + y = x^3 - x^2 + 106*x - 323 over Rational Field
  Denominator: 3
>>> chain.divide_left(psi) == phi
True

If the quotient is an isomorphism, it is found much more quickly:

sage: E = EllipticCurve(GF(419^2), [1,0])
sage: K = E.lift_x(28)
sage: phi = E.isogeny(K)
sage: psi = E.isogeny(K, model='montgomery')
sage: iso = psi.divide_right(phi); iso
Elliptic-curve morphism:
  From: Elliptic Curve defined by y^2 = x^3 + 333*x + 200 over Finite Field in z2 of size 419^2
  To:   Elliptic Curve defined by y^2 = x^3 + (156*z2+341)*x^2 + x over Finite Field in z2 of size 419^2
  Via:  (u,r,s,t) = (225*z2 + 261, 351, 0, 0)
sage: psi == iso * phi
True
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(419)**Integer(2)), [Integer(1),Integer(0)])
>>> K = E.lift_x(Integer(28))
>>> phi = E.isogeny(K)
>>> psi = E.isogeny(K, model='montgomery')
>>> iso = psi.divide_right(phi); iso
Elliptic-curve morphism:
  From: Elliptic Curve defined by y^2 = x^3 + 333*x + 200 over Finite Field in z2 of size 419^2
  To:   Elliptic Curve defined by y^2 = x^3 + (156*z2+341)*x^2 + x over Finite Field in z2 of size 419^2
  Via:  (u,r,s,t) = (225*z2 + 261, 351, 0, 0)
>>> psi == iso * phi
True
dual()[source]

Return the dual of this elliptic-curve morphism.

Implemented by child classes. For examples, see:

formal(prec=20)[source]

Return the formal isogeny associated to this elliptic-curve morphism as a power series in the variable \(t=-x/y\) on the domain curve.

INPUT:

  • prec – (default: 20) the precision with which the computations in the formal group are carried out

EXAMPLES:

sage: E = EllipticCurve(GF(13),[1,7])
sage: phi = E.isogeny(E(10,4))
sage: phi.formal()
t + 12*t^13 + 2*t^17 + 8*t^19 + 2*t^21 + O(t^23)
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(13)),[Integer(1),Integer(7)])
>>> phi = E.isogeny(E(Integer(10),Integer(4)))
>>> phi.formal()
t + 12*t^13 + 2*t^17 + 8*t^19 + 2*t^21 + O(t^23)

sage: E = EllipticCurve([0,1])
sage: phi = E.isogeny(E(2,3))
sage: phi.formal(prec=10)
t + 54*t^5 + 255*t^7 + 2430*t^9 + 19278*t^11 + O(t^13)
[Python]
>>> from sage.all import *
>>> E = EllipticCurve([Integer(0),Integer(1)])
>>> phi = E.isogeny(E(Integer(2),Integer(3)))
>>> phi.formal(prec=Integer(10))
t + 54*t^5 + 255*t^7 + 2430*t^9 + 19278*t^11 + O(t^13)

sage: E = EllipticCurve('11a2')
sage: R.<x> = QQ[]
sage: phi = E.isogeny(x^2 + 101*x + 12751/5)
sage: phi.formal(prec=7)
t - 2724/5*t^5 + 209046/5*t^7 - 4767/5*t^8 + 29200946/5*t^9 + O(t^10)
>>> from sage.all import *
>>> E = EllipticCurve('11a2')
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> phi = E.isogeny(x**Integer(2) + Integer(101)*x + Integer(12751)/Integer(5))
>>> phi.formal(prec=Integer(7))
t - 2724/5*t^5 + 209046/5*t^7 - 4767/5*t^8 + 29200946/5*t^9 + O(t^10)
inseparable_degree()[source]

Return the inseparable degree of this isogeny.

Implemented by child classes. For examples, see:

inverse_image(Q, all)[source]

Return an arbitrary element P in the domain such that self(P) == Q, or raise ValueError if no such element exists.

INPUT:

  • Q – a point

  • all – if true, returns an iterator over all points in the inverse image

EXAMPLES:

sage: E.<P, Q> = EllipticCurve(GF(5^2), [1, 2, 3, 3, 1])
sage: f = E.isogeny([P*3])
sage: f(f.inverse_image(f(Q))) == f(Q)
True
sage: E.scalar_multiplication(-1).inverse_image(P) == -P
True
sage: Ts = [pt for pt in f.codomain() if pt.weil_pairing(f(Q), f(Q).order())**3 != 1]
sage: f.inverse_image(choice(Ts))
Traceback (most recent call last):
...
ValueError: ...
sage: len(list(f.inverse_image(f(Q), all=True)))
2
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(5)**Integer(2)), [Integer(1), Integer(2), Integer(3), Integer(3), Integer(1)], names=('P', 'Q',)); (P, Q,) = E._first_ngens(2)
>>> f = E.isogeny([P*Integer(3)])
>>> f(f.inverse_image(f(Q))) == f(Q)
True
>>> E.scalar_multiplication(-Integer(1)).inverse_image(P) == -P
True
>>> Ts = [pt for pt in f.codomain() if pt.weil_pairing(f(Q), f(Q).order())**Integer(3) != Integer(1)]
>>> f.inverse_image(choice(Ts))
Traceback (most recent call last):
...
ValueError: ...
>>> len(list(f.inverse_image(f(Q), all=True)))
2

Check that the result is consistent with division_points():

sage: E = EllipticCurve('37a'); E
Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field
sage: P = E(0, -1)
sage: (P * 5).division_points(5)
[(0 : -1 : 1)]
sage: E.scalar_multiplication(5).inverse_image(P * 5)
(0 : -1 : 1)
[Python]
>>> from sage.all import *
>>> E = EllipticCurve('37a'); E
Elliptic Curve defined by y^2 + y = x^3 - x over Rational Field
>>> P = E(Integer(0), -Integer(1))
>>> (P * Integer(5)).division_points(Integer(5))
[(0 : -1 : 1)]
>>> E.scalar_multiplication(Integer(5)).inverse_image(P * Integer(5))
(0 : -1 : 1)

Points from wrong curves cannot be passed in:

sage: f.inverse_image(Q)
Traceback (most recent call last):
...
TypeError: input must be a point in the codomain
>>> from sage.all import *
>>> f.inverse_image(Q)
Traceback (most recent call last):
...
TypeError: input must be a point in the codomain
is_injective()[source]

Determine whether or not this morphism has trivial kernel.

The kernel is trivial if and only if this morphism is a purely inseparable isogeny.

EXAMPLES:

sage: E = EllipticCurve('11a1')
sage: R.<x> = QQ[]
sage: f = x^2 + x - 29/5
sage: phi = EllipticCurveIsogeny(E, f)
sage: phi.is_injective()
False
sage: phi = EllipticCurveIsogeny(E, R(1))
sage: phi.is_injective()
True
>>> from sage.all import *
>>> E = EllipticCurve('11a1')
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> f = x**Integer(2) + x - Integer(29)/Integer(5)
>>> phi = EllipticCurveIsogeny(E, f)
>>> phi.is_injective()
False
>>> phi = EllipticCurveIsogeny(E, R(Integer(1)))
>>> phi.is_injective()
True

sage: F = GF(7)
sage: E = EllipticCurve(j=F(0))
sage: phi = EllipticCurveIsogeny(E, [ E((0,-1)), E((0,1))])
sage: phi.is_injective()
False
sage: phi = EllipticCurveIsogeny(E, E(0))
sage: phi.is_injective()
True
[Python]
>>> from sage.all import *
>>> F = GF(Integer(7))
>>> E = EllipticCurve(j=F(Integer(0)))
>>> phi = EllipticCurveIsogeny(E, [ E((Integer(0),-Integer(1))), E((Integer(0),Integer(1)))])
>>> phi.is_injective()
False
>>> phi = EllipticCurveIsogeny(E, E(Integer(0)))
>>> phi.is_injective()
True

sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
sage: E = EllipticCurve([1,0])
sage: phi = EllipticCurveHom_composite(E, E(0,0))
sage: phi.is_injective()
False
sage: E = EllipticCurve_from_j(GF(3).algebraic_closure()(0))
sage: nu = EllipticCurveHom_composite.from_factors(E.automorphisms())
sage: nu
Composite morphism of degree 1 = 1^12:
  From: Elliptic Curve defined by y^2 = x^3 + x
        over Algebraic closure of Finite Field of size 3
  To:   Elliptic Curve defined by y^2 = x^3 + x
        over Algebraic closure of Finite Field of size 3
sage: nu.is_injective()
True
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
>>> E = EllipticCurve([Integer(1),Integer(0)])
>>> phi = EllipticCurveHom_composite(E, E(Integer(0),Integer(0)))
>>> phi.is_injective()
False
>>> E = EllipticCurve_from_j(GF(Integer(3)).algebraic_closure()(Integer(0)))
>>> nu = EllipticCurveHom_composite.from_factors(E.automorphisms())
>>> nu
Composite morphism of degree 1 = 1^12:
  From: Elliptic Curve defined by y^2 = x^3 + x
        over Algebraic closure of Finite Field of size 3
  To:   Elliptic Curve defined by y^2 = x^3 + x
        over Algebraic closure of Finite Field of size 3
>>> nu.is_injective()
True

sage: E = EllipticCurve(GF(23), [1,0])
sage: E.scalar_multiplication(4).is_injective()
False
sage: E.scalar_multiplication(5).is_injective()
False
sage: E.scalar_multiplication(1).is_injective()
True
sage: E.scalar_multiplication(-1).is_injective()
True
sage: E.scalar_multiplication(23).is_injective()
True
sage: E.scalar_multiplication(-23).is_injective()
True
sage: E.scalar_multiplication(0).is_injective()
False
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(23)), [Integer(1),Integer(0)])
>>> E.scalar_multiplication(Integer(4)).is_injective()
False
>>> E.scalar_multiplication(Integer(5)).is_injective()
False
>>> E.scalar_multiplication(Integer(1)).is_injective()
True
>>> E.scalar_multiplication(-Integer(1)).is_injective()
True
>>> E.scalar_multiplication(Integer(23)).is_injective()
True
>>> E.scalar_multiplication(-Integer(23)).is_injective()
True
>>> E.scalar_multiplication(Integer(0)).is_injective()
False

sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
sage: E = EllipticCurve(GF(11), [1,1])
sage: pi = EllipticCurveHom_frobenius(E, 5)
sage: pi.is_injective()
True
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
>>> E = EllipticCurve(GF(Integer(11)), [Integer(1),Integer(1)])
>>> pi = EllipticCurveHom_frobenius(E, Integer(5))
>>> pi.is_injective()
True
is_normalized()[source]

Determine whether this morphism is a normalized isogeny.

Note

An isogeny \(\varphi\colon E_1\to E_2\) between two given Weierstrass equations is said to be normalized if the \(\varphi^*(\omega_2) = \omega_1\), where \(\omega_1\) and \(\omega_2\) are the invariant differentials on \(E_1\) and \(E_2\) corresponding to the given equation.

EXAMPLES:

sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
sage: E = EllipticCurve(GF(7), [0,0,0,1,0])
sage: R.<x> = GF(7)[]
sage: phi = EllipticCurveIsogeny(E, x)
sage: phi.is_normalized()
True
sage: isom = WeierstrassIsomorphism(phi.codomain(), (3, 0, 0, 0))
sage: phi = isom * phi
sage: phi.is_normalized()
False
sage: isom = WeierstrassIsomorphism(phi.codomain(), (5, 0, 0, 0))
sage: phi = isom * phi
sage: phi.is_normalized()
True
sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1))
sage: phi = isom * phi
sage: phi.is_normalized()
True
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
>>> E = EllipticCurve(GF(Integer(7)), [Integer(0),Integer(0),Integer(0),Integer(1),Integer(0)])
>>> R = GF(Integer(7))['x']; (x,) = R._first_ngens(1)
>>> phi = EllipticCurveIsogeny(E, x)
>>> phi.is_normalized()
True
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(3), Integer(0), Integer(0), Integer(0)))
>>> phi = isom * phi
>>> phi.is_normalized()
False
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(5), Integer(0), Integer(0), Integer(0)))
>>> phi = isom * phi
>>> phi.is_normalized()
True
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(1), Integer(1), Integer(1), Integer(1)))
>>> phi = isom * phi
>>> phi.is_normalized()
True

sage: F = GF(2^5, 'alpha'); alpha = F.gen()
sage: E = EllipticCurve(F, [1,0,1,1,1])
sage: R.<x> = F[]
sage: phi = EllipticCurveIsogeny(E, x+1)
sage: isom = WeierstrassIsomorphism(phi.codomain(), (alpha, 0, 0, 0))
sage: phi.is_normalized()
True
sage: phi = isom * phi
sage: phi.is_normalized()
False
sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/alpha, 0, 0, 0))
sage: phi = isom * phi
sage: phi.is_normalized()
True
sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1))
sage: phi = isom * phi
sage: phi.is_normalized()
True
[Python]
>>> from sage.all import *
>>> F = GF(Integer(2)**Integer(5), 'alpha'); alpha = F.gen()
>>> E = EllipticCurve(F, [Integer(1),Integer(0),Integer(1),Integer(1),Integer(1)])
>>> R = F['x']; (x,) = R._first_ngens(1)
>>> phi = EllipticCurveIsogeny(E, x+Integer(1))
>>> isom = WeierstrassIsomorphism(phi.codomain(), (alpha, Integer(0), Integer(0), Integer(0)))
>>> phi.is_normalized()
True
>>> phi = isom * phi
>>> phi.is_normalized()
False
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(1)/alpha, Integer(0), Integer(0), Integer(0)))
>>> phi = isom * phi
>>> phi.is_normalized()
True
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(1), Integer(1), Integer(1), Integer(1)))
>>> phi = isom * phi
>>> phi.is_normalized()
True

sage: E = EllipticCurve('11a1')
sage: R.<x> = QQ[]
sage: f = x^3 - x^2 - 10*x - 79/4
sage: phi = EllipticCurveIsogeny(E, f)
sage: isom = WeierstrassIsomorphism(phi.codomain(), (2, 0, 0, 0))
sage: phi.is_normalized()
True
sage: phi = isom * phi
sage: phi.is_normalized()
False
sage: isom = WeierstrassIsomorphism(phi.codomain(), (1/2, 0, 0, 0))
sage: phi = isom * phi
sage: phi.is_normalized()
True
sage: isom = WeierstrassIsomorphism(phi.codomain(), (1, 1, 1, 1))
sage: phi = isom * phi
sage: phi.is_normalized()
True
>>> from sage.all import *
>>> E = EllipticCurve('11a1')
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> f = x**Integer(3) - x**Integer(2) - Integer(10)*x - Integer(79)/Integer(4)
>>> phi = EllipticCurveIsogeny(E, f)
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(2), Integer(0), Integer(0), Integer(0)))
>>> phi.is_normalized()
True
>>> phi = isom * phi
>>> phi.is_normalized()
False
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(1)/Integer(2), Integer(0), Integer(0), Integer(0)))
>>> phi = isom * phi
>>> phi.is_normalized()
True
>>> isom = WeierstrassIsomorphism(phi.codomain(), (Integer(1), Integer(1), Integer(1), Integer(1)))
>>> phi = isom * phi
>>> phi.is_normalized()
True

ALGORITHM: We check if scaling_factor() returns \(1\).

is_separable()[source]

Determine whether or not this morphism is a separable isogeny.

EXAMPLES:

sage: E = EllipticCurve(GF(17), [0,0,0,3,0])
sage: phi = EllipticCurveIsogeny(E,  E((0,0)))
sage: phi.is_separable()
True
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(17)), [Integer(0),Integer(0),Integer(0),Integer(3),Integer(0)])
>>> phi = EllipticCurveIsogeny(E,  E((Integer(0),Integer(0))))
>>> phi.is_separable()
True

sage: E = EllipticCurve('11a1')
sage: phi = EllipticCurveIsogeny(E, E.torsion_points())
sage: phi.is_separable()
True
[Python]
>>> from sage.all import *
>>> E = EllipticCurve('11a1')
>>> phi = EllipticCurveIsogeny(E, E.torsion_points())
>>> phi.is_separable()
True

sage: E = EllipticCurve(GF(31337), [0,1])                                   # needs sage.rings.finite_rings
sage: {f.is_separable() for f in E.automorphisms()}                         # needs sage.rings.finite_rings
{True}
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(31337)), [Integer(0),Integer(1)])                                   # needs sage.rings.finite_rings
>>> {f.is_separable() for f in E.automorphisms()}                         # needs sage.rings.finite_rings
{True}

sage: # needs sage.rings.finite_rings
sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
sage: E = EllipticCurve(GF(7^2), [3,2])
sage: P = E.lift_x(1)
sage: phi = EllipticCurveHom_composite(E, P); phi
Composite morphism of degree 7:
  From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2
  To:   Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2
sage: phi.is_separable()
True
[Python]
>>> from sage.all import *
>>> # needs sage.rings.finite_rings
>>> from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
>>> E = EllipticCurve(GF(Integer(7)**Integer(2)), [Integer(3),Integer(2)])
>>> P = E.lift_x(Integer(1))
>>> phi = EllipticCurveHom_composite(E, P); phi
Composite morphism of degree 7:
  From: Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2
  To:   Elliptic Curve defined by y^2 = x^3 + 3*x + 2 over Finite Field in z2 of size 7^2
>>> phi.is_separable()
True

sage: E = EllipticCurve(GF(11), [4,4])
sage: E.scalar_multiplication(11).is_separable()
False
sage: E.scalar_multiplication(-11).is_separable()
False
sage: E.scalar_multiplication(777).is_separable()
True
sage: E.scalar_multiplication(-1).is_separable()
True
sage: E.scalar_multiplication(77).is_separable()
False
sage: E.scalar_multiplication(121).is_separable()
False
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(11)), [Integer(4),Integer(4)])
>>> E.scalar_multiplication(Integer(11)).is_separable()
False
>>> E.scalar_multiplication(-Integer(11)).is_separable()
False
>>> E.scalar_multiplication(Integer(777)).is_separable()
True
>>> E.scalar_multiplication(-Integer(1)).is_separable()
True
>>> E.scalar_multiplication(Integer(77)).is_separable()
False
>>> E.scalar_multiplication(Integer(121)).is_separable()
False

sage: from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
sage: E = EllipticCurve(GF(11), [1,1])
sage: pi = EllipticCurveHom_frobenius(E)
sage: pi.degree()
11
sage: pi.is_separable()
False
sage: pi = EllipticCurveHom_frobenius(E, 0)
sage: pi.degree()
1
sage: pi.is_separable()
True
[Python]
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom_frobenius import EllipticCurveHom_frobenius
>>> E = EllipticCurve(GF(Integer(11)), [Integer(1),Integer(1)])
>>> pi = EllipticCurveHom_frobenius(E)
>>> pi.degree()
11
>>> pi.is_separable()
False
>>> pi = EllipticCurveHom_frobenius(E, Integer(0))
>>> pi.degree()
1
>>> pi.is_separable()
True

sage: E = EllipticCurve(GF(17), [0,0,0,3,0])
sage: phi = E.isogeny(E((1,2)), algorithm='velusqrt')
sage: phi.is_separable()
True
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(17)), [Integer(0),Integer(0),Integer(0),Integer(3),Integer(0)])
>>> phi = E.isogeny(E((Integer(1),Integer(2))), algorithm='velusqrt')
>>> phi.is_separable()
True
is_surjective()[source]

Determine whether or not this morphism is surjective.

EXAMPLES:

sage: E = EllipticCurve('11a1')
sage: R.<x> = QQ[]
sage: f = x^2 + x - 29/5
sage: phi = EllipticCurveIsogeny(E, f)
sage: phi.is_surjective()
True
>>> from sage.all import *
>>> E = EllipticCurve('11a1')
>>> R = QQ['x']; (x,) = R._first_ngens(1)
>>> f = x**Integer(2) + x - Integer(29)/Integer(5)
>>> phi = EllipticCurveIsogeny(E, f)
>>> phi.is_surjective()
True

sage: E = EllipticCurve(GF(7), [0,0,0,1,0])
sage: phi = EllipticCurveIsogeny(E,  E((0,0)))
sage: phi.is_surjective()
True
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(7)), [Integer(0),Integer(0),Integer(0),Integer(1),Integer(0)])
>>> phi = EllipticCurveIsogeny(E,  E((Integer(0),Integer(0))))
>>> phi.is_surjective()
True

sage: F = GF(2^5, 'omega')
sage: E = EllipticCurve(j=F(0))
sage: R.<x> = F[]
sage: phi = EllipticCurveIsogeny(E, x)
sage: phi.is_surjective()
True
>>> from sage.all import *
>>> F = GF(Integer(2)**Integer(5), 'omega')
>>> E = EllipticCurve(j=F(Integer(0)))
>>> R = F['x']; (x,) = R._first_ngens(1)
>>> phi = EllipticCurveIsogeny(E, x)
>>> phi.is_surjective()
True
is_zero()[source]

Check whether this elliptic-curve morphism is the zero map.

EXAMPLES:

sage: E = EllipticCurve(j=GF(7)(0))
sage: phi = EllipticCurveIsogeny(E, [E(0,1), E(0,-1)])
sage: phi.is_zero()
False
>>> from sage.all import *
>>> E = EllipticCurve(j=GF(Integer(7))(Integer(0)))
>>> phi = EllipticCurveIsogeny(E, [E(Integer(0),Integer(1)), E(Integer(0),-Integer(1))])
>>> phi.is_zero()
False
kernel_gens(**kwds)[source]

Return a list of points which generate the kernel subgroup of this isogeny.

ALGORITHM: Thin convenience wrapper around kernel_subgroup().

EXAMPLES:

sage: E = EllipticCurve(GF(419^2), [1,0])
sage: iota = E.automorphisms()[-1]
sage: pi = E.frobenius_isogeny()
sage: endo = 3 + iota
sage: endo.characteristic_polynomial()
x^2 - 6*x + 10
sage: P, = endo.kernel_gens()
sage: P.order()
10
sage: (3 + iota)(P)
(0 : 1 : 0)
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(419)**Integer(2)), [Integer(1),Integer(0)])
>>> iota = E.automorphisms()[-Integer(1)]
>>> pi = E.frobenius_isogeny()
>>> endo = Integer(3) + iota
>>> endo.characteristic_polynomial()
x^2 - 6*x + 10
>>> P, = endo.kernel_gens()
>>> P.order()
10
>>> (Integer(3) + iota)(P)
(0 : 1 : 0)

sage: endo = 4 + iota
sage: endo.characteristic_polynomial()
x^2 - 8*x + 17
sage: P, = endo.kernel_gens(extend=True)
sage: P.order()
17
sage: (4 + iota)._eval(P)
(0 : 1 : 0)
[Python]
>>> from sage.all import *
>>> endo = Integer(4) + iota
>>> endo.characteristic_polynomial()
x^2 - 8*x + 17
>>> P, = endo.kernel_gens(extend=True)
>>> P.order()
17
>>> (Integer(4) + iota)._eval(P)
(0 : 1 : 0)
kernel_points()[source]

Return an iterator over the points in the kernel of this elliptic-curve morphism.

EXAMPLES:

sage: E.<P, Q> = EllipticCurve(GF(5^2), [1, 2, 3, 3, 1])
sage: f = E.isogeny([P*3, Q*3])
sage: set(f.kernel_points())
{(0 : 1 : 0), (4 : 4 : 1), (2*z2 + 4 : 4*z2 + 4 : 1), (3*z2 + 1 : z2 + 3 : 1)}
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(5)**Integer(2)), [Integer(1), Integer(2), Integer(3), Integer(3), Integer(1)], names=('P', 'Q',)); (P, Q,) = E._first_ngens(2)
>>> f = E.isogeny([P*Integer(3), Q*Integer(3)])
>>> set(f.kernel_points())
{(0 : 1 : 0), (4 : 4 : 1), (2*z2 + 4 : 4*z2 + 4 : 1), (3*z2 + 1 : z2 + 3 : 1)}

In the inseparable case:

sage: E = EllipticCurve(GF(23), [1,1])
sage: set(E.scalar_multiplication(23).kernel_points())
{(0 : 1 : 0)}
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(23)), [Integer(1),Integer(1)])
>>> set(E.scalar_multiplication(Integer(23)).kernel_points())
{(0 : 1 : 0)}

Check that the result is consistent with division_points():

sage: set(E.scalar_multiplication(4).kernel_points()) == set(E(0).division_points(4))
True
>>> from sage.all import *
>>> set(E.scalar_multiplication(Integer(4)).kernel_points()) == set(E(Integer(0)).division_points(Integer(4)))
True
kernel_polynomial()[source]

Return the kernel polynomial of this elliptic-curve morphism.

Implemented by child classes. For examples, see:

kernel_subgroup(extend, algorithm)[source]

Return the kernel subgroup of this isogeny as an AdditiveAbelianGroupWrapper.

If extend is set to False (the default) and the subgroup does not consist of points defined over the base field, a ValueError is raised.

INPUT:

  • extend – boolean (default: False); whether or not to extend the base field as necessary to find all kernel points.

  • algorithm – string (optional); must be one of the following:

    • "kerpoly" – works in general

    • "structure" – works for finite base fields

ALGORITHM:

If algorithm is "kerpoly": Factor the kernel polynomial to find points in the kernel until they generate a large enough subgroup.

If algorithm is "structure": Determine a set of independent generators of either the group of rational \(n\)-torsion points (if extend is False), or of the (full) \(n\)-torsion subgroup, where \(n\) is the degree of this isogeny, and compute the kernel using matrix_on_subgroup() combined with linear algebra.

If no algorithm is specified, this method selects the most suitable algorithm automatically.

EXAMPLES:

sage: E0 = EllipticCurve(GF(419^2), [1,0])
sage: P,Q = E0.torsion_basis(7)
sage: phi = E0.isogeny(P)
sage: E1 = phi.codomain()
sage: psi = E1.isogeny(phi(Q))
sage: (psi * phi).kernel_subgroup()
Additive abelian group isomorphic to Z/7 + Z/7
 embedded in Abelian group of points
  on Elliptic Curve defined by y^2 = x^3 + x
   over Finite Field in z2 of size 419^2
>>> from sage.all import *
>>> E0 = EllipticCurve(GF(Integer(419)**Integer(2)), [Integer(1),Integer(0)])
>>> P,Q = E0.torsion_basis(Integer(7))
>>> phi = E0.isogeny(P)
>>> E1 = phi.codomain()
>>> psi = E1.isogeny(phi(Q))
>>> (psi * phi).kernel_subgroup()
Additive abelian group isomorphic to Z/7 + Z/7
 embedded in Abelian group of points
  on Elliptic Curve defined by y^2 = x^3 + x
   over Finite Field in z2 of size 419^2

sage: E0 = EllipticCurve(GF(419^2), [1,0])
sage: P,Q = E0.torsion_basis(5)
sage: phi = E0.isogeny(P)
sage: E1 = phi.codomain()
sage: T1 = (elt.element() for elt in E1.torsion_subgroup(5))
sage: R = next(pt for pt in T1 if not pt.weil_pairing(phi(Q), 5).is_one())
sage: chi = E1.isogeny(R)
sage: (chi * phi).kernel_subgroup()
Traceback (most recent call last):
...
ValueError: kernel subgroup has no generating points over the base field
sage: (chi * phi).kernel_subgroup(extend=True)
Additive abelian group isomorphic to Z/25
  embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x
    over Finite Field in t of size 419^10
[Python]
>>> from sage.all import *
>>> E0 = EllipticCurve(GF(Integer(419)**Integer(2)), [Integer(1),Integer(0)])
>>> P,Q = E0.torsion_basis(Integer(5))
>>> phi = E0.isogeny(P)
>>> E1 = phi.codomain()
>>> T1 = (elt.element() for elt in E1.torsion_subgroup(Integer(5)))
>>> R = next(pt for pt in T1 if not pt.weil_pairing(phi(Q), Integer(5)).is_one())
>>> chi = E1.isogeny(R)
>>> (chi * phi).kernel_subgroup()
Traceback (most recent call last):
...
ValueError: kernel subgroup has no generating points over the base field
>>> (chi * phi).kernel_subgroup(extend=True)
Additive abelian group isomorphic to Z/25
  embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + x
    over Finite Field in t of size 419^10

sage: E = EllipticCurve(GF(101^7), [5,5])
sage: pi = E.frobenius_endomorphism(); pi
Frobenius endomorphism of degree 107213535210701 = 101^7:
  From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z7 of size 101^7
  To:   Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z7 of size 101^7
sage: pi.kernel_subgroup()
Trivial group
  embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 5*x + 5
    over Finite Field in z7 of size 101^7
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(101)**Integer(7)), [Integer(5),Integer(5)])
>>> pi = E.frobenius_endomorphism(); pi
Frobenius endomorphism of degree 107213535210701 = 101^7:
  From: Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z7 of size 101^7
  To:   Elliptic Curve defined by y^2 = x^3 + 5*x + 5 over Finite Field in z7 of size 101^7
>>> pi.kernel_subgroup()
Trivial group
  embedded in Abelian group of points on Elliptic Curve defined by y^2 = x^3 + 5*x + 5
    over Finite Field in z7 of size 101^7

We can check that the (now proven insecure) key exchange “SIDH” works:

sage: a, b = 91, 57
sage: p = 2^a * 3^b - 1
sage: F.<i> = GF((p,2), modulus=[1,0,1])
sage: E = EllipticCurve(F, [1,0])
sage: E.set_order((p + 1)^2)
sage: P2, Q2 = E.torsion_basis(2^a)
sage: P3, Q3 = E.torsion_basis(3^b)
sage: K2 = P2 + randrange(2^a) * Q2; K2.set_order(2^a)
sage: K3 = P3 + randrange(3^b) * Q3; K3.set_order(3^b)
sage: phi2 = E.isogeny(K2); E2 = phi2.codomain()
sage: phi3 = E.isogeny(K3); E3 = phi3.codomain()
sage: psi2 = E3.isogeny(phi3(K2))
sage: psi3 = E2.isogeny(phi2(K3))
sage: ker23 = (psi3 * phi2).kernel_subgroup()
sage: [d.factor() for d in ker23.invariants()]
[2^91 * 3^57]
sage: ker32 = (psi2 * phi3).kernel_subgroup()
sage: [d.factor() for d in ker32.invariants()]
[2^91 * 3^57]
sage: ker23 == ker32
True
[Python]
>>> from sage.all import *
>>> a, b = Integer(91), Integer(57)
>>> p = Integer(2)**a * Integer(3)**b - Integer(1)
>>> F = GF((p,Integer(2)), modulus=[Integer(1),Integer(0),Integer(1)], names=('i',)); (i,) = F._first_ngens(1)
>>> E = EllipticCurve(F, [Integer(1),Integer(0)])
>>> E.set_order((p + Integer(1))**Integer(2))
>>> P2, Q2 = E.torsion_basis(Integer(2)**a)
>>> P3, Q3 = E.torsion_basis(Integer(3)**b)
>>> K2 = P2 + randrange(Integer(2)**a) * Q2; K2.set_order(Integer(2)**a)
>>> K3 = P3 + randrange(Integer(3)**b) * Q3; K3.set_order(Integer(3)**b)
>>> phi2 = E.isogeny(K2); E2 = phi2.codomain()
>>> phi3 = E.isogeny(K3); E3 = phi3.codomain()
>>> psi2 = E3.isogeny(phi3(K2))
>>> psi3 = E2.isogeny(phi2(K3))
>>> ker23 = (psi3 * phi2).kernel_subgroup()
>>> [d.factor() for d in ker23.invariants()]
[2^91 * 3^57]
>>> ker32 = (psi2 * phi3).kernel_subgroup()
>>> [d.factor() for d in ker32.invariants()]
[2^91 * 3^57]
>>> ker23 == ker32
True
matrix_on_subgroup(domain_gens, codomain_gens=None)[source]

Return the matrix by which this isogeny acts on the \(n\)-torsion subgroup with respect to the given bases.

INPUT:

  • domain_gens – basis \((P,Q)\) of some \(n\)-torsion subgroup on the domain of this elliptic-curve morphism

  • codomain_gens – basis \((R,S)\) of the \(n\)-torsion on the codomain of this morphism, or (default) None if self is an endomorphism

OUTPUT:

A \(2\times 2\) matrix \(M\) over \(\ZZ/n\), such that the image of any point \([a]P + [b]Q\) under this morphism equals \([c]R + [d]S\) where \((c\ d)^T = (a\ b) M\).

EXAMPLES:

sage: F.<i> = GF(419^2, modulus=[1,0,1])
sage: E = EllipticCurve(F, [1,0])
sage: P = E(3, 176*i)
sage: Q = E(i+7, 67*i+48)
sage: P.weil_pairing(Q, 420).multiplicative_order()
420
sage: iota = E.automorphisms()[2]; iota
Elliptic-curve endomorphism of Elliptic Curve defined by y^2 = x^3 + x over Finite Field in i of size 419^2
  Via:  (u,r,s,t) = (i, 0, 0, 0)
sage: iota^2 == E.scalar_multiplication(-1)
True
sage: mat = iota.matrix_on_subgroup((P,Q)); mat
[301 386]
[ 83 119]
sage: mat.parent()
Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 420
sage: iota(P) == 301*P + 386*Q
True
sage: iota(Q) == 83*P + 119*Q
True
sage: a,b = 123, 456
sage: c,d = vector((a,b)) * mat; (c,d)
(111, 102)
sage: iota(a*P + b*Q) == c*P + d*Q
True
>>> from sage.all import *
>>> F = GF(Integer(419)**Integer(2), modulus=[Integer(1),Integer(0),Integer(1)], names=('i',)); (i,) = F._first_ngens(1)
>>> E = EllipticCurve(F, [Integer(1),Integer(0)])
>>> P = E(Integer(3), Integer(176)*i)
>>> Q = E(i+Integer(7), Integer(67)*i+Integer(48))
>>> P.weil_pairing(Q, Integer(420)).multiplicative_order()
420
>>> iota = E.automorphisms()[Integer(2)]; iota
Elliptic-curve endomorphism of Elliptic Curve defined by y^2 = x^3 + x over Finite Field in i of size 419^2
  Via:  (u,r,s,t) = (i, 0, 0, 0)
>>> iota**Integer(2) == E.scalar_multiplication(-Integer(1))
True
>>> mat = iota.matrix_on_subgroup((P,Q)); mat
[301 386]
[ 83 119]
>>> mat.parent()
Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 420
>>> iota(P) == Integer(301)*P + Integer(386)*Q
True
>>> iota(Q) == Integer(83)*P + Integer(119)*Q
True
>>> a,b = Integer(123), Integer(456)
>>> c,d = vector((a,b)) * mat; (c,d)
(111, 102)
>>> iota(a*P + b*Q) == c*P + d*Q
True

One important application of this is to compute generators of the kernel subgroup of an isogeny, when the \(n\)-torsion subgroup containing the kernel is accessible:

sage: K = E(83*i-16, 9*i-147)
sage: K.order()
7
sage: phi = E.isogeny(K)
sage: R,S = phi.codomain().gens()
sage: mat = phi.matrix_on_subgroup((P,Q), (R,S))
sage: mat  # random -- depends on R,S
[124 263]
[115 141]
sage: kermat = mat.left_kernel_matrix(); kermat
[300  60]
sage: ker = [ZZ(v[0])*P + ZZ(v[1])*Q for v in kermat]
sage: {phi(T) for T in ker}
{(0 : 1 : 0)}
sage: phi == E.isogeny(ker)
True
[Python]
>>> from sage.all import *
>>> K = E(Integer(83)*i-Integer(16), Integer(9)*i-Integer(147))
>>> K.order()
7
>>> phi = E.isogeny(K)
>>> R,S = phi.codomain().gens()
>>> mat = phi.matrix_on_subgroup((P,Q), (R,S))
>>> mat  # random -- depends on R,S
[124 263]
[115 141]
>>> kermat = mat.left_kernel_matrix(); kermat
[300  60]
>>> ker = [ZZ(v[Integer(0)])*P + ZZ(v[Integer(1)])*Q for v in kermat]
>>> {phi(T) for T in ker}
{(0 : 1 : 0)}
>>> phi == E.isogeny(ker)
True

We can also compute the matrix of a Frobenius endomorphism (EllipticCurveHom_frobenius) on a large enough subgroup to verify point-counting results:

sage: F.<a> = GF((101, 36))
sage: E = EllipticCurve(GF(101), [1,1])
sage: EE = E.change_ring(F)
sage: P,Q = EE.torsion_basis(37)
sage: pi = EE.frobenius_isogeny()
sage: M = pi.matrix_on_subgroup((P,Q))
sage: M.parent()
Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 37
sage: M.trace()
34
sage: E.trace_of_frobenius()
-3
>>> from sage.all import *
>>> F = GF((Integer(101), Integer(36)), names=('a',)); (a,) = F._first_ngens(1)
>>> E = EllipticCurve(GF(Integer(101)), [Integer(1),Integer(1)])
>>> EE = E.change_ring(F)
>>> P,Q = EE.torsion_basis(Integer(37))
>>> pi = EE.frobenius_isogeny()
>>> M = pi.matrix_on_subgroup((P,Q))
>>> M.parent()
Full MatrixSpace of 2 by 2 dense matrices over Ring of integers modulo 37
>>> M.trace()
34
>>> E.trace_of_frobenius()
-3

See also

To compute a basis of the \(n\)-torsion, you may use torsion_basis().

minimal_polynomial()[source]

Return a minimal polynomial of the kernel subgroup of this isogeny, as defined in [EPSV2023], Definition 15: That is, some polynomial \(f\) such that the points on the domain curve whose \(x\)-coordinates are roots of \(f\) generate the kernel of this isogeny.

See also

EllipticCurve_field.kernel_polynomial_from_divisor()

EXAMPLES:

sage: E = EllipticCurve(GF(419), [32, 41])
sage: phi = E.isogeny(E.lift_x(30)); phi
Isogeny of degree 7
 from Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
 to Elliptic Curve defined by y^2 = x^3 + 316*x + 241 over Finite Field of size 419
sage: f = phi.minimal_polynomial(); f  # random -- one of x+161, x+201, x+389
x + 161
sage: f.divides(phi.kernel_polynomial())
True
sage: E.kernel_polynomial_from_divisor(f, 7) == phi.kernel_polynomial()
True
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(419)), [Integer(32), Integer(41)])
>>> phi = E.isogeny(E.lift_x(Integer(30))); phi
Isogeny of degree 7
 from Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
 to Elliptic Curve defined by y^2 = x^3 + 316*x + 241 over Finite Field of size 419
>>> f = phi.minimal_polynomial(); f  # random -- one of x+161, x+201, x+389
x + 161
>>> f.divides(phi.kernel_polynomial())
True
>>> E.kernel_polynomial_from_divisor(f, Integer(7)) == phi.kernel_polynomial()
True

It also works for rational isogenies with irrational kernel points:

sage: E = EllipticCurve(GF(127^2), [1,0])
sage: phi = E.isogenies_prime_degree(17)[0]; phi
Isogeny of degree 17
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
sage: phi.kernel_polynomial()
x^8 + (68*z2 + 97)*x^6 + (59*z2 + 40)*x^4 + (59*z2 + 38)*x^2 + 4*z2 + 13
sage: phi.kernel_polynomial().factor()
(x^4 + (11*z2 + 32)*x^2 + 48*z2 + 70) * (x^4 + (57*z2 + 65)*x^2 + 20*z2 + 25)
sage: phi.minimal_polynomial().factor()
x^4 + (57*z2 + 65)*x^2 + 20*z2 + 25
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(127)**Integer(2)), [Integer(1),Integer(0)])
>>> phi = E.isogenies_prime_degree(Integer(17))[Integer(0)]; phi
Isogeny of degree 17
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
>>> phi.kernel_polynomial()
x^8 + (68*z2 + 97)*x^6 + (59*z2 + 40)*x^4 + (59*z2 + 38)*x^2 + 4*z2 + 13
>>> phi.kernel_polynomial().factor()
(x^4 + (11*z2 + 32)*x^2 + 48*z2 + 70) * (x^4 + (57*z2 + 65)*x^2 + 20*z2 + 25)
>>> phi.minimal_polynomial().factor()
x^4 + (57*z2 + 65)*x^2 + 20*z2 + 25
push_subgroup(f)[source]

Given a minimal polynomial (see minimal_polynomial()) of a subgroup \(G\) of the domain curve of this isogeny, return a minimal polynomial of the image of \(G\) under this isogeny.

ALGORITHM: [EPSV2023], Algorithm 5 (PushSubgroup)

EXAMPLES:

sage: E = EllipticCurve(GF(419), [32, 41])
sage: K = E.lift_x(30)
sage: phi = E.isogeny(K); phi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 316*x + 241 over Finite Field of size 419
sage: psi = E.isogeny(E.lift_x(54), algorithm='factored'); psi
Composite morphism of degree 15 = 3*5:
  From: Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
  To:   Elliptic Curve defined by y^2 = x^3 + 36*x + 305 over Finite Field of size 419
sage: f = phi.minimal_polynomial(); f   # random -- one of x+161, x+201, x+389
x + 161
sage: g = psi.push_subgroup(f); g       # random -- one of x+148, x+333, x+249
x + 148
sage: h = psi.codomain().kernel_polynomial_from_divisor(g, phi.degree()); h
x^3 + 311*x^2 + 196*x + 44
sage: chi = psi.codomain().isogeny(h); chi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + 36*x + 305 over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 186*x + 37 over Finite Field of size 419
sage: (chi * psi)(K)
(0 : 1 : 0)
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(419)), [Integer(32), Integer(41)])
>>> K = E.lift_x(Integer(30))
>>> phi = E.isogeny(K); phi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 316*x + 241 over Finite Field of size 419
>>> psi = E.isogeny(E.lift_x(Integer(54)), algorithm='factored'); psi
Composite morphism of degree 15 = 3*5:
  From: Elliptic Curve defined by y^2 = x^3 + 32*x + 41 over Finite Field of size 419
  To:   Elliptic Curve defined by y^2 = x^3 + 36*x + 305 over Finite Field of size 419
>>> f = phi.minimal_polynomial(); f   # random -- one of x+161, x+201, x+389
x + 161
>>> g = psi.push_subgroup(f); g       # random -- one of x+148, x+333, x+249
x + 148
>>> h = psi.codomain().kernel_polynomial_from_divisor(g, phi.degree()); h
x^3 + 311*x^2 + 196*x + 44
>>> chi = psi.codomain().isogeny(h); chi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + 36*x + 305 over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 186*x + 37 over Finite Field of size 419
>>> (chi * psi)(K)
(0 : 1 : 0)

It also works for rational isogenies with irrational kernel points:

sage: E = EllipticCurve(GF(127^2), [1,0])
sage: phi = E.isogenies_prime_degree(13)[0]; phi
Isogeny of degree 13
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
sage: psi = E.isogenies_prime_degree(17)[0]; psi
Isogeny of degree 17
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
sage: f_phi = phi.minimal_polynomial()
sage: g_phi = psi.push_subgroup(f_phi)
sage: h_phi = psi.codomain().kernel_polynomial_from_divisor(g_phi, phi.degree())
sage: phi_pushed = psi.codomain().isogeny(h_phi); phi_pushed
Isogeny of degree 13 from Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2 to Elliptic Curve defined by y^2 = x^3 + (110*z2+61)*x over Finite Field in z2 of size 127^2
sage: f_psi = psi.minimal_polynomial()
sage: g_psi = phi.push_subgroup(f_psi)
sage: h_psi = phi.codomain().kernel_polynomial_from_divisor(g_psi, psi.degree())
sage: psi_pushed = phi.codomain().isogeny(h_psi); psi_pushed
Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2 to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
sage: any(iso * psi_pushed * phi == phi_pushed * psi
....:     for iso in psi_pushed.codomain().isomorphisms(phi_pushed.codomain()))
True
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(127)**Integer(2)), [Integer(1),Integer(0)])
>>> phi = E.isogenies_prime_degree(Integer(13))[Integer(0)]; phi
Isogeny of degree 13
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
>>> psi = E.isogenies_prime_degree(Integer(17))[Integer(0)]; psi
Isogeny of degree 17
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2
  to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
>>> f_phi = phi.minimal_polynomial()
>>> g_phi = psi.push_subgroup(f_phi)
>>> h_phi = psi.codomain().kernel_polynomial_from_divisor(g_phi, phi.degree())
>>> phi_pushed = psi.codomain().isogeny(h_phi); phi_pushed
Isogeny of degree 13 from Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2 to Elliptic Curve defined by y^2 = x^3 + (110*z2+61)*x over Finite Field in z2 of size 127^2
>>> f_psi = psi.minimal_polynomial()
>>> g_psi = phi.push_subgroup(f_psi)
>>> h_psi = phi.codomain().kernel_polynomial_from_divisor(g_psi, psi.degree())
>>> psi_pushed = phi.codomain().isogeny(h_psi); psi_pushed
Isogeny of degree 17 from Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 127^2 to Elliptic Curve defined by y^2 = x^3 + (16*z2+26)*x over Finite Field in z2 of size 127^2
>>> any(iso * psi_pushed * phi == phi_pushed * psi
...     for iso in psi_pushed.codomain().isomorphisms(phi_pushed.codomain()))
True

If the subgroup represented by \(f\) intersects nontrivially with the kernel of this isogeny, the method still works correctly:

sage: E = EllipticCurve(GF(419), [1,0])
sage: phi = next(E.isogenies_degree(7)); phi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 285*x + 87 over Finite Field of size 419
sage: psi = next(E.isogenies_degree(21)); psi
Composite morphism of degree 21 = 7*3:
  From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419
  To:   Elliptic Curve defined by y^2 = x^3 + 134*x + 230 over Finite Field of size 419
sage: phi.kernel_polynomial().gcd(psi.kernel_polynomial())
x^3 + 274*x^2 + 350*x + 6
sage: f = phi.minimal_polynomial()
sage: psi.push_subgroup(f)
1
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(419)), [Integer(1),Integer(0)])
>>> phi = next(E.isogenies_degree(Integer(7))); phi
Isogeny of degree 7
  from Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419
  to Elliptic Curve defined by y^2 = x^3 + 285*x + 87 over Finite Field of size 419
>>> psi = next(E.isogenies_degree(Integer(21))); psi
Composite morphism of degree 21 = 7*3:
  From: Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 419
  To:   Elliptic Curve defined by y^2 = x^3 + 134*x + 230 over Finite Field of size 419
>>> phi.kernel_polynomial().gcd(psi.kernel_polynomial())
x^3 + 274*x^2 + 350*x + 6
>>> f = phi.minimal_polynomial()
>>> psi.push_subgroup(f)
1
rational_maps()[source]

Return the pair of explicit rational maps defining this elliptic-curve morphism as fractions of bivariate polynomials in \(x\) and \(y\).

Implemented by child classes. For examples, see:

scaling_factor()[source]

Return the Weierstrass scaling factor associated to this elliptic-curve morphism.

The scaling factor is the constant \(u\) (in the base field) such that \(\varphi^* \omega_2 = u \omega_1\), where \(\varphi: E_1\to E_2\) is this morphism and \(\omega_i\) are the standard Weierstrass differentials on \(E_i\) defined by \(\mathrm dx/(2y+a_1x+a_3)\).

Implemented by child classes. For examples, see:

separable_degree()[source]

Return the separable degree of this isogeny.

The separable degree is the result of dividing the degree() by the inseparable_degree().

EXAMPLES:

sage: E = EllipticCurve(GF(11), [5,5])
sage: E.is_supersingular()
False
sage: E.scalar_multiplication(-77).separable_degree()
539
sage: E = EllipticCurve(GF(11), [5,0])
sage: E.is_supersingular()
True
sage: E.scalar_multiplication(-77).separable_degree()
49
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(11)), [Integer(5),Integer(5)])
>>> E.is_supersingular()
False
>>> E.scalar_multiplication(-Integer(77)).separable_degree()
539
>>> E = EllipticCurve(GF(Integer(11)), [Integer(5),Integer(0)])
>>> E.is_supersingular()
True
>>> E.scalar_multiplication(-Integer(77)).separable_degree()
49
trace()[source]

Return the trace of this elliptic-curve morphism, which must be an endomorphism.

ALGORITHM: compute_trace_generic()

EXAMPLES:

sage: E = EllipticCurve(QQ, [42, 42])
sage: m5 = E.scalar_multiplication(5)
sage: m5.trace()
10
>>> from sage.all import *
>>> E = EllipticCurve(QQ, [Integer(42), Integer(42)])
>>> m5 = E.scalar_multiplication(Integer(5))
>>> m5.trace()
10

sage: E = EllipticCurve(GF(71^2), [45, 45])
sage: P = E.lift_x(27)
sage: P.order()
71
sage: tau = E.isogeny(P, codomain=E)
sage: tau.trace()
-1
[Python]
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(71)**Integer(2)), [Integer(45), Integer(45)])
>>> P = E.lift_x(Integer(27))
>>> P.order()
71
>>> tau = E.isogeny(P, codomain=E)
>>> tau.trace()
-1
x_rational_map()[source]

Return the \(x\)-coordinate rational map of this elliptic-curve morphism as a univariate rational expression in \(x\).

Implemented by child classes. For examples, see:

sage.schemes.elliptic_curves.hom.compare_via_evaluation(left, right)[source]

Test if two elliptic-curve morphisms are equal by evaluating them at enough points.

INPUT:

ALGORITHM:

We use the fact that two isogenies of equal degree \(d\) must be the same if and only if they behave identically on more than \(4d\) points. (It suffices to check this on a few points that generate a large enough subgroup.)

If the domain curve does not have sufficiently many rational points, the base field is extended first: Taking an extension of degree \(O(\log(d))\) suffices.

EXAMPLES:

sage: E = EllipticCurve(GF(83), [1,0])
sage: phi = E.isogeny(12*E.0, model='montgomery'); phi
Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83
sage: psi = phi.dual(); psi
Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83
sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
sage: mu = EllipticCurveHom_composite.from_factors([phi, psi])
sage: from sage.schemes.elliptic_curves.hom import compare_via_evaluation
sage: compare_via_evaluation(mu, E.scalar_multiplication(7))
True
>>> from sage.all import *
>>> E = EllipticCurve(GF(Integer(83)), [Integer(1),Integer(0)])
>>> phi = E.isogeny(Integer(12)*E.gen(0), model='montgomery'); phi
Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83
>>> psi = phi.dual(); psi
Isogeny of degree 7 from Elliptic Curve defined by y^2 = x^3 + 70*x^2 + x over Finite Field of size 83 to Elliptic Curve defined by y^2 = x^3 + x over Finite Field of size 83
>>> from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
>>> mu = EllipticCurveHom_composite.from_factors([phi, psi])
>>> from sage.schemes.elliptic_curves.hom import compare_via_evaluation
>>> compare_via_evaluation(mu, E.scalar_multiplication(Integer(7)))
True

See also

  • sage.schemes.elliptic_curves.hom_composite.EllipticCurveHom_composite._richcmp_()

sage.schemes.elliptic_curves.hom.compute_trace_generic(phi)[source]

Compute the trace of the given elliptic-curve endomorphism.

ALGORITHM: Simple variant of Schoof’s algorithm. For enough small primes \(\ell\), we find an order-\(\ell\) point \(P\) on \(E\) and use a discrete-logarithm calculation to find the unique scalar \(t_\ell \in \{0,...,\ell-1\}\) such that \(\varphi^2(P)+[\deg(\varphi)]P = [t_\ell]\varphi(P)\). Then \(t_\ell\) equals the trace of \(\varphi\) modulo \(\ell\), which can therefore be recovered using the Chinese remainder theorem.

EXAMPLES:

It works over finite fields:

sage: from sage.schemes.elliptic_curves.hom import compute_trace_generic
sage: E = EllipticCurve(GF(31337), [1,1])
sage: compute_trace_generic(E.frobenius_endomorphism())
314
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom import compute_trace_generic
>>> E = EllipticCurve(GF(Integer(31337)), [Integer(1),Integer(1)])
>>> compute_trace_generic(E.frobenius_endomorphism())
314

It works over \(\QQ\):

sage: from sage.schemes.elliptic_curves.hom import compute_trace_generic
sage: E = EllipticCurve(QQ, [1,2,3,4,5])
sage: dbl = E.scalar_multiplication(2)
sage: compute_trace_generic(dbl)
4
[Python]
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom import compute_trace_generic
>>> E = EllipticCurve(QQ, [Integer(1),Integer(2),Integer(3),Integer(4),Integer(5)])
>>> dbl = E.scalar_multiplication(Integer(2))
>>> compute_trace_generic(dbl)
4

It works over number fields (for a CM curve):

sage: from sage.schemes.elliptic_curves.hom import compute_trace_generic
sage: x = polygen(QQ)
sage: K.<t> = NumberField(5*x^2 - 2*x + 1)
sage: E = EllipticCurve(K, [1,0])
sage: phi = E.isogeny([t,0,1], codomain=E)  # phi = 2 + i
sage: compute_trace_generic(phi)
4
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom import compute_trace_generic
>>> x = polygen(QQ)
>>> K = NumberField(Integer(5)*x**Integer(2) - Integer(2)*x + Integer(1), names=('t',)); (t,) = K._first_ngens(1)
>>> E = EllipticCurve(K, [Integer(1),Integer(0)])
>>> phi = E.isogeny([t,Integer(0),Integer(1)], codomain=E)  # phi = 2 + i
>>> compute_trace_generic(phi)
4
sage.schemes.elliptic_curves.hom.find_post_isomorphism(phi, psi)[source]

Given two isogenies \(\phi: E\to E'\) and \(\psi: E\to E''\) which are equal up to post-isomorphism defined over the same field, find that isomorphism.

In other words, this function computes an isomorphism \(\alpha: E'\to E''\) such that \(\alpha\circ\phi = \psi\).

ALGORITHM:

Start with a list of all isomorphisms \(E'\to E''\). Then repeatedly evaluate \(\phi\) and \(\psi\) at random points \(P\) to filter the list for isomorphisms \(\alpha\) with \(\alpha(\phi(P)) = \psi(P)\). Once only one candidate is left, return it. Periodically extend the base field to avoid getting stuck (say, if all candidate isomorphisms act the same on all rational points).

EXAMPLES:

sage: from sage.schemes.elliptic_curves.hom import find_post_isomorphism
sage: E = EllipticCurve(GF(7^2), [1,0])
sage: f = E.scalar_multiplication(1)
sage: g = choice(E.automorphisms())
sage: find_post_isomorphism(f, g) == g
True
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.hom import find_post_isomorphism
>>> E = EllipticCurve(GF(Integer(7)**Integer(2)), [Integer(1),Integer(0)])
>>> f = E.scalar_multiplication(Integer(1))
>>> g = choice(E.automorphisms())
>>> find_post_isomorphism(f, g) == g
True

sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
sage: from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
sage: x = polygen(ZZ, 'x')
sage: F.<i> = GF(883^2, modulus=x^2+1)
sage: E = EllipticCurve(F, [1,0])
sage: P = E.lift_x(117)
sage: Q = E.lift_x(774)
sage: w = WeierstrassIsomorphism(E, [i,0,0,0])
sage: phi = EllipticCurveHom_composite(E, [P,w(Q)]) * w
sage: psi = EllipticCurveHom_composite(E, [Q,w(P)])
sage: phi.kernel_polynomial() == psi.kernel_polynomial()
True
sage: find_post_isomorphism(phi, psi)
Elliptic-curve morphism:
  From: Elliptic Curve defined by y^2 = x^3 + 320*x + 482 over Finite Field in i of size 883^2
  To:   Elliptic Curve defined by y^2 = x^3 + 320*x + 401 over Finite Field in i of size 883^2
  Via:  (u,r,s,t) = (882*i, 0, 0, 0)
[Python]
>>> from sage.all import *
>>> from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism
>>> from sage.schemes.elliptic_curves.hom_composite import EllipticCurveHom_composite
>>> x = polygen(ZZ, 'x')
>>> F = GF(Integer(883)**Integer(2), modulus=x**Integer(2)+Integer(1), names=('i',)); (i,) = F._first_ngens(1)
>>> E = EllipticCurve(F, [Integer(1),Integer(0)])
>>> P = E.lift_x(Integer(117))
>>> Q = E.lift_x(Integer(774))
>>> w = WeierstrassIsomorphism(E, [i,Integer(0),Integer(0),Integer(0)])
>>> phi = EllipticCurveHom_composite(E, [P,w(Q)]) * w
>>> psi = EllipticCurveHom_composite(E, [Q,w(P)])
>>> phi.kernel_polynomial() == psi.kernel_polynomial()
True
>>> find_post_isomorphism(phi, psi)
Elliptic-curve morphism:
  From: Elliptic Curve defined by y^2 = x^3 + 320*x + 482 over Finite Field in i of size 883^2
  To:   Elliptic Curve defined by y^2 = x^3 + 320*x + 401 over Finite Field in i of size 883^2
  Via:  (u,r,s,t) = (882*i, 0, 0, 0)