Scotty 0.6.0 Released

Published on October 01, 2019 by Vasily Vasinov


Performance Improvements

This is a very important update that focuses on the simulator performance. In addition to the state vector memory allocation optimizations we rewrote the gate application algorithm from scratch. The new algorithm is three orders of magnitude faster than what we had before. In practical terms, it means that users can run much larger circuits in a fraction of the time required before. For example, a 15-qubit circuit with 1,000 gates now runs in under one second on a 4.2 GHz Intel Core i7 box. A 20-qubit circuit with 1,000 gates runs in about 20 seconds. Users can realistically run shallower circuits of up to 27 qubits with the max of 29 qubits.

In order to optimize for memory usage, we replaced the double-precision (Double) complex number representation with regular Floats. This cut our memory usage in half. Now, every complex number takes up 64 bits instead of 128. It means that Vector and Matrix types were redefined as:

type Vector = Array[Float]
type Matrix = Array[Array[Float]]

Multi-Trial Experiment Changes

The runAndMeasure version of the method that ran multiple trials of the same circuit was renamed to runExperiment to avoid confusion with the single shot runAndMeasure.

We also removed parallelism support for multiple trials since it doesn’t make sense to run experiments in parallel given that state initialization, gate application, and probability calculations are already parallelized.

Gate Changes

Single-qubit gate implementations now live at the framework level. Some are implemented with matrices and some are expressed through other gates.

CircuitConnector was removed in favor of CompositeGate. You can define CompositeGates to describe multi-qubit unitary gates and operations. Some gates in the Scotty simulator now use this technique. For example, the SWAP gate is implemented as a function that takes custom indexes as two parameters and returns a CompositeGate:

def SWAP(i0: Int, i1: Int) = CompositeGate(CNOT(i0, i1), CNOT(i1, i0), CNOT(i0, i1))

Performance improvements forced us to introduce limitations to how Dagger and Controlled modifiers work. Now, Dagger can only be applied to single-qubit gates and Controlled can either be applied to other Controlled gates or single-qubit gates. This behavior might change in the future.

Register Changes

QubitRegister can now be initialized from a binary string or an integer:

QubitRegister("1001")

// is equivalent to

QubitRegister(9)

Circuit also received a couple of helper methods for quick register initialization. Here are all the different ways you can set a custom register:

Circuit(CNOT(0, 2)).withRegister(QubitRegister(Qubit.one, Qubit.zero, Qubit.zero))

Circuit(CNOT(0, 2)).withRegister(Qubit.one, Qubit.zero, Qubit.zero)

Circuit(CNOT(0, 2)).withRegister("100")

Circuit(CNOT(0, 2)).withRegister(4)