Skip to content

Kotlin framework for writing quantum algorithms using QASM-like syntax

License

Notifications You must be signed in to change notification settings

Antimonit/Quantum

Repository files navigation

Quantum computing in Kotlin

Maven Central License CircleCI Code Coverage

Kotlin framework for writing quantum algorithms using QASM-like syntax.

Because the code is still compiled by Kotlin compiler we are not limited by QASM language features. We can use classical variables, loops, functions, etc. There is no need for binary-controlled gates as we can utilize standard if condition.

Examples

Quantum Teleportation

Quantum Teleportation

val message = ZERO // or ONE or random()

program(Register(message, ZERO, ZERO)) {
    // Entangle qubits q1 and q2 to form a fully entangled bell state
    Hadamard[2]
    CNot[2, 1]

    // Entangle message/state held by q0 with the rest.
    CNot[0, 1]
    Hadamard[0]

    // Measuring the first two qubits will change the state of the third qubit because
    // of the entanglement. This will teleport the message from the first qubit to the
    // third qubit.
    val (secret, shared) = measureAndCollapse(0, 1)

    // The last qubit can be in one of four superpositions now. We can use qubits
    // measured in the previous step to conditionally apply some gates to put it
    // into one specific superposition.
    if (shared == ONE) X[2]
    if (secret == ONE) Z[2]
}

// At this point the third qubit will be in the same superposition as the message
// defined at the beginning.

Grover's Algorithm

Grover's Algorithm

val oracle: Gate = oracleGate(ONE, ONE, ZERO)

program(3) {
    // Initialization
    step { H[0]; H[1]; H[2] }

    repeat(2) {
        // Oracle
        oracle[0, 1, 2]

        // Diffusion
        step { H[0]; H[1]; H[2] }
        step { X[0]; X[1]; X[2] }
        step { C(C(Z))[0, 1, 2] }
        step { X[0]; X[1]; X[2] }
        step { H[0]; H[1]; H[2] }
    }

    // Measurement
}.measure() // Returns [ONE, ONE, ZERO] with high probability

Quantum Adder

Quantum Adder

// We can even utilize entanglement to calculate two separate additions at the same time.

program(4) {
    // Prepare input
    H[0]
    CNot[0, 1]

    // Encode two combinations of input into an entangled pair.
    // By passing ∣00⟩ + ∣11⟩ / sqrt(2) as input to the adder
    // we are essentially calculating both 0 + 0 and 1 + 1.

    // Full Adder
    CCNot[0, 1, 3]
    CNot[0, 1]
    CCNot[1, 2, 3]
    CNot[1, 2]
    CNot[0, 1]
}

// At this point it is equally likely to observe result of 0 + 0 = 00 and 1 + 1 = 10.

Building blocks

Details

Complex numbers can be created either from cartesian coordinates:

Complex(re: Number = 0, im: Number = 0)

or from polar coordinates:

Complex.fromPolar(theta: Number, radius: Number = 1)

For most commonly used Complex numbers there are three predefined values:

val ONE = Complex(1, 0)
val ZERO = Complex(0, 0)
val I = Complex(0, 1)

There are several overloaded operators that allow for easier addition, subtraction, multiplication and division of complex numbers.

Details

Matrices can be created from number of rows and cols and 1D array of complex numbers:

Matrix(rows: Int, cols: Int, vararg values: Complex)

or directly from 2D array of complex numbers:

Matrix(m: List<List<Complex>>)

Identity matrix n×n can be created using:

Matrix.identity(size: Int)

There are many overloaded operators that allow for easier addition and subtraction of two matrices, multiplication and division by a number, complex number or another matrix, conjugate transpose and tensor product.

Qubits are defined by two complex numbers which define their probability amplitudes:

Qubit(alpha: Complex, beta: Complex)

Two commonly used qubit states are predefined:

val ZERO = Qubit(Complex.ONE, Complex.ZERO)
val ONE = Qubit(Complex.ZERO, Complex.ONE)

Qubits can be converted to bra ⟨φ∣ or ket ∣φ⟩ matrices and define dot, cross and tensor products.

Because two qubits may have different alpha and beta values although they represent the same physical state they can be normalized so that two qubits can be programmatically compared. This cannot be observed in the physical world but is invaluable in testing.

