Looking back at the QIO API in figure 5.1, we’ll see that there are still some constructs that we haven’t explained yet. We have explained some of the members of the U data-type, which is the type of unitary transformations in QIO. This type is defined as a monoid, and as such comes with the binary operator, mappend in Haskell, that corresponds to sequential composition. The identity of this monoidal structure, mempty, is just the empty computation.
It is useful to point out here, that although we have chosen our unitary con- structs to follow on from the constructs we looked at in defining the category FxC≃of circuits, the monoidal structure here doesn’t correspond to the monoidal structure present in FxC≃ that denotes parallel composition of circuits. In QIO, we can think of the corresponding parallel composition of circuits as the sequential composition of unitaries acting on different qubits in the system. The syntax itself doesn’t define any form of parallelising unitaries that act on different qubits, and neither does the semantics of our simulator functions. However, it would be pos- sible if the “plug-in” quantum hardware supports the parallel running of unitaries over different qubits, that a compiler could figure out optimisations where this could occur.
We can now give our definition of the one-sided conditional ifQ that has been used previously in our examples. It is simply defined using a conditional unitary that applies the mempty computation for the false part of the computation
ifQ :: Qbit → U → U
ifQ q u = cond q (λx → if x then u else mempty)
To increase readability, and simplify the presentation, the rest of this thesis shall be written using mappend as and mempty as •.
Rotations in QIO are another primitive operation in U , they are defined in a very similar manner to our one “bit” operations in FQC≃, by any unitary 2× 2 complex valued matrix. These matrices are given a functional definition by representing them as functions in (Bool , Bool )→ C. The matrix that corresponds
to an arbitrary rotation f :: (Bool , Bool )→ C can be thought of as;
f (F alse, F alse) f (F alse, T rue) f (T rue, F alse) f (T rue, T rue)
the rot construct of U , lifts these rotations to be unitary operators in U by defining which qubit they are to be applied to. We can can define the unot and uhad operations we have used previously, along with the uphase operation that when given a real argument (r :: R), corresponds to the phase rotation with phase θ = r.
unot :: Qbit → U
unot x = rot x (λ(x , y)→ if x ≡ y then 0 else 1) uhad :: Qbit → U
uhad x = rot x (λ(x , y)→ if x ∧ y then − h else h) whereh = (1 / sqrt 2)
uphase :: Qbit → R → U uphase x r = rot x (rphase r ) rphase :: R→ Rotation
rphase (False, False) = 1
rphase r (True, True) = exp (0 : + r )
rphase ( , ) = 0
For example, the Pauli-Z gate is an instance of a phase gate, with the argument π. Or in other words, we could define the rotation upauli z by
upauli z :: Qbit → U upauli z x = uphase x pi
Another primitive operation that is provided in U is the swap construct. The operation swap x y can be thought of as swapping the position of the two qubits x and y within the quantum register. The swap operation could actually be defined in terms of three controlled not operations, which it is already possible to define
as a QIO computation. The circuit
|ψi • X • |φi
|φi X • X |ψi
would just be implemented as swap :: Qbit → Qbit → U swap qa qb = ifQ qa (unot qb)
ifQ qb (unot qa) ifQ qa (unot qb)
However, we include swap as a primitive construct in U for reasons of efficiency. The two constructs we have left to look at are the urev function and the ulet primitive. The ulet primitive is used to introduce ancillary qubits into our computation, and as such has quite a complicated behaviour. We shall go on to look at the behaviour of the ulet operation in the next section, but for now we shall finish off this section by introducing the urev function.
The urev function comes about because of the unitary nature of our U data- type. That is, that every definable member of the U datatype should correspond to a unitary operator that can be applied to a quantum system. By definition, a unitary operator has an inverse that is also a unitary operator. The urev function simply takes a member of the U data-type and returns the member of the U data-type that corresponds to the inverse of the original. In fact, this is simply achieved at a syntactic level. The fact that urev can work at the syntactic level should arise from the fact that members of our U data-type only define unitary operations. This is the case in QIO, up to the problems we talk about in section 7.4. However, all these problems are caught with errors at run-time, and their inverses are treated in exactly the same way. We shall have a brief look later (in Chapter 7) at how the urev function is implemented.