utilities.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. """Utilities for algebraic number theory. """
  2. from sympy.core.sympify import sympify
  3. from sympy.ntheory.factor_ import factorint
  4. from sympy.polys.domains.rationalfield import QQ
  5. from sympy.polys.domains.integerring import ZZ
  6. from sympy.polys.matrices.exceptions import DMRankError
  7. from sympy.polys.numberfields.minpoly import minpoly
  8. from sympy.printing.lambdarepr import IntervalPrinter
  9. from sympy.utilities.decorator import public
  10. from sympy.utilities.lambdify import lambdify
  11. from mpmath import mp
  12. def is_rat(c):
  13. r"""
  14. Test whether an argument is of an acceptable type to be used as a rational
  15. number.
  16. Explanation
  17. ===========
  18. Returns ``True`` on any argument of type ``int``, :ref:`ZZ`, or :ref:`QQ`.
  19. See Also
  20. ========
  21. is_int
  22. """
  23. # ``c in QQ`` is too accepting (e.g. ``3.14 in QQ`` is ``True``),
  24. # ``QQ.of_type(c)`` is too demanding (e.g. ``QQ.of_type(3)`` is ``False``).
  25. #
  26. # Meanwhile, if gmpy2 is installed then ``ZZ.of_type()`` accepts only
  27. # ``mpz``, not ``int``, so we need another clause to ensure ``int`` is
  28. # accepted.
  29. return isinstance(c, int) or ZZ.of_type(c) or QQ.of_type(c)
  30. def is_int(c):
  31. r"""
  32. Test whether an argument is of an acceptable type to be used as an integer.
  33. Explanation
  34. ===========
  35. Returns ``True`` on any argument of type ``int`` or :ref:`ZZ`.
  36. See Also
  37. ========
  38. is_rat
  39. """
  40. # If gmpy2 is installed then ``ZZ.of_type()`` accepts only
  41. # ``mpz``, not ``int``, so we need another clause to ensure ``int`` is
  42. # accepted.
  43. return isinstance(c, int) or ZZ.of_type(c)
  44. def get_num_denom(c):
  45. r"""
  46. Given any argument on which :py:func:`~.is_rat` is ``True``, return the
  47. numerator and denominator of this number.
  48. See Also
  49. ========
  50. is_rat
  51. """
  52. r = QQ(c)
  53. return r.numerator, r.denominator
  54. @public
  55. def extract_fundamental_discriminant(a):
  56. r"""
  57. Extract a fundamental discriminant from an integer *a*.
  58. Explanation
  59. ===========
  60. Given any rational integer *a* that is 0 or 1 mod 4, write $a = d f^2$,
  61. where $d$ is either 1 or a fundamental discriminant, and return a pair
  62. of dictionaries ``(D, F)`` giving the prime factorizations of $d$ and $f$
  63. respectively, in the same format returned by :py:func:`~.factorint`.
  64. A fundamental discriminant $d$ is different from unity, and is either
  65. 1 mod 4 and squarefree, or is 0 mod 4 and such that $d/4$ is squarefree
  66. and 2 or 3 mod 4. This is the same as being the discriminant of some
  67. quadratic field.
  68. Examples
  69. ========
  70. >>> from sympy.polys.numberfields.utilities import extract_fundamental_discriminant
  71. >>> print(extract_fundamental_discriminant(-432))
  72. ({3: 1, -1: 1}, {2: 2, 3: 1})
  73. For comparison:
  74. >>> from sympy import factorint
  75. >>> print(factorint(-432))
  76. {2: 4, 3: 3, -1: 1}
  77. Parameters
  78. ==========
  79. a: int, must be 0 or 1 mod 4
  80. Returns
  81. =======
  82. Pair ``(D, F)`` of dictionaries.
  83. Raises
  84. ======
  85. ValueError
  86. If *a* is not 0 or 1 mod 4.
  87. References
  88. ==========
  89. .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
  90. (See Prop. 5.1.3)
  91. """
  92. if a % 4 not in [0, 1]:
  93. raise ValueError('To extract fundamental discriminant, number must be 0 or 1 mod 4.')
  94. if a == 0:
  95. return {}, {0: 1}
  96. if a == 1:
  97. return {}, {}
  98. a_factors = factorint(a)
  99. D = {}
  100. F = {}
  101. # First pass: just make d squarefree, and a/d a perfect square.
  102. # We'll count primes (and units! i.e. -1) that are 3 mod 4 and present in d.
  103. num_3_mod_4 = 0
  104. for p, e in a_factors.items():
  105. if e % 2 == 1:
  106. D[p] = 1
  107. if p % 4 == 3:
  108. num_3_mod_4 += 1
  109. if e >= 3:
  110. F[p] = (e - 1) // 2
  111. else:
  112. F[p] = e // 2
  113. # Second pass: if d is cong. to 2 or 3 mod 4, then we must steal away
  114. # another factor of 4 from f**2 and give it to d.
  115. even = 2 in D
  116. if even or num_3_mod_4 % 2 == 1:
  117. e2 = F[2]
  118. assert e2 > 0
  119. if e2 == 1:
  120. del F[2]
  121. else:
  122. F[2] = e2 - 1
  123. D[2] = 3 if even else 2
  124. return D, F
  125. @public
  126. class AlgIntPowers:
  127. r"""
  128. Compute the powers of an algebraic integer.
  129. Explanation
  130. ===========
  131. Given an algebraic integer $\theta$ by its monic irreducible polynomial
  132. ``T`` over :ref:`ZZ`, this class computes representations of arbitrarily
  133. high powers of $\theta$, as :ref:`ZZ`-linear combinations over
  134. $\{1, \theta, \ldots, \theta^{n-1}\}$, where $n = \deg(T)$.
  135. The representations are computed using the linear recurrence relations for
  136. powers of $\theta$, derived from the polynomial ``T``. See [1], Sec. 4.2.2.
  137. Optionally, the representations may be reduced with respect to a modulus.
  138. Examples
  139. ========
  140. >>> from sympy import Poly, cyclotomic_poly
  141. >>> from sympy.polys.numberfields.utilities import AlgIntPowers
  142. >>> T = Poly(cyclotomic_poly(5))
  143. >>> zeta_pow = AlgIntPowers(T)
  144. >>> print(zeta_pow[0])
  145. [1, 0, 0, 0]
  146. >>> print(zeta_pow[1])
  147. [0, 1, 0, 0]
  148. >>> print(zeta_pow[4]) # doctest: +SKIP
  149. [-1, -1, -1, -1]
  150. >>> print(zeta_pow[24]) # doctest: +SKIP
  151. [-1, -1, -1, -1]
  152. References
  153. ==========
  154. .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
  155. """
  156. def __init__(self, T, modulus=None):
  157. """
  158. Parameters
  159. ==========
  160. T : :py:class:`~.Poly`
  161. The monic irreducible polynomial over :ref:`ZZ` defining the
  162. algebraic integer.
  163. modulus : int, None, optional
  164. If not ``None``, all representations will be reduced w.r.t. this.
  165. """
  166. self.T = T
  167. self.modulus = modulus
  168. self.n = T.degree()
  169. self.powers_n_and_up = [[-c % self for c in reversed(T.rep.rep)][:-1]]
  170. self.max_so_far = self.n
  171. def red(self, exp):
  172. return exp if self.modulus is None else exp % self.modulus
  173. def __rmod__(self, other):
  174. return self.red(other)
  175. def compute_up_through(self, e):
  176. m = self.max_so_far
  177. if e <= m: return
  178. n = self.n
  179. r = self.powers_n_and_up
  180. c = r[0]
  181. for k in range(m+1, e+1):
  182. b = r[k-1-n][n-1]
  183. r.append(
  184. [c[0]*b % self] + [
  185. (r[k-1-n][i-1] + c[i]*b) % self for i in range(1, n)
  186. ]
  187. )
  188. self.max_so_far = e
  189. def get(self, e):
  190. n = self.n
  191. if e < 0:
  192. raise ValueError('Exponent must be non-negative.')
  193. elif e < n:
  194. return [1 if i == e else 0 for i in range(n)]
  195. else:
  196. self.compute_up_through(e)
  197. return self.powers_n_and_up[e - n]
  198. def __getitem__(self, item):
  199. return self.get(item)
  200. @public
  201. def coeff_search(m, R):
  202. r"""
  203. Generate coefficients for searching through polynomials.
  204. Explanation
  205. ===========
  206. Lead coeff is always non-negative. Explore all combinations with coeffs
  207. bounded in absolute value before increasing the bound. Skip the all-zero
  208. list, and skip any repeats. See examples.
  209. Examples
  210. ========
  211. >>> from sympy.polys.numberfields.utilities import coeff_search
  212. >>> cs = coeff_search(2, 1)
  213. >>> C = [next(cs) for i in range(13)]
  214. >>> print(C)
  215. [[1, 1], [1, 0], [1, -1], [0, 1], [2, 2], [2, 1], [2, 0], [2, -1], [2, -2],
  216. [1, 2], [1, -2], [0, 2], [3, 3]]
  217. Parameters
  218. ==========
  219. m : int
  220. Length of coeff list.
  221. R : int
  222. Initial max abs val for coeffs (will increase as search proceeds).
  223. Returns
  224. =======
  225. generator
  226. Infinite generator of lists of coefficients.
  227. """
  228. R0 = R
  229. c = [R] * m
  230. while True:
  231. if R == R0 or R in c or -R in c:
  232. yield c[:]
  233. j = m - 1
  234. while c[j] == -R:
  235. j -= 1
  236. c[j] -= 1
  237. for i in range(j + 1, m):
  238. c[i] = R
  239. for j in range(m):
  240. if c[j] != 0:
  241. break
  242. else:
  243. R += 1
  244. c = [R] * m
  245. def supplement_a_subspace(M):
  246. r"""
  247. Extend a basis for a subspace to a basis for the whole space.
  248. Explanation
  249. ===========
  250. Given an $n \times r$ matrix *M* of rank $r$ (so $r \leq n$), this function
  251. computes an invertible $n \times n$ matrix $B$ such that the first $r$
  252. columns of $B$ equal *M*.
  253. This operation can be interpreted as a way of extending a basis for a
  254. subspace, to give a basis for the whole space.
  255. To be precise, suppose you have an $n$-dimensional vector space $V$, with
  256. basis $\{v_1, v_2, \ldots, v_n\}$, and an $r$-dimensional subspace $W$ of
  257. $V$, spanned by a basis $\{w_1, w_2, \ldots, w_r\}$, where the $w_j$ are
  258. given as linear combinations of the $v_i$. If the columns of *M* represent
  259. the $w_j$ as such linear combinations, then the columns of the matrix $B$
  260. computed by this function give a new basis $\{u_1, u_2, \ldots, u_n\}$ for
  261. $V$, again relative to the $\{v_i\}$ basis, and such that $u_j = w_j$
  262. for $1 \leq j \leq r$.
  263. Examples
  264. ========
  265. Note: The function works in terms of columns, so in these examples we
  266. print matrix transposes in order to make the columns easier to inspect.
  267. >>> from sympy.polys.matrices import DM
  268. >>> from sympy import QQ, FF
  269. >>> from sympy.polys.numberfields.utilities import supplement_a_subspace
  270. >>> M = DM([[1, 7, 0], [2, 3, 4]], QQ).transpose()
  271. >>> print(supplement_a_subspace(M).to_Matrix().transpose())
  272. Matrix([[1, 7, 0], [2, 3, 4], [1, 0, 0]])
  273. >>> M2 = M.convert_to(FF(7))
  274. >>> print(M2.to_Matrix().transpose())
  275. Matrix([[1, 0, 0], [2, 3, -3]])
  276. >>> print(supplement_a_subspace(M2).to_Matrix().transpose())
  277. Matrix([[1, 0, 0], [2, 3, -3], [0, 1, 0]])
  278. Parameters
  279. ==========
  280. M : :py:class:`~.DomainMatrix`
  281. The columns give the basis for the subspace.
  282. Returns
  283. =======
  284. :py:class:`~.DomainMatrix`
  285. This matrix is invertible and its first $r$ columns equal *M*.
  286. Raises
  287. ======
  288. DMRankError
  289. If *M* was not of maximal rank.
  290. References
  291. ==========
  292. .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*
  293. (See Sec. 2.3.2.)
  294. """
  295. n, r = M.shape
  296. # Let In be the n x n identity matrix.
  297. # Form the augmented matrix [M | In] and compute RREF.
  298. Maug = M.hstack(M.eye(n, M.domain))
  299. R, pivots = Maug.rref()
  300. if pivots[:r] != tuple(range(r)):
  301. raise DMRankError('M was not of maximal rank')
  302. # Let J be the n x r matrix equal to the first r columns of In.
  303. # Since M is of rank r, RREF reduces [M | In] to [J | A], where A is the product of
  304. # elementary matrices Ei corresp. to the row ops performed by RREF. Since the Ei are
  305. # invertible, so is A. Let B = A^(-1).
  306. A = R[:, r:]
  307. B = A.inv()
  308. # Then B is the desired matrix. It is invertible, since B^(-1) == A.
  309. # And A * [M | In] == [J | A]
  310. # => A * M == J
  311. # => M == B * J == the first r columns of B.
  312. return B
  313. @public
  314. def isolate(alg, eps=None, fast=False):
  315. """
  316. Find a rational isolating interval for a real algebraic number.
  317. Examples
  318. ========
  319. >>> from sympy import isolate, sqrt, Rational
  320. >>> print(isolate(sqrt(2))) # doctest: +SKIP
  321. (1, 2)
  322. >>> print(isolate(sqrt(2), eps=Rational(1, 100)))
  323. (24/17, 17/12)
  324. Parameters
  325. ==========
  326. alg : str, int, :py:class:`~.Expr`
  327. The algebraic number to be isolated. Must be a real number, to use this
  328. particular function. However, see also :py:meth:`.Poly.intervals`,
  329. which isolates complex roots when you pass ``all=True``.
  330. eps : positive element of :ref:`QQ`, None, optional (default=None)
  331. Precision to be passed to :py:meth:`.Poly.refine_root`
  332. fast : boolean, optional (default=False)
  333. Say whether fast refinement procedure should be used.
  334. (Will be passed to :py:meth:`.Poly.refine_root`.)
  335. Returns
  336. =======
  337. Pair of rational numbers defining an isolating interval for the given
  338. algebraic number.
  339. See Also
  340. ========
  341. .Poly.intervals
  342. """
  343. alg = sympify(alg)
  344. if alg.is_Rational:
  345. return (alg, alg)
  346. elif not alg.is_real:
  347. raise NotImplementedError(
  348. "complex algebraic numbers are not supported")
  349. func = lambdify((), alg, modules="mpmath", printer=IntervalPrinter())
  350. poly = minpoly(alg, polys=True)
  351. intervals = poly.intervals(sqf=True)
  352. dps, done = mp.dps, False
  353. try:
  354. while not done:
  355. alg = func()
  356. for a, b in intervals:
  357. if a <= alg.a and alg.b <= b:
  358. done = True
  359. break
  360. else:
  361. mp.dps *= 2
  362. finally:
  363. mp.dps = dps
  364. if eps is not None:
  365. a, b = poly.refine_root(a, b, eps=eps, fast=fast)
  366. return (a, b)