Quantum Context and Simulator


Quantum Context

First and foremost, Scotty is a high-level quantum framework that defines abstract quantum primitives. At the top of it all we have QuantumContext—a trait with a handful of abstract methods. This is the heart of the framework and it’s meant to provide functions for quantum state and gate transitions that take place during circuit execution.

Here is the complete list of method signatures that QuantumContext provides:

trait QuantumContext {
  def run(circuit: Circuit): State

  def gateMatrix(gate: Gate): Matrix

  def tensorProduct(gate1: Gate, gate2: Gate): TargetGate

  def tensorProduct(sp1: Superposition, sp2: Superposition): Superposition

  def product(gate: Gate, sp: Superposition): Superposition

  def outerProduct(sp1: Superposition, sp2: Superposition): Matrix

  def densityMatrix(qubit: Qubit): Matrix

  def isUnitary(gate: Gate): Boolean

  def measure(register: QubitRegister, sp: Superposition): Collapsed

  def runAndMeasure(circuit: Circuit): Collapsed

  def runAndMeasure(circuit: Circuit, trialsCount: Int,
                    parallelismLevel: Int): ExperimentResult
}

There are many possible use cases where you’d want to roll out your own implementation of QuantumContext. For example, if you have a distributed quantum simulator running in the cloud that you want to use to run bigger circuits then implementing QuantumContext would make a lot of sense. The obvious benefit is that you get to keep your Scala programs and algorithms written with Scotty unmodified. Write once, run anywhere.

Quantum Simulator

QuantumSimulator implements QuantumContext with as few dependencies as possible, which allows you to run small—up to 12 qubits—circuits. Currently, the only dependency it relies on is Apache Commons Math for complex matrix operations.

The only way you can control QuantumSimulator right now is by providing a custom Random Scala implementation either directly or implicitly. It’s useful when you need to specify a random seed:

val sim = QuantumSimulator()(new Random(System.nanoTime))

or

implicit val r = new Random(System.nanoTime)

val sim = QuantumSimulator()

The multi-trial version of runAndMeasure uses Java ForkJoinPool to run multiple experiments in parallel. You can pass the level of parallelism required for your experiments and computer architecture. By default, parallelism is set to 7.

After all experiment trials are done executing, the method returns an ExperimentResult which contains a list of Collapsed states after each experiment.

The stateStats lazy variable is a list of tuples containing a binary representation of the collapsed state and the total number of state occurrences. Suppose, we entangle two qubits and run the experiment 1000 times:

QuantumSimulator().runAndMeasure(Circuit(H(0), CNOT(0, 1))).stateStats.toHumanString

As expected, we are going to end up with roughly 50% of both qubits being in the 00 state and another 50% being in the 11 state:

00: 483
01: 0
10: 0
11: 517