def test_composed_op(): from random import randrange for N in range(100): while 1: D1 = randrange(10) D2 = randrange(10) F = fmpz_poly([randrange(-2, 3) for n in range(D1)]) G = fmpz_poly([randrange(-2, 3) for n in range(D2)]) if F.degree() >= 1 and G.degree() >= 1: break H = composed_op(F, G, operator.add) for x, r in F.roots(): for y, r in G.roots(): v = H(x + y) assert v.contains(0) H = composed_op(F, G, operator.sub) for x, r in F.roots(): for y, r in G.roots(): v = H(x - y) assert v.contains(0) H = composed_op(F, G, operator.mul) for x, r in F.roots(): for y, r in G.roots(): v = H(x * y) assert v.contains(0) if G[0] != 0: H = composed_op(F, G, operator.truediv) for x, r in F.roots(): for y, r in G.roots(): v = H(x / y) assert v.contains(0)
def __init__(self, v=0, _minpoly=None, _enclosure=None): """ An *alg* instance can be created from an existing *alg* (this creates a copy), from a Python integer, or from a FLINT *fmpz* or *fmpq*: >>> alg(3) 3.00000 (deg 1) >>> alg(alg(3)) 3.00000 (deg 1) >>> alg(fmpq(2,3)) 0.666667 (deg 1) It is also possible to create an *alg* instance *unsafely* from the internal representation consisting of a minimal polynomial and an enclosure: >>> alg(_minpoly=fmpz_poly([-1,-1,1]), _enclosure=arb("1.618 +/- 0.001")) 1.61803 (deg 2) The minimal polynomial must be an *fmpz_poly* that is irreducible and normalized (no factors, GCD content removed, positive leading coefficient), and the enclosure must isolate a unique root of this polynomial. Warning: these properties are *not* checked. Providing invalid input will silently produce wrong results in subsequent computations. """ if _minpoly is not None: assert type(_minpoly) == fmpz_poly self._minpoly = _minpoly self._enclosure = _enclosure elif isinstance(v, (int, fmpz)): self._minpoly = fmpz_poly([-v, 1]) self._enclosure = None elif isinstance(v, fmpq): self._minpoly = fmpz_poly([-v.p, v.q]) self._enclosure = None elif isinstance(v, alg): self._minpoly = v._minpoly self._enclosure = v._enclosure elif isinstance(v, float): import math m, e = math.frexp(v) m = int(m * 2.0**53) e -= 53 v = alg(m) * alg(2)**e self._minpoly = v._minpoly self._enclosure = v._enclosure elif isinstance(v, complex): v = alg(v.real) + alg(v.imag) * alg.i() self._minpoly = v._minpoly self._enclosure = v._enclosure else: raise NotImplementedError self._hash = None
def root(self, n): assert n >= 0 if n == 0: raise ZeroDivisionError if n == 1: return self F = self._minpoly d = F.degree() check_degree_limit(d * n) H = [0] * (d * n + 1) H[::n] = list(F) H = fmpz_poly(H) c, factors = H.factor() prec = 64 orig_prec = ctx.prec try: while 1: ctx.prec = prec x = self.enclosure(pretty=True) z = acb(x).root(n) #if not z.imag == 0: # eps = arb(0, z.rad()) # z += acb(eps,eps) maybe_root = [ fac for fac, mult in factors if fac(z).contains(0) ] if len(maybe_root) == 1: fac = maybe_root[0] z2 = alg._validate_root_enclosure(fac, z) if z2 is not None: return alg(_minpoly=fac, _enclosure=z2) prec *= 2 finally: ctx.prec = orig_prec
def pow(self, n): if self.is_rational(): c0 = self._minpoly[0] c1 = self._minpoly[1] if abs(n) > 2: check_bits_limit(c0.height_bits() * abs(n)) check_bits_limit(c1.height_bits() * abs(n)) if n >= 0: return alg(fmpq(c0**n) / (-c1)**n) else: return alg((-c1)**(-n) / fmpq(c0**(-n))) if n == 0: return alg(1) if n == 1: return self if n < 0: return (1 / self).pow(-n) # fast detection of perfect powers pol, d = self.minpoly().deflation() if d % n == 0: # todo: is factoring needed here? H = list(self.minpoly()) H = H[::n] H = fmpz_poly(H) c, factors = H.factor() prec = 64 orig_prec = ctx.prec try: while 1: ctx.prec = prec x = self.enclosure(pretty=True) z = acb(x)**n maybe_root = [ fac for fac, mult in factors if fac(z).contains(0) ] if len(maybe_root) == 1: fac = maybe_root[0] z2 = alg._validate_root_enclosure(fac, z) if z2 is not None: return alg(_minpoly=fac, _enclosure=z2) prec *= 2 finally: ctx.prec = orig_prec if n == 2: return self * self v = self.pow(n // 2) v = v * v if n % 2: v *= self return v
def gaussian_integer(a, b): if b == 0: return alg(a) a = fmpz(a) b = fmpz(b) orig = ctx.prec ctx.prec = 64 try: z = acb(a, b) finally: ctx.prec = orig res = alg(_minpoly=fmpz_poly([a**2 + b**2, -2 * a, 1]), _enclosure=z) return res
def test_sage_examples_slow(self): if 0: x = fmpz_poly([0, 1]) pol = x**10 + x**9 - x**7 - x**6 - x**5 - x**4 - x**3 + x + 1 root = arb("[1.17628081825991751 +/- 3.46e-18]") alpha = alg(_minpoly=pol, _enclosure=root) lhs = alpha**630 - 1 rhs_num = (alpha**315 - 1) * (alpha**210 - 1) * ( alpha**126 - 1)**2 * (alpha**90 - 1) * (alpha**3 - 1)**3 * ( alpha**2 - 1)**5 * (alpha - 1)**3 rhs_den = (alpha**35 - 1) * (alpha**15 - 1)**2 * ( alpha**14 - 1)**2 * (alpha**5 - 1)**6 * alpha**68 rhs = rhs_num / rhs_den assert lhs == rhs
def test_sage_examples(self): # from the sage qqbar docs rt17 = alg(17).sqrt() rt2 = alg(2).sqrt() eps = (17 + rt17).sqrt() epss = (17 - rt17).sqrt() delta = rt17 - 1 alpha = (34 + 6 * rt17 + rt2 * delta * epss - 8 * rt2 * eps).sqrt() beta = 2 * (17 + 3 * rt17 - 2 * rt2 * eps - rt2 * epss).sqrt() x = rt2 * (15 + rt17 + rt2 * (alpha + epss)).sqrt() / 8 y = rt2 * (epss**2 - rt2 * (alpha + epss)).sqrt() / 8 cx, cy = alg(1), alg(0) #for i in range(34): # slow for i in range(3): cx, cy = x * cx - y * cy, x * cy + y * cx ax = fmpz_poly([0, 1]) x2 = alg.polynomial_roots(256 * ax**8 - 128 * ax**7 - 448 * ax**6 + 192 * ax**5 + 240 * ax**4 - 80 * ax**3 - 40 * ax**2 + 8 * ax + 1)[0][0] y2 = (1 - x2**2).sqrt() assert x == x2 assert y == y2
def imag(self): if self.is_real(): return alg(0) return (self - self.conjugate()) / alg(_minpoly=fmpz_poly([4, 0, 1]), _enclosure=acb(0, 2))
def i(): return alg(_minpoly=fmpz_poly([1, 0, 1]), _enclosure=acb(0, 1))
def guess(z, deg=None, verbose=False, try_smaller=True, check=True): """ Use LLL to try to find an algebraic number matching the real or complex enclosure *z* (given as a *flint.arb* or *flint.acb*). Returns *None* if unsuccessful. To determine the number of bits to use in the LLL matrix, the algorithm accounts for the accuracy of the input as well as the current Arb working precision (*flint.ctx.prec*). The maximum degree is determined by the *deg* parameter. By default the degree is set to roughly the square root of the working precision. If *try_smaller* is set, then the guessing is attempted recursively with lower degree bounds to speed up detection of lower-degree numbers. If *check* is True (default), the algorithm verifies that the result is contained in the complex interval *z*. (At high precision, this reduces the likelihood of spurious results.) """ z = acb(z) if not z.is_finite(): return None if deg is None: deg = max(1, int(ctx.prec**0.5)) if try_smaller and deg > 8: g = alg.guess(z, deg // 4, verbose=verbose, try_smaller=True, check=check) if g is not None: return g # todo: early detect exact (dyadic) real and imaginary parts? # (avoid reliance on enclosure(pretty=True) prec = ctx.prec z = acb(z) zpow = [z**i for i in range(deg + 1)] magn = max(abs(t) for t in zpow) if magn >= arb(2)**prec: return None # too large magnrad = max(abs(t.rad()) for t in zpow) if magnrad >= 0.125: return None # too imprecise magnrad = max(2**(max(1, prec // 20)) * magnrad, arb(2)**(-2 * prec)) scale = 1 / magnrad nonreal = z.imag != 0 A = fmpz_mat(deg + 1, deg + 2 + nonreal) for i in range(deg + 1): A[i, i] = 1 for i in range(deg + 1): v = zpow[i] * scale # fixme: don't use unique_fmpz A[i, deg + 1] = v.real.mid().floor().unique_fmpz() if nonreal: A[i, deg + 2] = v.imag.mid().floor().unique_fmpz() A = A.lll() poly = fmpz_poly([A[0, i] for i in range(deg + 1)]) if verbose: print("LLL-reduced matrix:") print(A) print("poly(z) =", poly(z)) if not check: candidates = alg.polynomial_roots(poly) or [(alg(0), 1)] return min([cand for (cand, mult) in candidates], key=lambda x: abs(x.enclosure() - z).mid()) if not poly(z).contains(0): return None orig = ctx.prec try: candidates = alg.polynomial_roots(poly) while ctx.prec <= 16 * orig: for cand, mult in candidates: r = cand.enclosure(pretty=True) if z.contains(r): return cand ctx.prec *= 2 finally: ctx.prec = orig return None