entangle-0.1.0: src/lib/Transitions.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Transitions where
import Quipper
import Quipper.Circuit
import Quipper.Monad
import BitQubitId
import Complex
import EntangleMonad
import Expr
import qualified GatesMatrices
import QMatrix
import QTuple
data StateName = StateName {
snId :: Integer, -- ^ state id
snBs :: [Bool] -- ^ boolean values in the state
} deriving Eq
instance Ord StateName where
compare a b
| snId a < snId b = LT
| snId a > snId b = GT
| otherwise = compare' (snBs a) (snBs b) where
compare' [] [] = EQ
compare' [] _ = LT
compare' _ [] = GT
compare' (True:_) (False:_) = LT
compare' (False:_) (True:_) = GT
compare' (_:as) (_:bs) = compare' as bs
instance Show StateName where
show (StateName i bs) = show i ++ if null bs then "" else "_" ++ map (\b -> if b then 'T' else 'F') (reverse bs)
data Transitions m v = Transitions {
trFromState :: StateName,
trDestinations :: [Transition m v]
}
instance Show (Transitions m v) where
show (Transitions from dests) = "[Transitions trFromState=" ++ show from ++ " trDestinations=" ++ show dests ++ "]"
data Transition m v = Transition {
trMatrix :: Maybe (m (Complex v)),
trToState :: StateName
}
instance Show (Transition m v) where
show (Transition _ to) = "[Transition trMatrix=... trToState=" ++ show to ++ "]"
type QubitCount = QubitId
type ControlCount = QubitId
-- |circMatrices takes a function returning a value in the 'Circ' monad,
-- and calculates the list of QPMC transitions needed to represent it.
circMatrices :: (FromDouble a, QTuple q, Show b, QCMatrix m a) => (b -> [Transition m a]) -> (q -> Circ b) -> [Transitions m a]
circMatrices final = treeToTransitions final . circToTree
circToTree :: QTuple a => (a -> Circ b) -> CircTree b
circToTree mcirc = tree where
arg = tupleFromList $ map qubit_of_wire [1..]
circ = extract_general arity_empty (mcirc arg)
argsLength = tupleSize arg
tree = buildTree circ argsLength
treeToTransitions :: (FromDouble a, Show b, QCMatrix m a) => (b -> [Transition m a]) -> CircTree b -> [Transitions m a]
treeToTransitions final t = go (StateName 0 []) t where
wires :: [QubitId]
wires = getWires t
qubit_max :: QubitId
qubit_max = foldr max minBound wires
go sn (LeafNode x) = if null f then [] else [Transitions sn f] where
f = final x
go sn@(StateName i bs) (GateNode name qs cts c) = Transitions sn [tr] : go state' c where
tr = Transition (Just mat) state'
mat = gateToMatrix qubit_max name qs cts
state' = StateName (succ i) bs
go sn@(StateName i bs) (ParameterizedGateNode name k qs cts c) = Transitions sn [tr] : go state' c where
tr = Transition (Just mat) state'
mat = parameterizedGateToMatrix qubit_max name k qs cts
state' = StateName (succ i) bs
go sn@(StateName i bs) (MeasureNode qi _ l r) = Transitions sn [lt, rt] : go ls l ++ go rs r where
lmat = between (pred qi) (QMatrix.measure UL) (qubit_max - qi)
ls = StateName (succ i) (bs ++ [False])
lt = Transition (Just lmat) ls
rmat = between (pred qi) (QMatrix.measure BR) (qubit_max - qi)
rs = StateName (succ i) (bs ++ [True])
rt = Transition (Just rmat) rs
-- |getWires returns the qubit numbers involved in a gate.
--getWires :: CircTree a -> [QubitId]
getWires :: Show a => CircTree a -> [QubitId]
getWires (LeafNode _) = []
getWires (GateNode _ qs cs c) = qs ++ cs ++ getWires c
getWires (ParameterizedGateNode _ _ qs cs c) = qs ++ cs ++ getWires c
getWires (MeasureNode q _ l r) = q : getWires l ++ getWires r
-- |sw q t is a function that swaps q and t
sw :: Eq a => a -> a -> (a -> a)
sw q t x | x == q = t
| x == t = q
| otherwise = x
-- |gateToMatrix takes the total number of qubits, a gate data and returns the matrix needed to represent it.
gateToMatrix :: QCMatrix m a => QubitCount -> String -> [QubitId] -> [QubitId] -> m (Complex a)
gateToMatrix size name qs cs =
let
wires = cs ++ qs
mi = minimum wires
swaps = reverse $ generateSwaps wires [mi..]
controlCount = qubitId $ length cs
qubitCount = qubitId $ length qs
ma = pred $ mi + controlCount + qubitCount
l = pred mi
m = nameToMatrix controlCount qubitCount name
r = size - ma
mat = between l m r
in
moving size swaps mat
-- |parameterizedGateToMatrix takes the total number of qubits, a gate data and returns the matrix needed to represent it.
parameterizedGateToMatrix :: (FromDouble a, QCMatrix m a) => QubitCount -> String -> Double -> [QubitId] -> [QubitId] -> m (Complex a)
parameterizedGateToMatrix size name t qs cs =
let
wires = cs ++ qs
mi = minimum wires
swaps = reverse $ generateSwaps wires [mi..]
controlCount = qubitId $ length cs
qubitCount = qubitId $ length qs
ma = pred $ mi + controlCount + qubitCount
l = pred mi
m = nameToParameterizedMatrix t controlCount qubitCount name
r = size - ma
mat = between l m r
in
moving size swaps mat
-- |generateSwaps takes a finite list of source qubits, a list of target qubits,
-- and returns a list of swaps that moves the qubits into place.
-- Algebrically this is a decomposition of a generic permutation into swaps.
generateSwaps :: Eq t => [t] -> [t] -> [(t, t)]
generateSwaps [] _ = []
generateSwaps _ [] = []
generateSwaps (q:qs) (t:ts)
| q == t = generateSwaps qs ts
| otherwise = (q, t) : generateSwaps (map (sw q t) qs) ts
-- |nameToMatrix is the matrix for the given named gate.
-- It returns a matrix with an identity in the top left
-- and the action in the bottom right.
nameToMatrix :: (Floating a, QCMatrix m a) => ControlCount -> QubitCount -> String -> m (Complex a)
nameToMatrix controlCount qubitCount name =
let
total_size = toSize (controlCount + qubitCount)
small_size = if qubitCount == 0 then 0 else toSize qubitCount
big_size = total_size - small_size
active = GatesMatrices.nameToMatrix name
in
if big_size == 0
then active
else
(identity big_size <|> zero big_size small_size)
<->
(zero small_size big_size <|> active)
-- |nameToParameterizedMatrix is the matrix for the given named parameterized gate.
-- It returns a matrix with an identity in the top left
-- and the action in the bottom right.
nameToParameterizedMatrix :: (FromDouble a, QCMatrix m a) => Double -> ControlCount -> QubitCount -> String -> m (Complex a)
nameToParameterizedMatrix t controlCount qubitCount name =
let
total_size = toSize (controlCount + qubitCount)
small_size = if qubitCount == 0 then 0 else toSize qubitCount
big_size = total_size - small_size
active = GatesMatrices.nameToParameterizedMatrix name t
in
if big_size == 0
then active
else
(identity big_size <|> zero big_size small_size)
<->
(zero small_size big_size <|> active)
-- |moving returns a matrix representing:
-- * moving the chosen qubits
-- * applying the given matrix
-- * moving the qubits back to their original position
moving :: QMatrix m a => QubitCount -> [(QubitId, QubitId)] -> m a -> m a
moving size moves m = back * m * forth where
forth = move size moves
back = move size $ reverse moves
-- |move is the matrix that moves the chosen qubits
move :: QMatrix m a => QubitCount -> [(QubitId, QubitId)] -> m a
move size = foldr f $ identity (toSize size) where
f (t1, t2) m = swapToMatrix size t1 t2 * m
-- |swapToMatrix is the matrix swapping the chosen qubits
swapToMatrix :: QMatrix m a => QubitCount -> QubitId -> QubitId -> m a
swapToMatrix size n m
| n > m = swapToMatrix size m n
| n == m = identity $ toSize size
| n < pred m = swapToMatrix size n (pred m) * swapToMatrix size (pred m) m
-- otherwise: n == pred m
| otherwise = between (pred n) QMatrix.swap (size - m)
-- |between takes a matrix and applies it to the chosen qubits,
-- without modifying the other ones
between :: QMatrix m a => QubitCount -> m a -> QubitCount -> m a
between b m a = before `kronecker` m `kronecker` after where
before = identity $ toSize b
after = identity $ toSize a