123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- from sympy.core.numbers import oo
- from sympy.core.relational import Eq
- from sympy.core.symbol import symbols
- from sympy.polys.domains import FiniteField, QQ, RationalField, FF
- from sympy.solvers.solvers import solve
- from sympy.utilities.iterables import is_sequence
- from sympy.utilities.misc import as_int
- from .factor_ import divisors
- from .residue_ntheory import polynomial_congruence
- class EllipticCurve:
- """
- Create the following Elliptic Curve over domain.
- `y^{2} + a_{1} x y + a_{3} y = x^{3} + a_{2} x^{2} + a_{4} x + a_{6}`
- The default domain is ``QQ``. If no coefficient ``a1``, ``a2``, ``a3``,
- it create curve as following form.
- `y^{2} = x^{3} + a_{4} x + a_{6}`
- Examples
- ========
- References
- ==========
- .. [1] J. Silverman "A Friendly Introduction to Number Theory" Third Edition
- .. [2] https://mathworld.wolfram.com/EllipticDiscriminant.html
- .. [3] G. Hardy, E. Wright "An Introduction to the Theory of Numbers" Sixth Edition
- """
- def __init__(self, a4, a6, a1=0, a2=0, a3=0, modulus = 0):
- if modulus == 0:
- domain = QQ
- else:
- domain = FF(modulus)
- a1, a2, a3, a4, a6 = map(domain.convert, (a1, a2, a3, a4, a6))
- self._domain = domain
- self.modulus = modulus
- # Calculate discriminant
- b2 = a1**2 + 4 * a2
- b4 = 2 * a4 + a1 * a3
- b6 = a3**2 + 4 * a6
- b8 = a1**2 * a6 + 4 * a2 * a6 - a1 * a3 * a4 + a2 * a3**2 - a4**2
- self._b2, self._b4, self._b6, self._b8 = b2, b4, b6, b8
- self._discrim = -b2**2 * b8 - 8 * b4**3 - 27 * b6**2 + 9 * b2 * b4 * b6
- self._a1 = a1
- self._a2 = a2
- self._a3 = a3
- self._a4 = a4
- self._a6 = a6
- x, y, z = symbols('x y z')
- self.x, self.y, self.z = x, y, z
- self._eq = Eq(y**2*z + a1*x*y*z + a3*y*z**2, x**3 + a2*x**2*z + a4*x*z**2 + a6*z**3)
- if isinstance(self._domain, FiniteField):
- self._rank = 0
- elif isinstance(self._domain, RationalField):
- self._rank = None
- def __call__(self, x, y, z=1):
- return EllipticCurvePoint(x, y, z, self)
- def __contains__(self, point):
- if is_sequence(point):
- if len(point) == 2:
- z1 = 1
- else:
- z1 = point[2]
- x1, y1 = point[:2]
- elif isinstance(point, EllipticCurvePoint):
- x1, y1, z1 = point.x, point.y, point.z
- else:
- raise ValueError('Invalid point.')
- if self.characteristic == 0 and z1 == 0:
- return True
- return self._eq.subs({self.x: x1, self.y: y1, self.z: z1})
- def __repr__(self):
- return 'E({}): {}'.format(self._domain, self._eq)
- def minimal(self):
- """
- Return minimal Weierstrass equation.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e1 = EllipticCurve(-10, -20, 0, -1, 1)
- >>> e1.minimal()
- E(QQ): Eq(y**2*z, x**3 - 13392*x*z**2 - 1080432*z**3)
- """
- char = self.characteristic
- if char == 2:
- return self
- if char == 3:
- return EllipticCurve(self._b4/2, self._b6/4, a2=self._b2/4, modulus=self.modulus)
- c4 = self._b2**2 - 24*self._b4
- c6 = -self._b2**3 + 36*self._b2*self._b4 - 216*self._b6
- return EllipticCurve(-27*c4, -54*c6, modulus=self.modulus)
- def points(self):
- """
- Return points of curve over Finite Field.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e2 = EllipticCurve(1, 1, 1, 1, 1, modulus=5)
- >>> e2.points()
- {(0, 2), (1, 4), (2, 0), (2, 2), (3, 0), (3, 1), (4, 0)}
- """
- char = self.characteristic
- all_pt = set()
- if char >= 1:
- for i in range(char):
- congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: i, self.z: 1}))
- sol = polynomial_congruence(congruence_eq, char)
- for num in sol:
- all_pt.add((i, num))
- return all_pt
- else:
- raise ValueError("Infinitely many points")
- def points_x(self, x):
- "Returns points on with curve where xcoordinate = x"
- pt = []
- if self._domain == QQ:
- for y in solve(self._eq.subs(self.x, x)):
- pt.append((x, y))
- congruence_eq = ((self._eq.lhs - self._eq.rhs).subs({self.x: x, self.z: 1}))
- for y in polynomial_congruence(congruence_eq, self.characteristic):
- pt.append((x, y))
- return pt
- def torsion_points(self):
- """
- Return torsion points of curve over Rational number.
- Return point objects those are finite order.
- According to Nagell-Lutz theorem, torsion point p(x, y)
- x and y are integers, either y = 0 or y**2 is divisor
- of discriminent. According to Mazur's theorem, there are
- at most 15 points in torsion collection.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e2 = EllipticCurve(-43, 166)
- >>> sorted(e2.torsion_points())
- [(-5, -16), (-5, 16), O, (3, -8), (3, 8), (11, -32), (11, 32)]
- """
- if self.characteristic > 0:
- raise ValueError("No torsion point for Finite Field.")
- l = [EllipticCurvePoint.point_at_infinity(self)]
- for xx in solve(self._eq.subs({self.y: 0, self.z: 1})):
- if xx.is_rational:
- l.append(self(xx, 0))
- for i in divisors(self.discriminant, generator=True):
- j = int(i**.5)
- if j**2 == i:
- for xx in solve(self._eq.subs({self.y: j, self.z: 1})):
- if not xx.is_rational:
- continue
- p = self(xx, j)
- if p.order() != oo:
- l.extend([p, -p])
- return l
- @property
- def characteristic(self):
- """
- Return domain characteristic.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e2 = EllipticCurve(-43, 166)
- >>> e2.characteristic
- 0
- """
- return self._domain.characteristic()
- @property
- def discriminant(self):
- """
- Return curve discriminant.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e2 = EllipticCurve(0, 17)
- >>> e2.discriminant
- -124848
- """
- return int(self._discrim)
- @property
- def is_singular(self):
- """
- Return True if curve discriminant is equal to zero.
- """
- return self.discriminant == 0
- @property
- def j_invariant(self):
- """
- Return curve j-invariant.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e1 = EllipticCurve(-2, 0, 0, 1, 1)
- >>> e1.j_invariant
- 1404928/389
- """
- c4 = self._b2**2 - 24*self._b4
- return self._domain.to_sympy(c4**3 / self._discrim)
- @property
- def order(self):
- """
- Number of points in Finite field.
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e2 = EllipticCurve(1, 0, modulus=19)
- >>> e2.order
- 19
- """
- if self.characteristic == 0:
- raise NotImplementedError("Still not implemented")
- return len(self.points())
- @property
- def rank(self):
- """
- Number of independent points of infinite order.
- For Finite field, it must be 0.
- """
- if self._rank is not None:
- return self._rank
- raise NotImplementedError("Still not implemented")
- class EllipticCurvePoint:
- """
- Point of Elliptic Curve
- Examples
- ========
- >>> from sympy.ntheory.elliptic_curve import EllipticCurve
- >>> e1 = EllipticCurve(-17, 16)
- >>> p1 = e1(0, -4, 1)
- >>> p2 = e1(1, 0)
- >>> p1 + p2
- (15, -56)
- >>> e3 = EllipticCurve(-1, 9)
- >>> e3(1, -3) * 3
- (664/169, 17811/2197)
- >>> (e3(1, -3) * 3).order()
- oo
- >>> e2 = EllipticCurve(-2, 0, 0, 1, 1)
- >>> p = e2(-1,1)
- >>> q = e2(0, -1)
- >>> p+q
- (4, 8)
- >>> p-q
- (1, 0)
- >>> 3*p-5*q
- (328/361, -2800/6859)
- """
- @staticmethod
- def point_at_infinity(curve):
- return EllipticCurvePoint(0, 1, 0, curve)
- def __init__(self, x, y, z, curve):
- dom = curve._domain.convert
- self.x = dom(x)
- self.y = dom(y)
- self.z = dom(z)
- self._curve = curve
- self._domain = self._curve._domain
- if not self._curve.__contains__(self):
- raise ValueError("The curve does not contain this point")
- def __add__(self, p):
- if self.z == 0:
- return p
- if p.z == 0:
- return self
- x1, y1 = self.x/self.z, self.y/self.z
- x2, y2 = p.x/p.z, p.y/p.z
- a1 = self._curve._a1
- a2 = self._curve._a2
- a3 = self._curve._a3
- a4 = self._curve._a4
- a6 = self._curve._a6
- if x1 != x2:
- slope = (y1 - y2) / (x1 - x2)
- yint = (y1 * x2 - y2 * x1) / (x2 - x1)
- else:
- if (y1 + y2) == 0:
- return self.point_at_infinity(self._curve)
- slope = (3 * x1**2 + 2*a2*x1 + a4 - a1*y1) / (a1 * x1 + a3 + 2 * y1)
- yint = (-x1**3 + a4*x1 + 2*a6 - a3*y1) / (a1*x1 + a3 + 2*y1)
- x3 = slope**2 + a1*slope - a2 - x1 - x2
- y3 = -(slope + a1) * x3 - yint - a3
- return self._curve(x3, y3, 1)
- def __lt__(self, other):
- return (self.x, self.y, self.z) < (other.x, other.y, other.z)
- def __mul__(self, n):
- n = as_int(n)
- r = self.point_at_infinity(self._curve)
- if n == 0:
- return r
- if n < 0:
- return -self * -n
- p = self
- while n:
- if n & 1:
- r = r + p
- n >>= 1
- p = p + p
- return r
- def __rmul__(self, n):
- return self * n
- def __neg__(self):
- return EllipticCurvePoint(self.x, -self.y - self._curve._a1*self.x - self._curve._a3, self.z, self._curve)
- def __repr__(self):
- if self.z == 0:
- return 'O'
- dom = self._curve._domain
- try:
- return '({}, {})'.format(dom.to_sympy(self.x), dom.to_sympy(self.y))
- except TypeError:
- pass
- return '({}, {})'.format(self.x, self.y)
- def __sub__(self, other):
- return self.__add__(-other)
- def order(self):
- """
- Return point order n where nP = 0.
- """
- if self.z == 0:
- return 1
- if self.y == 0: # P = -P
- return 2
- p = self * 2
- if p.y == -self.y: # 2P = -P
- return 3
- i = 2
- if self._domain != QQ:
- while int(p.x) == p.x and int(p.y) == p.y:
- p = self + p
- i += 1
- if p.z == 0:
- return i
- return oo
- while p.x.numerator == p.x and p.y.numerator == p.y:
- p = self + p
- i += 1
- if i > 12:
- return oo
- if p.z == 0:
- return i
- return oo
|