{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE GADTs #-} -- | This module provides functions for the representation and exact -- synthesis of multi-qubit Clifford+/T/ operators. -- -- The multi-qubit Clifford+/T/ exact synthesis algorithm is described -- in the paper: -- -- * Brett Giles, Peter Selinger. Exact synthesis of multiqubit Clifford+T -- circuits. /Physical Review A/ 87, 032332 (7 pages), 2013. Available -- from . -- -- It generalizes the single-qubit exact synthesis algorithm of -- Kliuchnikov, Maslov, and Mosca. module Quantum.Synthesis.MultiQubitSynthesis where import Quantum.Synthesis.Matrix import Quantum.Synthesis.Ring import Data.List -- ---------------------------------------------------------------------- -- * Residues -- | A type class for things that have residues. In a typical -- instance, /a/ is a ring whose elements are expressed with -- coefficients in ℤ, and /b/ is a corresponding ring whose elements -- are expressed with coefficients in ℤ[sub 2]. class Residue a b | a -> b where -- | Return the residue of something. residue :: a -> b instance Residue Integer Z2 where residue = parity instance Residue a b => Residue (Omega a) (Omega b) where residue (Omega a b c d) = Omega (residue a) (residue b) (residue c) (residue d) instance Residue a b => Residue (RootTwo a) (RootTwo b) where residue (RootTwo a b) = RootTwo (residue a) (residue b) instance (Residue a a', Residue b b') => Residue (a,b) (a',b') where residue (x,y) = (residue x, residue y) instance Residue () () where residue = const () instance (Residue a b) => Residue [a] [b] where residue = map residue instance (Residue a b) => Residue (Cplx a) (Cplx b) where residue (Cplx a b) = Cplx (residue a) (residue b) instance (Residue a b) => Residue (Vector n a) (Vector n b) where residue = vector_map residue instance (Residue a b) => Residue (Matrix m n a) (Matrix m n b) where residue (Matrix m) = Matrix (residue m) -- ---------------------------------------------------------------------- -- * One- and two-level operators -- ---------------------------------------------------------------------- -- ** Symbolic representation -- | An index for a row or column of a matrix. type Index = Int -- | Symbolic representation of one- and two-level operators. Note -- that the power /k/ in the 'TL_T' and 'TL_omega' constructors can be -- positive or negative, and should be regarded modulo 8. -- -- Note: when we use a list of 'TwoLevel' operators to express a -- sequence of operators, the operators are meant to be applied -- right-to-left, i.e., as in the mathematical notation for matrix -- multiplication. This is the opposite of the quantum circuit -- notation. data TwoLevel = TL_X Index Index -- ^ /X/[sub /i/,/j/]. | TL_H Index Index -- ^ /H/[sub /i/,/j/]. | TL_T Int Index Index -- ^ (/T/[sub /i/,/j/])[super /k/]. | TL_omega Int Index -- ^ (ω[sub /i/])[super /k/]. deriving (Show, Eq) -- | Invert a 'TwoLevel' operator. invert_twolevel :: TwoLevel -> TwoLevel invert_twolevel (TL_X i j) = TL_X i j invert_twolevel (TL_H i j) = TL_H i j invert_twolevel (TL_T m i j) = TL_T (-m) i j invert_twolevel (TL_omega m j) = TL_omega (-m) j -- | Invert a list of 'TwoLevel' operators. invert_twolevels :: [TwoLevel] -> [TwoLevel] invert_twolevels = reverse . map invert_twolevel -- ---------------------------------------------------------------------- -- ** Constructors for two-level matrices -- | Construct a two-level matrix with the given entries. twolevel_matrix :: (Ring a, Nat n) => (a,a) -> (a,a) -> Index -> Index -> Matrix n n a twolevel_matrix (a,b) (c,d) i j = matrix_of_function f where f x y | x == i && y == i = a | x == i && y == j = b | x == j && y == i = c | x == j && y == j = d | x == y = 1 | otherwise = 0 -- | Construct a one-level matrix with the given entry. onelevel_matrix :: (Ring a, Nat n) => a -> Index -> Matrix n n a onelevel_matrix a i = matrix_of_function f where f x y | x == i && y == i = a | x == y = 1 | otherwise = 0 -- | Convert a symbolic one- or two-level operator into a matrix. matrix_of_twolevel :: (ComplexRing a, RootHalfRing a, Nat n) => TwoLevel -> Matrix n n a matrix_of_twolevel (TL_X i j) = twolevel_matrix (0,1) (1,0) i j matrix_of_twolevel (TL_H i j) = twolevel_matrix (s,s) (s,-s) i j where s = roothalf matrix_of_twolevel (TL_T k i j) = twolevel_matrix (1,0) (0,omega^(k `mod` 8)) i j matrix_of_twolevel (TL_omega k i) = onelevel_matrix (omega^(k `mod` 8)) i -- | Convert a list of symbolic one- or two-level operators into a -- matrix. Note that the operators are to be applied right-to-left, -- exactly as in mathematical notation. matrix_of_twolevels :: (ComplexRing a, RootHalfRing a, Nat n) => [TwoLevel] -> Matrix n n a matrix_of_twolevels gs = foldl' (*) 1 [ matrix_of_twolevel g | g <- gs ] -- ---------------------------------------------------------------------- -- * Auxiliary list functions -- | Replace the /i/th element of a list by /x/. list_insert :: Index -> a -> [a] -> [a] list_insert 0 x (h:t) = x:t list_insert n x (h:t) = h:(list_insert (n-1) x t) list_insert n x [] = [] -- | Apply a unary operator to element /i/ of a list. transform_at :: (a -> a) -> Index -> [a] -> [a] transform_at op i lst = lst' where x = lst !! i x' = op x lst' = list_insert i x' lst -- | Apply a binary operator to elements /i/ and /j/ of a list. transform_at2 :: ((a,a) -> (a,a)) -> Index -> Index -> [a] -> [a] transform_at2 op i j lst = lst' where (x,y) = (lst !! i, lst !! j) (x',y') = op (x,y) lst' = list_insert i x' (list_insert j y' lst) -- | Split a list into pairs. Return a list of pairs, and a final -- element if the length of the list was odd. list_pairs :: [a] -> ([(a,a)], Maybe a) list_pairs [] = ([], Nothing) list_pairs [h] = ([], Just h) list_pairs (h:k:t) = ((h,k):t',r') where (t',r') = list_pairs t -- ---------------------------------------------------------------------- -- * Functions on ℤ[ω] -- | Given an element of the form ω[sup /m/], return /m/ ∈ {0,…,7}, or -- 'Nothing' if not of that form. log_omega :: ZOmega -> Maybe Int log_omega (Omega 0 0 0 1) = Just 0 log_omega (Omega 0 0 1 0) = Just 1 log_omega (Omega 0 1 0 0) = Just 2 log_omega (Omega 1 0 0 0) = Just 3 log_omega (Omega 0 0 0 (-1)) = Just 4 log_omega (Omega 0 0 (-1) 0) = Just 5 log_omega (Omega 0 (-1) 0 0) = Just 6 log_omega (Omega (-1) 0 0 0) = Just 7 log_omega _ = Nothing -- | Multiply a scalar by ω[sup /n/]. omega_power :: (OmegaRing a) => Int -> a -> a omega_power n x = x * omega^(n `mod` 8) -- | Divide an element of 'ZOmega' by √2, or throw an error if it is -- not divisible. reduce_ZOmega :: ZOmega -> ZOmega reduce_ZOmega (Omega a b c d) | even (a-c) && even (b-d) = Omega a' b' c' d' | otherwise = error "reduce_ZOmega: element not reducible" where a' = (b-d) `div` 2 b' = (c+a) `div` 2 c' = (b+d) `div` 2 d' = (c-a) `div` 2 -- | Apply the /X/ operator to a 2-dimensional vector over 'ZOmega'. opX_zomega :: (ZOmega, ZOmega) -> (ZOmega, ZOmega) opX_zomega (x,y) = (y,x) -- | Apply the /H/ operator to a 2-dimensional vector over -- 'ZOmega'. This throws an error if the result is not well-defined -- over 'ZOmega'. opH_zomega :: (ZOmega, ZOmega) -> (ZOmega, ZOmega) opH_zomega (x,y) = (reduce_ZOmega (x+y), reduce_ZOmega (x-y)) -- | Apply a 'TwoLevel' operator to a 'ZOmega'-vector, represented as -- a list. Throws an error if any operation produces a scalar that is -- not in 'ZOmega'. apply_twolevel_zomega :: TwoLevel -> [ZOmega] -> [ZOmega] apply_twolevel_zomega (TL_X i j) w = transform_at2 opX_zomega i j w apply_twolevel_zomega (TL_H i j) w = transform_at2 opH_zomega i j w apply_twolevel_zomega (TL_T k i j) w = transform_at (omega_power k) j w apply_twolevel_zomega (TL_omega k i) w = transform_at (omega_power k) i w -- | Apply a list of 'TwoLevel' operators to a 'ZOmega'-vector, -- represented as a list. Throws an error if any operation produces a -- scalar that is not in 'ZOmega'. apply_twolevels_zomega :: [TwoLevel] -> [ZOmega] -> [ZOmega] apply_twolevels_zomega gs w = foldr apply_twolevel_zomega' w gs where apply_twolevel_zomega' g w = apply_twolevel_zomega g w -- ---------------------------------------------------------------------- -- * Functions on residues -- | The /residue type/ of /t/ ∈ ℤ[ω] is the residue of /t/[sup †]/t/. -- It is 0000, 0001, or 1010. data ResidueType = RT_0000 | RT_0001 | RT_1010 deriving (Eq, Ord) -- | Return the residue's 'ResidueType'. residue_type :: Omega Z2 -> ResidueType residue_type r = t where (t, _) = residue_type_shift r -- | Return the residue's /shift/. -- -- The shift is defined so that: -- -- * 0001, 1110, 0011 have shift 0, -- -- * 0010, 1101, 0110 have shift 1, -- -- * 0100, 1011, 1100 have shift 2, and -- -- * 1000, 0111, 1001 have shift 3. -- -- Residues of type 'RT_0000' have shift 0. residue_shift :: Omega Z2 -> Int residue_shift r = s where (_, s) = residue_type_shift r -- | Return the residue's 'ResidueType' and the shift. residue_type_shift :: Omega Z2 -> (ResidueType, Int) residue_type_shift (Omega 0 0 0 0) = (RT_0000, 0) residue_type_shift (Omega 0 0 0 1) = (RT_0001, 0) residue_type_shift (Omega 0 0 1 0) = (RT_0001, 1) residue_type_shift (Omega 0 0 1 1) = (RT_1010, 0) residue_type_shift (Omega 0 1 0 0) = (RT_0001, 2) residue_type_shift (Omega 0 1 0 1) = (RT_0000, 0) residue_type_shift (Omega 0 1 1 0) = (RT_1010, 1) residue_type_shift (Omega 0 1 1 1) = (RT_0001, 3) residue_type_shift (Omega 1 0 0 0) = (RT_0001, 3) residue_type_shift (Omega 1 0 0 1) = (RT_1010, 3) residue_type_shift (Omega 1 0 1 0) = (RT_0000, 0) residue_type_shift (Omega 1 0 1 1) = (RT_0001, 2) residue_type_shift (Omega 1 1 0 0) = (RT_1010, 2) residue_type_shift (Omega 1 1 0 1) = (RT_0001, 1) residue_type_shift (Omega 1 1 1 0) = (RT_0001, 0) residue_type_shift (Omega 1 1 1 1) = (RT_0000, 0) residue_type_shift _ = undefined -- to turn off a compiler warning -- | Given two irreducible residues /a/ and /b/ of the same type, find -- an index /m/ such that /a/ + ω[sup /m/]/b/ = 0000. If no such index -- exists, find an index /m/ such that /a/ + ω[sup /m/]/b/ = 1111. residue_offset :: Omega Z2 -> Omega Z2 -> Int residue_offset a b = (residue_shift a - residue_shift b) `mod` 4 -- | Check whether a residue is reducible. A residue /r/ is called /reducible/ -- if it is of the form /r/ = √2 ⋅ /r/', i.e., /r/ ∈ {0000, 0101, 1010, 1111}. reducible :: Omega Z2 -> Bool reducible (Omega a b c d) = (a==c) && (b==d) -- ---------------------------------------------------------------------- -- * Exact synthesis -- | Perform a single row operation as in Lemma 4, applied to rows /i/ -- and /j/. The entries at rows /i/ and /j/ are /x/ and /y/, -- respectively, with respective residues /a/ and /b/. A precondition -- is that /x/ and /y/ are of the same residue type. Returns a list of -- two-level operations that decreases the denominator exponent. row_step :: ((Index, Omega Z2, ZOmega), (Index, Omega Z2, ZOmega)) -> [TwoLevel] row_step ((i,a,x), (j,b,y)) | reducible a && reducible b = [] | offs /= 0 = (TL_T offs i j) : row_step ((i,a,x), (j,b',y')) | otherwise = (TL_H i j) : row_step ((i,a1,x1), (j,b1,y1)) where offs = residue_offset b a y' = omega_power (-offs) y b' = residue y' (x1,y1) = opH_zomega (x,y) (a1,b1) = residue (x1,y1) -- | Row reduction: Given a unit column vector /v/, generate a -- sequence of two-level operators that reduces the /i/th standard -- basis vector /e/[sub /i/] to /v/. Any rows that are already 0 in -- both vectors are guaranteed not to be touched. reduce_column :: (Nat n) => Matrix n One (DOmega) -> Index -> [TwoLevel] reduce_column v i = aux w k where vlist = list_of_vector (vector_head (unMatrix v)) (w, k) = denomexp_decompose vlist aux w 0 = m1 ++ m2 where j = case findIndices (/= 0) w of [j] -> j _ -> error "reduce_column: not a unit vector" wj = w !! j l = case log_omega wj of Just l -> l Nothing -> error "reduce_column: not a unit vector" m1 = if i==j then [] else [TL_X i j] m2 = [TL_omega l i] aux w k = gates ++ aux w' (k-1) where res = residue w idx_res = zip3 [0..] res w res1010 = [ (i,a,x) | (i,a,x) <- idx_res, residue_type a == RT_1010 ] res0001 = [ (i,a,x) | (i,a,x) <- idx_res, residue_type a == RT_0001 ] res1010_pairs = case list_pairs res1010 of (p, Nothing) -> p _ -> error "reduce_column: not a unit vector" res0001_pairs = case list_pairs res0001 of (p, Nothing) -> p _ -> error "reduce_column: not a unit vector" m1010 = concat $ map row_step res1010_pairs m0001 = concat $ map row_step res0001_pairs gates = m1010 ++ m0001 w' = map (reduce_ZOmega) (apply_twolevels_zomega (invert_twolevels gates) w) -- | Input an exact /n/×/n/ unitary operator with coefficients in -- [bold D][ω], and output an equivalent sequence of two-level -- operators. This is the algorithm from the Giles-Selinger paper. It -- has superexponential complexity. -- -- Note: the list of 'TwoLevel' operators will be returned in -- right-to-left order, i.e., as in the mathematical notation for -- matrix multiplication. This is the opposite of the quantum circuit -- notation. synthesis_nqubit :: (Nat n) => Matrix n n DOmega -> [TwoLevel] synthesis_nqubit m = aux (unMatrix m) 0 where aux :: (Nat m) => Vector n (Vector m DOmega) -> Index -> [TwoLevel] aux Nil i = [] aux (c `Cons` cs) i = gates ++ aux (unMatrix m') (i+1) where gates = reduce_column (column_matrix c) i gates_matrix = matrix_of_twolevels (invert_twolevels gates) m' = gates_matrix .*. (Matrix cs) -- ---------------------------------------------------------------------- -- * Alternative algorithm -- $ Section 6 of the Giles-Selinger paper mentions an alternate -- version of the decomposition algorithm. It requires no ancillas, -- provided that the determinant of the operator permits this. -- | Symbolic representation of one- and two-level operators, with an -- alternate set of generators. -- -- Note: when we use a list of 'TwoLevel' operators to express a -- sequence of operators, the operators are meant to be applied -- right-to-left, i.e., as in the mathematical notation for matrix -- multiplication. This is the opposite of the quantum circuit -- notation. data TwoLevelAlt = TL_iX Index Index -- ^ /iX/[sub /i/,/j/]. | TL_TiHT Int Index Index -- ^ (/T/[super −/m/](iH)T[super /m/])[sub /i/,/j/]. | TL_W Int Index Index -- ^ /W/[super /m/][sub /i/,/j/]. | TL_omega_alt Int Index -- ^ (ω[sub /i/])[super /m/]. deriving (Show, Eq) -- | Convert from the alternate generators to the original generators. twolevels_of_twolevelalts :: [TwoLevelAlt] -> [TwoLevel] twolevels_of_twolevelalts [] = [] twolevels_of_twolevelalts (TL_iX j l : t) = TL_X j l : TL_omega 2 j : TL_omega 2 l : twolevels_of_twolevelalts t twolevels_of_twolevelalts (TL_TiHT m j l : t) = TL_T (-m) j l : TL_H j l : TL_omega 2 j : TL_omega 2 l : TL_T m j l : twolevels_of_twolevelalts t twolevels_of_twolevelalts (TL_W m j l : t) = TL_omega m j : TL_omega (-m) l : twolevels_of_twolevelalts t twolevels_of_twolevelalts (TL_omega_alt m j : t) = TL_omega m j : twolevels_of_twolevelalts t -- | Invert a list of 'TwoLevelAlt' operators, and convert the output -- to a list of 'TwoLevel' operators. invert_twolevels_alt :: [TwoLevelAlt] -> [TwoLevel] invert_twolevels_alt = invert_twolevels . twolevels_of_twolevelalts -- | Perform a single row operation as in Lemma 4, applied to rows /i/ -- and /j/, using the generators of Section 6. The entries at rows -- /i/ and /j/ are /x/ and /y/, respectively, with respective residues -- /a/ and /b/. A precondition is that /x/ and /y/ are of the same -- residue type. Returns a list of two-level operations that decreases -- the denominator exponent. row_step_alt :: ((Index, Omega Z2, ZOmega), (Index, Omega Z2, ZOmega)) -> [TwoLevelAlt] row_step_alt ((j,a,x), (l,b,y)) | reducible a && reducible b = [] | otherwise = (TL_TiHT m j l) : row_step_alt ((j,a1,x1), (l,b1,y1)) where m = residue_offset a b y' = omega_power m y (x1,y1') = opH_zomega (-i*x,-i*y') y1 = omega_power (-m) y1' (a1,b1) = residue (x1,y1) -- | Row reduction: Given a unit column vector /v/, generate a -- sequence of two-level operators that reduces the /i/th standard -- basis vector /e/[sub /i/] to /v/. Any rows that are already 0 in -- both vectors are guaranteed not to be touched, except possibly row -- /i/+1 may be multiplied by a scalar. reduce_column_alt :: (Nat n) => Matrix n One (DOmega) -> Index -> [TwoLevelAlt] reduce_column_alt v j = aux w k where vlist = list_of_vector (vector_head (unMatrix v)) n = length vlist (w, k) = denomexp_decompose vlist aux w 0 = m1 ++ m2 where l = case findIndices (/= 0) w of [l] -> l _ -> error "reduce_column: not a unit vector" m1 = if j==l then [] else [TL_iX j l] wl = if j==l then w !! j else -i*(w !! l) m = case log_omega wl of Just m -> m Nothing -> error "reduce_column: not a unit vector" m2 = if j==n-1 then [TL_omega_alt m j] else [TL_W m j (j+1)] aux w k = gates ++ aux w' (k-1) where res = residue w idx_res = zip3 [0..] res w res1010 = [ (j,a,x) | (j,a,x) <- idx_res, residue_type a == RT_1010 ] res0001 = [ (j,a,x) | (j,a,x) <- idx_res, residue_type a == RT_0001 ] res1010_pairs = case list_pairs res1010 of (p, Nothing) -> p _ -> error "reduce_column: not a unit vector" res0001_pairs = case list_pairs res0001 of (p, Nothing) -> p _ -> error "reduce_column: not a unit vector" m1010 = concat $ map row_step_alt res1010_pairs m0001 = concat $ map row_step_alt res0001_pairs gates = m1010 ++ m0001 w' = map reduce_ZOmega (apply_twolevels_zomega (invert_twolevels_alt gates) w) -- | Input an exact /n/×/n/ unitary operator with coefficients in -- [bold D][ω], and output an equivalent sequence of two-level -- operators (in the alternative generators, where all but at most one -- of the generators has determinant 1). This is the algorithm from -- the Giles-Selinger paper, Section 6. It has superexponential -- complexity. -- -- Note: the list of 'TwoLevelAlt' operators will be returned in -- right-to-left order, i.e., as in the mathematical notation for -- matrix multiplication. This is the opposite of the quantum circuit -- notation. synthesis_nqubit_alt :: (Nat n) => Matrix n n DOmega -> [TwoLevelAlt] synthesis_nqubit_alt m = aux (unMatrix m) 0 where aux :: (Nat m) => Vector n (Vector m DOmega) -> Index -> [TwoLevelAlt] aux Nil i = [] aux (c `Cons` cs) i = gates ++ aux (unMatrix m') (i+1) where gates = reduce_column_alt (column_matrix c) i gates_matrix = matrix_of_twolevels (invert_twolevels_alt gates) m' = gates_matrix .*. (Matrix cs)