QPerfect MIMIQ
Running circuits on QPerfect's MIMIQ emulator through the QbraidProvider, with exact state vector simulation up to 32 qubits or matrix product state runs on 100 qubits and more, singly or as a batch.
Device IDs: qperfect:qperfect:sim:mimiq-sv (state vector, 32 qubits) and
qperfect:qperfect:sim:mimiq-mps (matrix product state) · batch jobs ·
free to run · accessed through QbraidProvider
Using your own MIMIQ account? See QPerfect (self-managed).
Devices on qBraid
MIMIQ is QPerfect’s cloud emulator for gate-model circuits. qBraid offers it as two devices, one per simulation engine:
| Device | Device ID | Engine | Qubits |
|---|---|---|---|
| MIMIQ SV | qperfect:qperfect:sim:mimiq-sv | State vector: exact | Up to 32 |
| MIMIQ MPS | qperfect:qperfect:sim:mimiq-mps | Matrix product state (MPS) | No fixed limit (see below) |
- State vector simulates every amplitude, so results are exact, but memory doubles with every qubit. Circuits over 32 qubits are refused.
- MPS compresses the state, so it handles hundreds of qubits, as long as the circuit doesn’t build up too much entanglement. A strongly entangled circuit can come back approximate, and each result reports how exact it was.
The device fixes the engine: choose it by choosing the device.
MIMIQ currently costs 0 credits to run. You need a qBraid API key, but no QPerfect account. See Pricing.
Quick start
from qbraid.runtime import QbraidProvider
from qiskit import QuantumCircuit
provider = QbraidProvider()
device = provider.get_device("qperfect:qperfect:sim:mimiq-sv")
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.measure_all()
job = device.run(circuit, shots=200)
print(job.result().data.get_counts())
# {'000': 101, '111': 99}MIMIQ takes OpenQASM, and qBraid transpiles to it for you, so a Qiskit, Cirq, Braket, pyQuil, or
pytket circuit can go straight to run(). device.supported_run_inputs() lists them.
Runtime options
MIMIQ’s settings are passed as a dict through runtime_options, which qBraid forwards to the
emulator:
device = provider.get_device("qperfect:qperfect:sim:mimiq-mps")
job = device.run(circuit, shots=200, runtime_options={"bonddim": 64, "seed": 7})| Option | Type | Devices | Default | Effect |
|---|---|---|---|---|
bonddim | int | MPS only | chosen by MIMIQ | MPS bond dimension: the most entanglement kept across any cut. Higher is more accurate and slower. |
entdim | int | MPS only | chosen by MIMIQ | MPS entanglement dimension, which bounds the multi-qubit gates applied in one step. |
mpscutoff | float | MPS only | chosen by MIMIQ | Singular-value truncation cutoff for MPS. Smaller is more accurate and slower. |
seed | int | both | random | Seed for sampling. The same seed returns identical counts. |
timelimit | int | both | chosen by MIMIQ | Maximum execution time, in minutes. |
An option name MIMIQ doesn’t recognize fails the job rather than being ignored. See When a job fails.
Advanced options
MIMIQ can also rewrite the circuit before simulating it, and tune how MPS applies gates. Each of these is left to MIMIQ unless you set it:
| Option | Type | Devices | Values | Effect |
|---|---|---|---|---|
fuse | bool | both | True, False | Merges consecutive gates on the same qubits into one gate. Fewer operations, but each one is larger. |
remove_swaps | bool | both | True, False | Absorbs SWAP gates into the qubit ordering instead of simulating them, which can cut depth and gate count considerably. |
canonicaldecompose | bool | both | True, False | Decomposes the circuit into MIMIQ’s canonical gate set (U and CX) before simulating. |
reorderqubits | bool or str | both | True, False, or a method name (below) | Reorders qubits so that interacting qubits sit close together. This mostly helps MPS, where long-range gates are expensive. |
reorderqubits_seed | int | both | any integer | Seed for the stochastic reordering methods, independent of seed. Fixes the ordering without fixing the sampling, or the other way round. |
mpsmethod | str | MPS only | "dmpo", "vmpoa", "vmpob" | How each gate is applied to the MPS: "dmpo" applies it directly and then compresses; "vmpoa" and "vmpob" use a variational fit. |
mpotraversal | str | MPS only | "sequential" (default), "bfs" | The order in which the circuit is grouped into MPOs. Sequential is the standard; breadth-first can suit some circuit structures. |
streaming | bool | MPS | True, False | On MIMIQ MPS, uses the streaming simulator, which doesn’t keep the full state in memory. It fits larger circuits but can’t compute amplitudes. |
reorderqubits=True uses "sa_warm_start". The methods are:
- Deterministic, which ignore
reorderqubits_seed:"greedy"(greedy weight-first insertion),"spectral"(Fiedler-vector ordering of the interaction graph) and"rcm"(reverse Cuthill–McKee, a bandwidth-reduction ordering). - Stochastic:
"sa_warm_start"(a greedy start refined by simulated annealing),"sa_only"(simulated annealing alone),"memetic"(an evolutionary search),"multilevel"(coarsen, then refine),"grasp"(GRASP with path relinking) and"hybrid"(multilevel plus GRASP: the best orderings, at the highest cost)."memetic","grasp"and"hybrid"are limited to 50 qubits.
job = mps.run(
circuit,
shots=200,
runtime_options={"remove_swaps": True, "reorderqubits": "spectral", "mpsmethod": "dmpo"},
)Pass booleans as True or False, not strings: {"fuse": "yes"} fails the job with
Invalid input parameters. Malformed request. An invalid mpsmethod or mpotraversal fails
with a message listing the allowed values. An unrecognized reorderqubits method name is not
rejected, so check its spelling. QPerfect’s
simulation parameters
page has more detail on each option.
Device options do not go in the signature. device.run(circuit, bonddim=64) raises
TypeError. Put them in runtime_options={...}. shots is the exception: it is an argument of
run() itself.
Choosing a device
MIMIQ MPS handles 100 qubits when entanglement stays low
A GHZ circuit at 100 qubits, well past MIMIQ SV’s 32, runs on MIMIQ MPS:
def ghz(n: int) -> QuantumCircuit:
qc = QuantumCircuit(n)
qc.h(0)
for i in range(n - 1):
qc.cx(i, i + 1)
qc.measure_all()
return qc
mps = provider.get_device("qperfect:qperfect:sim:mimiq-mps")
result = mps.run(ghz(100), shots=200).result()
print(result.data.get_counts())
# two outcomes: '000…000' (100 shots) and '111…111' (100 shots)
print(result.data.extra.get("fidelity"))
# 0.9999999999999996, exact up to floating-point roundingA GHZ state is only lightly entangled however wide it is: any cut through the chain carries one bit of entanglement. MPS cost depends on entanglement rather than qubit count, so this runs exactly.
The reverse also holds. A deep, highly entangling circuit can be hard for MPS even at modest
width. Then you trade accuracy for speed with bonddim and mpscutoff, and cap the run with
timelimit:
job = mps.run(ghz(100), shots=200, runtime_options={"bonddim": 32, "timelimit": 5})How exact an MPS result is
When MPS has to truncate the state, the counts are approximate. Each MIMIQ MPS result reports
MIMIQ’s fidelity lower bound in result.data.extra, where 1 means exact:
fidelity = result.data.extra.get("fidelity")Use .get(): the value is left out when MIMIQ cannot report one. In a batch it is on each
circuit’s result, result.results[i].data.extra, rather than the batch’s. If the fidelity is well below 1, raise bonddim, or run
the circuit on MIMIQ SV if it fits in 32 qubits.
Reproducible counts with seed
Two identical runs normally return different counts. With the same seed they match exactly:
sv = provider.get_device("qperfect:qperfect:sim:mimiq-sv")
opts = {"seed": 7}
sv.run(ghz(3), shots=200, runtime_options=opts).result().data.get_counts()
# {'000': 113, '111': 87}
sv.run(ghz(3), shots=200, runtime_options=opts).result().data.get_counts()
# {'000': 113, '111': 87}Batch jobs
Both devices support batch jobs
(device.profile.batch_job_support is True). Pass a list of circuits with as_batch=True and
they run as one job, with one job ID and one status, and you get one set of counts per
circuit, in order:
import numpy as np
angles = np.linspace(0, 2 * np.pi, 12)
sweep = []
for theta in angles:
qc = QuantumCircuit(1)
qc.ry(theta, 0)
qc.measure_all()
sweep.append(qc)
job = sv.run(sweep, shots=500, as_batch=True)
counts = job.result().data.get_counts() # a list of 12 count dicts
p1 = [c.get("1", 0) / 500 for c in counts] # follows sin²(θ/2)Without as_batch=True, a list of circuits is submitted as separate jobs, one per circuit,
and run() returns a list of jobs.
Circuits in a batch share one seed. MIMIQ takes a single seed per job, drawn at random when
you don’t pass one, so every circuit in a batch is sampled from the same random stream. Circuits
with the same outcome distribution return identical counts: in the sweep above, mirror-image
angles θ and 2π − θ come back identical.
Each circuit’s counts are correctly distributed on their own, but they are not independent of the other circuits in the batch. If your analysis needs independent samples per circuit, submit the circuits as separate jobs.
Reading the results
Results come back at your circuit’s own width, and qubit ordering is little-endian: qubit 0 is the rightmost bit.
qc = QuantumCircuit(3)
qc.x(0)
qc.measure_all()
sv.run(qc, shots=100).result().data.get_counts()
# {'001': 100}
# ^ qubit 0When a job fails
When a job fails, the reason is on its status:
job.wait_for_final_state()
status = job.status()
if status.name == "FAILED":
print(status.status_message)Read it there. On a failed job, job.result() returns success=False with no data, so
get_counts() raises a generic ValueError: Counts data is not available.
Execution time
Small jobs, from a 3-qubit circuit to a 100-qubit GHZ state on MIMIQ MPS, take 20 to 45 seconds
end to end. Most of that is submission and queueing overhead, not simulation. A batch of twelve
circuits completes in about the same time as a single job, which makes as_batch=True the fast way
to run a sweep.
Troubleshooting
| Symptom | Cause |
|---|---|
TypeError: submit() got an unexpected keyword argument | Device options belong in runtime_options={...}, not the run() signature. See Runtime options. |
ValueError: Counts data is not available. | The job failed. Read job.status().status_message. See When a job fails. |
Runtime option(s) [...] only apply to MPS | An MPS option such as bonddim was sent to MIMIQ SV. Submit to mimiq-mps. |
Unsupported MIMIQ submit option(s): [...] | A misspelled or unknown option name in runtime_options. |
Invalid input parameters. Malformed request. | An option has the wrong type, such as "yes" for a boolean. See Advanced options. |
KeyError: 'fidelity' | Read the fidelity with result.data.extra.get("fidelity"). See How exact an MPS result is. |
| Circuits in a batch return identical counts | Circuits in a batch share one seed. See Batch jobs. |
| Results differ between identical runs | Sampling is random. Pass a seed to fix it. |
Related links
- Run on qBraid: installing the SDK and saving your qBraid API key
- Batch & group jobs: running several circuits as separate, batch, or group jobs
- QPerfect (self-managed):
QPerfectProviderwith your own MIMIQ account - Pricing: MIMIQ is free to run
- MIMIQ documentation: QPerfect’s reference for the options above
- QPerfect
Thanks for your feedback.

