Haskell Simulator of Quantum Computer Jan Skibinski, Numeric Quest Inc., Huntsville, Ontario, Canada Initialized: 2001-05-02, last modified: 2001-05-20

This is a preliminary version of an introduction to the Haskell module QuantumComputer.hs. The assumed readers of this document are Haskell users hence I am not going to praise Haskell here nor to dwell on Haskell related issues. I will just point out few of its strong points in context of this simulator.

This document is not a tutorial on Quantum Computation. There are many good tutorials available, some of which are listed in the reference section, at the bottom of this page. I do provide a very short introduction to this field but this is by no means a substitution for a real tutorial.

This simulator is far from being complete. I do not show such things as Shor's factorization or the Grover's search algorithms -- both being the flagship achievements in this field. But I do concentrate on the development of a reasonably efficient framework for implementation and verification of such and similar algorithms.

The module QuantumComputer depends on module QuantumVector, which is presented as literate Haskell module in HTML format. Rename it as QuantumVector.lhs or make a symbolic link with such name to the HTML file. If you wish to use GHC (or GHCi) disable import of module Fraction. We do not need it here and a fact is that I have never redesigned the Fraction yet to comply with Haskell-98 standard. Other than that both modules fit very well and QuantumVector provides all what is needed by QuantumComputer in a silent, non-obtrusive way.

Part I: Concepts

Why Quantum Computing, why simulator and why Haskell?

The answer to the first question is the standard one. Because Quantum Computing (QC) is out there. Because many, many bright people devote their time to advance the theory and the experiments. Because new papers are being produced on daily basis for the past few years. Because it does not look any more like a meteor about to burn out and die. Because it is a bleeding edge of science and technology. Because it is an intellectual challenge.

Many algorithms for quantum computation have been developed and are being developed now. Because of great complexity involved some of them are being tested only by hand and on very small examples only. But do they really work? Are there any limitations related to problem size? Are they really efficient? A simulator of a quantum computer should help with such answers.

Why Haskell? Because it fits well into the concept of QC. Because it provides many functionalities that we, the Haskell users, love and appreciate, but others out there do not have a clue about their existence. QC offers such a computing challenge that it levels the field between the imperative and functional languages, efficiency-wise. Either languages could fail miserably, but functional ones have a good chance to succeed here because of their .. functionality. There are few quantum simulators implemented in C++ out there and I am not about to criticize any of them or challenge them. I only think that Haskell can help to greatly simplify the coding concepts and provide easy to use tool for researchers in Quantum Computing. If succeeded, the Haskell Simulator of Quantum Computer could be one of the very few Haskell killer applications, I hope. It is worth to try.

Short overview of Quantum Computing

Quantum computing is a revolutionary idea of recent years about efficient treatment of classically exponential computing problems. It is based on several principles of Quantum Mechanics, such as superposition and entanglement. Tiny quantum computers are being build now and extensive theoretical and experimental research is taking place in several fields of this new and exciting discipline.

a, b

The qubit is not the only choice amongst possible quantum computing devices; one can imagine using elementary quantum systems with three, four or more basic states -- similarly as one can use binary, tertiary, or hexdecimal representation of classical numbers. But so far, the qubit is in the midst of the attention of Quantum Computing community, since two-level quantum systems are easy to understand and there are many practical examples of such systems that qualify as the basic building blocks of quantum registers.

A quantum register is a controlled collection of such qubits. Ideally, we would like to assure that we can control individual qubits on one hand and, at the same token, have some mechanism to propagate information from one qubit to another in some controlled fashion. We also want the qubits to be well isolated from their environment, so they will retain our encoded information long enough to be considered a true computing register. Another words, we want the register to be in a coherent state, and if a decoherence is inevitable than we at least want to have some scheme for error correction which will allow us to consider our computing device reliable enough. These are the interesting problems being experimented on now.

