Simulation

Simulation modalities available are specified in Table 1. Users must specify a simulator for their Guppy program execution, otherwise the Quest simulator will be used by default.

Quest and Stim enable statevector and stabilizer simulation. Coinflip and Classical Replay further enable debugging for Guppy programs.

A 4-qubit GHZ state-preparation is compiled to hugr and used to demonstrate Selene’s simulation capabilities.

from guppylang import guppy
from guppylang.std.builtins import array, result
from guppylang.std.quantum import qubit, measure, h, cx, measure

@guppy
def main() -> None:
    qubit_top = qubit()
    h(qubit_top)
    qubits = array(qubit() for _ in range(3))
    for q in qubits:
        cx(qubit_top, q)
        m = measure(q)
        result("result", m)
    m_top = measure(qubit_top)
    result("result", m_top)

hugr = main.compile()

Statevector

Statevector simulation is enabled by the Quantum Exact Simulation Toolkit (Quest) plugin. QuEST utilizes multithreading, GPU acceleration and distribution.

The method selene_sim.build.build requires a HUGR object and a name (string) as input and returns a selene_sim.instance.SeleneInstance. The build method internally compiles HUGR into a binary executable.

The statevector simulation is initiated by calling selene_sim.instance.SeleneInstance.run_shots to run \(N\) shots. Alternatively, selene_sim.instance.SeleneInstance.run runs 1 shot. A selene_sim.Quest instance and a kwarg specifying the number of simulation qubits, n_qubits, are required as input to run_shots (run).

from selene_sim import build
from selene_sim import Quest
from hugr.qsystem.result import QsysResult

runner = build(hugr)

simulator = Quest()
qsys_result = QsysResult(runner.run_shots(
    simulator, 
    n_qubits=4,
    n_shots=100,
))
print(qsys_result.collated_counts())
Counter({(('result', '1111'),): 51, (('result', '0000'),): 49})

run_shots (run) returns a list of results.

Stabilizer

Selene uses Stim to perform stabilizer simulations. A requirement for Stim usage is a Guppy program using Clifford operations exclusively. A user-customizable parameter specifies a numerical threshold to constrain and invalidate illegal gate angle values. The primary benefit is to avoid numerical instability, or to inject approximations.

from selene_sim import Stim

simulator = Stim()

qsys_result = QsysResult(runner.run_shots(
    simulator, 
    n_qubits=4,
    n_shots=100,
))
print(qsys_result.collated_counts())
Counter({(('result', '0000'),): 53, (('result', '1111'),): 47})

Coinflip

from selene_sim import Coinflip

measurement_result = QsysResult(runner.run_shots(
  Coinflip(), 
  random_seed=249, 
  n_qubits=4, 
  n_shots=100
))
print(qsys_result.collated_counts())
Counter({(('result', '0000'),): 53, (('result', '1111'),): 47})

Classical Replay

The classical replay simulator (selene_sim.ClassicalReplayPlugin) enables debugging of guppy programs. An optional kwarg, measurements, allows users to inject predetermined measurement results, at a per-shot level, to inspect and debug arbitrary conditional branching within a program. There is no quantum simulation with classical replay. The program specified below performs additional quantum operations controlled by an MCMR operation.

from guppylang import guppy

@guppy
def main() -> None:
  q0: qubit = qubit()
  h(q0)
  c0: bool = measure(q0)
  result("c0", c0)
  if c0:
    q1: qubit = qubit()
    h(q1)
    c1: bool = measure(q1)
    result("c1", c1)

hugr_conditional = main.compile()

There are three main use cases for selene_sim.ClassicalReplayPlugin:

  1. To debug the classical user program without running the quantum simulator.

  2. To perform tests of user code with predetermined results

  3. To validate control flow by ensuring that a set of measurements have been made within a complete shot. E.g. consider a user program that halts on the first measurement result of \(|1\rangle\). This simulator will raise an error if the supplied measurements do not contain a \(|1\rangle\) result, or contain results following a \(|1\rangle\) result.

The code-cell below uses verifies the additive block is conditioned upon a successful True measurement.

from selene_sim import ClassicalReplay

runner = build(hugr_conditional)

measurements = [
  [False],
  [True, False]
]
simulator = ClassicalReplay(measurements=measurements)

shots = QsysResult(runner.run_shots(
  simulator, 
  n_qubits=2,
  n_shots=2,
))

print(shots)
QsysResult(results=[QsysShot(entries=[('c0', 0)]), QsysShot(entries=[('c0', 1), ('c1', 0)])])