-- | This module implements the classical operations on ideals used in Hallgren’s
-- algorithm (including also classical versions of the quantum operations required).
module Quipper.Algorithms.CL.RegulatorClassical where
import Data.Maybe
import Quipper.Libraries.Arith
import Quipper.Libraries.FPReal
import Quipper.Algorithms.CL.Auxiliary
import Quipper.Algorithms.CL.Types
-- ======================================================================
-- * Basic operations on ideals
-- | @'unit_ideal' /bigD/ /l/@: the unit ideal /O/, for Δ = /bigD/, with the ideal’s coefficients given as /l/-bit integers.
-- ([Jozsa 2003], Prop. 14.)
unit_ideal :: CLIntP -> Ideal
unit_ideal = forget_reduced . unit_idealRed
-- | Like 'unit_ideal', but considered as a reduced ideal.
unit_idealRed :: CLIntP -> IdealRed
unit_idealRed bigD = assert (is_valid_bigD bigD) ("unit_idealRed: " ++ show bigD ++ " not valid discriminant")
$ IdealRed bigD 1 (tau bigD (fromIntegral bigD) 1)
-- | The integer constant /c/ of an ideal.
-- ([Jozsa 2003], page 14 bottom: \"Since 4/a/ divides /b/[sup 2]-/D/
-- (cf. proposition 16) we introduce the integer /c/ = |/D/ − /b/[sup 2]|\/(4/a/)\")
c_of_ideal :: Ideal -> CLInt
c_of_ideal i@(Ideal bigD m l a b) =
if (num `mod` denom == 0) then num `div` denom
else error error_string
where num = abs ((fromIntegral bigD) - b*b)
denom = 4*a
error_string = "rho of ideal [" ++ show i ++ "] produces non-integer c"
-- | γ(/I/) = (/b/+√Δ)\/(2/a/) for a given ideal /I/.
-- ([Jozsa 2003], Sec. 6.2.)
gamma_of_ideal :: Ideal -> AlgNum
gamma_of_ideal (Ideal bigD m l a b) = AlgNum a' b' bigD
where
a' = (fromIntegral b / fromIntegral (2*a)) :: CLRational
b' = (fromIntegral 1 / fromIntegral (2*a)) :: CLRational
-- Recall: @'AlgNum' u v bigD@ represents (u + v * √bigD).
-- | The reduction function ρ on ideals.
-- ([Jozsa 2003], Sec. 6.2.)
rho :: Ideal -> Ideal
rho ii@(Ideal bigD m l a b) = (Ideal bigD m'' l'' a' b')
where
-- With little algebra, one can derive these:
m' = m * a
l' = l * a'
m'' = m' `div` (gcd m' l')
l'' = l' `div` (gcd m' l')
-- From [Jozsa 2003], p.15, equation (10)
a' = c_of_ideal ii
b' = tau bigD (-b) a'
-- | The ρ[sup -1] function on ideals. Inverse to 'rho'.
-- ([Jozsa 2003], Sec. 6.4.)
rho_inv :: Ideal -> Ideal
rho_inv (Ideal bigD m l a b) = (Ideal bigD m'' l'' a'' b'')
where
-- Create m and l
m' = m * a
l' = l * a''
-- Reduce them to have no common denominator
m'' = m' `div` (gcd m' l')
l'' = l' `div` (gcd m' l')
-- Calculate b* (b' below)
b' = tau bigD (-b) a
-- Calculate new a and b
a'' = ((fromIntegral bigD) - b'*b') `divchk` (4*a)
b'' = tau bigD b' a''
-- | The ρ operation on reduced ideals.
rho_red :: IdealRed -> IdealRed
rho_red = to_reduced . rho . forget_reduced
-- | The ρ[sup –1] operation on reduced ideals.
rho_inv_red :: IdealRed -> IdealRed
rho_inv_red = to_reduced . rho_inv . forget_reduced
-- | The ρ operation on ideals-with-distance.
rho_d :: IdDist -> IdDist
rho_d (ii, del) = (rho ii, del + del_change)
where
gamma = gamma_of_ideal ii
gamma_bar_by_gamma = (floating_of_AlgNum $ conjugate gamma) / (floating_of_AlgNum gamma)
del_change = (log $ abs gamma_bar_by_gamma) / 2
-- | The ρ[sup –1] operation on ideals-with-distance.
rho_inv_d :: IdDist -> IdDist
rho_inv_d (ii, del) = (ii', del - del_change)
where
ii' = rho_inv ii
gamma = gamma_of_ideal ii'
gamma_bar_by_gamma = (floating_of_AlgNum $ conjugate gamma) / (floating_of_AlgNum gamma)
del_change = (log $ abs gamma_bar_by_gamma) / 2
-- | The ρ operation on ideals-with-generator (i.e. pairs of an ideal /I/ and an 'AlgNum' /x/ such that /I/ is the principal ideal (/x/)).
rho_num :: (Ideal,AlgNum) -> (Ideal,AlgNum)
rho_num (ii, gen) = (rho ii, gen / (gamma_of_ideal ii))
-- | Apply ρ to an reduced-ideals-with-generator
rho_red_num :: (IdealRed,AlgNum) -> (IdealRed,AlgNum)
rho_red_num (ii, gen) = (rho_red ii, gen / (gamma_of_ideal $ forget_reduced ii))
-- ======================================================================
-- * Ideal reductions (bounded)
-- | Reduce an ideal, by repeatedly applying ρ.
reduce :: Ideal -> IdealRed
reduce ii = to_reduced $ while (not . is_reduced) rho ii
-- | Reduce an ideal within a bounded loop. Applies the ρ function
-- until the ideal is reduced. Used in 'star' and 'fJN' algorithms.
bounded_reduce :: IdDist -> IdDist
bounded_reduce k@(ideal@(Ideal bigD m l a b),dist) =
fst $ bounded_while condition bound func (k,0)
where
-- NOTE: some uncertainty regarding loop bound.
bound = ceiling $ bound_log + 1
bound_log = (logBase 2 ((fromIntegral bound_a)
/ (sqrt $ fromIntegral $ bigD_of_Ideal $ ideal)))
bound_a = 2 ^ (fromJust (intm_length $ a))
condition (k,itr) = not $ is_reduced $ fst k
func (k,itr) = ((rho_d k),(itr+1))
-- | Apply a function (like ρ,ρ[sup -1],ρ[sup 2]) to an ideal, bounded
-- by 3*ln(Δ)\/2*ln(2). Execute while satisfies condition function.
bounded_step :: (IdDist -> Bool) -> (IdDist -> IdDist) -> IdDist -> IdDist
bounded_step condition step_function ideal =
-- Execute a bounded while loop
bounded_while condition bound step_function ideal
where
bound = ceiling $ (3 * (log $ fromIntegral $ bigD_of_Ideal $ fst ideal)) / (2 * log 2)
-- | Like 'bounded_step', but the condition is checked against delta of the
-- current ideal.
bounded_step_delta :: (CLReal -> Bool) -> (IdDist -> IdDist) -> IdDist -> IdDist
bounded_step_delta condition step_function ideal =
bounded_step new_condition step_function ideal
where new_condition = (\k -> condition (delta k))
-- ======================================================================
-- * Products of ideals
-- | The ordinary (not necessarily reduced) product of two reduced fractional ideals.
--
-- /I/⋅/J/ of [Jozsa 2003], Sec 7.1, following the description
-- given in Prop. 34.
-- NOTE: assumes I, J reduced. Type should reflect this!
dot :: IdDist -> IdDist -> IdDist
dot i1@(Ideal bigD1 m1 l1 a1 b1, delta1) i2@(Ideal bigD2 m2 l2 a2 b2, delta2) =
assert_reduced (fst i1) $
assert_reduced (fst i2) $
(Ideal bigD1 m l a3 b3, del)
where
sqrtd :: CLReal
sqrtd = sqrt (fromIntegral bigD1)
(k', u', v') = extended_euclid a1 a2
(k, x, w ) = extended_euclid k' ((b1 + b2) `divchk` 2)
a3 = (a1 * a2) `divchk` (k * k)
t1 = x * u' * a1 * b2
t2 = x * v' * a2 * b1
t3 = w*(b1 * b2 + (fromIntegral bigD1)) `divchk` 2
t = (t1 + t2 + t3) `divchk` k
b3 = tau bigD1 t a3
m = k
l = a3
del = ((delta i1) + (delta i2))
-- | The dot-square /I/⋅/I/ of an ideal-with-distance /I/.
dot' :: IdDist -> IdDist
dot' i1@(Ideal bigD1 m1 l1 a1 b1, delta1) =
assert_reduced (fst i1) $
(Ideal bigD1 m l a3 b3, del)
where
sqrtd :: CLReal
sqrtd = sqrt (fromIntegral bigD1)
(k, u, w) = extended_euclid (abs a1) (abs b1)
a3 = (a1 * a1) `divchk` (k * k)
t1 = u * a1 * b1
t3 = w*(b1 * b1 + (fromIntegral bigD1)) `divchk` 2
t = (t1 + t3) `divchk` k
b3 = tau bigD1 t a3
m = k
-- l = a3
l = 1
del = ((delta i1) + (delta i1))
-- | The star-product of two ideals-with-distance.
--
-- This is /I/*/J/ of [Jozsa 2003], Sec. 7.1, defined as the first reduced
-- ideal-with-distance following /I/⋅/J/.
-- NOTE: assumes I, J reduced. Type should reflect this!
star :: IdDist -> IdDist -> IdDist
star i j =
if (delta k1 <= delta i_dot_j)
then bounded_step_delta (<= delta i_dot_j) rho_d k1
else rho_d $ bounded_step_delta (>= delta i_dot_j) rho_inv_d k1
where
i_dot_j = i `dot` j
-- FIX: Over bound (infinite loop) in reducing the following:
-- <m:1 l:3 a:3 b:2 bigD:28 del:1.1>*<m:1 l:3 a:3 b:4 bigD:28 del:1.6>
k1 = bounded_reduce i_dot_j
-- ======================================================================
-- * The function f[sub /N/], and variants
-- | Compute the expression i\/N + j\/L.
compute_injl :: (Integral int) => CLInt -> int -> CLInt -> int -> CLReal
compute_injl i nn j ll =
(fromIntegral i)/(fromIntegral nn) + (fromIntegral j)/(fromIntegral ll)
-- | @'fN' /i/ /j/ /n/ /l/ Δ@: find the minimal ideal-with-distance (/J/,δ[sub /J/]) such that δ[sub /J/] > /x/, where /x/ = /i/\//N/ + /j/\//L/, where /N/ = 2[sup /n/], /L/ = 2[sup /l/]. Return (/i/,/J/,δ[sub /J/]–/x/). Work under the assumption that /R/ < 2[sup /s/].
--
-- This is the function /h/ of [Jozsa 2003, Section 9], discretized with precision 1\//N/ = 2[sup −/n/],
-- and perturbed by the jitter parameter /j/\//L/.
fN :: (Integral int) => CLInt -> CLInt -> int -> int -> CLIntP -> (Ideal, CLInt)
fN i j nn ll bigD =
let ((ideal_J, _), diff) = fN_d i j nn ll bigD
in (ideal_J, diff)
-- | Like 'fN', but returning an ideal-with-distance not just an ideal.
fN_d :: (Integral int) => CLInt -> CLInt -> int -> int -> CLIntP -> (IdDist, CLInt)
fN_d i j nn ll bigD =
(j_star_19, floor $ (fromIntegral nn) * (toRational $ injl - (snd j_star_19)))
where
-- Expression "i/N + j/L" used repeatedly
injl = compute_injl i nn j ll
-- Generate J1
j1 = rho_d $ rho_d $ (unit_ideal bigD, 0)
-- Generate Jk's (make an infinite list and take only what is needed)
jks = takeWhile (\jk -> (delta jk) <= injl) $
bounded_iterate bound_jks
(\jk -> jk `star` jk) j1
where
-- Bound for jk generation
max_i = 2^(fromJust (intm_length $ i))
bound_jks = ceiling $
(log $ fromIntegral max_i) /
((fromIntegral nn) * (delta j1))
-- Apply all Jk's to J* using '*' in reverse (remember that the last
-- element is J* itself)
j_star_14 = foldr1 applyJkIfConditionHolds jks
-- Apply Jk to J* if a condition holds.
applyJkIfConditionHolds :: IdDist -> IdDist -> IdDist
applyJkIfConditionHolds jk jstar =
if (delta (jstar `star` jk) <= injl) then jstar `star` jk else jstar
-- Go forward one step at a time as much as needed
j_star_17 = bounded_step_delta (< injl) (rho_d.rho_d) j_star_14
-- Go back one step if needed
j_star_19 = if (delta (rho_inv_d j_star_17) >= injl)
then rho_inv_d j_star_17
else j_star_17
-- | Analogue of 'fN', working within the cycle determined by a given ideal /J/.
-- ([Hallgren 2006], Section 5.)
fJN :: IdDist -> CLInt -> CLInt -> CLInt -> CLInt -> CLIntP -> (Ideal, CLInt)
fJN ideal_J i j nn ll bigD =
let ((ideal_J', _), diff) = fJN_d ideal_J i j nn ll bigD
in (ideal_J', diff)
-- | Like 'fJN', but returning an ideal-with-distance not just an ideal.
fJN_d :: IdDist -> CLInt -> CLInt -> CLInt -> CLInt -> CLIntP -> (IdDist, CLInt)
fJN_d ideal_J i j nn ll bigD =
(ideal_KFinal, floor $ (fromIntegral nn) * (injl - (delta ideal_KFinal)))
where
-- Expression "i/N + j/L"
injl = compute_injl i nn j ll
-- Generate I and K
ideal_I = fst (fN_d i j nn ll bigD)
ideal_K = bounded_reduce (ideal_I `dot` ideal_J)
-- Step forward/backward as much as needed
ideal_KFinal =
if (delta ideal_K <= injl)
then bounded_step_delta (< injl) (rho_d) ideal_K
else rho_d $ bounded_step_delta (>= injl) (rho_inv_d) ideal_K
-- ======================================================================
-- * Classical period-finding
-- $ Functions for classically finding the regulator and fundamental unit of a field using the period of /f_N/.
-- ======================================================================
-- ** Auxiliary functions
-- | Find the order of an endofunction on an argument. That is, @'order' /f/ /x/@ returns the first /n/ > 0 such that /f/[sup /n/]\(/x/) = /x/.
--
-- Method: simple brute-force search/comparison.
order :: (Eq a) => (a -> a) -> a -> Int
order = order_with_projection id
-- | Given a function /p/, an endofunction /f/, and an argument /x/, returns the first /n/ > 0 such that /p/(/f/[sup /n/]\(/x/)) = /p/(/x/).
--
-- Method: simple brute-force search/comparison.
order_with_projection :: (Eq b) => (a -> b) -> (a -> a) -> a -> Int
order_with_projection p f x = 1 + (length $ takeWhile (\y -> p y /= p x) (tail $ iterate f x))
-- | Given a function /p/, an endofunction /f/, and an argument /x/, return /f/[sup /n/]\(/x/), for the first /n/ > 0 such that /p/(/f/[sup /n/]\(/x/)) = /p/(/x/).
--
-- Method: simple brute-force search/comparison.
first_return_with_projection :: (Eq b) => (a -> b) -> (a -> a) -> a -> a
first_return_with_projection p f x = head $ dropWhile (\y -> p y /= p x) $ tail $ iterate f x
-- | Given a bound /b/, a function /p/, an endofunction /f/, and an argument /x/, return /f/[sup /n/]\(/x/), for the first /n/ > 0 such that /p/(/f/[sup /n/]\(/x/)) = /p/(/x/), if there exists such an /n/ ≤ /b/.
--
-- Method: simple brute-force search/comparison.
first_return_with_proj_bdd :: (Eq b) => Int -> (a -> b) -> (a -> a) -> a -> Maybe a
first_return_with_proj_bdd b p f x = listToMaybe $ dropWhile (\y -> p y /= p x) $ take b $ tail $ iterate f x
-- | Find the period of a function on integers, assuming that it is periodic and injective on its period. That is, @'period' /f/@ returns the first /n/ > 0 such that /f/(/n/) = /f/(0). Method: simple brute-force search/comparison.
period :: (Eq a, Integral int) => (int -> a) -> int
period f = minimum [ n | n <- [1..], f n == f 0 ]
-- ======================================================================
-- ** Haskell native arithmetic
-- $ The functions of this section use Haskell’s native integer and floating computation.
-- | Find the regulator /R/ = log ε[sub 0] of a field, given the discriminant Δ,
-- by finding (classically) the order of ρ.
--
-- Uses 'IdDist' and 'rho_d'.
regulator :: CLIntP -> FPReal
regulator bigD = snd $ head $ dropWhile (\(ii,_) -> ii /= calO) $ tail $ iterate rho_d $ (calO,0)
where calO = unit_ideal bigD
-- | Find the fundamental unit ε[sub 0] of a field, given the discriminant Δ,
-- by finding (classically) the order of ρ.
--
-- Uses '(Ideal,Number)' and 'rho_num'.
fundamental_unit :: CLIntP -> AlgNum
fundamental_unit bigD = maximum [eps, -eps, 1/eps, -1/eps]
where eps = snd $ first_return_with_projection fst rho_num (calO,1)
calO = unit_ideal bigD
-- | Find the fundamental solution of Pell’s equation, given /d/.
--
-- Solutions of Pell’s equations are integer pairs (/x/,/y/) such that
-- /x/,/y/ > 0, and (/x/ + /y/√d)(/x/ – /y/√d) = 1.
--
-- In this situation, (/x/ + /y/√d) is a unit of the algebraic integers
-- of /K/, and is >1, so we simply search the powers of ε[sub 0] for a
-- unit of the desired form.
fundamental_solution :: CLIntP -> (Integer,Integer)
fundamental_solution d = head pell_solutions
where bigD = bigD_of_d d
eps0 = fundamental_unit bigD
pell_solutions =
[ (round x, round y) | n <- [1..],
let eps@(AlgNum a b _) = eps0^n,
a >= 0, b >= 0,
let x = a,
let y = if bigD == d then b else 2*b,
is_int x, is_int y,
eps * (conjugate eps) == 1]
-- ======================================================================
-- ** Fixed-precision arithmetic
-- $ The functions of this section perform period-finding using fixed-precision arithmetic.
-- This should parallel closely (though at present not exactly, due to the implementations
-- of floating-point operations) the quantum circuit implementations, and hence allows
-- testing of whether the chosen precisions are accurate.
-- | Find the regulator /R/ = log ε[sub 0] of a field, given the discriminant Δ,
-- by finding (classically) the order of ρ
-- using fixed-precision arithmetic: 'fix_sizes_Ideal' for 'Ideal's,
-- and given an assumed bound /b/ on log[sub 2] /R/.
--
-- Uses 'IdDist' and 'rho_d'.
regulator_fixed_prec :: Int -> CLIntP -> Maybe FPReal
regulator_fixed_prec b bigD = fmap snd (first_return_with_proj_bdd b' fst rho_d (calO,zero))
where calO = fix_sizes_Ideal $ unit_ideal bigD
n = n_of_bigD bigD
-- S = 2RN, so this /i/ gives an assumed bound on log_2 S:
i = 1 + b + n
-- Following precisions are as used in 'approximate_regulator_circuit', 'q_fN':
q = 2 + 2 * i
l = 4 + q + n
p = precision_for_fN bigD n l
zero = fprealx (-p) (intm (q - n + p) 0)
-- δ(I, ρ^2 (I)) ≥ √2 for any I, so the order of ρ is at most 2R / (√2 / 2):
b' = ceiling $ 2 * sqrt(2) * (fromIntegral 2^b)
{-
Waiting for 'AlgNum' to be rebased using 'IntM' instead of 'Integer'.
-- | Find the fundamental unit ε[sub 0] of a field, given the discriminant Δ,
-- by finding (classically) the order of ρ,
-- using fixed-precision arithmetic: 'fix_sizes_Ideal' for 'Ideal's,
-- and a given /l/ for the 'AlgNum's generating them.
--
-- Uses '(Ideal,Number)' and 'rho_num'.
fundamental_unit_with_fixed :: Int -> CLIntP -> AlgNum
fundamental_unit_with_fixed l bigD = maximum [eps, -eps, 1/eps, -1/eps]
where eps = snd $ head $ dropWhile (\(ii,_) -> ii /= calO) $ tail $ iterate rho_num $ (calO,one)
calO = fix_sizes_Ideal $ unit_ideal bigD
one = intm_with_length (Just l) 1
-}