Asssuming the ideal world where all experimental issues can be sooner or later resolved, much of the effort must be also directed towards understanding of basic principles, making some predictions, inventing some clever algorithms and pointing out some amazing advantages of the quantum computing over the classical one. This is the second tier of quantum computing activity taking place right now. Any tutorial cited in the attached bibliography will deal with these issues rather well, so we will skip these issues in this overview.

But we can roughly point at two other theoretical principles that have a significant impact on this simulator. Firstly, because each qubit of a quantum register can be in a superposition of base states | 0 > and | 1 > a quantum register can simultaneously hold 2^n numbers, and quantum computer can perform some operations in parallel on all those numbers. This is called the quantum paralleism.

But not all quantum states can be represented as simple products of individual qubits; more often than not they must be considered sums of such products. This is called a quantum entanglement. It poses an efficiency challenge to any simulator, including this one. One of the sections below describes this problem rather thoroughly.

The computing process, and thus all the transformations performed on quantum register, must be unitary. Unitary transformations are reversible, that is; any such operation U | x > can be inverted by applying its Harmitian conjugate U+ U | x > = | x > . This imposes some restrictions on a practical realization of quantum gates. The classical AND gate is not reversible, so it cannot be used as a building block of a quantum computation, but the NOT or XOR gates are reversible, so they are used instead. But even with these limitations any logical operation, including AND, can be implemented on quantum computer by some crafty engineering of universal networks of unitary gates.

Big picture of Haskell simulation

Quantum computation is usually embedded into a larger picture:

Semi-classical preparation of the registers by "cooling down" their qubits to some known ground state, usually the zero state. Purely quantum computation, such as loading a register with superposition of many numbers, then performing some parallel unitary transformations of that register. Quantum measurement of the register, which results in a single eigenvalue and collapses the quantum register to its associated eigenvector. All parallel information that used to be stored in the register is gone at this point. Classical evaluation of the result and perhaps restarting of the process again

This scheme maps nicely to purely functional language Haskell. All pure quantum computations are implemented as pure functions without side effects. But the measurement is probabilistic in nature, so it does not have guaranteed deterministic outcome and it cannot be therefore simulated as a pure function. However, it can be nicely implemented as probabilistic IO action, using Haskell IO monad.

By using pure functions for pure quantum computations we can strictly adhere to the ground rules that all such computations should be unitary and reversible. We can even provide some proofs that functions we use have indeed those properties. Even the fact that pure Haskell functions do not perform storage update but instead generate fresh copies of registers cannot be held against Haskell: the old and the new versions often differ so dramatically that in-place updating does not offer any signicant advantage any more. Quantum registers cannot be naively represented as arrays, but rather as linked lists or trees and here the Haskell support for abstract data structure offers very definite advantage over imperative languages.

The declarative nature of Haskell allows us to clearly describe the quantum algorithms, and gracefully handle such problems as representation of registers via recursive tree data structure. Function composition is yet another powerful concept which is used here to build any quantum network from any number of elementary quantum gates.

Efficiency of the simulator

No classical computer can efficiently simulate a quantum computer, because that would go against a basic principle that quantum computer must be build from quantum subsystems. In particular, parallel polynomial quantum computation quickly explodes to exponential computation in any simulator, both space-wise and time-wise. Do not therefore expect from this simulator any real computing power: in many cases you will have to limit yourself to very small problem sizes, described perhaps by five-qubit registers, perhaps by eight-qubit registers, etc.

On the other hand, some exercises in quantum paralleism demonstrate that even this simulator can compute certain operations in polynomial time, and therefore even the interpreter Hugs can handle some big problems quite gracefully -- using perhaps 100-qubit registers or so.

It all depends on whether or not the state vector can be factorized and kept more or less factorized during all stages of computation. For example, there is no pain in preparation of equally weighed superposition of, 2^n say, numbers. There are only n simple transformations involved and the result can be stored in n-qubit register at ease. Any combination of single-qubit transformations performed on the assemble of qubits of such a register is again very fast and efficient space-wise. But the things become hairy when entangling transformations are employed to achieve entanglement of qubits. This produces in effect an ansemble of parallel computation paths, which can quickly explode up to a number of 2^n possibilities.