Registers are created from a list of qubits:

Register(vararg qubits: Qubit)

In contrast to a simple List<Qubit> that can only hold independent qubits, a Register can also hold entangled qubits, such as Bell states. This is crucial for creation of more complex algorithms that depend on entanglement.

Just like single qubits, registers can be normalized for testing purposes.

Most of the commonly used gates are already predefined:

Identity X (Not) Y Z S T
$\begin{bmatrix} 1 &amp; 0 \\ 0 &amp; 1 \end{bmatrix}$ $\begin{bmatrix} 0 &amp; 1 \\ 1 &amp; 0 \end{bmatrix}$ $\begin{bmatrix} 0 &amp; -i \\ i &amp; 0 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 \\ 0 &amp; -1 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 \\ 0 &amp; i \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 \\ 0 &amp; e^{i\pi/4} \end{bmatrix}$
Hadamard Rx Ry Rz Phase
$\frac{1}{\sqrt{2}} \begin{bmatrix} 1 &amp; 1 \\ 1 &amp; -1 \end{bmatrix}$ $\begin{bmatrix} cos(\frac{\theta}{2}) &amp; -i sin(\frac{\theta}{2}) \\ -i sin(\frac{\theta}{2}) &amp; cos(\frac{\theta}{2}) \end{bmatrix}$ $\begin{bmatrix} cos(\frac{\theta}{2}) &amp; -sin(\frac{\theta}{2}) \\ sin(\frac{\theta}{2}) &amp; cos(\frac{\theta}{2}) \end{bmatrix}$ $\begin{bmatrix} e^{-i\frac{\theta}{2}} &amp; 0 \\ 0 &amp; e^{i\frac{\theta}{2}} \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 \\ 0 &amp; e^{i\theta} \end{bmatrix}$
CNot (CX) Swap Square Root of Swap Controlled
$\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 \\ 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; \frac{1}{2}(1+i) &amp; \frac{1}{2}(1-i) &amp; 0 \\ 0 &amp; \frac{1}{2}(1-i) &amp; \frac{1}{2}(1+i) &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; u_00 &amp; u_01 \\ 0 &amp; 0 &amp; u_10 &amp; u_11 \end{bmatrix}$
Toffoli (CCNot) Fredkin (CSwap)
$\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix}$ $\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}$

It should be rather straightforward to create custom gates.

Gates can be applied to a register. The size of the gate must match the number of qubits stored by the register.

GateSwap * Register(Qubit.ONE, Qubit.ZERO) // Register(Qubit.ZERO, Qubit.ONE)

Gates that act on a single qubit can be also applied to qubits.

GateX * Qubit.ONE // Qubit.ZERO

Instead of manually applying gates to registers and qubits like this:

val bellState = CNot * Register(Hadamard * ZERO, ZERO)

program classes provide a less cluttered and more natural way to combine multiple gates into one, reorder inputs of a gate, apply gates to registers with different sizes and apply multiple gates to a register.

Precomputed Program

Pre-computes transformations of multiple gates as a standalone gate. As we apply gates within the program, their transformation matrices are combined. It allows us to pre-compute a part of a larger program that is executed repeatedly and apply the result gate instead.

// Swap gate made using CNot gates 
val swap: Gate = gate(2) {
    CNot[0, 1]
    CNot[1, 0]
    CNot[0, 1]
}

Runnable Program

Applies multiple gates to a register, changing the state of its qubits with each step.

// Fully entangled Bell state (∣00⟩ + ∣11⟩) / sqrt(2)  
val bellState: Register = program(2) {
    Hadamard[0]
    CNot[0, 1]
}

Compared to gate that does not have any state per se, program has a register that we can measure at any point.

fun measureAndCollapse(vararg qubitIndices: Int): List<Qubit>

Doing so will collapse the state of specified qubits to ∣0⟩ or ∣1⟩ based on their probabilities. Other qubits in the register entangled with any of the measured qubits will have their probabilities updated as well to satisfy constraints imposed by entangled states before the measurement.

Download

The project is also available as a maven dependency from MavenCentral repository:

implementation("io.github.antimonit:quantum:1.0.1")

About

Kotlin framework for writing quantum algorithms using QASM-like syntax

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages