{-# LANGUAGE FlexibleContexts #-} -- | This module provides functions for solving one- and -- two-dimensional grid problems. module Quantum.Synthesis.GridProblems where import Quantum.Synthesis.Ring import Quantum.Synthesis.Matrix import Quantum.Synthesis.QuadraticEquation import Control.Monad import Data.Maybe import System.Random -- ---------------------------------------------------------------------- -- * 1-dimensional grid problems -- $ The /1-dimensional grid problem/ is the following: given closed -- intervals /A/ and /B/ of the real numbers, find all α ∈ ℤ[√2] such -- that α ∈ /A/ and α[sup •] ∈ /B/. -- -- Let Δx be the size of /A/, and Δy the size of /B/. It is a theorem -- that the 1-dimensional grid problem has at least one solution if -- ΔxΔy ≥ (1 + √2)², and at most one solution if ΔxΔy < 1. -- Asymptotically, the expected number of solutions is ΔxΔy/\√8. -- -- The following functions provide solutions to a number of variations -- of the grid problem. All functions are formulated so that the -- intervals can be specified exactly (using a type such as -- 'QRootTwo'), or approximately (using a type such as 'Double' or -- 'FixedPrec' /e/). -- ---------------------------------------------------------------------- -- ** General solutions -- | Given two intervals /A/ = [/x/₀, /x/₁] and /B/ = [/y/₀, /y/₁] of -- real numbers, output all solutions α ∈ ℤ[√2] of the 1-dimensional -- grid problem for /A/ and /B/. The list is produced lazily, and is -- sorted in order of increasing α. gridpoints :: (RootTwoRing r, Fractional r, Floor r, Ord r) => (r, r) -> (r, r) -> [ZRootTwo] gridpoints (x0, x1) (y0, y1) = do [ beta | beta' <- gridpoints_internal (x0', x1') (y0', y1'), let beta = beta' + alpha, test beta ] where a = floor_of (x0 + y0) `div` 2 b = floor_of (roottwo * (x0 - y0)) `div` 4 alpha = RootTwo a b xoff = fromZRootTwo alpha yoff = fromZRootTwo (adj2 alpha) x0' = x0 - xoff x1' = x1 - xoff y0' = y0 - yoff y1' = y1 - yoff test x = fromZRootTwo x `within` (x0, x1) && fromZRootTwo (adj2 x) `within` (y0, y1) -- | Like 'gridpoints', but only produce solutions /a/ + /b/√2 where -- /a/ has the same parity as the given integer. gridpoints_parity :: (RootTwoRing r, Fractional r, Floor r, Ord r) => Integer -> (r, r) -> (r, r) -> [ZRootTwo] gridpoints_parity e (x0,x1) (y0,y1) = do z' <- gridpoints (x0', x1') (-y1', -y0') return (roottwo * z' + fromInteger e2) where x0' = (x0 - e') / roottwo x1' = (x1 - e') / roottwo y0' = (y0 - e') / roottwo y1' = (y1 - e') / roottwo e' = fromInteger e2 e2 = e `mod` 2 -- ---------------------------------------------------------------------- -- ** Randomized solutions -- | Given two intervals /A/ = [/x/₀, /x/₁] and /B/ = [/y/₀, /y/₁] of -- real numbers, and a source of randomness, output a random solution -- α ∈ ℤ[√2] of the 1-dimensional grid problem for /A/ and /B/. -- -- Note: the randomness is not uniform. To ensure that the set of -- solutions is non-empty, we must have ΔxΔy ≥ (1 + √2)², where Δx = -- /x/₁ − /x/₀ ≥ 0 and Δy = /y/₁ − /y/₀ ≥ 0. If there are no solutions -- at all, the function returns 'Nothing'. gridpoint_random :: (RootTwoRing r, Fractional r, Floor r, Ord r, RandomGen g) => (r, r) -> (r, r) -> g -> Maybe ZRootTwo gridpoint_random (x0, x1) (y0, y1) g = z where dx = max 0 (x1 - x0) dy = max 0 (y1 - y0) area = dx * dy n = floor_of (area + 1) (i,_) = randomR (0, n-1) g r = fromInteger i / fromInteger n pts = gridpoints (x0 + r * dx, x1) (y0, y1) ++ gridpoints (x0, x1) (y0, y1) z = case pts of h:t -> Just h [] -> Nothing -- | Like 'gridpoint_random', but only produce solutions /a/ + /b/√2 -- where /a/ has the same parity as the given integer. gridpoint_random_parity :: (RootTwoRing r, Fractional r, Floor r, Ord r, RandomGen g) => Integer -> (r, r) -> (r, r) -> g -> Maybe ZRootTwo gridpoint_random_parity e (x0,x1) (y0,y1) g = do z' <- gridpoint_random (x0', x1') (-y1', -y0') g return (roottwo * z' + fromInteger e2) where x0' = (x0 - e') / roottwo x1' = (x1 - e') / roottwo y0' = (y0 - e') / roottwo y1' = (y1 - e') / roottwo e' = fromInteger e2 e2 = e `mod` 2 -- ---------------------------------------------------------------------- -- ** Scaled solutions -- $ The scaled version of the 1-dimensional grid problem is the -- following: given closed intervals /A/ and /B/ of the real numbers, -- and /k/ ≥ 0, find all α ∈ ℤ[√2] \/ √2[sup /k/] such that α ∈ /A/ -- and α[sup •] ∈ /B/. -- | Given intervals /A/ = [/x/₀, /x/₁] and /B/ = [/y/₀, /y/₁], and an -- integer /k/ ≥ 0, output all solutions α ∈ ℤ[√2] \/ √2[sup /k/] of -- the scaled 1-dimensional grid problem for /A/, /B/, and /k/. The -- list is produced lazily, and is sorted in order of increasing /α/. gridpoints_scaled :: (RootTwoRing r, Fractional r, Floor r, Ord r) => (r, r) -> (r, r) -> Integer -> [DRootTwo] gridpoints_scaled (x0, x1) (y0, y1) k = do w <- gridpoints (x0', x1') (y0', y1') return (scale * fromZRootTwo w) where scale = roothalf^k scale_inv = roottwo^k (x0', x1') = (scale_inv * x0, scale_inv * x1) (y0', y1') | even k = (scale_inv * y0, scale_inv * y1) | otherwise = (-scale_inv * y1, -scale_inv * y0) -- | Like 'gridpoints_scaled', but assume /k/ ≥ 1, take an additional -- parameter β ∈ ℤ[√2] \/ √2[sup /k/], and return only those α such -- that β − α ∈ ℤ[√2] \/ √2[sup /k-1/]. gridpoints_scaled_parity :: (RootHalfRing r, Fractional r, Floor r, Ord r) => DRootTwo -> (r, r) -> (r, r) -> Integer -> [DRootTwo] gridpoints_scaled_parity beta (x0, x1) (y0, y1) k | denomexp beta <= k-1 = gridpoints_scaled (x0, x1) (y0, y1) (k-1) | otherwise = do z' <- gridpoints_scaled (x0+offs', x1+offs') (y0+offs_bul', y1+offs_bul') (k-1) return (z' - offs) where offs = roothalf^k offs_bul = adj2 offs offs' = fromDRootTwo offs offs_bul' = fromDRootTwo offs_bul -- ---------------------------------------------------------------------- -- * 2-dimensional grid problems -- $ The /2-dimensional grid problem/ is the following: given bounded -- convex subsets /A/ and /B/ of ℂ with non-empty interior, find all -- /u/ ∈ ℤ[ω] such that /u/ ∈ /A/ and /u/[sup •] ∈ /B/. -- ---------------------------------------------------------------------- -- ** Representation of convex sets -- $ Since convex sets /A/ and /B/ are inputs of the 2-dimensional -- grid problem, we need a way to specify convex subsets of ℂ. Our -- specification of a convex sets consists of three parts: -- -- * a /bounding ellipse/ for the convex set; -- -- * a /characteristic function/, which tests whether any given point -- is an element of the convex set; and -- -- * a /line intersector/, which estimates the intersection of any -- given straight line and the convex set. -- | A point in the plane. type Point r = (r,r) -- | Convert a point with coordinates in 'DRootTwo' to a point with -- coordinates in any 'RootHalfRing'. point_fromDRootTwo :: (RootHalfRing r) => Point DRootTwo -> Point r point_fromDRootTwo (x, y) = (fromDRootTwo x, fromDRootTwo y) -- | An operator is a real 2×2-matrix. type Operator a = Matrix Two Two a -- | An /ellipse/ is given by an operator /D/ and a center /p/; the -- ellipse in this case is -- -- /A/ = { /v/ | (/v/-/p/)[sup †] /D/ (/v/-/p/) ≤ 1}. data Ellipse r = Ellipse (Operator r) (Point r) deriving (Show) -- | The /characteristic function/ of a set /A/ inputs a point /p/, -- and outputs 'True' if /p/ ∈ /A/ and 'False' otherwise. -- -- The point /p/ is given of an exact type, so characteristic -- functions have the opportunity to use infinite precision. type CharFun = Point DRootTwo -> Bool -- | A /line intersector/ knows about some compact convex set -- /A/. Given a straight line /L/, it computes an approximation of the -- intersection of /L/ and /A/. -- -- More specifically, /L/ is given as a parametric equation /p/(/t/) = -- /v/ + /tw/, where /v/ and /w/ ≠ 0 are vectors. Given /v/ and /w/, -- the line intersector returns (an approximation of) /t/₀ and /t/₁ -- such that /p/(/t/) ∈ /A/ iff /t/ ∈ [/t/₀, /t/₁]. type LineIntersector r = (Point DRootTwo -> Point DRootTwo -> Maybe (r, r)) -- | A compact convex set is given by a bounding ellipse, a -- characteristic function, and a line intersector. data ConvexSet r = ConvexSet (Ellipse r) CharFun (LineIntersector r) instance (Show r) => Show (ConvexSet r) where show (ConvexSet ell tst int) = "ConvexSet (" ++ show ell ++ ", ..., ...)" -- ---------------------------------------------------------------------- -- ** Specific convex sets -- | The closed unit disk. unitdisk :: (Fractional r, Ord r, RootHalfRing r, Quadratic QRootTwo r) => ConvexSet r unitdisk = ConvexSet ell tst int where ell = Ellipse 1 (0,0) int p v | q == Nothing = Nothing | otherwise = Just (t0, t1) where a = iprod v v b = 2 * iprod v p c = iprod p p - 1 q = quadratic (fromDRootTwo a :: QRootTwo) (fromDRootTwo b) (fromDRootTwo c) Just (t0, t1) = q tst (x,y) = x^2 + y^2 <= 1 -- | A closed disk of radius √/s/, centered at the origin. Assume /s/ > 0. disk :: (Fractional r, Ord r, RootHalfRing r, Quadratic QRootTwo r) => DRootTwo -> ConvexSet r disk s = ConvexSet ell tst int where ell = Ellipse (1/fromDRootTwo s `scalarmult` 1) (0,0) int p v | q == Nothing = Nothing | otherwise = Just (t0, t1) where a = iprod v v b = 2 * iprod v p c = iprod p p - s q = quadratic (fromDRootTwo a :: QRootTwo) (fromDRootTwo b) (fromDRootTwo c) Just (t0, t1) = q tst (x,y) = x^2 + y^2 <= s -- | A closed rectangle with the given dimensions. rectangle :: (Fractional r, Ord r, RootHalfRing r) => (r,r) -> (r,r) -> ConvexSet r rectangle (x0,x1) (y0,y1) = ConvexSet ell tst int where w = x1-x0 h = y1-y0 center = ((x0+x1) / 2, (y0+y1) / 2) mat = toOperator ((2/w^2,0), (0,2/h^2)) ell = Ellipse mat center tst (x, y) = (fromDRootTwo x `within` (x0, x1)) && (fromDRootTwo y `within` (y0, y1)) int p v = int_internal (point_fromDRootTwo p) (point_fromDRootTwo v) int_internal p v | vx == 0 && px `within` (x0, x1) = Just (min t0y t1y, max t0y t1y) | vx == 0 = Nothing | vy == 0 && py `within` (y0, y1) = Just (min t0x t1x, max t0x t1x) | vy == 0 = Nothing | otherwise = Just (t0, t1) where (px, py) = p (vx, vy) = v t0x = (x0 - px) / vx t1x = (x1 - px) / vx t0y = (y0 - py) / vy t1y = (y1 - py) / vy t0 = max (min t0x t1x) (min t0y t1y) t1 = min (max t0x t1x) (max t0y t1y) -- ---------------------------------------------------------------------- -- ** General solutions -- | Given bounded convex sets /A/ and /B/, enumerate all solutions -- /u/ ∈ ℤ[ω] of the 2-dimensional grid problem for /A/ and /B/. gridpoints2 :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r, Floor r) => ConvexSet r -> ConvexSet r -> [DOmega] gridpoints2 setA setB = gridpoints2_scaled setA setB 0 -- ---------------------------------------------------------------------- -- ** Scaled solutions -- $ The scaled version of the 2-dimensional grid problem is the -- following: given bounded convex subsets /A/ and /B/ of ℂ with -- non-empty interior, and /k/ ≥ 0, find all /u/ ∈ ℤ[ω] \/ √2[sup /k/] -- such that /u/ ∈ /A/ and /u/[sup •] ∈ /B/. -- | Given bounded convex sets /A/ and /B/, return a function that can -- input a /k/ and enumerate all solutions of the two-dimensional -- scaled grid problem for /A/, /B/, and /k/. -- -- Note: a large amount of precomputation is done on the sets /A/ and -- /B/, so it is beneficial to call this function only once for a -- given pair of sets, and then possibly call the result many times -- for different /k/. In other words, for optimal performance, the -- function should be used like this: -- -- > let solver = gridpoints2_scaled setA setB -- > let solutions0 = solver 0 -- > let solutions1 = solver 1 -- > ... -- -- Note: the gridpoints are computed in some deterministic (but -- unspecified) order. They are not randomized. gridpoints2_scaled :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r, Floor r) => ConvexSet r -> ConvexSet r -> Integer -> [DOmega] gridpoints2_scaled setA setB = gridpoints2_scaled_with_gridop setA setB opG where opG = to_upright_sets setA setB -- | Like 'gridpoints2_scaled', except that instead of performing a -- precomputation, we input the desired grid operator. It must make -- the two given sets upright. gridpoints2_scaled_with_gridop :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r, Floor r) => ConvexSet r -> ConvexSet r -> Operator DRootTwo -> Integer -> [DOmega] gridpoints2_scaled_with_gridop setA setB opG = solutions_fun where ConvexSet ellA tstA intA = setA ConvexSet ellB tstB intB = setB -- Find the grid operator opG_inv = special_inverse opG -- Change the coordinate system setA' = convex_transform opG_inv setA setB' = convex_transform (adj2 opG_inv) setB bboxA' = boundingbox setA' bboxB' = boundingbox setB' ConvexSet ellA' tstA' intA' = setA' ConvexSet ellB' tstB' intB' = setB' ((x0A, x1A), (y0A, y1A)) = bboxA' ((x0B, x1B), (y0B, y1B)) = bboxB' solutions_fun k = do -- Enumerate the solutions in the y-coordinate beta' <- gridpoints_scaled (fatten_interval (y0A, y1A)) (fatten_interval (y0B, y1B)) (k+1) let beta'_bul = adj2 beta' let xs = gridpoints_scaled (x0A, x1A+lambda) (x0B, x1B+lambda) (k+1) x0 <- take 1 xs let x0_bul = adj2 x0 let dx = roothalf^k let dx_bul = adj2 dx -- Intersect that y-coordinate with the convex sets let iA = intA' (x0, beta') (dx, 0) let iB = intB' (x0_bul, beta'_bul) (dx_bul, 0) guard (isJust iA) guard (isJust iB) let Just (t0A, t1A) = iA let Just (t0B, t1B) = iB -- offsets for slightly fattening the intervals, in a way that -- does not add more than a small constant number of candidates -- alpha' on both sides of the interval. let dtA = 10 / max 10 (2^k * (t1B - t0B)) let dtB = 10 / max 10 (2^k * (t1A - t0A)) -- Enumerate the solutions in the x-coordinate (ensuring correct -- parity to make sure it's a grid point) -- -- For parity, we need: -- 1/√2^k | alpha' - beta' -- <=> 1/√2^k | alpha'_offs * dx + x0 - beta' -- <=> 1 | alpha'_offs + (x0 - beta') * √2^k alpha'_offs <- gridpoints_scaled_parity ((beta'-x0)*roottwo^k) (t0A-dtA, t1A+dtA) (t0B-dtB, t1B+dtB) 1 let alpha' = alpha'_offs * dx + x0 -- Convert back to the original coordinate system let (alpha,beta) = point_transform opG (alpha',beta') case tstA (alpha,beta) && tstB (adj2 alpha, adj2 beta) of True -> do let z = fromDRootTwo alpha + i * fromDRootTwo beta :: DOmega return z False -> do [] -- | Given bounded convex sets /A/ and /B/, enumerate all solutions of -- the two-dimensional scaled grid problem for all /k/ ≥ 0. Each -- solution is only enumerated once, and the solutions are enumerated -- in order of increasing /k/. The results are returned in the form -- -- > [ (0, l0), (1, l1), (2, l2), ... ], -- -- where /l0/ is a list of solutions for /k/=0, /l1/ is a list of -- solutions for /k/=1, and so on. gridpoints2_increasing :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r, Floor r) => ConvexSet r -> ConvexSet r -> [(Integer, [DOmega])] gridpoints2_increasing setA setB = gridpoints2_increasing_with_gridop setA setB opG where opG = to_upright_sets setA setB -- | Like 'gridpoints2_increasing', except that instead of performing -- a precomputation, we input the desired grid operator. It must make -- the two given sets upright. gridpoints2_increasing_with_gridop :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r, Floor r) => ConvexSet r -> ConvexSet r -> Operator DRootTwo -> [(Integer, [DOmega])] gridpoints2_increasing_with_gridop setA setB opG = solutions where solutions_fun = gridpoints2_scaled_with_gridop setA setB opG solutions = (0, solutions_fun 0) : additional_solutions 1 additional_solutions k = (k, exact_solutions k) : additional_solutions (k+1) exact_solutions k = [ z | z <- solutions_fun k, denomexp z == k ] -- ---------------------------------------------------------------------- -- * Implementation details -- ---------------------------------------------------------------------- -- ** One-dimensional grid problems -- | Similar to 'gridpoints', except: -- -- 1. Assume that /x0/ and /y0/ are not too far from the origin (say, -- between -10 and 10). This is to avoid problems with numeric -- instability when /x0/ and /y0/ are much larger than /dx/ and /dy/, -- respectively. /y0/ are not too far from the origin. -- -- 2. The function potentially returns some non-solutions, so the -- caller should test for accuracy. gridpoints_internal :: (RootTwoRing r, Fractional r, Floor r, Ord r) => (r, r) -> (r, r) -> [ZRootTwo] gridpoints_internal (x0, x1) (y0, y1) | dy <= 0 && dx > 0 = map adj2 $ gridpoints_internal (y0, y1) (x0, x1) | dy >= lambda && even n = map (lambda_inv_n *) $ gridpoints_internal (lambda_n*x0, lambda_n*x1) (lambda_bul_n*y0, lambda_bul_n*y1) | dy >= lambda && odd n = map (lambda_inv_n *) $ gridpoints_internal (lambda_n*x0, lambda_n*x1) (lambda_bul_n*y1, lambda_bul_n*y0) | dy > 0 && dy < 1 && even n = map (lambda_m *) $ gridpoints_internal (lambda_inv_m*x0, lambda_inv_m*x1) (lambda_bul_inv_m*y0, lambda_bul_inv_m*y1) | dy > 0 && dy < 1 && odd n = map (lambda_m *) $ gridpoints_internal (lambda_inv_m*x0, lambda_inv_m*x1) (lambda_bul_inv_m*y1, lambda_bul_inv_m*y0) | otherwise = [ RootTwo a b | a <- [amin..amax], b <- [bmin a..bmax a] ] where dx = x1 - x0 dy = y1 - y0 (n, _) = floorlog lambda dy m = -n lambda_m = lambda^m lambda_n = lambda^n lambda_bul_n = (-lambda_inv)^n lambda_inv_m = lambda_inv^m lambda_bul_inv_m = (-lambda)^m lambda_inv_n = lambda_inv^n within x (x0, x1) = x0 <= x && x <= x1 amin = ceiling_of ((x0 + y0) / 2) amax = floor_of ((x1 + y1) / 2) bmin a = ceiling_of ((fromInteger a - y1) / roottwo) bmax a = floor_of ((fromInteger a - y0) / roottwo) -- ---------------------------------------------------------------------- -- ** Two-dimensional grid problems -- $ Our solution of the 2-dimensional grid problem follows the paper -- -- * N. J. Ross and P. Selinger, \"Optimal ancilla-free Clifford+/T/ -- approximation of /z/-rotations\". <http://arxiv.org/abs/1403.2975>. -- ---------------------------------------------------------------------- -- *** Positive operators and ellipses -- | Construct a 2×2-matrix, by rows. toOperator :: ((a, a), (a, a)) -> Operator a toOperator ((a, b), (c, d)) = matrix2x2 (a,b) (c,d) -- | Extract the entries of a 2×2-matrix, by rows. fromOperator :: Operator a -> ((a, a), (a, a)) fromOperator m = from_matrix2x2 m -- | Convert an operator with entries in 'DRootTwo' to an operator with -- entries in any 'RootHalfRing'. op_fromDRootTwo :: (RootHalfRing r) => Operator DRootTwo -> Operator r op_fromDRootTwo m = matrix_map fromDRootTwo m -- | The (/b/,/z/)-representation of a positive operator with determinant 1 is -- -- \[image bz.png] -- -- where /b/, /z/ ∈ ℝ and /e/ > 0 with /e/² = /b/² + 1. Create such an -- operator from parameters /b/ and /z/. operator_from_bz :: (RootTwoRing a, Floating a) => a -> a -> Operator a operator_from_bz b z = toOperator ((a, b), (b, d)) where lambda_z = lambda**z a = e/lambda_z d = e*lambda_z e = sqrt(1 + b^2) -- | Conversely, given a positive definite real operator of -- determinant 1, return the parameters (/b/, /z/). This is the -- inverse of 'operator_from_bz'. For efficiency reasons, the -- parameter /z/, which is a logarithm, is modeled as a 'Double'. operator_to_bz :: (Fractional a, Real a, RootTwoRing a) => Operator a -> (a, Double) operator_to_bz m = (b, z) where ((a, b), (c, d)) = fromOperator m lambda_z_squared = d / a z = 0.5 * logBase_double lambda lambda_z_squared -- | A version of 'operator_to_bz' that returns (/b/, λ[sup 2/z/]) -- instead of (/b/, /z/). -- This is a critical optimization, as this function is called often, -- and logarithms are relatively expensive to compute. operator_to_bl2z :: (Floating a, Real a) => Operator a -> (a, a) operator_to_bl2z m = (b, l2z) where ((a, b), (c, d)) = fromOperator m l2z = d / a -- | The determinant of a 2×2-matrix. det :: (Ring a) => Operator a -> a det m = a*d - b*c where ((a, b), (c, d)) = fromOperator m -- | Compute the skew of a positive operator of determinant 1. We -- define the /skew/ of a positive definite real operator /D/ to be -- -- \[image skew.png] operator_skew :: (Ring a) => Operator a -> a operator_skew m = b*c where ((a, b), (c, d)) = fromOperator m -- | Compute the uprightness of a positive operator /D/. -- -- The /uprightness/ of /D/ is the ratio of the area of the ellipse -- /E/ = {/v/ | /v/[sup †]/D//v/ ≤ 1} to the area of its bounding box -- /R/. It is given by -- -- \[image area.png] -- -- \[image ellipse-rectangle.png] uprightness :: (Floating a) => Operator a -> a uprightness m = pi/4 * sqrt(det m / (a*d)) where ((a, b), (c, d)) = fromOperator m -- ---------------------------------------------------------------------- -- *** States -- | A state is a pair (/D/, Δ) of real positive definite matrices of -- determinant 1. It encodes a pair of ellipses. type OperatorPair a = (Operator a, Operator a) -- | The /skew/ of a state is the sum of the skews of the two -- operators. skew :: (Ring a) => OperatorPair a -> a skew (m1,m2) = operator_skew m1 + operator_skew m2 -- | The /bias/ of a state is ζ - /z/. bias :: (Fractional a, Real a, RootTwoRing a) => OperatorPair a -> Double bias (matA,matB) = (zeta - z) where (b, z) = operator_to_bz matA (beta, zeta) = operator_to_bz matB -- ---------------------------------------------------------------------- -- *** Grid operators -- $ Consider the set ℤ[ω] ⊆ ℂ. In identifying ℂ with ℝ², we can -- alternatively identify ℤ[ω] with the set of all vectors (/x/, -- /y/)[sup †] ∈ ℝ² of the form -- -- * /x/ = /a/ + /a/'\/√2, -- -- * /y/ = /b/ + /b/'\/√2, -- -- such that /a/, /a/', /b/, /b/' ∈ ℤ and /a/' ≡ /b/' (mod 2). -- -- A /grid operator/ is a linear operator /G/ : ℝ² → ℝ² such that /G/ -- maps ℤ[ω] to itself. We can characterize the grid operators as -- the operators of the form -- -- \[image gridop.png] -- -- such that: -- -- * /a/ + /b/ + /c/ + /d/ ≡ 0 (mod 2) and -- -- * /a/' ≡ /b/' ≡ /c/' ≡ /d/' (mod 2). -- -- A /special grid operator/ is a grid operator of determinant ±1. All -- special grid operators are invertible, and the inverse is again a -- special grid operator. -- -- Since all coordinates of ℤ[ω] (as a subset of ℝ²), and all entries -- of grid operators, can be represented as elements of the ring [bold -- D][√2], the automorphism /x/ ↦ /x/[sup •], which maps /a/ + /b/√2 -- to /a/ - /b/√2 (for rational /a/ and /b/), is well-defined for -- them. -- -- In this section, we define some particular special grid operators -- that are used in the Step Lemma. -- | The special grid operator /R/: a clockwise rotation by 45°. -- -- \[image gridop-R.png] opR :: (RootHalfRing r) => Operator r opR = roothalf * toOperator ((1, -1), (1, 1)) -- | The special grid operator /A/: a clockwise shearing with offset -- 2, parallel to the /x/-axis. -- -- \[image gridop-A.png] opA :: (Ring r) => Operator r opA = matrix2x2 (1, -2) (0, 1) -- | The special grid operator /A/⁻¹: a counterclockwise shearing with offset -- 2, parallel to the /x/-axis. -- -- \[image gridop-Ai.png] opA_inv :: (Ring r) => Operator r opA_inv = matrix2x2 (1, 2) (0, 1) -- | The operator /A/[sup /k/]. opA_power :: (RootTwoRing r) => Integer -> Operator r opA_power k | k >= 0 = opA^k | otherwise = opA_inv^(-k) -- | The special grid operator /B/: a clockwise shearing with offset -- √2, parallel to the /x/-axis. -- -- \[image gridop-B.png] opB :: (RootTwoRing r) => Operator r opB = matrix2x2 (1, roottwo) (0, 1) -- | The special grid operator /B/⁻¹: a counterclockwise shearing with offset -- √2, parallel to the /x/-axis. -- -- \[image gridop-Bi.png] opB_inv :: (RootTwoRing r) => Operator r opB_inv = matrix2x2 (1, -roottwo) (0, 1) -- | The operator /B/[sup /k/]. opB_power :: (RootTwoRing r) => Integer -> Operator r opB_power k | k >= 0 = opB^k | otherwise = opB_inv^(-k) -- | The special grid operator /K/. -- -- \[image gridop-K.png] opK :: (RootHalfRing r) => Operator r opK = roothalf * matrix2x2 (-lambda_inv, -1) (lambda, 1) -- | The Pauli /X/ operator is a special grid operator. -- -- \[image gridop-X.png] opX :: (Ring r) => Operator r opX = matrix2x2 (0, 1) (1, 0) -- | The Pauli operator /Z/ is a special grid operator. -- -- \[image gridop-Z.png] opZ :: (Ring r) => Operator r opZ = matrix2x2 (1, 0) (0, -1) -- | The special grid operator /S/: a scaling by λ = 1+√2 in the -- /x/-direction, and by λ⁻¹ = -1+√2 in the /y/-direction. -- -- \[image gridop-S.png] -- -- The operator /S/ is not used in the paper, but we use it here for -- a more efficient implementation of large shifts. The point is that -- /S/ is a grid operator, but shifts in increments of 4, whereas the -- Shift Lemma uses non-grid operators but shifts in increments of 2. opS :: (RootTwoRing r) => Operator r opS = toOperator((lambda, 0), (0, lambda_inv)) -- | The special grid operator /S/⁻¹, the inverse of 'opS'. -- -- \[image gridop-Si.png] opS_inv :: (RootTwoRing r) => Operator r opS_inv = matrix2x2 (lambda_inv, 0) (0, lambda) -- | Return /S/[sup /k/]. opS_power :: (RootTwoRing r) => Integer -> Operator r opS_power k | k >= 0 = opS^k | otherwise = opS_inv^(-k) -- ---------------------------------------------------------------------- -- *** Action of grid operators on states -- | Compute the right action of a grid operator /G/ on a state (/D/, -- Δ). This is defined as: -- -- (/D/, Δ) ⋅ /G/ := (/G/[sup †]/D//G/, /G/[sup •T]Δ/G/[sup •]). action :: (RealFrac r, RootHalfRing r, Adjoint r) => (Operator r, Operator r) -> Operator DRootTwo -> (Operator r, Operator r) action (a,b) g = (g1 * a * g2, g3 * b * g4) where g1 = adj g2 g2 = op_fromDRootTwo g g3 = adj g4 g4 = op_fromDRootTwo (adj2 g) -- ---------------------------------------------------------------------- -- *** Shifts -- $ A shift is not quite the application of a grid operator, because -- the shifts σ and τ actually involve a square root of λ. However, -- they can be used to define an operation on states. -- | Given an operator /D/, compute σ[sup /k/]/D/σ[sup /k/]. shift_sigma :: (RootTwoRing a) => Integer -> Operator a -> Operator a shift_sigma k m = matrix2x2 (lambdapower k * a, b) (c, lambdapower (-k) * d) where ((a,b),(c,d)) = fromOperator m -- | Given an operator Δ, compute τ[sup /k/]Δτ[sup /k/]. shift_tau :: (RootTwoRing a) => Integer -> Operator a -> Operator a shift_tau k m = matrix2x2 (lambdapower (-k) * a, signpower k * b) (c * signpower k, lambdapower k * d) where ((a,b),(c,d)) = fromOperator m -- | Compute the /k/-shift of a state (/D/,Δ). shift_state :: (RootTwoRing a) => Integer -> OperatorPair a -> OperatorPair a shift_state k (d,delta) = (shift_sigma k d, shift_tau k delta) -- ---------------------------------------------------------------------- -- *** Skew reduction -- | An implementation of the /A/-Lemma. Given /z/ and ζ, compute the -- integer /m/ such that the operator /A/[sup /m/] reduces the skew. lemma_A :: (RealFrac r, RootTwoRing r, Floating r) => r -> r -> Integer lemma_A z zeta = n where n = max 1 (floor (lambda ** c / 2)) c = min z zeta -- | An implementation of the /B/-Lemma. Given /z/ and ζ, compute the -- integer /m/ such that the operator /B/[sup /m/] reduces the skew. lemma_B :: (RealFrac r, RootTwoRing r, Floating r) => r -> r -> Integer lemma_B z zeta = n where n = max 1 (floor (lambda ** c / roottwo)) c = min z zeta -- | A version of 'lemma_A' that inputs λ[sup 2/z/] instead of /z/ and -- λ[sup 2ζ] instead of ζ. Compute the constant /m/ such that the -- operator /A/[sup /m/] reduces the skew. lemma_A_l2 :: (RealFrac r, RootTwoRing r, Floating r) => r -> r -> Integer lemma_A_l2 l2z l2zeta = n where n = max 1 (intsqrt (floor (l2c / 4))) l2c = min l2z l2zeta -- | A version of 'lemma_B' that inputs λ[sup 2/z/] instead of /z/ and -- λ[sup 2ζ] instead of ζ. Compute the constant /m/ such that the -- operator /B/[sup /m/] reduces the skew. lemma_B_l2 :: (RealFrac r, RootTwoRing r, Floating r) => r -> r -> Integer lemma_B_l2 l2z l2zeta = n where n = max 1 (intsqrt (floor (l2c / 2))) l2c = min l2z l2zeta -- | An implementation of the Step Lemma. Input a state (/D/,Δ). If -- the skew is > 15, produce a special grid operator whose action -- reduces Skew(/D/,Δ) by at least 5%. If the skew is ≤ 15 and β ≥ 0 -- and z + ζ ≥ 0, do nothing. Otherwise, produce a special grid -- operator that ensures β ≥ 0 and z + ζ ≥ 0. step_lemma :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r) => OperatorPair r -> Maybe (Operator DRootTwo) step_lemma (matA,matB) -- First ensure that β ≥ 0, by applying /Z/ if necessary. | beta < 0 = wlog_using opZ -- Then ensure that z + ζ ≥ 0, by applying /X/ if necessary. -- Case: z + zeta < 0. | l2z * l2zeta < 1 = wlog_using opX -- If the bias is greater than 2, use the grid operator /S/. This is -- more efficient than applying the Shift Lemma. -- Todo: ensure numeric stability -- Case: |z-ζ| > 2. | l2z_minus_zeta > 33.971 || l2z_minus_zeta < 0.029437 = wlog_using (opS_power (round (logLambda l2z_minus_zeta / 8))) -- If the skew is below threshold, stop. | skew (matA,matB) <= 15 = Nothing -- If the bias is greater than 1, apply a shift. -- Todo: ensure numeric stability -- Case: |z-ζ| > 1. | l2z_minus_zeta > 5.8285 || l2z_minus_zeta < 0.17157 = with_shift (round (logLambda l2z_minus_zeta / 4)) -- Cases 1.1 and 2.1: z ∈ [-0.8, 0.8] and ζ ∈ [-0.8, 0.8]. -- Region R. | l2z `within` (0.24410, 4.0968) && l2zeta `within` (0.24410, 4.0968) = Just opR -- Case 1.2: b ≥ 0 and z ≤ 0.3 and ζ ≥ 0.8. -- Region K. | b >= 0 && l2z <= 1.6969 = Just opK -- Case 1.4: b ≥ 0 and z ≥ 0.8 and ζ ≤ 0.3. -- Region K•. | b >= 0 && l2zeta <= 1.6969 = Just (adj2 opK) -- Case 1.6: b ≥ 0 and z ≥ 0.3 and zeta ≥ 0.3. -- Region A^m. | b >= 0 = Just (opA_power (lemma_A_l2 l2z l2zeta)) -- Case 2.2: b ≤ 0 and z ≥ -0.2 and zeta ≥ -0.2. -- Region B^m. | otherwise = Just (opB_power (lemma_B_l2 l2z l2zeta)) where (b, l2z) = operator_to_bl2z matA (beta, l2zeta) = operator_to_bl2z matB logLambda a = logBase_double lambda a l2z_minus_zeta = l2z / l2zeta -- λ[sup 2(/z/-ζ)] wlog_using op = let (matA',matB') = action (matA, matB) op maybe_op2 = step_lemma (matA',matB') in case maybe_op2 of Nothing -> Just op Just op2 -> Just (op * op2) with_shift k = let (matA', matB') = shift_state k (matA, matB) maybe_op2 = step_lemma (matA', matB') in case maybe_op2 of Nothing -> Nothing Just op2 -> Just (shift_sigma k op2) -- | Repeatedly apply the Step Lemma to the given state, until the -- skew is 15 or less. reduction :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r) => OperatorPair r -> Operator DRootTwo reduction st = case step_lemma st of Nothing -> 1 Just opG -> opG * opG' where opG' = reduction (action st opG) -- | Given a pair of ellipses, return a grid operator /G/ such that -- the uprightness of each ellipse is greater than 1\/6. This is -- essentially the same as 'reduction', except we do not assume that -- the input operators have determinant 1. to_upright :: (RealFrac r, Floating r, Ord r, RootTwoRing r, RootHalfRing r, Adjoint r) => OperatorPair r -> Operator DRootTwo to_upright (a,b) = opG where a' = a `scalardiv` (sqrt (det a)) b' = b `scalardiv` (sqrt (det b)) opG = reduction (a',b') -- | Given a pair of convex sets, return a grid operator /G/ making -- both sets upright. to_upright_sets :: (Adjoint r, RootHalfRing r, RealFrac r, Floating r) => ConvexSet r -> ConvexSet r -> Operator DRootTwo to_upright_sets setA setB = opG where ConvexSet ellA tstA intA = setA ConvexSet ellB tstB intB = setB Ellipse matA ctrA = ellA Ellipse matB ctrB = ellB opG = to_upright (matA, matB) -- ---------------------------------------------------------------------- -- *** Action of special grid operators on convex sets -- | Apply a linear transformation /G/ to a point /p/. point_transform :: (Ring r) => Operator r -> Point r -> Point r point_transform opG (x,y) = (x',y') where ((a,b), (c,d)) = fromOperator opG x' = a * x + b * y y' = c * x + d * y -- | Apply a special linear transformation /G/ to an ellipse /A/. This -- results in the new ellipse /G/(/A/) = { /G/(/z/) | /z/ ∈ /A/ }. ellipse_transform :: (Ring r, Adjoint r) => Operator r -> Ellipse r -> Ellipse r ellipse_transform opG (Ellipse matA ctrA) = (Ellipse matA' ctrA') where matA' = adj opG_inv * matA * opG_inv ctrA' = point_transform opG ctrA opG_inv = special_inverse opG -- | Apply a special grid operator /G/ to a characteristic function. charfun_transform :: Operator DRootTwo -> CharFun -> CharFun charfun_transform opG f = f' where f' p = f (point_transform opG_inv p) opG_inv = special_inverse opG -- | Apply a special linear transformation /G/ to a line -- intersector. If the input line intersector was for a convex set -- /A/, then the output line intersector is for the set /G/(/A/) -- = { /G/(/z/) | /z/ ∈ /A/ }. lineintersector_transform :: (Ring r) => Operator DRootTwo -> LineIntersector r -> LineIntersector r lineintersector_transform opG intA = intA' where opG_inv = special_inverse opG intA' v' w' = intA v w where v = point_transform opG_inv v' w = point_transform opG_inv w' -- | Apply a special linear transformation /G/ to a convex set -- /A/. This results in the new convex set /G/(/A/) = { /G/(/z/) | /z/ -- ∈ /A/ }. convex_transform :: (Ring r, Adjoint r, RootHalfRing r) => Operator DRootTwo -> ConvexSet r -> ConvexSet r convex_transform opG (ConvexSet ellA tstA intA) = (ConvexSet ellA' tstA' intA') where ellA' = ellipse_transform (op_fromDRootTwo opG) ellA intA' = lineintersector_transform (op_fromDRootTwo opG) intA tstA' = charfun_transform opG tstA -- ---------------------------------------------------------------------- -- *** Bounding boxes -- | Calculate the bounding box for an ellipse. boundingbox_ellipse :: (Floating r) => Ellipse r -> ((r, r), (r, r)) boundingbox_ellipse (Ellipse matA ctrA) = ((x-w, x+w), (y-h, y+h)) where (x,y) = ctrA ((a, b), (c, d)) = fromOperator matA w = sqrt d / sqrt_det h = sqrt a / sqrt_det sqrt_det = sqrt (det matA) -- | Calculate a bounding box for a convex set. Returns ((/x/₀, /x/₁), -- (/y/₀, /y/₁)). boundingbox :: (Floating r) => ConvexSet r -> ((r, r), (r, r)) boundingbox (ConvexSet ell tst int) = boundingbox_ellipse ell -- ---------------------------------------------------------------------- -- * Auxiliary functions -- | We write /x/ \`@within@\` (/a/,/b/) for /a/ ≤ /x/ ≤ /b/, or -- equivalently, /x/ ∈ [/a/, /b/]. within :: (Ord a) => a -> (a,a) -> Bool within x (a,b) = a <= x && x <= b -- | Given an interval, return a slightly bigger one. fatten_interval :: (Fractional a) => (a,a) -> (a,a) fatten_interval (x,y) = (x - epsilon, y + epsilon) where epsilon = 0.0001 * (y-x) -- | The constant λ = 1 + √2. lambda :: (RootTwoRing r) => r lambda = 1 + roottwo -- | The constant λ⁻¹ = √2 - 1. lambda_inv :: (RootTwoRing r) => r lambda_inv = roottwo - 1 -- | Return λ[sup /k/], where /k/ ∈ ℤ. This works in any 'RootTwoRing'. -- -- Note that we can't use '^', because it requires /k/ ≥ 0, nor '**', -- because it requires the 'Floating' class. lambdapower :: (RootTwoRing r) => Integer -> r lambdapower k | k >= 0 = lambda^k | otherwise = lambda_inv^(-k) -- | Return (-1)[sup /k/], where /k/ ∈ ℤ. signpower :: (Num r) => Integer -> r signpower k | even k = 1 | otherwise = -1 -- | Given positive numbers /b/ and /x/, return (/n/, /r/) such that -- -- * /x/ = /r/ /b/[sup /n/] and -- -- * 1 ≤ /r/ < /b/. -- -- In other words, let /n/ = ⌊log[sub /b/] /x/⌋ and -- /r/ = /x/ /b/[sup −/n/]. This can be more efficient than 'floor' -- ('logBase' /b/ /x/) depending on the type; moreover, it also works -- for exact types such as 'Rational' and 'QRootTwo'. floorlog :: (Fractional b, Ord b) => b -> b -> (Integer, b) floorlog b x | x <= 0 = error "floorlog: argument not positive" | 1 <= x && x < b = (0, x) | 1 <= x*b && x < 1 = (-1, b*x) | r < b = (2*n, r) | otherwise = (2*n+1, r/b) where (n, r) = floorlog (b^2) x -- | A version of the natural logarithm that returns a 'Double'. The -- logarithm of just about any value can fit into a 'Double'; so if -- not a lot of precision is required in the mantissa, this function -- is often faster than 'log'. logBase_double :: (Fractional a, Real a) => a -> a -> Double logBase_double b x | b > 1 = y | b <= 0 = 0/0 -- NaN | b == 1 = 1/0 -- Infinity | otherwise = - logBase_double (1/b) x where (n, r) = floorlog b x y = fromInteger n + logBase (to_double b) (to_double r) to_double = fromRational . toRational -- | The inner product of two points. iprod :: (Num r) => Point r -> Point r -> r iprod (x,y) (a,b) = x*a + y*b -- | Subtract two points. point_sub :: (Num r) => Point r -> Point r -> Point r point_sub (x,y) (a,b) = (x-a, y-b) -- | Calculute the inverse of an operator of determinant 1. Note: this -- does not work correctly for operators whose determinant is not 1. special_inverse :: (Ring r) => Operator r -> Operator r special_inverse opG = opG_inv where ((a,b), (c,d)) = fromOperator opG opG_inv = det opG `scalarmult` toOperator ((d,-b), (-c,a))