{-# 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