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
:
To debug the classical user program without running the quantum simulator.
To perform tests of user code with predetermined results
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)])])