Part II: Module overview

Classical bits Qubit and single-qubit transformations Haskell notation for Dirac expressions Qubit register Applying transformations to a qubit register Register preparation Two-qubit controlled-U transformations Composition of quantum networks Quantum Fourier Transform and friends Arithmetic on quantum registers Register and state vector trimming Simulated measurement of qubits and registers

1. Classical bits

A qubit is described by a two-dimensional vector, with two base vectors that are traditionally denoted by | 0 > and | 1 >. But this is just a matter of notation and we could instead use any other convenient pair of symbols: | down > and | up > (suggesting spin orientation in a magnetic field), | - > and | + >, | zero > and | one >, etc. It does not matter what is chosen as formal representation of these two vectors; module QuantumVector would support any such choice.

What matters is a fact that when a qubit is in one of its two pure states we associate with it one of the two numerical values: 0 or 1. These are the eigenvalues of the bit operator:

bit | 0 > = 0 | 0 > bit | 1 > = 1 | 1 >

Int

type Bit = Int

[0,1]

Bit

data Bit -- A bit can assume one of the two values: 'Zero' or 'One'. -- In order to qualify as a label in ket notation this datatype -- must be an instance of classes Ord and Eq -- = Zero | One deriving (Eq, Ord) instance Show Bit where -- Here we define how the bits are displayed. We will use -- the "0/1" representation, but you can change it to anything -- you like: "-/+", "zero/one", "down/up", etc. Such a change -- would have no impact on the rest of this module. show Zero = "0" show One = "1"

2. Qubit and single-qubit transformations

We formally define qubit as a object of type Qubit :

type Qubit = Ket Bit .

U

U U+ = U+ U = U U-1 = 1

u-

All single-qubit operators have this generic type:

u :: Qubit -> Qubit

u

