{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | This module provides a Quipper interface to the efficient
-- simulation of Clifford group circuits, using the Stabilizer
-- formalism.
--
-- This module provides the internal implementation of the library,
-- and can be imported by other libraries. The public interface to
-- simulation is "Quipper.Libraries.Simulation".
module Quipper.Libraries.Simulation.CliffordSimulation where
import Quipper
import Quipper.Internal
-- The following is a bunch of stuff we need to import because,
-- temporarily, CliffordSimulation.hs uses low-level interfaces. It
-- should be re-implemented using only high-level interfaces, or in
-- some cases, more stuff should be exported from Quipper.hs.
import Quipper.Internal.Generic (encapsulate_generic, qc_unbind)
import Quipper.Internal.Transformer (transform_bcircuit_rec, bindings_empty)
import Quipper.Libraries.Simulation.QuantumSimulation (gateQinv, rotQinv)
import Quipper.Libraries.Simulation.ClassicalSimulation (simulate_cgate)
import Quipper.Utils.Auxiliary
import qualified Quipper.Utils.Stabilizers.Clifford as C
import Control.Monad.State (StateT)
import Data.Either
-- ----------------------------------------------------------------------
-- * The stabilizer transformer
-- | The stabilizer transformer. Used to transform a Quipper circuit,
-- made up of Clifford group operators, directly into the 'C.CliffordCirc'
-- monad (where 'C.CliffordCirc' is a type-synonym for StateT Tableau IO).
-- Note that this transformer only deals with 1 and 2-qubit operators.
stabilizer_transformer :: Transformer (StateT C.Tableau IO) C.Qubit Bool
stabilizer_transformer (T_QGate "not" 1 0 _ ncf f) = f $
-- X and controlled-X are already provided by C.CliffordCirc
\[qt] [] c -> case control_info c of
None -> do
C.gate_X qt
return ([qt], [], c)
Classical b -> do
if b then C.gate_X qt else return ()
return ([qt], [], c)
OneQuantum (Signed qc positive) b -> do
if not positive then C.gate_X qc else return ()
if b then C.controlled_X qc qt else return ()
if not positive then C.gate_X qc else return ()
return ([qt], [], c)
_ -> error "stabilizer_transformer: Toffoli gate not available"
stabilizer_transformer (T_QGate "multinot" _ 0 _ ncf f) = f $
-- X and controlled-X are already provided by C.CliffordCirc
-- and can be mapped over a list of qubits
\ws [] c -> case control_info c of
None -> do
mapM_ C.gate_X ws
return (ws, [], c)
Classical b -> do
if b then mapM_ C.gate_X ws else return ()
return (ws, [], c)
OneQuantum (Signed qc positive) b -> do
if not positive then C.gate_X qc else return ()
if b then mapM_ (C.controlled_X qc) ws else return ()
if not positive then C.gate_X qc else return ()
return (ws, [], c)
_ -> error "stabilizer_transformer: (Multi) Toffoli gate not available"
stabilizer_transformer (T_QGate "H" 1 0 _ ncf f) = f $
-- Hadamard is already provided by C.CliffordCirc
\[qt] [] c -> case control_info c of
None -> do
C.gate_H qt
return ([qt], [], c)
Classical b -> do
if b then C.gate_H qt else return ()
return ([qt], [], c)
-- TODO: ???
_ -> error "stabilizer_transformer: controlled-Hadamard currently not supported"
stabilizer_transformer (T_QGate "swap" 2 0 _ ncf f) = f $
-- (classically-controlled) swap is already provided by C.CliffordCirc
\[w, v] [] c -> case control_info c of
None -> do
C.swap w v
return ([w, v], [], [])
Classical b -> do
if b then C.swap w v else return ()
return ([w, v], [], [])
_ -> error "stabilizer_transformer: quantum controlled-swap not available"
stabilizer_transformer (T_QGate "W" 2 0 _ ncf f) = f $
-- TODO: ???
\[w, v] [] c -> error "stabilizer_transformer: W currently not supported"
stabilizer_transformer (T_QGate name _ _ inv ncf f) = f $
\ws vs c ->
case vs of
[] ->
case ws of
[qt] -> case control_info c of
None -> do
C.gate_Unitary u1 qt
return ([qt], vs, c)
Classical b -> do
if b then C.gate_Unitary u1 qt else return ()
return ([qt], vs, c)
OneQuantum (Signed qc positive) b -> do
if b then C.gate_Unitary2 u2 qc qt else return ()
return ([qt], vs, c)
_ -> error "stabilizer_transformer: Multiple quantum controls not supported"
where u1 = case name of
"X" -> C.x
"Y" -> C.y
"Z" -> C.z
"S" -> C.s
"E" -> C.e
name -> C.from_matrix (gateQinv name inv)
u2 = case name of
"X" -> C.cnot
"Z" -> C.cz
name -> C.from_matrix_controlled (gateQinv name inv)
_ -> error "stabilizer_transformer: Named gates on multiple Qubits not available"
_ -> error "stabilizer_transformer: generalised controls not currently supported"
stabilizer_transformer (T_QRot name _ _ inv t ncf f) = f $
\ws vs c -> error "stabilizer_transformer: QRot not currently supported" --return (ws, vs, c)
stabilizer_transformer (T_GPhase t ncf f) = f $
-- TODO: ???
\qs c -> error "stabilizer_transformer: GPhase currently not supported"
stabilizer_transformer (T_CNot ncf f) = f $
-- we can do a classical not depending on the controls
\q c -> case control_info c of
None -> return (not q,c)
Classical b -> if b then return (not q,c) else return (q,c)
_ -> error "stabilizer_transformer: Quantum control on classical gate"
stabilizer_transformer (T_CGate name ncf f) = f $
-- we can reuse the classical simulator cgate implementation
\ws -> return (simulate_cgate name ws,ws)
stabilizer_transformer (T_CGateInv name ncf f) = f $
-- we can check that the inverse is correct using the classical simulator cgate implementation
\v ws -> if (simulate_cgate name ws == v) then return ws else error "stabilizer_transformer: CGateInv not inverse"
stabilizer_transformer (T_CSwap ncf f) = f $
-- we can do a classical swap depending on the controls
\w v c -> case control_info c of
None -> return (w,v,c)
Classical b -> if b then return (v,w,c) else return (w,v,c)
_ -> error "stabilizer_transformer: Quantum control on classical gate"
stabilizer_transformer (T_QPrep ncf f) = f $
-- TODO: ??
\w -> error "stabilizer_transformer: QPrep currently not supported"
stabilizer_transformer (T_QUnprep ncf f) = f $
-- TODO: ??
\w -> error "stabilizer_transformer: QUnprep currently not supported"
stabilizer_transformer (T_QInit b ncf f) = f $
-- initialization is already provided by C.CliffordCirc
C.init_qubit b
stabilizer_transformer (T_CInit b ncf f) = f $
-- initialisation of a boolean value
return b
stabilizer_transformer (T_QTerm b ncf f) = f $
-- we can check termination conditions at runtime, using measurement
\w -> do
res <- C.measure_qubit w
if res == b then return () else error "stabilizer_transformer: QTerm condition failed"
stabilizer_transformer (T_CTerm b ncf f) = f $
-- we can check termination conditions at runtime
\w -> if w == b then return () else error "stabilizer_transformer: CTerm condition failed"
stabilizer_transformer (T_QMeas f) = f $
-- measurement is already provided by C.CliffordCirc
\w -> C.measure_qubit w
stabilizer_transformer (T_QDiscard f) = f $
-- we can ignore discards
\w -> return ()
stabilizer_transformer (T_CDiscard f) = f $
-- we can ignore discards
\w -> return ()
stabilizer_transformer (T_DTerm b f) = f $
-- TODO: ??
\w -> error "stabilizer_transformer: DTerm currently not supported" --return ()
stabilizer_transformer (T_Subroutine n inv ncf scf ws_pat a1 vs_pat a2 rep f) = f $
-- TODO: we can "open" subroutines
\namespace ws c -> error "stabilizer_transformer: Subroutine currently not supported" --return (ws,c)
stabilizer_transformer (T_Comment s inv f) = f $
-- we can ignore comments
\ws -> return ()
-- | A datatype to represent controls that we can simulate
data ControlInfo = None | Classical Bool | OneQuantum (Signed C.Qubit) Bool | ManyQuantum
-- | Construct an element of ControlInfo from a list of Controls.
control_info :: Ctrls C.Qubit Bool -> ControlInfo
control_info cs = case split_controls cs of
([],[]) -> None
([],cs) -> Classical (all_equal cs)
([q],cs) -> OneQuantum q (all_equal cs)
_ -> ManyQuantum
where
split_controls :: Ctrls a b -> ([Signed a],[Signed b])
split_controls cs = partitionEithers (map either_control cs)
either_control :: Signed (B_Endpoint a b) -> Either (Signed a) (Signed b)
either_control (Signed (Endpoint_Qubit a) value) = Left (Signed a value)
either_control (Signed (Endpoint_Bit b) value) = Right (Signed b value)
all_equal :: [Signed Bool] -> Bool
all_equal cs = and (map (\(Signed b val) -> b == val) cs)
-- ----------------------------------------------------------------------
-- * High-level functions
-- | Use the 'stabilizer_transformer' to transform a Quipper circuit
-- into a 'C.CliffordCirc', ready for simulation.
toCliffordCirc :: (QCData qa, QCDataPlus qb) => (qa -> Circ qb) -> (BType qa -> C.CliffordCirc (BType qb))
toCliffordCirc (f :: qa -> Circ qb) input = do
let ((), circuit, cb) = encapsulate_generic errmsg (\() -> qc_init input >>= \qi -> f qi >>= \qi' -> qc_measure qi') ()
out_bind' <- transform_bcircuit_rec stabilizer_transformer circuit bindings_empty
let out_bind = out_bind'
let output = qc_unbind out_bind cb
return output
where
errmsg x = ("simulate: " ++ x)
-- | Return the tableau resulting from simulating the given Quipper
-- circuit.
eval_unary :: (QCData qa, QCDataPlus qb) => (qa -> Circ qb) -> (BType qa -> IO C.Tableau)
eval_unary circ input = C.eval (toCliffordCirc circ input)
-- | Efficiently simulate a unary Quipper circuit that consists
-- entirely of Clifford group operators, using the stabilizer
-- formalism.
run_clifford_unary :: (QCData qa, QCDataPlus qb) => (qa -> Circ qb) -> (BType qa -> IO (BType qb))
run_clifford_unary circ input = C.sim (toCliffordCirc circ input)
-- | Efficiently simulate a Quipper circuit that consists entirely of
-- Clifford group operators, using the stabilizer formalism.
--
-- Inputs a quantum circuit, and outputs a corresponding probabilistic
-- boolean function. The inputs to the quantum circuit are initialized
-- according to the given boolean arguments. The outputs of the
-- quantum circuit are measured, and the boolean measurement outcomes
-- are returned. Because the measurement outcomes are probabilistic,
-- this function takes place in the 'IO' monad.
--
-- The type of this heavily overloaded function is difficult to
-- read. In more readable form, it has all of the following types (for
-- example):
--
-- > run_clifford_generic :: (QCData qa) => Circ qa -> IO (BType qa)
-- > run_clifford_generic :: (QCData qa, QCData qb) => (qa -> Circ qb) -> BType qa -> IO (BType qb)
-- > run_clifford_generic :: (QCData qa, QCData qb, QCData qc) => (qa -> qb -> Circ qc) -> BType qa -> BType qb -> IO (BType qc)
--
-- and so forth.
run_clifford_generic :: (QCData qa, QCDataPlus qb, QCurry qfun qa qb, Curry qfun' (BType qa) (IO (BType qb))) => qfun -> qfun'
run_clifford_generic f = g where
f1 = quncurry f
g1 = run_clifford_unary f1
g = mcurry g1