{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE DoAndIfThenElse #-}
-- | This module provides functions and operators that are \"generic\"
-- on quantum data. We say that a function is generic if it works at
-- any quantum data type, rather than just a specific type such as
-- 'Qubit'. For example, the generic function 'qinit' can be used to
-- initialize a qubit from a boolean, or a pair of qubits from a pair
-- of booleans, or a list of qubits from a list of booleans, and so
-- forth.
--
-- Some functions are also generic in the /number/ of arguments they
-- take, in addition to the type of the arguments.
module Quipper.Internal.Generic (
-- * Generic gates
-- ** Initialization and termination
qinit,
qterm,
qdiscard,
cinit,
cterm,
cdiscard,
qc_init,
qc_init_with_shape,
qc_term,
qc_discard,
-- ** Measurement and preparation
measure,
prepare,
qc_measure,
qc_prepare,
-- ** Global phase gate
global_phase_anchored,
-- ** Mapped gates
map_hadamard,
map_hadamard_at,
swap,
swap_at,
controlled_not,
controlled_not_at,
bool_controlled_not,
bool_controlled_not_at,
qmultinot,
qmultinot_at,
-- ** Copying and uncopying
qc_copy_fun,
qc_uncopy_fun,
qc_copy,
qc_uncopy,
-- ** Classical gates
cgate_if,
circ_if,
-- ** Named gates
named_gate,
named_gate_at,
named_rotation,
named_rotation_at,
extended_named_gate,
extended_named_gate_at,
-- ** Dynamic lifting
dynamic_lift,
-- * Mapping
mapUnary,
mapBinary,
mapBinary_c,
map2Q,
qc_mapBinary,
-- * Conversion to lists
-- $CONVERSION
qubits_of_qdata,
qdata_of_qubits,
endpoints_of_qcdata,
qcdata_of_endpoints,
-- * Shape related operations
qc_false,
qshape,
-- * Bindings
qc_bind,
qc_unbind,
-- * Generic controls
-- $CONTROL
(.&&.),
(.==.),
(./=.),
-- * Generic encapsulation
-- $encapsulate
encapsulate_generic,
encapsulate_generic_in_namespace,
unencapsulate_generic,
-- $dynamic_encapsulate
encapsulate_dynamic,
unencapsulate_dynamic,
-- * Generic reversing
reverse_generic,
reverse_generic_curried,
reverse_simple,
reverse_simple_curried,
reverse_generic_endo,
reverse_generic_imp,
reverse_endo_if,
reverse_imp_if,
-- * The QCurry type class
QCurry (..),
-- * Generic circuit transformations
transform_unary_dynamic_shape,
transform_unary_dynamic,
transform_unary,
transform_generic,
transform_unary_shape,
transform_generic_shape,
-- * Generic block structure
with_ancilla_init,
with_ancilla_list,
with_computed_fun,
with_computed,
with_basis_change,
with_classical_control,
-- * Boxed subcircuits
provide_subroutine_generic,
box,
nbox,
box_loopM,
loopM_boxed_if,
inline_subroutine
) where
-- import other Quipper stuff
import Quipper.Internal.Circuit
import Quipper.Internal.Monad
import Quipper.Utils.Auxiliary
import Quipper.Utils.Tuple
import Quipper.Internal.Transformer
import Quipper.Internal.Control
import Quipper.Internal.QData
-- import other stuff
import Control.Monad
import Prelude
import Data.Typeable
import qualified Control.Monad.State as State
import Data.Map (Map)
import qualified Data.Map as Map
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
-- ======================================================================
-- * Generic gates
-- ** Initialization and termination
-- | Initialize a qubit from a boolean parameter. More generally,
-- initialize a data structure of qubits from a corresponding data
-- structure of boolean parameters. Examples:
--
-- > q <- qinit False
-- > (q0, q1) <- qinit (True, False)
-- > [q0, q1, q2] <- qinit [True, False, True]
qinit :: (QShape ba qa ca) => ba -> Circ qa
qinit ba = qdata_mapM (shapetype_b ba) qinit_qubit ba
-- | Terminate a qubit, asserting its state to equal the boolean
-- parameter. More generally, terminate a data structure of qubits,
-- asserting that their state is as given by a data structure of
-- booleans parameters. Examples:
--
-- > qterm False q
-- > qterm (False, False) (q0, q1)
-- > qterm [False, False, False] [q0, q1, q2]
--
-- In some cases, it is permissible for some aspect of the parameter's
-- shape to be underspecified, e.g., a longer than necessary list, or
-- an integer of indeterminate length. It is therefore possible, for
-- example, to write:
--
-- > qterm 17 qa -- when qa :: QDInt,
-- > qterm [False..] qa -- when qa :: [Qubit].
--
-- The rules for when a boolean argument can be \"promoted\" in this
-- way are specific to each individual data type.
qterm :: (QShape ba qa ca) => ba -> qa -> Circ ()
qterm ba qa = do
let shape = shapetype_b ba -- shape type
let ba' = qdata_promote ba qa errmsg -- shape data
let z = qdata_zip shape bool qubit ba' qa errmsg
qdata_mapM_op shape (\(x,y) -> qterm_qubit x y) z
return ()
where
errmsg s = "qterm: shape of parameter does not match data: " ++ s
-- | Discard a qubit, ignoring its state. This can leave the quantum
-- system in a mixed state, so is not a reversible operation. More
-- generally, discard all the qubits in a quantum data
-- structure. Examples:
--
-- > qdiscard q
-- > qdiscard (q0, q1)
-- > qdiscard [q0, q1, q2]
qdiscard :: (QData qa) => qa -> Circ ()
qdiscard qa = do
qdata_mapM_op (shapetype_q qa) qdiscard_qubit qa
return ()
-- | Initialize a 'Bit' (boolean input) from a 'Bool' (boolean
-- parameter). More generally, initialize the a data structure of Bits
-- from a corresponding data structure of Bools. Examples:
--
-- > b <- cinit False
-- > (b0, b1) <- cinit (True, False)
-- > [b0, b1, b2] <- cinit [True, False, True]
cinit :: (QShape ba qa ca) => ba -> Circ ca
cinit ba = qdata_mapM (shapetype_b ba) cinit_bit ba
-- | Terminate a 'Bit', asserting its state to equal the given
-- 'Bool'. More generally, terminate a data structure of Bits,
-- asserting that their state is as given by a data structure of
-- Bools. Examples:
--
-- > cterm False b
-- > cterm (False, False) (b0, b1)
-- > cterm [False, False, False] [b0, b1, b2]
--
-- In some cases, it is permissible for some aspect of the parameter's
-- shape to be underspecified, e.g., a longer than necessary list, or
-- an integer of indeterminate length. It is therefore possible, for
-- example, to write:
--
-- > cterm 17 ca -- when ca :: CInt,
-- > cterm [False..] ca -- when ca :: [Bit].
--
-- The rules for when a boolean argument can be \"promoted\" in this
-- way are specific to each individual data type.
cterm :: (QShape ba qa ca) => ba -> ca -> Circ ()
cterm ba ca = do
-- shape type
let shape = shapetype_b ba
-- shape data
let ba' = qdata_promote_c ba ca errmsg
let z = qdata_zip shape bool bit ba' ca errmsg
qdata_mapM_op shape (\(x,y) -> cterm_bit x y) z
return ()
where
errmsg s = "cterm: shape of parameter does not match data: " ++ s
-- | Discard a 'Bit', ignoring its state. This can leave the system in
-- a mixed state, so is not a reversible operation. More generally,
-- discard all the Bits in a data structure. Examples:
--
-- > cdiscard b
-- > cdiscard (b0, b1)
-- > cdiscard [b0, b1, b2]
cdiscard :: (CData ca) => ca -> Circ ()
cdiscard ca = do
qdata_mapM_op (shapetype_c ca) cdiscard_bit ca
return ()
-- | Heterogeneous version of 'qinit'. Please note that the type of
-- the result of this function cannot be inferred from the type of the
-- argument. For example,
--
-- > x <- qc_init False
--
-- is ambiguous, unless it can be inferred from the context whether
-- /x/ is a 'Bit' or a 'Qubit'. If the type cannot be inferred from
-- the context, it needs to be stated explicitly, like this:
--
-- > x <- qc_init False :: Circ Qubit
--
-- Alternatively, 'qc_init_with_shape' can be used to fix a specific
-- type.
qc_init :: (QCData qc) => BType qc -> Circ qc
qc_init bs = qc_init_with_shape (undefined :: qc) bs
-- | A version of 'qc_init' that uses a shape type parameter. The
-- first argument is the shape type parameter, and the second argument
-- is a data structure containing boolean initializers. The shape type
-- argument determines which booleans are used to initialize qubits,
-- and which ones are used to initialize classical bits.
--
-- Example:
--
-- > (x,y) <- qc_init_with_shape (bit,[qubit]) (True, [False,True])
--
-- This will assign to /x/ a classical bit initialized to 1, and to
-- /y/ a list of two qubits initialized to |0〉 and |1〉, respectively.
qc_init_with_shape :: (QCData qc) => qc -> BType qc -> Circ qc
qc_init_with_shape shape bs = qcdata_mapM shape qinit_qubit cinit_bit bs
-- | Heterogeneous version of 'qterm'.
qc_term :: (QCData qc) => BType qc -> qc -> Circ ()
qc_term bs qc = do
let bs' = qcdata_promote bs qc errmsg
let z = qcdata_zip qc bool bool qubit bit bs' qc errmsg
qcdata_mapM_op qc map_qubit map_bit z
return ()
where
map_qubit :: (Bool, Qubit) -> Circ ()
map_qubit (b,q) = qterm_qubit b q
map_bit :: (Bool, Bit) -> Circ ()
map_bit (b,q) = cterm_bit b q
errmsg s = "qc_term: shape of parameter does not match data: " ++ s
-- | Heterogeneous version of 'qdiscard'.
qc_discard :: (QCData qc) => qc -> Circ ()
qc_discard qc = do
qcdata_mapM_op qc qdiscard_qubit cdiscard_bit qc
return ()
-- ----------------------------------------------------------------------
-- ** Measurement and preparation
-- | Measure a 'Qubit', resulting in a 'Bit'. More generally, measure
-- all the Qubits in a quantum data structure, resulting in a
-- corresponding data structure of Bits. This is not a reversible
-- operation. Examples:
--
-- > b <- measure q
-- > (b0, b1) <- measure (q0, q1)
-- > [b0, b1, b2] <- measure [q0, q1, q2]
measure :: (QShape ba qa ca) => qa -> Circ ca
measure qa = qdata_mapM_op (shapetype_q qa) measure_qubit qa
-- | Prepare a 'Qubit' initialized from a 'Bit'. More generally,
-- prepare a data structure of Qubits, initialized from a corresponding
-- data structure of Bits. Examples:
--
-- > q <- prepare b
-- > (q0, q1) <- prepare (b0, b1)
-- > [q0, q1, q2] <- prepare [b0, b1, b2]
prepare :: (QShape ba qa ca) => ca -> Circ qa
prepare ca = qdata_mapM (shapetype_c ca) prepare_qubit ca
-- | Heterogeneous version of 'measure'. Given a heterogeneous data
-- structure, measure all of its qubits, and leave any classical bits
-- unchanged.
qc_measure :: (QCData qc) => qc -> Circ (QCType Bit Bit qc)
qc_measure qc = qcdata_mapM_op qc measure_qubit do_bit qc
where
do_bit :: Bit -> Circ Bit
do_bit = return
-- | Heterogeneous version of 'prepare'. Given a heterogeneous data
-- structure, prepare qubits from all classical bits, and leave any
-- qubits unchanged.
qc_prepare :: (QCData qc) => qc -> Circ (QCType Qubit Qubit qc)
qc_prepare qc = qcdata_mapM qc do_qubit prepare_qubit qc
where
do_qubit :: Qubit -> Circ Qubit
do_qubit = return
-- ----------------------------------------------------------------------
-- * Global phase gate
-- | Like 'global_phase', except the gate is also \"anchored\" at a
-- qubit, a bit, or more generally at some quantum data. The anchor
-- is only used as a hint for graphical display. The gate, which is a
-- zero-qubit gate, will potentially be displayed near the anchor(s).
global_phase_anchored :: (QCData qc) => Double -> qc -> Circ ()
global_phase_anchored t qc = global_phase_anchored_list t qs where
qs = endpoints_of_qcdata qc
-- ----------------------------------------------------------------------
-- * Mapped gates
-- | Apply a Hadamard gate to every qubit in a quantum data structure.
map_hadamard :: (QData qa) => qa -> Circ qa
map_hadamard = mapUnary hadamard
-- | Imperative version of 'map_hadamard'.
map_hadamard_at :: (QData qa) => qa -> Circ ()
map_hadamard_at qa = do
map_hadamard qa
return ()
-- | Apply a swap gate to two qubits. More generally, apply swap gates
-- to every corresponding pair of qubits in two pieces of quantum
-- data.
swap :: (QCData qc) => qc -> qc -> Circ (qc,qc)
swap a b = qc_mapBinary swap_qubit swap_bit a b
-- | Apply a swap gate to two qubits. More generally, apply swap gates
-- to every corresponding pair of qubits in two pieces of quantum
-- data.
swap_at :: (QCData qc) => qc -> qc -> Circ ()
swap_at a b = do
swap a b
return ()
-- | Apply a controlled-not gate to every corresponding pair of
-- quantum or classical bits in two pieces of QCData. The first
-- argument is the target and the second the (positive) control.
--
-- For now, we require both pieces of QCData to have the same type,
-- i.e., classical bits can be controlled only by classical bits and
-- quantum bits can be controlled only by quantum bits.
--
-- Example:
--
-- > ((a',b'), (x,y)) <- controlled_not (a,b) (x,y)
--
-- is equivalent to
--
-- > a' <- qnot a `controlled` x
-- > b' <- qnot b `controlled` y
controlled_not :: (QCData qc) => qc -> qc -> Circ (qc, qc)
controlled_not qc ctrl = do
let z = qcdata_zip qc qubit bit qubit bit qc ctrl errmsg
z' <- qcdata_mapM qc map_qubit map_bit z
let (qc', ctrl') = qcdata_unzip qc qubit bit qubit bit z'
return (qc', ctrl')
where
map_qubit :: (Qubit, Qubit) -> Circ (Qubit, Qubit)
map_qubit (q,c) = do
qnot_at q `controlled` c
return (q,c)
map_bit :: (Bit, Bit) -> Circ (Bit, Bit)
map_bit (b,c) = do
cnot_at b `controlled` c
return (b,c)
errmsg s = "controlled_not: shapes of control and controlee do not match: " ++ s
-- | Imperative version of 'controlled_not'. Apply a controlled-not
-- gate to every corresponding pair of quantum or classical bits in
-- two pieces of QCData. The first argument is the target and the
-- second the (positive) control.
controlled_not_at :: (QCData qc) => qc -> qc -> Circ ()
controlled_not_at a b = do
controlled_not a b
return ()
-- | A version of 'controlled_not' where the control consists of
-- boolean data. Example:
--
-- > bool_controlled_not (q, r, s) (True, True, False)
--
-- negates /q/ and /r/, but not /s/.
bool_controlled_not :: (QCData qc) => qc -> BType qc -> Circ qc
bool_controlled_not qc a = do
bool_controlled_not_at qc a
return qc
-- | A version of 'controlled_not_at' where the control consists of
-- boolean data. Example:
--
-- > bool_controlled_not_at (q, r, s) (True, True, False)
--
-- negates /q/ and /r/, but not /s/.
bool_controlled_not_at :: (QCData qc) => qc -> BType qc -> Circ ()
bool_controlled_not_at qc a = do
qmultinot_list_at vq
cmultinot_list_at vc
where
v = Map.toList $ qc_bind qc a
vq = [ (qubit_of_wire q, b) | (q, Endpoint_Qubit b) <- v ]
vc = [ (bit_of_wire c, b) | (c, Endpoint_Bit b) <- v ]
-- | Negate all qubits in a quantum data structure.
qmultinot :: (QData qa) => qa -> Circ qa
qmultinot qa = do
qmultinot_at qa
return qa
-- | Negate all qubits in a quantum data structure.
qmultinot_at :: (QData qa) => qa -> Circ ()
qmultinot_at qa =
qmultinot_list_at [ (q,True) | q <- qubits_of_qdata qa ]
-- ----------------------------------------------------------------------
-- ** Copying and uncopying
-- | Initialize a new piece of quantum data, as a copy of a given
-- piece. Returns both the original and the copy.
qc_copy_fun :: (QCData qc) => qc -> Circ (qc,qc)
qc_copy_fun orig = do
copy <- qc_init (qc_false orig)
(copy, orig) <- controlled_not copy orig
return (orig, copy)
-- | Given two pieces of quantum data, assumed equal (w.r.t. the
-- computational basis), terminate the second piece (and return the
-- first, unmodified). This is the inverse of 'qc_copy_fun', in the sense
-- that the following sequence of instructions behaves like the
-- identity function:
--
-- > (orig, copy) <- qc_copy_fun orig
-- > orig <- qc_uncopy_fun orig copy
qc_uncopy_fun :: (QCData qc) => qc -> qc -> Circ qc
qc_uncopy_fun orig copy = reverse_generic qc_copy_fun orig (orig,copy)
-- | Create a fresh copy of a piece of quantum data. Note: copying is
-- performed via a controlled-not operation, and is not cloning. This
-- is similar to 'qc_copy_fun', except it returns only the copy, and not
-- the original.
qc_copy :: (QCData qc) => qc -> Circ qc
qc_copy qc = do
(qc, qc1) <- qc_copy_fun qc
return qc1
-- | \"Uncopy\" a piece of quantum data; i.e. terminate /copy/,
-- assuming it's a copy of /orig/. This is the inverse of
-- 'qc_copy', in the sense that the following sequence of
-- instructions behaves like the identity function:
--
-- > b <- qc_copy a
-- > qc_uncopy a b
qc_uncopy :: (QCData qc) => qc -> qc -> Circ ()
qc_uncopy orig copy = do
qc_uncopy_fun orig copy
return ()
-- ----------------------------------------------------------------------
-- ** Classical gates
-- | If /a/ is 'True', return a copy of /b/, else return a copy of
-- /c/. Here /b/ and /c/ can be any data structures consisting of
-- Bits, but /b/ and /c/ must be of the same type and shape (for
-- example, if they are lists, they must be of equal
-- length). Examples:
--
-- > output <- cgate_if a b c
-- > (out0, out1) <- cgate_if a (b0, b1) (c0, c1)
-- > [out0, out1, out2] <- cgate_if a [b0, b1, b2] [c0, c1, c2]
cgate_if :: (CData ca) => Bit -> ca -> ca -> Circ ca
cgate_if a b c = do
let shape = shapetype_c b
let z = qdata_zip shape bit bit b c errmsg
d <- qdata_mapM shape (\(x,y) -> cgate_if_bit a x y) z
return d
where
errmsg s = "cgate_if: shapes of 'then' and 'else' part do not match: " ++ s
-- | 'circ_if' is an if-then-else function for classical circuits.
-- It is a wrapper around 'cgate_if', intended to be used like this:
--
-- > result <- circ_if <<<condition>>> (
-- > <<then-part>>>
-- > )(
-- > <<<else-part>>>
-- > )
--
-- Unlike 'cgate_if', this is a meta-operation, i.e., the bodies of
-- the \"then\" and \"else\" parts can be circuit building
-- operations.
--
-- What makes this different from the usual boolean \"if-then-else\"
-- is that the condition is of type 'Bit', i.e., it is only known at
-- circuit execution time. Therefore the generated circuit contains
-- /both/ the \"then\" and \"else\" parts, suitably
-- controlled. Precondition: the \"then\" and \"else\" parts must be
-- of the same type and shape.
circ_if :: (CData ca) => Bit -> Circ ca -> Circ ca -> Circ ca
circ_if a b c = do
b' <- b
c' <- c
cgate_if a b' c'
-- ----------------------------------------------------------------------
-- ** Named gates
-- | Define a new functional-style gate of the given name. Like
-- 'named_gate', except that the generated gate is extended with
-- \"generalized controls\". The generalized controls are additional
-- inputs to the gate that are guaranteed not to be modified if they
-- are in a computational basis state. They are rendered in a special
-- way in circuit diagrams. Usage:
--
-- > my_new_gate :: (Qubit,Qubit) -> Qubit -> Circ (Qubit,Qubit)
-- > my_new_gate = extended_named_gate "Q"
--
-- This defines a new gate with name "Q", two inputs, and one
-- generalized input.
extended_named_gate :: (QData qa, QData qb) => String -> qa -> qb -> Circ qa
extended_named_gate name operands gencontrols = do
named_gate_qulist_at name False (qubits_of_qdata operands) (qubits_of_qdata gencontrols)
return operands
-- | Like 'extended_named_gate', except defines an imperative style gate.
-- Usage:
--
-- > my_new_gate_at :: (Qubit,Qubit) -> Qubit -> Circ ()
-- > my_new_gate_at = extended_named_gate_at "Q"
--
-- This defines a new gate with name "Q", two inputs, and one
-- generalized input.
extended_named_gate_at :: (QData qa, QData qb) => String -> qa -> qb -> Circ ()
extended_named_gate_at name operands gencontrols = do
extended_named_gate name operands gencontrols
return ()
-- | Define a new functional-style gate of the given name. Usage:
--
-- > my_unary_gate :: Qubit -> Circ Qubit
-- > my_unary_gate = named_gate "Q"
--
-- > my_binary_gate :: (Qubit, Qubit) -> Circ (Qubit, Qubit)
-- > my_binary_gate = named_gate "R"
--
-- This defines a new unary gate and a new binary gate, which will be
-- rendered as "Q" and "R", respectively, in circuit diagrams.
-- Implementation note: contrary to our usual convention, the binary
-- gate defined above is not in curried form. It would be nice to have
-- a version of this operator that curries the gate.
named_gate :: (QData qa) => String -> qa -> Circ qa
named_gate name operands = do
extended_named_gate name operands ()
-- | Define a new imperative-style gate of the given name. Usage:
--
-- > my_unary_gate_at :: Qubit -> Circ ()
-- > my_unary_gate_at = named_gate_at "Q"
--
-- > my_binary_gate_at :: (Qubit, Qubit) -> Circ ()
-- > my_binary_gate_at = named_gate_at "R"
--
-- This defines a new unary gate and a new binary gate, which will be
-- rendered as "Q" and "R", respectively, in circuit diagrams.
named_gate_at :: (QData qa) => String -> qa -> Circ ()
named_gate_at name operands = do
named_gate name operands
return ()
-- | Define a new functional-style gate of the given name, and
-- parameterized by a real-valued parameter. This is typically used
-- for rotations or phase gates that are parameterized by an angle.
-- The name can contain \'%\' as a place holder for the parameter.
-- Usage:
--
-- > my_unary_gate :: Qubit -> Circ Qubit
-- > my_unary_gate = named_rotation "exp(-i%Z)" 0.123
--
-- > my_binary_gate :: TimeStep -> (Qubit, Qubit) -> Circ (Qubit, Qubit)
-- > my_binary_gate t = named_rotation "Q(%)" t
named_rotation :: (QData qa) => String -> Timestep -> qa -> Circ qa
named_rotation name theta operands = do
named_rotation_qulist_at name False theta (qubits_of_qdata operands) []
return operands
-- | Define a new imperative-style gate of the given name, and
-- parameterized by a real-valued parameter. This is typically used
-- for rotations or phase gates that are parameterized by an angle.
-- The name can contain \'%\' as a place holder for the parameter.
-- Usage:
--
-- > my_unary_gate_at :: Qubit -> Circ ()
-- > my_unary_gate_at = named_rotation "exp(-i%Z)" 0.123
--
-- > my_binary_gate_at :: TimeStep -> (Qubit, Qubit) -> Circ ()
-- > my_binary_gate_at t = named_rotation "Q(%)" t
named_rotation_at :: (QData qa) => String -> Timestep -> qa -> Circ ()
named_rotation_at name theta operands = do
named_rotation name theta operands
return ()
----------------------------------------------------------------------
-- ** Dynamic lifting
-- | Convert a 'Bit' (boolean circuit output) to a 'Bool' (boolean
-- parameter). More generally, convert a data structure of Bits to a
-- corresponding data structure of Bools.
--
-- For use in algorithms that require the output of a measurement to
-- be used as a circuit-generation parameter. This is the case, for
-- example, for sieving methods, and also for some iterative
-- algorithms.
--
-- Note that this is not a gate, but a meta-operation. The input
-- consists of classical circuit endpoints (whose values are known at
-- circuit execution time), and the output is a boolean parameter
-- (whose value is known at circuit generation time).
--
-- The use of this operation implies an interleaving between circuit
-- execution and circuit generation. It is therefore a (physically)
-- expensive operation and should be used sparingly. Using the
-- 'dynamic_lift' operation interrupts the batch mode operation of the
-- quantum device (where circuits are generated ahead of time), and
-- forces interactive operation (the quantum device must wait for the
-- next portion of the circuit to be generated). This operation is
-- especially expensive if the current circuit contains unmeasured
-- qubits; in this case, the qubits must be preserved while the
-- quantum device remains on standby.
--
-- Also note that this operation is not supported in all contexts. It
-- is an error, for example, to use this operation in a circuit that
-- is going to be reversed, or in the body of a boxed subroutine.
-- Also, not all output devices (such as circuit viewers) support this
-- operation.
dynamic_lift :: (QShape ba qa ca) => ca -> Circ ba
dynamic_lift ca = qdata_mapM (shapetype_c ca) dynamic_lift_bit ca
-- ----------------------------------------------------------------------
-- * Mapping
-- | Map a single qubit gate across every qubit in the data structure.
mapUnary :: (QData qa) => (Qubit -> Circ Qubit) -> qa -> Circ qa
mapUnary f qa = qdata_mapM (shapetype_q qa) f qa
-- | Map a binary gate across every corresponding pair of qubits in
-- two quantum data structures of equal shape.
mapBinary :: (QData qa) => (Qubit -> Qubit -> Circ (Qubit, Qubit)) -> qa -> qa -> Circ (qa, qa)
mapBinary f q1 q2 = do
let shape = shapetype_q q1
let z = qdata_zip shape qubit qubit q1 q2 errmsg
z' <- qdata_mapM shape (\(x,y) -> f x y) z
let (q1', q2') = qdata_unzip shape qubit qubit z'
return (q1', q2')
where
errmsg s = "mapBinary: shapes of arguments do not match: " ++ s
-- | Like 'mapBinary', except the second data structure is classical.
mapBinary_c :: (QShape ba qa ca) => (Qubit -> Bit -> Circ (Qubit, Bit)) -> qa -> ca -> Circ (qa, ca)
mapBinary_c f q1 c2 = do
let shape = shapetype_q q1
let z = qdata_zip shape qubit bit q1 c2 errmsg
z' <- qdata_mapM shape (\(x,y) -> f x y) z
let (q1', c2') = qdata_unzip shape qubit bit z'
return (q1', c2')
where
errmsg s = "mapBinary_c: shapes of arguments do not match: " ++ s
-- | Map a binary qubit circuit to every pair of qubits in the quantum
-- data-type. It is a run-time error if the two structures do not have
-- the same size.
map2Q :: (QData qa) => ((Qubit, Qubit) -> Circ Qubit) -> (qa, qa) -> Circ qa
map2Q f (q,p) = do
let shape = shapetype_q q
let z = qdata_zip shape qubit qubit q p errmsg
d <- qdata_mapM shape f z
return d
where
errmsg s = "map2Q: shapes of arguments do not match: " ++ s
-- | Heterogeneous version of 'mapBinary'. Map a binary gate /f/
-- across every corresponding pair of qubits, and a binary gate /g/
-- across every corresponding pair of bits, in two quantum data
-- structures of equal shape.
qc_mapBinary :: (QCData qc) => (Qubit -> Qubit -> Circ (Qubit, Qubit)) -> (Bit -> Bit -> Circ (Bit, Bit)) -> qc -> qc -> Circ (qc, qc)
qc_mapBinary f g x y = do
let z = qcdata_zip x qubit bit qubit bit x y errmsg
z' <- qcdata_mapM x map_qubit map_bit z
let (x', y') = qcdata_unzip x qubit bit qubit bit z'
return (x', y')
where
map_qubit :: (Qubit, Qubit) -> Circ (Qubit, Qubit)
map_qubit (x,y) = f x y
map_bit :: (Bit, Bit) -> Circ (Bit, Bit)
map_bit (x,y) = g x y
errmsg s = "qc_mapBinary: shapes of arguments do not match: " ++ s
-- ----------------------------------------------------------------------
-- * Conversion to lists
-- $CONVERSION The functions in this section can be used to convert
-- quantum data structures to and from lists. Do not use them! The
-- conversion is unsafe in the same way pointers to void are unsafe in
-- the C programming language. There is almost always a better and
-- more natural way to accomplish what you need to do.
-- | Return the list of qubits representing the given quantum data.
-- The qubits are ordered in some fixed, but arbitrary way. It is
-- guaranteed that two pieces of qdata of the same given shape will be
-- ordered in the same way. No other property of the order is
-- guaranteed, In particular, the order may change without notice from
-- one version of Quipper to the next.
qubits_of_qdata :: (QData qa) => qa -> [Qubit]
qubits_of_qdata qa = qdata_sequentialize (shapetype_q qa) qa
-- | Take a specimen piece of quantum data to specify the \"shape\"
-- desired (length of lists, etc); then reads the given list of qubits
-- in as a piece of quantum data of the same shape. The ordering of
-- the input qubits is the same as 'qubits_of_qdata' produces for the
-- given shape.
--
-- A \"length mismatch\" error occurs if the list does not have
-- exactly the required length.
qdata_of_qubits :: (QData qa) => qa -> [Qubit] -> qa
qdata_of_qubits qa list = qdata_unsequentialize qa list
-- | Return the list of endpoints that form the leaves of the given
-- 'QCData'. The leaves are ordered in some fixed, but arbitrary
-- way. It is guaranteed that two pieces of data of the same given
-- shape will be ordered in the same way. No other property of the
-- order is guaranteed. In particular, the order may change without notice from
-- one version of Quipper to the next.
endpoints_of_qcdata :: (QCData qc) => qc -> [Endpoint]
endpoints_of_qcdata qc = qcdata_sequentialize qc qc
-- | Take a specimen piece of 'QCData' to specify the \"shape\"
-- desired (length of lists, etc); then reads the given list of
-- endpoints in as a piece of quantum data of the same shape. The
-- ordering of the input endpoints equals that produced by
-- 'endpoints_of_qcdata' for the given shape.
--
-- A \"length mismatch\" error occurs if the list does not have
-- exactly the required length. A \"shape mismatch\" error occurs if
-- the list contains a 'Qubit' when a 'Bit' was expected, or vice versa.
qcdata_of_endpoints :: (QCData qc) => qc -> [Endpoint] -> qc
qcdata_of_endpoints qc list = qcdata_unsequentialize qc list where
-- | Take a specimen piece of 'QCData' to specify a shape;
-- return a 'CircuitTypeStructure' that structures appropriate
-- lists of wires with arities into data of this shape, and conversely
-- destructures data of this shape into wires and an arity.
--
-- The caveats mentioned in 'endpoints_of_qcdata' apply equally for
-- this function.
circuit_type_structure_of_qcdata :: (QCData qc) => qc -> CircuitTypeStructure qc
circuit_type_structure_of_qcdata qc = CircuitTypeStructure
(wires_with_arity_of_endpoints . endpoints_of_qcdata)
(\(ws,a) -> qcdata_of_endpoints qc $ endpoints_of_wires_in_arity a ws)
-- ----------------------------------------------------------------------
-- * Shape related operations
-- | Return a boolean data structure of the given shape, with every
-- leaf initialized to 'False'.
qc_false :: (QCData qc) => qc -> BType qc
qc_false qc = qcdata_map qc map_qubit map_bit qc
where
map_qubit = const False :: Qubit -> Bool
map_bit = const False :: Bit -> Bool
-- | Return a quantum data structure of the given boolean shape, with
-- every leaf initialized to the undefined dummy value 'qubit'.
qshape :: (QData qa) => BType qa -> qa
qshape ba = qdata_map (shapetype_b ba) map_qubit ba
where
map_qubit = const qubit :: Bool -> Qubit
-- ----------------------------------------------------------------------
-- * Bindings
-- | Take two pieces of quantum data of the same shape (the first of
-- which consists of wires of a low-level circuit) and create
-- bindings.
qc_bind :: (QCData qc) => qc -> QCType a b qc -> Bindings a b
qc_bind qc as = qc_bind_aux qc as bindings_empty
where
-- This can't be inlined without upsetting the type checker.
qc_bind_aux :: (QCData qc) => qc -> QCType a b qc -> Bindings a b -> Bindings a b
qc_bind_aux qc as (bind_in :: Bindings a b) = bindings where
a = (dummy :: a) -- shape type
b = (dummy :: b) -- shape type
z = qcdata_zip qc qubit bit a b qc as errmsg
bindings = qcdata_fold qc do_qubit do_bit z bind_in
do_qubit :: (Qubit, a) -> Bindings a b -> Bindings a b
do_qubit (q, binding) = bind_qubit q binding
do_bit :: (Bit, b) -> Bindings a b -> Bindings a b
do_bit (c, binding) = bind_bit c binding
errmsg s = "qc_bind: shapes of arguments do not match: " ++ s
-- | Apply bindings to a piece of quantum and/or classical data
-- holding low-level wires, to get data of the same shape.
qc_unbind :: (QCData qc) => Bindings a b -> qc -> QCType a b qc
qc_unbind (bindings :: Bindings a b) qc =
qcdata_map qc map_qubit map_bit qc
where
map_qubit :: Qubit -> a
map_qubit q = unbind_qubit bindings q
map_bit :: Bit -> b
map_bit b = unbind_bit bindings b
-- ======================================================================
-- * Generic controls
-- $CONTROL The following functions define a convenient syntax for
-- controls. With this, we can write controls in much the same way as
-- one would write (a restricted class of) boolean
-- expressions. Examples:
--
-- > q1 .==. 0 .&&. q2 .==. 1 for Qubits q1, q2
--
-- > q .&&. p means q .==. 1 .&&. p .==. 1
--
-- > qx .==. 5 for a QDInt qx
--
-- > q1 .==. 0 .&&. z <= 7 we can combine quantum and classical controls
--
-- > q ./=. b the negation of q .==. b;
-- > here b is a boolean.
--
-- > [p,q,r,s] a list of positive controls
--
-- > [(p, True), (q, False), (r, False), (s, True)]
-- > a list of positive and negative controls
--
-- Among these infix operators, @(.&&.)@ binds more weakly than
-- @(.==.)@, @(./=.)@.
-- | Given a piece of quantum data and a possible value for it, return
-- a 'ControlList' representing the condition that the quantum data
-- has that value.
--
-- If some aspect of the value's shape is indeterminate, it is
-- promoted to the same shape as the quantum data; therefore, it is
-- possible, for example, to write:
--
-- > qc_control qa 17 -- when qa :: QDInt
-- > qc_control qa [False..] -- when qa :: [Qubit]
qc_control :: (QCData qc) => qc -> BType qc -> ControlList
qc_control qc b = clist where
b' = qcdata_promote b qc errmsg
z = qcdata_zip qc qubit bit bool bool qc b' errmsg
clist = qcdata_fold qc do_qubit do_bit z clist_empty
do_qubit :: (Qubit, Bool) -> ControlList -> ControlList
do_qubit (q, b) = clist_add_qubit q b
do_bit :: (Bit, Bool) -> ControlList -> ControlList
do_bit (c, b) = clist_add_bit c b
errmsg s = "qc_control: shape of control value does not match data: " ++ s
-- | This is an infix operator to concatenate two controls, forming
-- their logical conjunction.
(.&&.) :: (ControlSource a, ControlSource b) => a -> b -> ControlList
exp1 .&&. exp2 = combine (to_control exp1) (to_control exp2)
-- | @(qx .==. x)@: a control which is true just if quantum data /qx/ is in the specified state /x/.
(.==.) :: (QCData qc) => qc -> BType qc -> ControlList
qx .==. x = qc_control qx x
-- | The notation @(q ./=. x)@ is shorthand for @(q .==. not x)@, when
-- /x/ is a boolean parameter.
--
-- Unlike '.==.', which is defined for any shape of quantum data,
-- './=.' is only defined for a single control bit or qubit.
(./=.) :: (QCLeaf q) => q -> Bool -> ControlList
q ./=. b = to_control [Signed q (not b)]
-- Set the precedence for infix operators '.&&.', '.==.', and './=.'.
infixr 3 .&&. -- same precedence as (&&)
infix 4 .==. -- same precedence as (==)
infix 4 ./=. -- same precedence as (/=)
-- The following allows us to write 0 and 1 instead of 'False' and
-- 'True' everywhere.
instance Num Bool where
(+) = (/=)
(*) = (&&)
(-) = (/=)
negate = id
signum = id
abs = id
fromInteger n = (n `mod` 2 == 1)
-- ======================================================================
-- * Generic encapsulation
-- $encapsulate
--
-- An encapsulated circuit is a low-level circuit together with data
-- structures holding the input endpoints and output endpoints. A
-- circuit-generating function, with fully specified parameters, can
-- be turned into an encapsulated circuit; conversely, an encapsulated
-- circuit can be turned into a circuit-generating function. Thus,
-- encapsulation and unencapsulation are the main interface for
-- passing between high- and low-level data structures.
-- | Allocate new quantum data of the given shape, in the given
-- arity. Returns the quantum data and the updated arity.
qc_alloc :: (QCData qc) => qc -> ExtArity -> (qc, ExtArity)
qc_alloc qc arity = qcdata_fold_map qc do_qubit do_bit qc arity where
do_qubit :: Qubit -> ExtArity -> (Qubit, ExtArity)
do_qubit q arity = (qubit_of_wire w, a)
where
(w, a) = arity_alloc Qbit arity
do_bit :: Bit -> ExtArity -> (Bit, ExtArity)
do_bit c arity = (bit_of_wire w, a)
where
(w, a) = arity_alloc Cbit arity
-- | Extract an encapsulated circuit from a circuit-generating
-- function. This requires a shape parameter.
encapsulate_generic :: (QCData x) => ErrMsg -> (x -> Circ y) -> x -> (x, BCircuit, y)
encapsulate_generic e f shape = (x, circ, y) where
(x, arity) = qc_alloc shape arity_empty
(circ, y) = extract_simple e arity (f x)
-- | As 'encapsulate_generic', but passes the current namespace
-- into the circuit-generating function, to save recomputing
-- shared subroutines
encapsulate_generic_in_namespace :: (QCData x) => ErrMsg -> (x -> Circ y) -> x -> Circ (x, BCircuit, y)
encapsulate_generic_in_namespace e f shape = do
let (x, arity) = qc_alloc shape arity_empty
(circ, y) <- extract_in_current_namespace e arity (f x)
return (x, circ, y)
-- | Turn an encapsulated circuit back into a circuit-generating
-- function.
unencapsulate_generic :: (QCData x, QCData y) => (x, BCircuit, y) -> (x -> Circ y)
unencapsulate_generic (c_in, c, c_out) input = do
let in_bindings = qc_bind c_in input
out_bindings <- apply_bcircuit_with_bindings c in_bindings
let output = qc_unbind out_bindings c_out
return output
-- $dynamic_encapsulate
--
-- A dynamic encapsulated circuit is to an encapsulated circuit like a
-- 'DBCircuit' to a 'BCircuit'. The output is not a static circuit,
-- but an interactive computation expressed through the 'ReadWrite'
-- monad, which can be run on a quantum device to get a static circuit
-- out.
-- | Extract an encapsulated dynamic circuit from a circuit-generating
-- function. This requires a shape parameter.
encapsulate_dynamic :: (QCData x) => (x -> Circ y) -> x -> (x, DBCircuit y)
encapsulate_dynamic f shape = (x, comp) where
(x, arity) = qc_alloc shape arity_empty
comp = extract_general arity (f x)
-- | Turn an encapsulated dynamic circuit back into a
-- circuit-generating function.
--
-- This currently fails if the dynamic circuit contains output
-- liftings, because the transformer interface has not yet been
-- updated to work with dynamic circuits.
unencapsulate_dynamic :: (QCData x, QCData y) => (x, DBCircuit y) -> (x -> Circ y)
unencapsulate_dynamic (c_in, comp) input = do
let in_bindings = qc_bind c_in input
(out_bindings, c_out) <- apply_dbcircuit_with_bindings comp in_bindings
let output = qc_unbind out_bindings c_out
return output
-- ======================================================================
-- * Generic reversing
-- | Like 'reverse_unary', but also takes a stub error message.
reverse_errmsg :: (QCData x, QCData y) => ErrMsg -> (x -> Circ y) -> x -> (y -> Circ x)
reverse_errmsg e f shape y = do
circuit <- encapsulate_generic_in_namespace errmsg f shape
let circuit_inv = reverse_encapsulated circuit
f_inv = unencapsulate_generic circuit_inv
f_inv y
where
errmsg x = e ("operation not permitted in reversible circuit: " ++ x)
-- | Reverse a non-curried circuit-generating function. The second
-- parameter is a shape parameter.
reverse_unary :: (QCData x, QCData y) => (x -> Circ y) -> x -> (y -> Circ x)
reverse_unary = reverse_errmsg errmsg
where
errmsg x = "reverse_unary: " ++ x
-- | Reverse a circuit-generating function. The reversed function
-- requires a shape parameter, given as the input type of the original
-- function.
--
-- The type of this highly overloaded function is quite difficult to
-- read. It can have for example the following types:
--
-- > reverse_generic :: (QCData x, QCData y) => (x -> Circ y) -> x -> (y -> Circ x)
-- > reverse_generic :: (QCData x, QCData y, QCData z) => (x -> y -> Circ z) -> x -> y -> (z -> Circ (x,y))
reverse_generic :: (QCData x, QCData y, TupleOrUnary xt x, QCurry x_y x y, Curry x_y_xt x (y -> Circ xt)) => x_y -> x_y_xt
reverse_generic f =
mcurry $ aux f
where
-- An auxiliary function for defining 'reverse_generic'. (Inlining
-- this causes difficulty with the type inference for 'mcurry'.)
--aux :: (QCData x, QCData y, TupleOrUnary xt x, QCurry x_y x y) => x_y -> x -> (y -> Circ xt)
aux f shape =
(fmap weak_tuple) . (reverse_errmsg errmsg (quncurry f) shape)
errmsg x = "reverse_generic: " ++ x
-- | Like 'reverse_generic', but takes functions whose output is a
-- tuple, and curries the reversed function. Differs from
-- 'reverse_generic' in an example such as:
--
-- > f :: (x -> y -> Circ (z,w))
-- > reverse_generic f :: x -> y -> ((z,w) -> Circ (x,y))
-- > reverse_generic_curried f :: x -> y -> (z -> w -> Circ (x,y))
--
-- Note: the output /must/ be a /n/-tuple, where /n/ = 0 or /n/ ≥
-- 2. Applying this to a circuit whose output is a non-tuple type is a
-- type error; in this case, 'reverse_generic' should be used.
reverse_generic_curried :: (QCData x, QCData y, TupleOrUnary xt x, Tuple yt y, QCurry x_yt x yt, QCurry y_xt y xt, Curry x_y_xt x y_xt) => x_yt -> x_y_xt
reverse_generic_curried f =
mcurry $ aux f
where
-- An auxiliary function for 'reverse_generic_curried'. (Inlining
-- this causes difficulty with the type inference for 'mcurry'.)
aux :: (QCData x, QCData y, TupleOrUnary xt x, Tuple yt y, QCurry x_yt x yt, QCurry y_xt y xt) => x_yt -> x -> y_xt
aux f =
(qcurry .) $ \x y -> (fmap weak_tuple) $ (reverse_errmsg errmsg $ (fmap untuple) . (quncurry f)) x y
errmsg x = "reverse_generic_curried: " ++ x
-- | Like 'reverse_generic', but only works at simple types, and
-- therefore requires no shape parameters. Typical type instances:
--
-- > reverse_simple :: (QCData_Simple x, QCData y) => (x -> Circ y) -> (y -> Circ x)
-- > reverse_simple :: (QCData_Simple x, QCData_Simple y, QCData z) => (x -> y -> Circ z) -> (z -> Circ (x,y))
reverse_simple :: (QCData_Simple x, QCData y, TupleOrUnary xt x, QCurry x_y x y) => x_y -> y -> Circ xt
reverse_simple f = (fmap weak_tuple) . (reverse_errmsg errmsg (quncurry f) fs_shape)
where
errmsg x = "reverse_simple: " ++ x
-- | Like 'reverse_simple', but takes functions whose output is a
-- tuple, and curries the reversed function. Typical type instance:
--
-- > reverse_simple_curried :: (QCData_Simple x, QCData y, QCData z) => (x -> Circ (y,z)) -> (y -> z -> Circ x)
--
-- Note: the output /must/ be a /n/-tuple, where /n/ = 0 or /n/ ≥
-- 2. Applying this to a circuit whose output is a non-tuple type is a
-- type error; in this case, 'reverse_generic' should be used.
reverse_simple_curried :: (QCData_Simple x, QCData y, TupleOrUnary xt x, Tuple yt y, QCurry x_yt x yt, QCurry y_xt y xt)
=> x_yt -> y_xt
reverse_simple_curried f = qcurry $
(fmap weak_tuple) . (reverse_errmsg errmsg ((fmap untuple) . (quncurry f)) fs_shape)
where
errmsg x = "reverse_simple_curried: " ++ x
-- | Like 'reverse_generic', but specialized to endomorphic circuits,
-- i.e., circuits where the input and output have the same type (modulo
-- possibly currying) and shape. In this case, unlike 'reverse_generic',
-- no additional shape parameter is required, and the reversed function
-- is curried if the original function was. Typical type instances:
--
-- > reverse_generic_endo :: (QCData x) => (x -> Circ x) -> (x -> Circ x)
-- > reverse_generic_endo :: (QCData x, QCData y) => (x -> y -> Circ (x,y)) -> (x -> y -> Circ (x,y))
reverse_generic_endo :: (QCData x, TupleOrUnary xt x, QCurry x_xt x xt) => x_xt -> x_xt
reverse_generic_endo = qcurry . ((fmap weak_tuple) .) .
(\f x -> reverse_errmsg errmsg f x x)
. ((fmap weak_untuple) .) . quncurry
where
errmsg x = "reverse_generic_endo: " ++ x
-- | Like 'reverse_generic_endo', but applies to endomorphic circuits
-- expressed in \"imperative\" style. Typical type instances:
--
-- > reverse_generic_endo :: (QCData x) => (x -> Circ ()) -> (x -> Circ ())
-- > reverse_generic_endo :: (QCData x, QCData y) => (x -> y -> Circ ()) -> (x -> y -> Circ ())
reverse_generic_imp :: (QCData x, QCurry x__ x ()) => x__ -> x__
reverse_generic_imp f = qcurry $ \input -> do
reverse_generic_endo f' input
return ()
where
f' x = do
(quncurry f) x
return x
-- | Conditional version of 'reverse_generic_endo'. Invert the
-- endomorphic quantum circuit if the boolean is true; otherwise,
-- insert the non-inverted circuit.
reverse_endo_if :: (QCData x, TupleOrUnary xt x, QCurry x_xt x xt) => Bool -> x_xt -> x_xt
reverse_endo_if False f = f
reverse_endo_if True f = reverse_generic_endo f
-- | Conditional version of 'reverse_generic_imp'. Invert the
-- imperative style quantum circuit if the boolean is true; otherwise,
-- insert the non-inverted circuit.
reverse_imp_if :: (QCData qa, QCurry fun qa ()) => Bool -> fun -> fun
reverse_imp_if False f = f
reverse_imp_if True f = reverse_generic_imp f
-- ======================================================================
-- * The QCurry type class
-- | The 'QCurry' type class is similar to the 'Curry' type class,
-- except that the result type is guarded by the 'Circ' monad. It
-- provides a family of type isomorphisms
--
-- @fun ≅ args -> Circ res,@
--
-- where
--
-- > fun = a1 -> a2 -> ... -> an -> Circ res,
-- > args = (a1, (a2, (..., (an, ())))).
--
-- The benefit of having @Circ@ in the result type is that it ensures
-- that the result type is not itself a function type, and therefore
-- /fun/ has a /unique/ arity /n/. Then /args/ and /res/ are uniquely
-- determined by /fun/, which can be used to write higher-order
-- operators that consume /fun/ of any arity and \"do the right
-- thing\".
class QCurry fun args res | fun -> args res, args res -> fun where
qcurry :: (args -> Circ res) -> fun
quncurry :: fun -> (args -> Circ res)
instance QCurry (Circ b) () b where
qcurry g = g ()
quncurry x = const x
instance QCurry fun args res => QCurry (a -> fun) (a,args) res where
qcurry g x = qcurry (\xs -> g (x,xs))
quncurry f (x,xs) = quncurry (f x) xs
-- ======================================================================
-- * Generic circuit transformations
-- | Like 'transform_unary_shape', but also takes a stub error message.
transform_errmsg :: (QCData x, QCData y, x' ~ QCType a b x, y' ~ QCType a b y, Monad m) => ErrMsg -> Transformer m a b -> (x -> Circ y) -> x -> (x' -> m y')
transform_errmsg e transformer f shape input = do
let (x, circuit, y) = encapsulate_generic errmsg f shape
let in_bind = qc_bind x input
out_bind <- transform_bcircuit_rec transformer circuit in_bind
let output = qc_unbind out_bind y
return output
where
errmsg x = e ("operation not currently permitted in transformed circuit: " ++ x)
-- | Like 'transform_generic', but applies to arbitrary transformers
-- of type
--
-- > Transformer m a b
--
-- instead of the special case
--
-- > Transformer Circ Qubit Bit.
--
-- This requires an additional shape argument.
transform_unary_shape :: (QCData x, QCData y, x' ~ QCType a b x, y' ~ QCType a b y, Monad m) => Transformer m a b -> (x -> Circ y) -> x -> (x' -> m y')
transform_unary_shape = transform_errmsg errmsg
where
errmsg x = "transform_unary_shape: " ++ x
-- | Apply the given transformer to a circuit.
transform_unary :: (QCData x, QCData y) => Transformer Circ Qubit Bit -> (x -> Circ y) -> (x -> Circ y)
transform_unary transformer f x = transform_errmsg errmsg transformer f x x
where
errmsg x = "transform_unary: " ++ x
-- | Like transform_unary_shape but for a dynamic transformer
transform_unary_dynamic_shape :: (QCData x, QCData y, x' ~ QCType a b x, y' ~ QCType a b y, Monad m) => DynamicTransformer m a b -> (x -> Circ y) -> x -> (x' -> m y')
transform_unary_dynamic_shape dtransformer f shape input = do
let (x, dbcircuit) = encapsulate_dynamic f shape
let in_bind = qc_bind x input
(y,out_bind) <- transform_dbcircuit dtransformer dbcircuit in_bind
let output = qc_unbind out_bind y
return output
-- | Like transform_unary but for a dynamic transformer
transform_unary_dynamic :: (QCData x, QCData y) => DynamicTransformer Circ Qubit Bit -> (x -> Circ y) -> (x -> Circ y)
transform_unary_dynamic dtransformer f x = transform_unary_dynamic_shape dtransformer f x x
-- | Like 'transform_generic', but applies to arbitrary transformers
-- of type
--
-- > Transformer m a b
--
-- instead of the special case
--
-- > Transformer Circ Qubit Bit.
--
-- This requires an additional shape argument.
--
-- The type of this heavily overloaded function is difficult to
-- read. In more readable form, it has all of the following types:
--
-- > transform_generic :: (QCData x) => Transformer m a b -> Circ x -> m (QCData a b x)
-- > transform_generic :: (QCData x, QCData y) => Transformer m a b -> (x -> Circ y) -> x -> (QCData a b x -> m (QCData a b y))
-- > transform_generic :: (QCData x, QCData y, QCData z) => Transformer m a b -> (x -> y -> Circ z) -> x -> y -> (QCData a b x -> QCData a b y -> m (QCData a b z))
--
-- and so forth.
transform_generic_shape :: (QCData x, QCData y, QCurry qfun x y, Curry qfun' x' (m y'), Curry qfun'' x qfun', x' ~ QCType a b x, y' ~ QCType a b y, Monad m) => Transformer m a b -> qfun -> qfun''
transform_generic_shape transformer f = g where
f1 = quncurry f
g1 = transform_errmsg errmsg transformer f1
g2 = \x -> mcurry (g1 x)
g = mcurry g2
errmsg x = "transform_generic: " ++ x
-- | Apply the given transformer to a circuit. Unlike
-- 'transform_unary', this function 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:
--
-- > transform_generic :: (QCData x) => Transformer Circ Qubit Bit -> Circ x -> Circ x
-- > transform_generic :: (QCData x, QCData y) => Transformer Circ Qubit Bit -> (x -> Circ y) -> (x -> Circ y)
-- > transform_generic :: (QCData x, QCData y, QCData z) => Transformer Circ Qubit Bit -> (x -> y -> Circ z) -> (x -> y -> Circ z)
--
-- and so forth.
transform_generic :: (QCData x, QCData y, QCurry qfun x y) => Transformer Circ Qubit Bit -> qfun -> qfun
transform_generic transformer f = g where
f1 = quncurry f
g1 = \x -> transform_errmsg errmsg transformer f1 x x
g = qcurry g1
errmsg x = "transform_generic: " ++ x
-- ======================================================================
-- * Generic block structure
-- | Execute a block with local ancillas. Opens a block, initializing an ancilla with a specified classical value, and terminates it with the same value when the block closes. Note: it is the programmer's responsibility to return the ancilla to its original state at the end of the enclosed block. Usage:
--
-- > with_ancilla_init True $ \a -> do {
-- > <<<code block using ancilla a initialized to True>>>
-- > }
--
-- > with_ancilla_init [True,False,True] $ \a -> do {
-- > <<<code block using list of ancillas a initialized to [True,False,True]>>>
-- > }
with_ancilla_init :: (QShape a qa ca) => a -> (qa -> Circ b) -> Circ b
with_ancilla_init x f = do
qx <- without_controls (qinit x)
qy <- f qx
without_controls (qterm x qx)
return qy
-- | Like 'with_ancilla', but creates a list of /n/ ancillas, all
-- initialized to |0〉. Usage:
--
-- > with_ancilla_list n $ \a -> do {
-- > <<<code block using list of ancillas a>>>
-- > }
with_ancilla_list :: Int -> (Qulist -> Circ a) -> Circ a
with_ancilla_list n f =
with_ancilla_init (replicate n False) f
-- | @'with_computed_fun' /x/ /f/ /g/@: computes /x' := f(x)/; then computes /g(x')/, which should be organized as a pair /(x',y)/; then uncomputes /x'/ back to /x/, and returns /(x,y)/.
--
-- Important subtlety in usage: all quantum data referenced in /f/, even as controls, must be explicitly bound and returned by /f/, or the reversing may rebind it incorrectly. /g/, on the other hand, can safely refer to anything that is in scope outside the 'with_computed_fun'.
with_computed_fun :: (QCData x, QCData y) => x -> (x -> Circ y) -> (y -> Circ (y,b)) -> Circ (x,b)
with_computed_fun x f g = do
y <- without_controls (f x)
(y,b) <- g y
x <- without_controls (reverse_generic f x y)
return (x,b)
-- | @'with_computed' /computation/ /code/@: performs /computation/
-- (with result /x/), then performs /code/ /x/, and finally performs
-- the reverse of /computation/, for example like this:
--
-- \[image with_computed.png]
--
-- Both /computation/ and /code/ may refer to any qubits that exist in
-- the current environment, and they may also create new
-- qubits. /computation/ may produce arbitrary garbage in addition to
-- its output.
--
-- This is a very general but relatively unsafe operation. It is the
-- user's responsibility to ensure that the computation can indeed be
-- undone. In particular, if /computation/ contains any
-- initializations, then /code/ must ensure that the corresponding
-- assertions will be satisfied in /computation/[sup −1].
--
-- Related more specialized, but potentially safer, operations are:
--
-- * 'with_basis_change', which is like 'with_computed', but assumes
-- that /computation/ is unitary, and
--
-- * 'Quipper.classical_to_reversible', which assumes that /computation/ is
-- classical (or pseudo-classical), and /code/ is a simple
-- copy-by-controlled-not operation.
with_computed :: (QCData x) => Circ x -> (x -> Circ b) -> Circ b
with_computed computation code = do
(bcirc, dirty, x) <- extract_in_context errmsg computation
without_controls $ do
unextract_in_context bcirc
y <- with_reserve dirty $ do
code x
without_controls $ do
unextract_in_context (reverse_bcircuit bcirc)
return y
where
errmsg x = "with_computed: operation not permitted in pre-computation: " ++ x
-- | @'with_basis_change' /basischange/ /code/@: performs a basis change,
-- then the /code/, then the inverse of the basis change. Both
-- /basischange/ and /code/ are in imperative style. It is the user's
-- responsibility to ensure that the image of /code/ is contained in
-- the image of /basischange/, or else there will be unmet assertions
-- or runtime errors. Usage:
--
-- > with_basis_change basischange $ do
-- > <<<code>>>
-- >
-- > where
-- > basischange = do
-- > <<<gates>>>
with_basis_change :: Circ () -> Circ b -> Circ b
with_basis_change basischange code = do
with_computed basischange (\x -> code)
-- ======================================================================
-- * Boxed subcircuits
-- | Bind a name to a function as a subroutine in the current
-- namespace. This requires a shape argument, as well as complete
-- parameters, so that it is uniquely determined which actual circuit
-- will be the subroutine. It is an error to call that subroutine
-- later with a different shape argument. It is therefore the user's
-- responsibility to ensure that the name is unique to the subroutine,
-- parameters, and shape.
--
-- This function does nothing if the name
-- already exists in the namespace; in particular, it does /not/ check
-- whether the given function is equal to the stored subroutine.
provide_subroutine_generic :: (QCData x, QCData y) => ErrMsg -> BoxId -> Bool -> (x -> Circ y) -> x -> Circ ()
provide_subroutine_generic e name is_classically_controllable f shape = do
main_state <- get_namespace
if (Map.member name main_state)
then return ()
else do
(x, bcircuit, y) <- encapsulate_generic_in_namespace errmsg f shape
-- The 'y' element only corresponds to the output type of the box,
-- not the complete list of wires outputted by the circuit. This
-- information is gathered and stored in forgotten_output_qcdata
-- as ([Qubit],[Bit]).
let ((_,_,aout,_),_) = bcircuit
forgotten_output_arity = strip_qcdata_from_arity y aout
forgotten_output_qcdata = extract_from_arity forgotten_output_arity
let ein = endpoints_of_qcdata x
eout = endpoints_of_qcdata (y,forgotten_output_qcdata)
win = map wire_of_endpoint ein
wout = map wire_of_endpoint eout
input_destructure = wires_with_arity_of_endpoints . endpoints_of_qcdata
input_structure = (\(ws,a) -> qcdata_of_endpoints x $ endpoints_of_wires_in_arity a ws)
input_CircTypeStructure = CircuitTypeStructure input_destructure input_structure
output_destructure = wires_with_arity_of_endpoints . endpoints_of_qcdata
output_structure = (\(ws,a) -> qcdata_of_endpoints (y,forgotten_output_qcdata) $ endpoints_of_wires_in_arity a ws)
output_CircTypeStructure = CircuitTypeStructure output_destructure output_structure
provide_subroutine name (ob_circuit win bcircuit wout) input_CircTypeStructure output_CircTypeStructure is_classically_controllable
where
errmsg x = e ("operation not permitted in boxed subroutine: " ++ x)
-- Make a 'QCData' out of an arity.
extract_from_arity :: Arity -> ([Qubit],[Bit])
extract_from_arity x =
fst $ IntMap.mapAccumWithKey record_wire ([],[]) x
where
record_wire :: ([Qubit],[Bit]) -> Int -> Wiretype -> (([Qubit],[Bit]),Wiretype)
record_wire (qs,bs) wire Qbit = (((qubit_of_wire wire):qs,bs), Qbit)
record_wire (qs,bs) wire Cbit = ((qs,(bit_of_wire wire):bs), Cbit)
-- Take a 'QCData' /x/ and an 'Arity' /a/ and remove all the wires
-- of /a/ that are already existing in /x/.
strip_qcdata_from_arity :: (QCData x) => x -> Arity -> Arity
strip_qcdata_from_arity x a =
snd $ State.runState (qcdata_mapM x delete_qubit delete_bit x) a
where
delete_qubit :: Qubit -> State.State Arity ()
delete_qubit q = do
s <- State.get
State.put $ flip IntMap.delete s $ wire_of_qubit q
delete_bit :: Bit -> State.State Arity ()
delete_bit b = do
s <- State.get
State.put $ flip IntMap.delete s $ wire_of_bit b
-- | A generic interface for wrapping a circuit-generating function
-- into a boxed and named subroutine. This takes a name and a
-- circuit-generating function, and returns a new circuit-generating
-- function of the same type, but which inserts a boxed subroutine
-- instead of the actual body of the subroutine.
--
-- It is intended to be used like this:
--
-- > somefunc :: Qubit -> Circ Qubit
-- > somefunc a = do ...
-- >
-- > somefunc_boxed :: Qubit -> Circ Qubit
-- > somefunc_boxed = box "somefunc" somefunc
--
-- Here, the type of @somefunc@ is just an example; this could indeed
-- be a function with any number and type of arguments, as long as the
-- arguments and return type are quantum data.
--
-- It is also possible to inline the 'box' operator directly, in which
-- case it should be done like this:
--
-- > somefunc :: Qubit -> Circ Qubit
-- > somefunc = box "somefunc" $ \a -> do ...
--
-- Note: The 'box' operator wraps around a complete function,
-- including all of its arguments. It would be incorrect to apply the
-- 'box' operator after some quantum variables have already been
-- defined. Thus, the following is incorrect:
--
-- > incorrect_somefunc :: Qubit -> Circ Qubit
-- > incorrect_somefunc a = box "somefunc" $ do ...
--
-- It is the user's responsibility not to use the same name for
-- different subroutines. If 'box' is called more than once with the
-- same name and shape of input, Quipper assumes, without checking,
-- that they are subsequent calls to the same subroutine.
--
-- The type of the 'box' operator is overloaded and quite difficult to
-- read. It can have for example the following types:
--
-- > box :: String -> (Qubit -> Circ Qubit) -> (Qubit -> Circ Qubit)
-- > box :: String -> (QDInt -> QDInt -> Circ (QDInt,QDInt,QDInt)) -> (QDInt -> QDInt -> Circ (QDInt,QDInt,QDInt))
box :: (QCData qa, QCData qb, QCurry qa_qb qa qb)
=> String -> qa_qb -> qa_qb
box n = qcurry . (box_internal err n $ RepeatFlag 1) . quncurry
where
err e = "box: " ++ e
-- | A version of 'box' with iteration. The second argument is an
-- iteration count.
--
-- This can only be applied to functions of a single argument, where
-- the input and output types are the same.
nbox :: (QCData qa) => String -> Integer -> (qa -> Circ qa) -> qa -> Circ qa
nbox n rep = qcurry . (box_internal err n (RepeatFlag rep)) . quncurry
where
err e = "nbox: " ++ e
-- | A version of 'nbox' with same type as 'loopM'.
box_loopM :: (Integral int, QCData qa)
=> String -> int -> qa -> (qa -> Circ qa) -> Circ qa
box_loopM n rep = flip (nbox n $ fromIntegral rep)
-- | A version of 'loopM' that will be boxed conditionally on a
-- boolean condition. Typical usage:
--
-- > loopM_boxed_if (s > 1) "name" s x $ \x -> do
-- > <<<body>>>
-- > return x
loopM_boxed_if :: (Integral int, QCData qa) => Bool -> String -> int -> qa -> (qa -> Circ qa) -> Circ qa
loopM_boxed_if True name = box_loopM name
loopM_boxed_if False name = loopM
-- | The underlying implementation of 'box' and 'nbox'. It behaves
-- like 'box', but is restricted to unary functions, and takes an
-- 'ErrMsg' argument.
box_internal :: (QCData qa, QCData qb)
=> ErrMsg -> String -> RepeatFlag -> (qa -> Circ qb) -> (qa -> Circ qb)
box_internal e n r f x = do
let boxid = BoxId n (canonical_shape x)
provide_subroutine_generic e boxid False f x -- By default, fall back on the general controlling scheme:
-- set the classical-control flag to False.
call_subroutine boxid r x
-- | Classical control on a function with same shape of input and
-- output: if the control bit is true the function is fired, otherwise
-- the identity map is used.
-- Note: the constraint on the types is dynamically checked.
with_classical_control :: QCData qa => Bit -> String -> qa -> (qa -> Circ qa) -> Circ qa
with_classical_control c n x f = do
let boxid = BoxId n (canonical_shape x)
provide_subroutine_generic err boxid True f x
call_subroutine boxid (RepeatFlag 1) x `controlled` c
where
err e = "with_classical_control: " ++ e
-- | Like 'call_subroutine', except inline the subroutine body from
-- the given namespace, instead of inserting a subroutine call.
--
-- Implementation note: this currently copies /all/ subroutine
-- definitions from the given namespace into the current namespace,
-- and not just the ones used by the current subroutine.
--
-- Implementation note: this currently only works on lists of endpoints.
inline_subroutine :: BoxId -> Namespace -> [Endpoint] -> Circ [Endpoint]
inline_subroutine name ns inputs = do
let mc = Map.lookup name ns
case mc of
Nothing ->
error ("inline_subroutine: subroutine " ++ show name ++ " does not exist in the given namespace: " ++ showNames ns)
Just (TypedSubroutine ocircuit _ _ scf) -> do
let OCircuit (win, circuit, wout) = ocircuit
provide_subroutines ns
when (length win /= length inputs) $ do
error ("inline_subroutine: subroutine " ++ show name ++ " 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