It is a trivial exercise to find Hermitian conjugates of unitary operators in two-dimensional space. The Haskell operator (#) can perform such operation on any u- function. Its name supposes to imitate the dagger symbol, which traditionally signifies Hermitian conjugates. If q represents a qubit, and u is a unitary operator, then one of the two following forms would signify application of Hermitian conjugate to such qubit:

u # q (#) u q

Alternatively, one can predefine the adjoint operators explicitely. Due to ASCII limitations we cannot use a dagger symbol in Haskell programs, and therefore we signify the predefined Hermitian conjugates by character ' appended to a name of operators. For example u_phase' is the Hermitian conjugate of u_phase .

Some of the operators, such as u_hadamard are Hermitian, also called self-adjoint, because they satisfy U U = 1 . For those there is no need for application of Haskell operator (#) , or for pre-definitions of adjoints.

Here is an example of one such self-adjoint operator:

u_not :: Qubit -> Qubit u_not x -- Flip unitary operation, or NOT operation. -- (This operator is also Hermitian) | x == Ket Zero = Ket One | x == Ket One = Ket Zero | otherwise = ((Bra One <> x) :|> Ket Zero) :+> ((Bra Zero <> x) :|> Ket One)

|

3. Haskell notation for Dirac expressions

Perhaps, some explanation about the notation used above is in order. It is inherited from module QuantumVector, which provides support for abstract algebra in Hilbert vector spaces.

Ket Zero and Ket One generate base vectors | 0 > and | 1 >, respectively. The third clause above generates a linear superposition of such vectors. Bra Zero and Bra One represent bra vectors < 0 | and < 1 |, respectively. The operation <> represents the bracket, the scalar product of two vectors:

Dirac notation: < x | y > Haskell notation: x <> y

Bra One <> x

< 1 | x >

Symbol :|> signifies a product of a scalar and a ket vector, and finally :+> is a sum of two ket vectors. Therefore the entire third line in the definition of u_not is a direct translation of this Dirac expression:

< 1 | x > | 0 > + < 0 | x > | 1 >

We could have used Haskell operators |> and +> instead of data constructors :|> and :+> above. They are a bit stronger facilities, with established fixities of precedences, which would allow us to skip some of the parenthesis:

... | otherwise = (Bra One <> x) |> Ket Zero +> (Bra Zero <> x) |> Ket One

Module QuantumVector defines similar data constructors and operators for bra vectors:

:<|, :<+, <|, <+

><

<>

One could perhaps question the complexity of notation and perhaps one could be right here -- given enough thought the Haskell's class system could be used to replace <+ and +> by a simple + , and <* and *> by a * . There are some problems however with the current class hierarchy that does not allow us to find an ellegant solution which adheres to strict mathematical definitions. We would have to make our Ket and Bra the instances of Num class. But Num defines certain operations, such as signum, which we cannot and should not support here. We will therefore leave the notation as it is for a time being, since it is expressive enough and definitely correct.

4. Qubit register

The recursive tree data structure, named Register, is designed to hold both ends of such complexity spectrum: simple product-wise registers, or any combination of sums and products of chains of qubits. Some algorithms are already optimized for time-space efficiency, but there is still much to do in order to optimize the behaviour of this simulator. But again, keep in mind, that no simulator can efficiently simulate the real quantum computer.

A state |x> of n qubits is generally entangled as sum c(k) | k >, meaning that it can rarely be factorized into a simple tensor product of its individual qubits:

...(a2 |0> + b2 |1>) <*> (a1 |0> + b1 |1>) <*> (a0 |0> + b0 |1>)

[0..2^n-1]

Consider these examples:

QuantumComputer> zeroRegister 5 |0>|0>|0>|0>|0>

mix

QuantumComputer> mix (zeroRegister 5) [0.7071067811865475 |0> + 0.7071067811865475 |1>] [0.7071067811865475 |0> + 0.7071067811865475 |1>] [0.7071067811865475 |0> + 0.7071067811865475 |1>] [0.7071067811865475 |0> + 0.7071067811865475 |1>] [0.7071067811865475 |0> + 0.7071067811865475 |1>]

QuantumComputer> state $ mix (zeroRegister 5) 0.1767766952966368 |0> + 0.1767766952966368 |16> + 0.1767766952966368 |8> + 0.1767766952966368 |24> + 0.1767766952966368 |4> + 0.1767766952966368 |20> + 0.1767766952966368 |12> + 0.1767766952966368 |28> + 0.1767766952966368 |2> + 0.1767766952966368 |18> + 0.1767766952966368 |10> + 0.1767766952966368 |26> + 0.1767766952966368 |6> + 0.1767766952966368 |22> + 0.1767766952966368 |14> + 0.1767766952966368 |30> + 0.1767766952966368 |1> + 0.1767766952966368 |17> + 0.1767766952966368 |9> + 0.1767766952966368 |25> + 0.1767766952966368 |5> + 0.1767766952966368 |21> + 0.1767766952966368 |13> + 0.1767766952966368 |29> + 0.1767766952966368 |3> + 0.1767766952966368 |19> + 0.1767766952966368 |11> + 0.1767766952966368 |27> + 0.1767766952966368 |7> + 0.1767766952966368 |23> + 0.1767766952966368 |15> + 0.1767766952966368 |31>

|28> = |1>|1>|1>|0>|0>

32-qubit

32

2^32

5-qubit

It is therefore important to be able to represent the states in factorized form whenever possible and only use the entangled form as a second choice -- unless we really wish to introduce such entanglement for a purpose, which we often do. But even then we should try to have at least partial factorization in place for the sake of efficiency. For this reason we introduce a Register data structure, which embodies both cases. It is implemented as a recursive tree.

data Register = Single Qubit | Product Register Register | Sum (Scalar, Register) (Scalar, Register)

We define the register size as a count n of physical qubits which we simulate. In reality, the total count of storage units will be usually much greater than the number of simulated qubits n ; in the worst case scenario their number will be n*2^n , representing 2^n possible realizations (paths) of the register of size n .

However, all unitary operations will refer to the physical qubits only by their physical indices, if neeeded. It is up to the implementation of a particular algorithm to appropriately propagate unitary operations across the register tree. The indices of n-qubit register are (n-1)..2,1,0 and they grow from right to left, in accordance with a prevailing scheme adopted in quantum computing literature.

5. Applying transformations to a qubit register

No matter what the internal Haskell representation of the qubit registers the end users shouldn't be bothered with such details. All algorithms should work regardless whether a register is represented as a simple tensor product or a weighed sum of such products. It's good to know why those algorithms are sometimes slow or sometimes fast but it should suffice to work with a mental picture of a linear array of qubits, described by some superposition of, possibly entangled, states.

All exported algorithms should be specified in terms of operators acting at specific locations of the qubit register. But in order to provide such a working model this module must define a low-level framework for doing all the hard work on internal representation of the registers. And indeed it does, providing a family of apply functions:

applyList :: [Qubit -> Qubit] -> Register -> Register applyAcross :: (Qubit -> Qubit) -> Register -> Register apply :: (Qubit -> Qubit) -> Int -> Register -> Register c_apply :: (Qubit -> Qubit) -> Int -> Int -> Register -> Register

id

Function applyAcross applies a single operator to all the qubits of the register. In fact it first produces a list of replicas of a given operator and then hands the job over to the applyList.

Function apply applies a single-qubit operator to a specific index (n-1), (n-2) .... 2, 1, 0 of the register. It first produces a list of id operators, replaces one of them by the specified operator, then calls the applyList.

Function c_apply, controlled-apply, accepts a single-qubit operator, and two indices. The first index is a control index and the second index specifies a target qubit. This function applies a specified operator to the target qubit, providing that the control qubit is in state | 1 >, otherwise it applies id operator to the target. If control qubit is in a superposition of both states then c_apply will behave accordingly, creating a sum of two paths.

We do not need any other low-level functions accepting three and more indices, because they can be composed from the primitives just defined.

And all single-qubit or two-qubit controlled operations, acting on qubits of a register, also known as gates, can be constructed from the above functions. For example, function hadamard is defined as:

hadamard :: Int -> Register -> Register hadamard i register = apply u_hadamard i register

c_not :: Int -> Int -> Register -> Register c_not i j register = c_apply u_not i j register

6. Register preparation

The basic register preparation function, zeroRegister, simulates physical process of cooling all the qubits to the ground, zero state, | 0 >; that is, loading a register with a numerical value 0.

zeroRegister :: Int -> Register

mix :: Register -> Register loadDecimal :: Int -> Integer -> Register loadState :: Int -> Ket Integer -> Register

n

[0,1..2^n-1]

1/2^(n/2)

Function loadDecimal loads n-qubit register with one specific Integer. It is implemented as a sequence of one-qubit unitary transformations of zeroRegister and hence it is by itself a unitary transformation too.

Function loadState loads a zero register with any state representing superposition of some Integers. It attempts to represent it by somehow factorized form which, although not as efficient as a pure product state, is still more efficient than a completely unfactorized, entangled form. Since at the end it uses the unitary transformation loadDecimal, it is by itself unitary as well.

Registers can be also constructed directly, qubit by qubit, by using two arithmetic operations: product of two registers <*> , and weighed sum of two registers, <+> ,

(<*>) :: Register -> Register -> Register (<+>) :: (Scalar, Register) -> (Scalar, Register) -> Register

These are also useful for inputing data to be tested. For example, assuming that: s0 = Single (Ket Zero), s1 = Single (Ket One), s2 = Single ((0.8 :|> Ket Zero) :|+> (0.6 :|> Ket One)) one can enter something like this: s0 <*> s1 <*> s2 which is a three-qubit product register, displayed as: |0>|1>[0.8 |0> + 0.6 |1>].

7. Two-qubit controlled-U transformations

As it was already mentioned, any general transformation of a register can be composed with a series of two generic types of elementary transformations: one-qubit and two-qubit controlled-U gates.

In this implementation, both accept indices to qubits within the register: one index for one-qubit and two indices for two-qubit controlled transformations. Both types can be considered the dressed-up versions of unitary transformations U acting on a single qubit. This simulator provides two such dressing-up functions, described before:

apply :: (Qubit -> Qubit) -> Int -> Register -> Register c_apply :: (Qubit -> Qubit) -> Int -> Int -> Register -> Register

The second one refers to two indices: the first being an index to a control qubit and the second -- to a target qubit. In the simplest case, where the control qubit is in one of the basic states | 0 > or | 1 >, and the target qubit is in some general, linear superposition of basic states, | x >, the mapping of a controlled-U transformation is as follows:

|0>|x> ==> |0>|x> |1>|x> ==> |1>(U |x>)

But when the control qubit is not described by a pure state then:

(a | 0 > + b | 1 >) | x > ==> a | 0 >| x > + b | 1 >(U | x >),

One could define dozens and dozens useful single-qubit transformations U, and then dress them up into controlled-U transformations. Two examples are controlled-NOT (c_not) and controlled-phase (c_phase) gates -- the latter being a part of a network for computation of Quantum Fourier Transform, to be described later.

But this is not the heart of this simulator; this is a task for its end user. What this simulator should provide is a framework that assures that any user-defined gate or network will be properly and efficiently executed within such framework. This means that the family of apply functions should be well optimized (which is not really done at this time yet), or replaced by better alternatives.

8. Composition of quantum networks

It has been proven that any logical operation, and therefore any arithmetic operation on classical computer can be composed from some universal set of logical primitives, acting at most on three bits a time. It has been also shown that any classical computation can be made reversible by composing a classical network from a special set of reversible gates. One of such gates, which acts on three bits a time is called a Toffoli gate, or controlled-controlled-NOT gate.

Similarly, it has been shown that any quantum network can be composed from some universal set of two-qubit and one-qubit quantum gates. The three-qubit quantum gates can be reduced to networks of simpler two-qubit and one-qubit gates. Such reduction would not be possible in classical case.

The reversible quantum Toffoli gate plays significant role in such networks since is provides us with a quantum realization of reversible AND operation. One possible implementation of the Toffoli gate follows. cc_not :: Int -> Int -> Int -> Register -> Register cc_not i j k -- Example implementation of reversible controlled-controlled-NOT gate -- using hadamard, c_quarter, c_quarter' and c_not gates. -- -- This gate acts as reversible AND operation when the target qubit -- at 'k' is initially set to |0>. After the operation is performed -- the target qubit contains logical AND of control qubits 'i' and 'j'. -- -- But when the target qubit is initially in some general state |xk>, -- then this operation is: |xi>|xj>|xk> ==> |xi>|xj>|xk XOR (xi AND xj)> -- | i == j || i == k || j == k = error "indices i,j,k should be distinct" | otherwise = hadamard k . c_quarter i k . c_not i j . c_quarter' j k . c_not i j . c_quarter j k . hadamard k The reason we have brought this example up is three-fold. Firstly, composing functions from other functions in Haskell is an easy task, and we all know it. Secondly, this example nicely translates to network diagrams used in Quantum Computing literature.

Just draw three vertical lines, label them i, j, k from left to right, place the gates on lines labelled by the rightmost index, a target, ( hadamard k will be on line k ) and connect the gates horizontally to their control lines, labelled by the remaining (control) indices. Put the black dots at intersections with appriopriate control lines. Notice, that the indices i, j, k can take any values from the available range of qubit indices of the register, such as 7, 1, 3 . They do not have to be ordered and the only requirement is that i /= j /=k .

But the typical quantum networks are drawn horizontally, rather than vertically, as we described here. So you would have to rotate such a diagram by 90 degrees to be consistent with those in literature. Or maybe by minus 90 degrees? Problem is that the literature is inconsistent on this issue. Network engineers like to see inputs on the left side and outputs on the right side. Physicists on the other hand tend to think in terms of operators acting on the register, so for them inputs are on the right hand side [3,4].

And this is the third advantage of our Haskell notation: we know exactly what this fuction represents, we know that the bottom operator ( hadamard k ) will act first to the right, followed by the operation c_quarter j k , etc. And the input is on the right to the operator (composed function), as physicists have it. No confusion, simple!

9. Quantum Fourier Transform and friends

This is a workhorse of many quantum algorithms, including the famous Shor's factorization algorithm. It is extremely fast, and - under some circumstances - it is also fast in this simulator. The usual explanation of the algorithm deals with the transformation of basis states only, such as

|0>|1>|0>|1>,

The QFT is executed by a network made of two gates: hadamard and controlled-phase. For example, a network for 4-qubit register looks like this:

hadamard 0 . c 0 1 . hadamard 1 . c 0 2 . c 1 2 . hadamard 2 . c 0 3 . c 1 3 . c 2 3 . hadamard 3 where c i j = c_phase (phi i j) i j

qft :: Register -> Register qft register -- Quantum Fourier Transform. -- = (foldl1 (.) [u k | k <- [0..n]]) register where u 0 = hadamard 0 u k = (foldl1 (.) [c j k | j <- [0..(k-1)]]) . hadamard k c j k = c_phase (pi/(2.0^(k-j))) j k n = size register - 1

Let's try some example. The expression trimState 5 $ state $ (qft' . qft) (loadDecimal 6 5) should produce | 5 >. We first load the integer 5 to a 6-qubit register (loadDecimal 6 5) then we act on this register with a product of qft' . qft, which supposes to be equivalent to id, and then we read the state -- rounding it first to five decimal places to reject all very tiny components caused by inaccuracy of numerical computations. As a result we end up with 1.0 |5> -- the vector we started with.

The first qft transformation decomposes the original state | 5 > into superposition of 2^6=64 states; while the inverse operation qft' composes them back together to the original state.

Barenco at al (see references) analysed aproximate version of QFT, AQFT, which ignores all long-distance interactions in the QFT algorithm. They claimed that the AQFT should behave better than QFT in the presence of decoherence caused by interaction of the registers with environment. Surprise, surprise -- the AQFT (being simpler, faster but less accurate) supposes to be better in real application of QC!

Implementations of aqft :: Int -> Register -> Register and its adjoint aqft' are trivial modifications of qft and its adjoint qft', respectively.

10. Arithmetic on quantum registers

Aside from several early papers related to basic arithmetic on quantum computer, not much attention is being paid to this problem nowadays. The thing seems to be obvious and solved. It is also not as exciting as search algorithms, etc. But a simulator like this one must provide such support.

I have not done much work here, but one particular algorithm caught my attention because it is an interesting variation on QFT. It is due to Draper (see references). It uses smaller number of qubits (Qubits will be probably very precious resources, at least initially, so it is important to have economical algorithms) than other traditional sum and carry algorithms.

The example below adds immediate number 10 to a register holding a superposition of three numbers 1, 7, 13, using unitary operator add_via_qft: trimState 3 $ state $ add_via_qft 10 (loadState 5 $ normalize $ (Ket 1 +> Ket 7 +> Ket 13)) ===> 0.577 |17> + 0.577 |11> + 0.577 |23> A trivial extension is an addition of two registers -- one of which holds a superposition af many numbers and another a single number.

11. Register and state vector trimming

This module provides two utilities:

trim :: Int -> Register -> Register trimState :: Ord a => Int -> Ket a -> Ket a

Neither function is unitary or reversible, and as such none can be considered a quantum computing operator. But both are helpful in two respects:

As far as presentation is concerned we often do not need the extreme accuracy provided by accuracy of Double. Besides, some of the components are often so tiny that they can be safely ignored alltogether.

Numerical inaccuracies of computations introduce noise in form of many tiny components. But when a state is being computed from a register all those components still have to be accounted for. This process is time consuming and it grows exponentially with a register size. By early register trimming we can see some approximate results in acceptable time, while the computation of the exact state would be extremely slow for some register sizes.

12. Simulated measurement of qubits and registers

To simulate measurement of a qubit

| q > = a | 0 > + b | 1 >

Zero

p0=|a|^2

One

p1=|b|^2

The measurement of a quantum system is not only non-deterministic but also destructive, since it collapses the measured state into a new state, the eigenstate corresponding to just measured eigenvalue. Another words, a mere fact of measuring a qubit alters its state.

The following monadic IO functions simulate such behaviour, returning pairs of eigenvalues and eigenvectors, enclosed in IO envelopes.

readRegister :: Register -> IO (Integer, Register) readQubitAt :: Int -> Register -> IO (Bit, Register) readQubit :: Qubit -> IO (Bit, Qubit)

To measure the register as a whole we simulate a sequence of qubit-by-qubit measurements. [Alternatively, we could measure an integer rather than the sequence of its individual bits; this method would be much faster, since we could simulate a single toss of a biased n-dimensional die, rather than n tosses of a biased coin.]

For example, the measurement of the 8-qubit register loaded with two numbers 1 and 8 results in one of the two outcomes - each given with probability 50%: readRegister (loadState 8 ((Ket 1 +> Ket 8))) >>= print ===> (8,|0>|0>|0>|0>|1>|0>|0>|0>) or ===> (1,|0>|0>|0>|0>|0>|0>|0>|1>) Each single-qubit measurement modifies the register; in fact it specializes more general forms to simpler forms by removing certain branches from the register tree.

For example, the entangled register 0.707 |0>|0> <+> 0.707 |1>|1> represents a superposition of two numbers: 0 and 3. There is 50% chance that either one of its two qubits, is in the state | 0 >. After one of those qubits, qubit at 0 say, is measured the register will collapse to either |0>|0> or |1>|1> , forcing the qubit at 1 to the same state as qubit at 0. No measurement of such curiously entangled state should transform it either to |0>|1> or to |1>|0> state.

References

Perhaps the most comprehensive source of information about quantum computing can be found in LANL e-print archive, section quant-ph: http://xxx.lanl.gov. But for a reader's convenience, we list here variety of introductions and tutorials designed for readers with different backgrounds: physicists, computer scientists, and aspiring students of both disciplines alike.

Recommended tutorials and reviews

Artur Ekert, Patrick Hayden and Hitoshi Inamori, Basic concepts in quantum computation, Centre for Quantum Computation, University of Oxford, 16 January 2000 Eleanor Rieffel, Wolfgang Polak, An Introduction to Quantum Computing for Non-Physicists, FX Palo Alto Laboratory, e-print http://xxx.lanl.gov/abs/quant-ph/9809016, v2 2000-01-19 N David Mermin, Lecture Notes on Quantum Computation and Quantum Information Theory, Cornell University, Physics 481-681, CS 483; Fall 2000. [six parts] N David Mermin, Notes for Physicists on the Theory of Quantum Computation, Laboratory of Atomic an Solid State Physics,Cornell University, Informal notes on three lectures at LASSP Autumn School on Quantum Computation, Cornell, September 20, 22 and 24, 1999. Samuel J Lomonaco Jr., A Rosetta Stone for Quantum Mechanics with an Introduction to Quantum Computation, Version 1.5, e-print http://xxx.lanl.gov/abs/quant-ph/0007045 R Cleve, A Ekert, C Macchiavello, M Mosca, Quantum Algorithms Revisited, e-print http://xxx.lanl.gov/abs/quant-ph/9708016 Andrew Steane, Quantum Computing, Department of Atomic and Laser Physics, University of Oxford, Clarendon Laboratory, July 1997, e-print http://xxx.lanl.gov/abs/quant-ph/9708022 Andre Berthiaume, Quantum Computation, Centrum voor Wiskunde en Informatica, The Netherlands, berthiau@cwi.nl, http://andre.cs.depaul.edu/Andre/publications/CTR.ps.gz Preskill's lectures on quantum computation, http://www.theory.caltech.edu/~preskill/ph229 (for theoretical physicists)

Specific issues