We think of quantum computations as acting in a quantum device attached to our classical computer. We think of the device abstractly in terms of the commands it can be instructed to carry out. In this sense, the quantum device must contain a register of qubits that can be addressed, and set by the device to one of the computational base states, (i.e. |0i = False or |1i = True). The device must also be able to apply unitary operations over one or several of the qubits it contains, and finally it must have the ability to measure the qubits, and return the outcomes of measurements as the corresponding boolean value. As we have seen previously, it is this measurement operation that can lead to side-effects occurring in the rest of the quantum system in the device, and gives rise to our monadic approach. The results of the measurements from the quantum device will be probabilistic. The run function we provide implements a classical simulation of this idealised quantum device, although we do also provide a quantum simulator function (sim) that (as it is only simulating the quantum state, and hence has full knowledge of the state) is able to return a probability distribution over all the possible results. In this approach, any classical computation which uses results from a quantum computation is able to occur in our classical system, or in this case standard Haskell.
Figure 5.1 is an overview of the Quantum IO monad’s API. The type Qbit is used to represent the qubits in the quantum device, and the type U is the type of unitary transformations that can be constructed. The type constructor QIO is the monadic type constructor for the Quantum IO monad, and for any type a, the type QIO a can be thought of as a quantum computation that returns a member of the underlying type a. Specifying that the type constructor QIO is a monadic type simply means that we have the standard monadic functions available for it (return and >>=). This API also shows how we are able to keep the definition of our unitary operators in U separate from the monadic structure of QIO, as it is defined with its own monoidal structure, which corresponds to having an mappend
Qbit ::∗ QIO ::∗ → ∗ U ::∗
instanceMonad QIO
mkQbit :: Bool → QIO Qbit applyU :: U → QIO ()
measQbit :: Qbit → QIO Bool
instanceMonoid U swap :: Qbit → Qbit → U
cond :: Qbit → (Bool → U ) → U rot :: Qbit → ((Bool, Bool) → C) → U ulet :: Bool → (Qbit → U ) → U urev :: U → U
Prob ::∗ → ∗
instanceMonad Prob
run :: QIO a → IO a
sim :: QIO a → Prob a runC :: QIO a → a Figure 5.1: The QIO API
operation that is the sequential composition of unitaries, with the identity unitary given by mempty.
As has been previously introduced in section 4.5, the monadic structure of QIO allows us to use Haskell’s do notation to give our quantum computation a more imperative feel. In the rest of this section, we shall start to look at the rest of the constructs introduced in figure 5.1. It is quite clear to see how the constructors of the monadic QIO type correspond to the behaviour of our generalised quan- tum device, and to some extent how the underlying members of the U data-type correspond to some of the morphisms in our category FQC≃ of finite quantum circuits. We shall introduce them here to give a clearer understanding of how they are used, and what they can achieve.
Our first example, is just a simple quantum computation that initialises a qubit into the quantum base state |0i, and then returns the result of measuring that qubit.
hqw :: QIO Bool
hqw = do q ← mkQbit False measQbit q
The computation uses Haskell’s do notation to assign the variable name q to the initialised qubit, and then bind this assignment into the measurement of the qubit q. Running the hqw computation would simply return False with probability 1.
It is easy to see that this is the case as the qubit is initialised into the state |0i that corresponds to the classical state False, and is then simply measured without any unitary operations having been applied to the qubit.
Computations in QIO become more interesting when we start looking at ap- plying unitary operations to our qubits. To keep things simple, we shall look at the Hadamard transform.
1 √ 2 1 1 1 −1
The Hadamard transform is used quite extensively in quantum computation, so we provide it as an instance of a rotation in QIO. We shall see a little later how we have implemented the Hadamard transform, but for now we just need to use the function uhad :: Qbit → U .
rnd :: QIO Bool
rnd = do q ← mkQbit False applyU (uhad q) measQbit q
The expression applyU :: U → QIO () is used to apply the supplied argument unitary. It is this operation that allows us to embed the reversible (unitary) operations into the non-reversible quantum computations. The computation can be read as simply initialising the qubit q into the state |0i corresponding to the classical state False, then applying the Hadamard transform to qubit q, and finally returning the result of measuring the qubit q. Running this quantum computation will result in a random Boolean result, with each Boolean (False or True), having an equal probability of 0.5.
In QIO, we could use our quantum simulator function run :: QIO a → IO a to simulate the running of the rnd function giving us a probabilistic result. This is possible because the run function embeds the QIO computation into the IO monad, which gives us access to Haskell’s random number generator, and hence this form of probabilistic computation. The other quantum simulator function
we provide could also be called, using sim rnd , and would result in a probability distribution being returned ([(True, 0.5), (False, 0.5)]). The sim function doesn’t need to embed the result in the IO monad as it doesn’t need access to the prob- abilistic functions of IO. The result however, still isn’t pure as the computation also has to simulate the side-effects of the quantum aspects of the computation, and thus we have defined the Prob monad in which we can embed these probability distributions. We shall look at the Prob monad in more detail later in Chapter 7. Although, we have now introduced all three monadic constructs of QIO, we haven’t actually given any computations that are truly quantum in their nature (the rnd function is simply a classical probabilistic computation). We move on now to look more at the unitary transformations we can define, of type U , and the first truly quantum computation we look at takes advantage of the entangling properties of the conditional unitary cond to produce a bell state, which as we have seen is a maximally entangled two-qubit state. The conditional unitary (like our choice morphism in FQC≃) doesn’t measure the control qubit, and as such, if the control qubit is in a super-position we can introduce quantum parallelism into our computations. For example, given q :: Qbit and t, u :: U the expression cond q (λb → if b then t else u) intuitively runs the unitary t or u depending on q. However, in any case where the qubit q is in a super-position, both unitaries t and u will contribute to the result, giving an entangled state, where the result of t is entangled with the |1i part of q, and the result of u is entangled with the |0i part of q.
To create a bell state, we can use a one-sided version of the conditional (ifQ), that is a conditional that applies the empty computation when the control qubit is in the state|0i.
testBell :: QIO (Bool , Bool ) testBell = do qa ← mkQbit False
qb ← mkQbit False applyU (uhad qa)
applyU (ifQ qa (unot qb)) a ← measQbit qa
b ← measQbit qb return (a, b)
The computation can be thought of as initialising two qubits (qa and qb) into the base state |0i, and then applying the Hadamard transform to one of these qubits (qa) to give us a qubit in the equal super-position state 1
√
2(|0i + |1i). This qubit
is then used as the control qubit in our one-sided conditional (ifQ) to apply the not rotation (unot) to the second qubit (qb). This can be thought of as taking the original state 1
√
2(|00i + |10i) to the state 1 √
2(|00i + |11i, as the unitary is only
applied to the part of the state in which the control qubit is in the state|1i. Our computation then goes on to measure the two-qubits individually, and return the pair of the results of the two measurements.
Evaluating sim testBell reveals that the two apparently independent mea- surements always agree: [((True, True), 0.5), ((False, False), 0.5)]. This is because when we measure one of the qubits in the entangled state 1
√
2(|00i+|11i), the entire
quantum state must be projected into one of the states |00i and |11i, depending upon the outcome of the measurement, leaving the second qubit in the same state as the first. The the probability of either measurement is 1
2 =| 1 √
2|
2 (This exactly
corresponds to the example we gave in Chapter 2.3.3).
We shall look later at some of the draw-backs of this implementation of condi- tionals, as the syntax does allow us to define conditionals that don’t actually give rise to a semantically unitary operation (see section 7.4). This problem arises from the type-system of Haskell being too weak to define that the state of the control qubit must be separable from the state of any qubits that are used in the branches. In this Haskell implementation of QIO, we are able to catch errors of this kind at run-time, but a more-expressive system could allow these exceptions to be caught at compile time by the type checker, before any code is actually run on a quantum system. This, and a few other semantic side-conditions are described in section
7.4, and are the main reasons for the re-development of QIO in Agda, which is presented later in Chapter 9.
Using Haskell as the language in which we embed QIO means that we are able to use all the functional abstractions that it provides. These functional abstrac- tions give rise to many ways to organise our programs more succinctly, and means we are able to reuse functions that have previously been defined. This approach could be used to provide a library of QIO computations, and unitaries (in U ) that can be imported along with the QIO API, so they can be used in other quantum programs. As an example, the reversible arithmetic functions we define are in a separate file, and can be imported when arithmetic functions are needed in other QIO programs (See the on-line code repository ([Gre09]) for more details).
One example is that of quantum sharing. The idea of quantum sharing is that instead of being able to copy a quantum state (which is impossible due to the no-cloning theorem) we are able to share a quantum state amongst groups of entangled qubits. For example, quantum sharing is used in QML ([AG05]) and for the linear-algebraic λ-calculus ([AD08]) to model non-linear use of quantum variables. Also, we have already made use of quantum sharing in our definition of the testBell example. In the testBell example, we share the state of the qubit qa, when it is in the state 1
√
2(|0i + |1i), with the qubit qb. We can define the function
share as a quantum computation in QIO, that when given a qubit, returns a new qubit with which it now shares its state.
share :: Qbit → QIO Qbit
share qa = do qb ← mkQbit False
applyU (ifQ qa (unot qb)) return qb
It is important to realise that the quantum state is not copied (as this would con- tradict the no-cloning theorem), but merely shared, with the state of the original qubit exactly defining the entangled state of the pair of qubits. That is, if the input qubit is in an arbitrary state α|0i + β |1i, then the state after sharing is
α|00i + β |11i.
Another example is of creating a qubit already in a super-position, for example as a way of creating qubits in a different basis to the computational basis. For ex- ample, one such basis is the|+i, |−i basis, which is simply a Hadamard rotation of the computational basis. Functions to create qubits in these states can be defined
by: |+i :: QIO Qbit
|+i = do q ← mkQbit False applyU (uhad q) return q
|−i :: QIO Qbit
|−i = do q ← mkQbit True applyU (uhad q) return q
In our testBell example, we measured the bell state to show the correspondence between the entangled qubits, but if we wanted to use a bell state in a further computation then we would want a function that just returns the pair of qubits that are in the entangled bell state. Using the functional abstractions we have defined above, this could now simply be given by:
bell :: QIO (Qbit, Qbit) bell = do qa ← |+i
qb ← share qa return (qa, qb)
The function testBell that measures the bell state can now be given by: testBell = do (qa, qb)← bell
a ← measQbit qa b ← measQbit qb return (a, b)
Looking at our computations now, it seems that we could make our programs more succinct if we were able to define measurement operations for larger quantum data-structures than just the single qubits. The next section shall look at how we can achieve this by defining a type-class in Haskell of Quantum data-types. We shall come back later to look at the other members of the U datatype that haven’t been introduced yet.