newsynth-0.2: Quantum/Synthesis/Diophantine.hs
-- | This module provides some number-theoretic functions,
-- particularly functions for solving the Diophantine equation
--
-- \[center /t/[sup †]/t/ = ξ,]
--
-- where ξ ∈ ℤ[√2] and /t/ ∈ ℤ[ω], or ξ ∈ [bold D][√2] and /t/ ∈ [bold D][ω].
--
-- In general, solving this equation can be hard, as it depends on the
-- ability to factor the integer /n/ = ξ[sup •]ξ into primes. We
-- formulate the solution as a step computation (see
-- "Quantum.Synthesis.StepComp"), so that the caller can dynamically
-- determine how much time to spend on solving the equation, or can
-- attempt to solve several such equations in parallel.
--
-- In many cases, even a partial factorization of /n/ is sufficient to
-- determine that no solution exists. This implementation is written
-- to take advantage of such cases.
module Quantum.Synthesis.Diophantine (
-- * Diophantine solvers
diophantine,
diophantine_dyadic,
diophantine_associate,
-- * Factoring
find_factor,
relatively_prime_factors,
-- * Computations in ℤ[sub /n/]
power_mod,
root_of_negative_one,
root_mod,
) where
import Quantum.Synthesis.StepComp
import Quantum.Synthesis.EuclideanDomain
import Quantum.Synthesis.Ring
import System.Random
import Control.Exception
-- ----------------------------------------------------------------------
-- * Diophantine solvers
-- | Given ξ ∈ ℤ[√2], find /t/ ∈ ℤ[ω] such that /t/[sup †]/t/ = ξ, if
-- such /t/ exists, or return 'Nothing' otherwise.
diophantine :: (RandomGen g) => g -> ZRootTwo -> StepComp (Maybe ZOmega)
diophantine g xi
| xi == 0 = return (Just 0)
| xi < 0 = return Nothing
| adj2 xi < 0 = return Nothing
| otherwise = do
t <- diophantine_associate g xi
case t of
Nothing -> return Nothing
Just t -> do
let xi_associate = zroottwo_of_zomega (adj t * t)
let u = euclid_div xi xi_associate
case zroottwo_root u of
Nothing -> return Nothing
Just v -> return (Just (fromZRootTwo v * t))
-- | Given an element ξ ∈ [bold D][√2], find /t/ ∈ [bold D][ω] such
-- that /t/[sup †]/t/ = ξ, if such /t/ exists, or return 'Nothing'
-- otherwise.
-- Implementation note: In the reduction from [bold D][√2] to ℤ[√2],
-- we can multiply by a power of λ√2 instead of √2. This has the same
-- effect of reducing the denominator exponent to 0 (note that λ is a
-- unit of the ring ℤ[√2]), but has the additional advantage that λ√2
-- (unlike √2) is doubly positive, thereby preserving solvability of
-- the Diophantine equation.
--
-- Similarly, in translating the solution back from ℤ[ω] to
-- [bold D][ω], we use the fact that 1\/(λ√2) = /u/[sup †]/u/, where
-- /u/ = (ω - /i/)\/√2. Also note that /u/ = δ⁻¹, where δ = 1 + ω.
diophantine_dyadic :: (RandomGen g) => g -> DRootTwo -> StepComp (Maybe DOmega)
diophantine_dyadic g xi = do
let k = denomexp xi
let (k',k'') = k `divMod` 2
let xi' = to_whole ((lambda * roottwo)^k'' * 2^k' * xi)
t' <- diophantine g xi'
case t' of
Nothing -> return Nothing
Just t' -> return (Just (u^k'' * roothalf^k' * from_whole t'))
where
u = roothalf * (omega - i)
lambda = 1 + roottwo
-- | Given ξ ∈ ℤ[√2], find /t/ ∈ ℤ[ω] such that /t/[sup †]/t/ ~ ξ, if
-- such /t/ exists, or 'Nothing' otherwise. Unlike 'diophantine', the
-- equation is only solved up to associates, i.e., up to a unit of the
-- ring.
diophantine_associate :: (RandomGen g) => g -> ZRootTwo -> StepComp (Maybe ZOmega)
diophantine_associate g xi
| xi == 0 = return (Just 0)
| otherwise = do
let d = euclid_gcd xi (adj2 xi)
let xi' = euclid_div xi d
res <- parallel_maybe (dioph_zroottwo_selfassociate g1 d) (dioph_zroottwo_assoc g2 xi')
case res of
Nothing -> return Nothing
Just (t1, t2) -> return (Just (t1*t2))
where
(g1, g2) = split g
-- ----------------------------------------------------------------------
-- * Factoring
-- | Given a positive composite integer /n/, find a non-trivial factor
-- of /n/ using a simple Pollard-rho method. The expected runtime is
-- O(√/p/), where /p/ is the size of the smallest prime factor. If
-- /n/ is not composite (i.e., if /n/ is prime or 1), this function
-- diverges.
find_factor :: (RandomGen g) => g -> Integer -> StepComp Integer
find_factor g n
| even n && n > 2 = return 2
| otherwise = tick >> aux 2 (f 2)
where
(a, g2) = randomR (1, n-1) g
f x = (x^2 + a) `mod` n
aux x y
| d == 1 = tick >> aux (f x) (f (f y))
| d == n = find_factor g2 n
| otherwise = return d
where
d = gcd (x-y) n
-- | Given a factorization /n/ = /ab/ of some element of a Euclidean domain, find a factorization of /n/ into relatively prime factors,
--
-- \[center /n/ = /u/ /c/[sub 1][sup /k/[sub 1]] ⋅ … ⋅ /c/[sub /m/][sup /k/[sub /m/]],]
--
-- where /m/ ≥ 2, /u/ is a unit, and /c/[sub 1], …, /c/[sub /m/] are
-- pairwise relatively prime.
--
-- While this is not quite a prime factorization of /n/, it can be a
-- useful intermediate step for computations that proceed by recursion
-- on relatively prime factors (such as Euler's φ-function, the
-- solution of Diophantine equations, etc.).
relatively_prime_factors :: (EuclideanDomain a) => a -> a -> (a, [(a, Integer)])
relatively_prime_factors a b = aux 1 [a,b] [] where
aux u [] fs = (u, fs)
aux u (h:t) fs
| is_unit h = aux (h*u) t fs
aux u (h:t) fs = aux (u'*u) (hs ++ t) fs' where
(u', hs, fs') = aux2 h fs
aux2 h [] = (1, [], [(h,1)])
aux2 h ((f,k) : fs)
| euclid_associates h f = (u', [], (f,k+1) : fs)
| is_unit d = (u, hs, (f,k) : fs')
| otherwise = (1, [h `euclid_div` d, d] ++ replicate (fromInteger k) (f `euclid_div` d) ++ replicate (fromInteger k) d, fs)
where
d = euclid_gcd h f
(u, hs, fs') = aux2 h fs
u' = h `euclid_div` f
-- ----------------------------------------------------------------------
-- * Computations in ℤ[sub /n/]
-- | Modular exponentiation, using the method of repeated squaring.
-- 'power_mod' /a/ /k/ /n/ computes /a/[sup /k/] (mod /n/).
power_mod :: Integer -> Integer -> Integer -> Integer
power_mod a k n
| k == 0 = 1
| k == 1 = a `mod` n
| even k = (b*b) `mod` n
| otherwise = (b*b*a) `mod` n
where
b = power_mod a (k `div` 2) n
-- | Compute a root of −1 in ℤ[sub /n/], where /n/ > 0. If /n/ is a
-- positive prime satisfying /n/ ≡ 1 (mod 4), this succeeds within an
-- expected number of 2 ticks. Otherwise, it probably diverges.
--
-- As a special case, if this function notices that /n/ is not prime,
-- then it diverges without doing any additional work.
root_of_negative_one :: (RandomGen g) => g -> Integer -> StepComp Integer
root_of_negative_one g n = do
tick
let (b, g') = randomR (1, n-1) g
let h = power_mod b ((n-1) `div` 4) n
let r = (h*h) `mod` n
if r == n-1 then
return h
else
if r /= 1 then
diverge
else
root_of_negative_one g' n
-- | Compute a root of /a/ in ℤ[sub /n/], where /n/ > 0. If /n/ is an
-- odd prime and /a/ is a non-zero square in ℤ[sub /n/], then this
-- succeeds in an expected number of 2 ticks. Otherwise, it probably
-- diverges.
root_mod :: (RandomGen g) => g -> Integer -> Integer -> StepComp Integer
root_mod g n a
| a `mod` n == -1 -- handle this special case more efficiently
= root_of_negative_one g n
| otherwise = tick >> res
where
(b, g') = randomR (0, n-1) g
(r,s) = (2*b `mod` n, b^2-a `mod` n)
(c,d) = pow (1,0) ((n-1) `div` 2)
res = case inv_mod n c of
Just c'
| (t1^2 - a) `mod` n == 0 -> return t1
where
t = (1-d) * c'
t1 = (t+b) `mod` n
_ -> root_mod g' n a
-- | 'mul' performs a multiplication in the ring
-- ℤ[sub /n/][t]\/(/t/²+/rt/+/s/). The elements /at/+/b/ are
-- represented as pairs (/a/,/b/).
mul :: (Integer, Integer) -> (Integer, Integer) -> (Integer, Integer)
mul (a,b) (c,d) = (a'',b'') where
(x,y,z) = (a*c, a*d+b*c, b*d) -- multiply polynomials
(a',b') = (y - x*r,z - x*s) -- reduce modulo /t/²+/rt/+/s/
(a'',b'') = (a' `mod` n, b' `mod` n) -- reduce modulo /n/
-- | 'pow' takes a power in the ring
-- ℤ[sub /n/][t]\/(/t/²+/rt/+/s/. The elements /at/+/b/ are
-- represented as pairs (/a/,/b/).
pow :: (Integer, Integer) -> Integer -> (Integer, Integer)
pow x m
| m <= 0 = (0,1)
| odd m = x `mul` (x `pow` (m-1))
| otherwise = y `mul` y where y = x `pow` (m `div` 2)
-- ----------------------------------------------------------------------
-- * Implementation details
-- $ Our implementation of the top-level Diophantine equation solvers
-- proceeds through a series of special cases. The following functions
-- handle the special cases, and are not of independent interest.
-- ----------------------------------------------------------------------
-- ** Case: ξ is an integer
-- | Given an integer /n/ ∈ ℤ, attempt to find /t/ ∈ ℤ[ω] such that
-- /t/[sup †]/t/ ~ /n/, or return 'Nothing' if no such /t/ exists.
--
-- This function is optimized for the case when /n/ is prime, and
-- succeeds in an expected number of 2 ticks in this case. If /n/ is
-- not prime, this function probably diverges.
dioph_int_assoc_prime :: (RandomGen g) => g -> Integer -> StepComp (Maybe ZOmega)
dioph_int_assoc_prime g n
| n < 0 = dioph_int_assoc_prime g (-n)
| n == 0 = return (Just 0)
| n == 2 = return (Just roottwo)
| n_mod_4 == 1 = do
h <- root_of_negative_one g n
let t = euclid_gcd (fromInteger h+i) (fromInteger n) :: ZOmega
assert (adj t * t == fromInteger n) $ return (Just t)
| n_mod_8 == 3 = do
h <- root_mod g n (-2)
let t = euclid_gcd (fromInteger h+i*roottwo) (fromInteger n) :: ZOmega
assert (adj t * t == fromInteger n) $ return (Just t)
| n_mod_8 == 7 = do
h <- root_mod g n 2
-- if n is prime, then 2 is a square. Conversely, if 2 is a
-- square, even if n is not prime, it implies that the Diophantine
-- equation has no solution. Because in this case, 2 is a square
-- for every prime divisor of n, so each such divisor must be
-- congruent to 1 or 7 (mod 8), so there must be at least one
-- prime divisor that occurs as an odd power and is congruent to 7
-- mod n.
return Nothing
where
n_mod_4 = n `mod` 4
n_mod_8 = n `mod` 8
-- | Given an integer /n/ ∈ ℤ, find /t/ ∈ ℤ[ω] such that /t/[sup †]/t/
-- ~ /n/, if such /t/ exists, or return 'Nothing' if no such /t/
-- exists.
--
-- This function alternately calls 'dioph_int_assoc_prime' and
-- attempts to factor /n/. Therefore, it will eventually succeed;
-- however, the runtime depends on how hard it is to factor ξ.
dioph_int_assoc :: (RandomGen g) => g -> Integer -> StepComp (Maybe ZOmega)
dioph_int_assoc g n
| n < 0 = dioph_int_assoc g (-n)
| n == 0 = return (Just 0)
| n == 1 = return (Just 1)
| otherwise = interleave prime_solver factor_solver where
interleave p f = do
p <- subtask 4 p
case p of
Done res -> return res
_ -> do
f <- subtask 1000 f
case f of
Done (a, k) -> do
let b = n `div` a
let (u, facs) = relatively_prime_factors a b
forward (k `div` 2) $ dioph_int_assoc_powers g3 facs
_ -> interleave p f
(g1, g') = split g
(g2, g3) = split g'
prime_solver = dioph_int_assoc_prime g1 n
factor_solver = with_counter $ speedup 30 $ find_factor g2 n
-- | Given a factorization /n/ = /q/[sub 1][sup /k/[sub 1]]⋅…⋅/q/[sub
-- /m/][sup /k/[sub /m/]] of an integer /n/, where /q/[sub 1], …,
-- /q/[sub /m/] are pairwise relatively prime, find /t/ ∈ ℤ[ω] such
-- that /t/[sup †]/t/ ~ /n/, if such /t/ exists, or return 'Nothing'
-- if no such /t/ exists.
dioph_int_assoc_powers :: (RandomGen g) => g -> [(Integer, Integer)] -> StepComp (Maybe ZOmega)
dioph_int_assoc_powers g facs = do
res <- parallel_list_maybe [dioph_int_assoc_power g (n,k) | (n,k) <- facs]
case res of
Nothing -> return Nothing
Just sols -> return (Just (product sols))
-- | Given a pair of integers (/n/, /k/), find /t/ ∈ ℤ[ω] such that
-- /t/[sup †]/t/ ~ /n/[sup /k/], if such /t/ exists, or return
-- 'Nothing' if no such /t/ exists.
dioph_int_assoc_power :: (RandomGen g) => g -> (Integer, Integer) -> StepComp (Maybe ZOmega)
dioph_int_assoc_power g (n,k)
| even k = return (Just (fromInteger (n^(k `div` 2))))
| otherwise = do
t <- dioph_int_assoc g n
case t of
Nothing -> return Nothing
Just t -> return (Just (t^k))
-- ----------------------------------------------------------------------
-- ** Case: ξ ~ ξ[sup •]
-- | Given ξ ∈ ℤ[√2] such that ξ ~ ξ[sup •], find /t/ ∈ ℤ[ω] such that
-- /t/[sup †]/t/ ~ ξ, if such /t/ exists, or return 'Nothing' if no
-- such /t/ exists.
dioph_zroottwo_selfassociate :: (RandomGen g) => g -> ZRootTwo -> StepComp (Maybe ZOmega)
dioph_zroottwo_selfassociate g xi
| xi == 0 = return (Just 0)
| otherwise = do
res <- dioph_int_assoc g n
case res of
Nothing -> return Nothing
Just t -> if euclid_divides roottwo r then
return (Just ((1+omega) * t))
else
return (Just t)
where
RootTwo a b = xi
n = gcd a b
r = euclid_div xi (fromInteger n)
-- ----------------------------------------------------------------------
-- ** Case: gcd(ξ, ξ[sup •]) = 1
-- | Given ξ ∈ ℤ[√2] such that gcd(ξ, ξ[sup •]) = 1, attempt to find
-- /t/ ∈ ℤ[ω] such that /t/[sup †]/t/ ~ ξ, or return 'Nothing' if no
-- such /t/ exists.
--
-- This function is optimized for the case when ξ is a prime in the
-- ring ℤ[√2]. In this case, it succeeds quickly, in an expected
-- number of 2 ticks. If ξ is not prime, this function probably
-- diverges.
dioph_zroottwo_assoc_prime :: (RandomGen g) => g -> ZRootTwo -> StepComp (Maybe ZOmega)
dioph_zroottwo_assoc_prime g xi
| xi == 0 = return (Just 0)
| n_mod_8 == 1 = do
h <- root_of_negative_one g n
let t = euclid_gcd (fromInteger h+i) (fromZRootTwo xi) :: ZOmega
assert ((adj t * t) `euclid_associates` fromZRootTwo xi) $ return (Just t)
| n_mod_8 == 7 = return Nothing
| otherwise = diverge
where
n_mod_8 = n `mod` 8
n = abs (norm xi)
-- | Given ξ ∈ ℤ[√2] such that gcd(ξ, ξ[sup •]) = 1, find /t/ ∈ ℤ[ω]
-- such that /t/[sup †]/t/ ~ ξ, if such /t/ exists, or return
-- 'Nothing' if no such /t/ exists.
--
-- This function alternately calls 'dioph_int_assoc_prime' and
-- attempts to factor ξ. Therefore, it will eventually succeed.
-- However, the runtime depends on how hard it is to factor ξ.
dioph_zroottwo_assoc :: (RandomGen g) => g -> ZRootTwo -> StepComp (Maybe ZOmega)
dioph_zroottwo_assoc g xi
| xi == 0 = return (Just 0)
| otherwise = interleave prime_solver factor_solver where
interleave p f = do
p <- subtask 4 p
case p of
Done res -> return res
_ -> do
f <- subtask 1000 f
case f of
Done (a, k) -> do
let alpha = euclid_gcd xi (fromInteger a)
let beta = xi `euclid_div` alpha
let (u, facs) = relatively_prime_factors alpha beta
forward (k `div` 2) $ dioph_zroottwo_assoc_powers g3 facs
_ -> interleave p f
(g1, g') = split g
(g2, g3) = split g'
prime_solver = dioph_zroottwo_assoc_prime g1 xi
factor_solver = with_counter $ speedup 30 $ find_factor g2 n
n = abs (norm xi)
-- | Given a factorization ξ = /q/[sub 1][sup /k/[sub 1]]⋅…⋅/q/[sub
-- /m/][sup /k/[sub /m/]] of some ξ ∈ ℤ[√2], where /q/[sub 1], …,
-- /q/[sub /m/] are pairwise relatively prime, find /t/ ∈ ℤ[ω] such
-- that /t/[sup †]/t/ ~ ξ, if such /t/ exists, or return 'Nothing'
-- if it can be proven not to exist.
dioph_zroottwo_assoc_powers :: (RandomGen g) => g -> [(ZRootTwo, Integer)] -> StepComp (Maybe ZOmega)
dioph_zroottwo_assoc_powers g facs = do
res <- parallel_list_maybe [dioph_zroottwo_assoc_power g (q,k) | (q,k) <- facs]
case res of
Nothing -> return Nothing
Just sols -> return (Just (product sols))
-- | Given a pair (ξ, /k/), with ξ ∈ ℤ[√2] and /k/ ≥ 0, find /t/ ∈
-- ℤ[ω] such that /t/[sup †]/t/ ~ ξ[sup /k/], if such /t/ exists, or
-- return 'Nothing' if no such /t/ exists.
dioph_zroottwo_assoc_power :: (RandomGen g) => g -> (ZRootTwo, Integer) -> StepComp (Maybe ZOmega)
dioph_zroottwo_assoc_power g (xi,k)
| even k = return (Just (fromZRootTwo (xi^(k `div` 2))))
| otherwise = do
t <- dioph_zroottwo_assoc g xi
case t of
Nothing -> return Nothing
Just t -> return (Just (t^k))