Quobly Alloy Forge
Running circuits against Quobly's silicon spin-qubit emulator through the QbraidProvider, with and without the Pioneer hardware noise model.
Device ID: qbraid:quobly:sim:alloy-forge · 15 qubits
· silicon spin qubits · free to run · accessed through
QbraidProvider
Alloy Forge is Quobly’s physics-based emulator of Pioneer, their 10-qubit silicon spin-qubit QPU. Quobly builds qubits from electron spins in silicon, manufactured on standard CMOS processes, so the error model the emulator reproduces is a semiconductor one rather than the superconducting or trapped-ion behavior you may be used to.
The emulator goes wider than the hardware: 15 qubits, in both noiseless and noisy modes, on a linear nearest-neighbor coupling map.
Alloy Forge currently costs 0 credits to run. You still need a qBraid API key, but no Quobly account and no payment. See Pricing.
Quick start
from qbraid.runtime import QbraidProvider
from qiskit import QuantumCircuit
provider = QbraidProvider()
device = provider.get_device("qbraid:quobly:sim:alloy-forge")
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
job = device.run(circuit, shots=200)
print(job.result().data.get_counts())
# {'00': 106, '11': 92, '01': 1, '10': 1}One thing in that output surprises almost everyone: the distribution is not clean, because noise is on by default.
Runtime options
Alloy Forge takes three device options. They are passed as a dict through runtime_options,
not as keyword arguments:
| Option | Type | Default | Effect |
|---|---|---|---|
shots | int | — | 1 to 1000. Passed directly to run(), not in the dict. |
noise | bool | True | Apply the Pioneer hardware noise model. |
seed | int | None | Seed the sampler. Makes noisy runs exactly reproducible. |
Device options do not go in the signature. This raises TypeError:
job = device.run(circuit, shots=200, noise=False)
# TypeError: QbraidDevice.submit() got an unexpected keyword argument 'noise'Put them in runtime_options instead:
job = device.run(circuit, shots=200, runtime_options={"noise": False, "seed": 42})shots is the exception — it is a first-class argument of run(). The options documented on the
Runtime Options page (transpile, transform, validate,
prepare) are a different thing again: those are pipeline steps, set with device.set_options().
noise is on by default
Unlike most simulators on qBraid, Alloy Forge applies its hardware noise model unless you tell it
not to. A plain device.run(circuit, shots=200) is a noisy run.
ideal = device.run(circuit, shots=200, runtime_options={"noise": False})
noisy = device.run(circuit, shots=200, runtime_options={"noise": True})The distinction is the point of the device — a noiseless run of a Bell pair gives you exactly two outcomes, while the noisy run shows what Pioneer’s silicon spin qubits would actually return.
seed makes noisy runs reproducible
Two runs with the same seed return bit-identical counts, noise included:
opts = {"noise": True, "seed": 42}
device.run(circuit, shots=1000, runtime_options=opts).result().data.get_counts()
# {'00': 513, '11': 484, '10': 3}
device.run(circuit, shots=1000, runtime_options=opts).result().data.get_counts()
# {'00': 513, '11': 484, '10': 3}Use it for anything you need to reproduce: notebooks, tests, tutorials, recorded demos.
Reading the results
Counts match your circuit’s width
Results come back at your circuit’s own width — a two-qubit circuit returns two-bit keys.
Qubit ordering is little-endian: qubit 0 is the rightmost bit.
circuit = QuantumCircuit(3)
circuit.x(2)
circuit.measure_all()
device.run(circuit, shots=100, runtime_options={"noise": False}).result().data.get_counts()
# {'100': 100}
# ^ qubit 2Native gates and the coupling map
Pioneer’s native gate set is RX, RY, RZ for single-qubit rotations and RZZ for the
two-qubit interaction. RZZ is only physically available between adjacent qubits on the
linear array: 0–1, 1–2, 2–3, and so on.
You do not have to write circuits in that gate set. Decomposition happens inside the emulator, after submission:
device.transform(qasm) # returns the program unchanged -- this is a no-op for Alloy ForgeUnlike Rigetti or IonQ, qBraid does not rewrite your gates for this device. An h/cx circuit
reaches Quobly as h/cx and Quobly transpiles it to RX/RY/RZ/RZZ on its side.
You do not have to route your own circuits. A two-qubit gate on non-adjacent qubits is accepted and runs, and the counts come back under the qubit indices you wrote.
That is not free, though. To satisfy the connectivity constraint the transpiler places your logical qubits wherever the chain allows, which can be a long way from the indices you named — Alloy Forge then reports each bit at the position of the physical qubit that carried it, and qBraid maps it back for you before returning the result.
What a non-local circuit does cost is native two-qubit gates, and the noise model charges for every one of them. Measured against the Pioneer target:
| Circuit | Two-qubit gates written | RZZ after transpilation |
|---|---|---|
| GHZ-6, along the chain | 5 | 5 |
| GHZ-9, along the chain | 8 | 8 |
| Star on 8 qubits | 7 | 22 |
| QFT on 6 qubits | 15 | 66 |
A chain costs exactly what you wrote. A star costs about three times as much, and a QFT more than
four. If a circuit can be expressed along neighboring pairs (i, i+1), that is the version worth
running — not for correctness, but for fidelity.
A GHZ state written as a nearest-neighbor ladder satisfies that constraint with no routing at all:
def ghz(n: int) -> QuantumCircuit:
qc = QuantumCircuit(n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1) # every pair is adjacent
qc.measure_all()
return qcExecution time
Alloy Forge integrates the physics shot by shot, so the shot count dominates the wall clock, more than circuit width does. The same 4-qubit GHZ circuit:
| Shots | Wall clock |
|---|---|
| 100 | 32 s |
| 1000 | 136 s |
Roughly 20 seconds of fixed overhead plus a per-shot cost. Across a 2-to-9 qubit GHZ sweep at 200 shots, individual jobs ran from 22 s to 89 s.
Develop at 100–200 shots and raise it only for the final run. Batch submission is not supported
on this device (profile.batch_job_support is False), so a sweep is sequential — budget the
wall clock before you launch one.
Example: measuring noise accumulation
Growing a GHZ chain one qubit at a time is a direct read on how quickly the Pioneer error model
accumulates. The metric is the share of shots landing in |0…0⟩ or |1…1⟩, which is 100% for an
ideal GHZ state.
from qbraid.runtime import QbraidProvider
from qiskit import QuantumCircuit
device = QbraidProvider().get_device("qbraid:quobly:sim:alloy-forge")
SHOTS = 200
for n in range(2, 10):
qc = QuantumCircuit(n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1)
qc.measure_all()
job = device.run(qc, shots=SHOTS, runtime_options={"noise": True, "seed": 1234})
counts = job.result().data.get_counts()
population = (counts.get("0" * n, 0) + counts.get("1" * n, 0)) / SHOTS
print(f"GHZ-{n}: {population:.1%}")Measured on the live device:
| Qubits | Noiseless | Pioneer noise model |
|---|---|---|
| 2 | 100% | 100% |
| 3 | 100% | 98.0% |
| 4 | 100% | 92.5% |
| 5 | 100% | 84.0% |
| 6 | 100% | 78.0% |
| 7 | 100% | 62.5% |
| 8 | 100% | 47.5% |
| 9 | 100% | 29.5% |
The noiseless column is flat at 100% by construction; the noisy column is the emulator’s answer to “how big a GHZ state can Pioneer hold together?”
Troubleshooting
| Symptom | Cause |
|---|---|
TypeError: submit() got an unexpected keyword argument 'noise' | Device options belong in runtime_options={...}, not the run() signature. See Runtime options. |
| Counts are narrower or wider than you expected | Results come back at your circuit’s own width, little-endian. See Reading the results. |
| A “noiseless” run has extra outcomes | noise defaults to True. Pass runtime_options={"noise": False}. |
| A non-local circuit is noisier than you expected | Satisfying the linear connectivity costs extra RZZ gates. See Native gates and the coupling map. |
| Results differ between identical runs | Noise is stochastic. Pass a seed to fix it. |
| A sweep is taking far longer than expected | Wall clock scales with shots, and batch submission is unsupported. See Execution time. |
Related links
- QbraidProvider: installation, authentication, device discovery
- Runtime Options: the pipeline options set with
set_options() - Pricing: Alloy Forge is free to run
- Quantum jobs: tracking submissions in qBraid Lab
- Quobly: silicon spin qubits on standard CMOS processes
Thanks for your feedback.

