{-# LANGUAGE Rank2Types #-}
-- | Functions to decompose circuits into various gate bases. This
-- decompositions are \"legacy\". The 'GB_Toffoli' and 'GB_CliffordT'
-- decomposers contained here are being superseded by their
-- counterparts in "Quipper.Libraries.Decompose.CliffordT".
module Quipper.Libraries.Decompose.Legacy where
import Quipper
import Quipper.Internal
import Quipper.Libraries.GateDecompositions
-- The following is a bunch of stuff we need to import because,
-- temporarily, Decompose.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.Circuit
import Quipper.Internal.Monad
import Quipper.Internal.Transformer (bindings_empty, bind_list, unbind_list, DynamicTransformer(..))
import Quipper.Internal.Generic (provide_subroutine_generic, transform_unary_dynamic)
import Control.Monad
-- ----------------------------------------------------------------------
-- * Legacy gatebase
-- | An enumeration type for the three gate bases handled by this
-- module.
data LegacyGateBase = GB_Toffoli | GB_Binary | GB_CliffordT
deriving (Show)
-- ----------------------------------------------------------------------
-- * Helper functions
-- | Decompose quantum controls recursively until at most /n/ remain,
-- and then pass these reduced controls to the given circuit.
-- Precondition: /n/ ≥ 1. The decomposition is done using Toffoli
-- gates, decomposed into the gate base. Classical controls are left
-- untouched.
--
-- For example, when /n/=2, this typically yields a circuit such as
-- the following (but with the Toffoli gates further decomposed into
-- the 'LegacyGateBase'):
--
-- \[image decompose2Controls.png]
--
-- And for /n/=1, the circuit typically looks like this:
--
-- \[image decomposeControls.png]
with_combined_controls_gb :: LegacyGateBase -> Int -> [Signed Endpoint] -> ([Signed Qubit] -> Circ a) -> Circ a
with_combined_controls_gb GB_Toffoli = with_combined_controls toffoli_plain_at
with_combined_controls_gb GB_Binary = with_combined_controls toffoli_V_at
with_combined_controls_gb GB_CliffordT = with_combined_controls toffoli_AMMR_at
-- | Decompose a Toffoli gate into the given 'LegacyGateBase'. The controls
-- on the Toffoli gate may be positive or negative, as specified.
decomposeQToffoli :: LegacyGateBase -> Qubit -> (Signed Qubit, Signed Qubit) -> Circ ()
decomposeQToffoli GB_Toffoli q (c1, c2) = qnot_at q `controlled` [c1,c2]
decomposeQToffoli GB_Binary q (c1, c2) = do
toffoli_V_at q c1 c2
decomposeQToffoli GB_CliffordT q3 (c1, c2) = do
toffoli_AMMR_at q3 c1 c2
-- | The inverse of 'decomposeQToffoli'.
decomposeQToffoli_inv :: LegacyGateBase -> Qubit -> (Signed Qubit, Signed Qubit) -> Circ ()
decomposeQToffoli_inv gb = reverse_generic_imp (decomposeQToffoli gb)
-- | Implement a QMultinot gate in terms of QNot gates.
decomposeQMultinot :: [Qubit] -> Circ ()
decomposeQMultinot [] = return ()
decomposeQMultinot (q:qs) = do
qnot_at q
decomposeQMultinot qs
-- ----------------------------------------------------------------------
-- * Decomposition transformers
-- | A transformer to decompose a circuit into 'LegacyGateBase' gates. Note
-- that in the case of classically-controlled quantum gates, the
-- classical controls are unaffected.
decompose_transformer :: LegacyGateBase -> Transformer Circ Qubit Bit
decompose_transformer gb (T_QGate "not" 1 0 _ ncf f) = f $
\[q] [] cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 2 cs $ \qcs -> do
case qcs of
-- two controls
[c1, c2] -> do
decomposeQToffoli gb q (c1,c2)
return ([q], [], cs)
-- zero or one control
qcs -> do
qnot_at q `controlled` qcs
return ([q], [], cs)
decompose_transformer gb (T_QGate "multinot" _ 0 _ ncf f) = f $
\qs [] cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
decomposeQMultinot qs `controlled` qcs
return (qs, [], cs)
decompose_transformer gb (T_QGate "H" 1 0 _ ncf f) = f $
\[q] [] cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
hadamard q `controlled` qcs
return ([q], [], cs)
decompose_transformer gb (T_QGate "swap" 2 0 _ ncf f) = f $
\[q1, q2] [] cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
case qcs of
-- one control
[c] -> do
qnot_at q1 `controlled` q2
decomposeQToffoli gb q2 (c,(Signed q1 True))
qnot_at q1 `controlled` q2
return ([q1, q2], [], cs)
-- zero controls
qcs -> do
swap_at q1 q2
return ([q1, q2], [], cs)
decompose_transformer gb (T_QGate "W" 2 0 _ ncf f) = f $
\[q1, q2] [] cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
case qcs of
-- one control
[c] -> do
qnot q2 `controlled` q1
w' <- qinit_qubit False
decomposeQToffoli gb w' (c,(Signed q2 True))
hadamard q1 `controlled` w'
decomposeQToffoli_inv gb w' (c,(Signed q2 True))
qterm_qubit False w'
qnot q2 `controlled` q1
return ([q1, q2], [], cs)
-- zero controls
qcs -> do
gate_W q1 q2
return ([q1, q2], [], cs)
decompose_transformer gb (T_QGate name _ _ inv ncf f) = f $
\qs vs cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
named_gate_qulist_at name inv qs vs `controlled` qcs
return (qs, vs, cs)
decompose_transformer gb (T_QRot name _ _ inv theta ncf f) = f $
\qs vs cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
named_rotation_qulist_at name inv theta qs vs `controlled` qcs
return (qs, vs, cs)
decompose_transformer gb (T_GPhase t ncf f) = f $
\qs cs -> without_controls_if ncf $ do
with_combined_controls_gb gb 1 cs $ \qcs -> do
global_phase_anchored_list t qs `controlled` qcs
return cs
-- The subroutine transformer clause is called when a subroutine gate appears,
-- for now we decompose the controls just like for other gates. The recursive
-- decomposition of a subroutine is taken care of in the dynamic transformer.
decompose_transformer gb (T_Subroutine n inv ncf scf ws_pat a1 vs_pat a2 rep f) = f $
\namespace ws cs -> without_controls_if ncf $ do
let qws = [w | Endpoint_Qubit w <- ws]
with_combined_controls_gb gb 1 cs $ \qcs -> do
provide_subroutines namespace
case qcs of
-- one control
[c] -> if length qws /= length ws then error "Classical subroutine, used with quantum controls" else do
vs <- subroutine n inv scf rep ws_pat a1 vs_pat a2 ws `controlled` c
return (vs,cs)
-- zero controls
qcs -> do
vs <- subroutine n inv scf rep ws_pat a1 vs_pat a2 ws
return (vs,cs)
-- We list catch-all cases explicitly, so that the type-checker can
-- warn about new gates that must be added to the list.
decompose_transformer gb gate@(T_CNot _ _) = identity_transformer gate
decompose_transformer gb gate@(T_CGate _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_CGateInv _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_CSwap _ _) = identity_transformer gate
decompose_transformer gb gate@(T_QPrep _ _) = identity_transformer gate
decompose_transformer gb gate@(T_QUnprep _ _) = identity_transformer gate
decompose_transformer gb gate@(T_QInit _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_CInit _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_QTerm _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_CTerm _ _ _) = identity_transformer gate
decompose_transformer gb gate@(T_QMeas _) = identity_transformer gate
decompose_transformer gb gate@(T_QDiscard _) = identity_transformer gate
decompose_transformer gb gate@(T_CDiscard _) = identity_transformer gate
decompose_transformer gb gate@(T_DTerm _ _) = identity_transformer gate
decompose_transformer gb gate@(T_Comment _ _ _) = identity_transformer gate
-- | Return a circuit producing function from a TypedSubroutine
open_subroutine :: TypedSubroutine -> [Endpoint] -> Circ [Endpoint]
open_subroutine (TypedSubroutine ocircuit _ _ scf) inputs = do
let OCircuit (win, circuit, wout) = ocircuit
when (length win /= length inputs) $ do
error ("open_subroutine: subroutine has been applied to incorrect size of QCData")
let in_bind = bind_list win inputs bindings_empty
out_bind <- apply_circuit_with_bindings circuit in_bind
let outputs = unbind_list out_bind wout
return outputs
-- | Apply the decompose transformer to the given TypedSubroutine
-- Note: by default, set the classical-control flag to false.
decompose_subroutine :: LegacyGateBase -> BoxId -> TypedSubroutine -> Circ ()
decompose_subroutine gb boxid sub@(TypedSubroutine ocirc is os ctl) = do
let circ = open_subroutine sub
let circ' = decompose_legacy_unary gb circ
let OCircuit (win, (arity,_,_,_), _) = ocirc
let ein = endpoints_of_wires_in_arity arity win
provide_subroutine_generic (\x -> "decompose_subroutine: " ++ x) boxid False circ' ein
-- | A dynamic transformer variant of the decompose transformer
decompose_dynamic_transformer :: LegacyGateBase -> DynamicTransformer Circ Qubit Bit
decompose_dynamic_transformer gb = identity_dynamic_transformer {
transformer = decompose_transformer gb,
define_subroutine = decompose_subroutine gb}
-- ----------------------------------------------------------------------
-- * Generic decomposition
-- | Decompose a circuit into gates from the given 'LegacyGateBase'.
decompose_legacy_unary :: (QCData qa, QCData qb) => LegacyGateBase -> (qa -> Circ qb) -> (qa -> Circ qb)
decompose_legacy_unary gb circ = transform_unary_dynamic (decompose_dynamic_transformer gb) circ
-- | Decompose a circuit into gates from the given 'LegacyGateBase'. Unlike
-- 'decompose_legacy_unary', this can be applied to a circuit-generating
-- function in curried form with /n/ arguments, for any /n/ ≥ 0.
--
-- The type of this heavily overloaded function is difficult to
-- read. In more readable form, it has all of the following types:
--
-- > decompose_legacy_generic :: (QCData qa) => LegacyGateBase -> Circ qa -> Circ qa
-- > decompose_legacy_generic :: (QCData qa, QCData qb) => LegacyGateBase -> (qa -> Circ qb) -> (qa -> Circ qb)
-- > decompose_legacy_generic :: (QCData qa, QCData qb, QCData qc) => LegacyGateBase -> (qa -> qb -> Circ qc) -> (qa -> qb -> Circ qc)
--
-- and so forth.
decompose_legacy_generic :: (QCData qa, QCData qb, QCurry qfun qa qb) => LegacyGateBase -> qfun -> qfun
decompose_legacy_generic gatebase f = g where
f1 = quncurry f
g1 = decompose_legacy_unary gatebase f1
g = qcurry g1