Usage¶
Quick Start¶
import fastqsim
client = fastqsim.init() # reads FASTQUBIT_SESSION_TOKEN from environment
qasm = """
OPENQASM 3.0;
include "stdgates.inc";
qubit[2] q;
bit[2] c;
h q[0];
cx q[0], q[1];
c = measure q;
"""
# Synchronous - blocks until the job finishes
job = client.run(qasm, shots=2048, asynchronous=False)
result = job.result()
print(job.job_id, job.status.value) # e.g. "3fa8... completed"
print(result.counts) # {"00": 1024, "11": 1024}
What’s New¶
Server-side long-polling for
wait()and synchronousrun()to reduce client polling round-trips.Parallelized
run_batch()submissions using a thread pool withmax_parallelcontrol.Session ID caching per client instance to avoid repeated resolution calls.
Reuse of latest fetched terminal job snapshot to reduce redundant API reads.
Usage Scenarios¶
Synchronous Run¶
Block until the job completes and inspect results inline. Best for interactive notebooks and short circuits.
What it does: submits one circuit and waits until it reaches a terminal state.
Use when: you want a simple request/response style flow.
Returns: a completed Job and a Result with measurement data.
job = client.run(
qasm,
backend="qiskit",
shots=1024,
asynchronous=False, # default is False - blocks here
)
result = job.result()
print(result.counts) # {"00": 512, "11": 512}
print(job.execution_time_seconds) # e.g. 0.34
Output shape: result.counts is a bitstring-to-count mapping.
Common failure + fix: validation errors usually mean malformed QASM or invalid
arguments; verify circuit syntax and shot count.
Async / Fire-and-Forget¶
Submit a job and do other work while it runs. Retrieve results later with
wait() or get().
What it does: queues work without blocking the caller thread immediately.
Use when: your application has parallel work while simulations run.
Returns: an initial Job (queued/running), then a completed Job after
wait().
job = client.run(qasm, shots=4096, asynchronous=True)
print(f"Queued: {job.job_id}")
# ... do other work ...
# Block until terminal, returns updated Job
completed_job = client.wait(job.job_id, timeout=300)
result = completed_job.result()
print(result.counts)
Output shape: first response contains job metadata; final response includes
terminal status and result payload.
Common failure + fix: timeout can occur on long jobs; increase timeout or
handle JobTimeoutError and continue polling.
Batch Execution¶
Submit multiple circuits in parallel. All jobs share the same backend, device,
and shots. Use client.wait() to block on all of them.
What it does: submits multiple circuits concurrently via a client thread pool.
Use when: you need throughput for independent circuits.
Returns: a list of Job objects from submission and a list of completed jobs
after waiting.
circuits = [qasm_bell, qasm_ghz, qasm_qft]
# Submitted concurrently (up to max_parallel threads)
jobs = client.run_batch(
circuits,
shots=1024,
max_parallel=3, # controls client-side thread concurrency
)
print([j.job_id for j in jobs])
# Wait for all to complete
completed = client.wait([j.job_id for j in jobs], timeout=600)
for job in completed:
print(job.job_id, job.result().counts)
Output shape: list of jobs where each job.result().counts has per-circuit
measurement totals.
Common failure + fix: partial failures may appear in mixed job lists; inspect
each job status and handle failed ones individually.
Cancel a Job¶
Cancellation is idempotent. Cancelling an already-terminal job is a no-op and returns the current state.
What it does: records cancellation intent for a queued/running job.
Use when: a queued/running simulation is no longer needed.
Returns: an updated Job reflecting cancelled or existing terminal state.
job = client.run(qasm, asynchronous=True)
# Request cancellation
updated_job = client.cancel(job.job_id)
print(updated_job.status.value) # "cancelled" or already-terminal status
# Cancel via the Job object directly
job2 = client.run(qasm, asynchronous=True)
job2.cancel()
print(job2.status.value)
Output shape: returns a job object with latest status fields. Common failure + fix: cancellation is not a hard kill for in-flight worker execution; poll until terminal status is confirmed.
Warning
Cancellation signals the platform to stop scheduling this job but does not kill an already-running worker pod. If a worker is mid-execution it may finish, but any terminal state updates are ignored once cancelled.
Deadline-Safe Polling¶
Use wait() with a timeout and catch JobTimeoutError for robust
production workflows.
What it does: enforces a client-side deadline while waiting for job completion.
Use when: you need bounded latency in production control flows.
Returns: a completed Job on success or a timeout exception.
from fastqsim import JobTimeoutError, JobFailedError
job = client.run(qasm, asynchronous=True)
try:
completed = client.wait(job.job_id, timeout=120)
result = completed.result()
print(result.counts)
except JobTimeoutError:
print(f"Job {job.job_id} did not finish within 120 s")
# Optionally cancel it
client.cancel(job.job_id)
except JobFailedError as exc:
print(f"Job failed: {exc}")
Output shape: success path returns terminal job state with result access. Common failure + fix: repeated timeouts indicate long or congested workloads; retry with larger timeout or switch to async orchestration.
Search Job History¶
Query your job history with optional filters. Returns up to limit jobs
ordered by most-recently-updated first.
What it does: fetches historical jobs filtered by status/backend/date.
Use when: you need observability, debugging context, or reporting.
Returns: a list of Job records with metadata and timing fields.
from datetime import datetime, timezone, timedelta
# All completed jobs in the last 24 hours
yesterday = datetime.now(timezone.utc) - timedelta(days=1)
jobs = client.search(
run_status="completed",
start_date=yesterday,
limit=20,
)
for job in jobs:
print(job.job_id, job.status.value, job.execution_time_seconds)
# Filter by backend
qiskit_jobs = client.search(backend="qiskit", limit=50)
# Failed jobs for investigation
failed = client.search(run_status="failed", limit=10)
for job in failed:
print(job.job_id, job.error_message)
Output shape: list of jobs sorted by recent update time. Common failure + fix: empty result sets are usually filter mismatch; relax filters or widen date ranges.
Statevector Retrieval¶
Request a full statevector simulation. The statevector is returned as a list
of complex values.
What it does: runs a statevector simulation and reads amplitudes from results.
Use when: you need full quantum state inspection, not only counts.
Returns: List[complex] amplitudes via job.get_statevector().
job = client.run(
qasm,
shots=0, # No measurement shots needed
simulation_type="statevector",
asynchronous=False,
)
sv = job.get_statevector()
print(f"State dimension: {len(sv)}") # 2^n_qubits
print(f"|00> amplitude: {sv[0]:.4f}")
print(f"|11> amplitude: {sv[3]:.4f}")
Output shape: vector length is 2^n for n qubits.
Common failure + fix: very large states may be stored externally; rely on
job.get_statevector() artifact fetch behavior and avoid excessive qubits.
Warning
For large qubit counts the statevector may exceed the inline-response limit.
In that case it is stored as an external artifact and get_statevector()
fetches it automatically.
External Artifact Download¶
When job.has_external_artifact is True, the full result JSON was
uploaded externally. job.result() fetches and merges it automatically.
job = client.run(large_circuit_qasm, shots=8192, asynchronous=False)
if job.has_external_artifact:
# result() transparently downloads the artifact
result = job.result()
print(result.counts)
if result.statevector:
print(f"Statevector length: {len(result.statevector)}")
else:
# Results fit inline - no extra download needed
print(job.result_counts)
Density Matrix and MPS Simulation¶
Density matrix is useful for open-system or noisy simulation and supports all gates.
job = client.run(
qasm,
simulation_type="density_matrix",
shots=1024,
asynchronous=False,
)
result = job.result()
print(result.counts)
# Density matrix may be available in result.density_matrix
MPS (Matrix Product State) is efficient for low-entanglement circuits with many qubits.
job = client.run(
large_qasm,
simulation_type="mps",
shots=2048,
backend="qiskit",
asynchronous=False,
)
print(job.result().counts)
CHP (stabilizer/Clifford) can be exponentially faster for Clifford-only circuits.
job = client.run(
clifford_qasm,
simulation_type="chp",
shots=4096,
asynchronous=False,
)
print(job.result().counts)
Error Handling¶
All SDK exceptions inherit from FastQSimError.
from fastqsim import (
FastQSimConnectionError,
AuthenticationError,
QuotaExceededError,
ValidationError,
JobFailedError,
JobTimeoutError,
JobNotFoundError,
JobCouldNotCancelError,
)
try:
client = fastqsim.init()
except FastQSimConnectionError as exc:
print(f"Cannot reach API: {exc}")
raise SystemExit(1)
except AuthenticationError as exc:
print(f"Auth failed - check FASTQUBIT_SESSION_TOKEN (fqp_...): {exc}")
raise SystemExit(1)
try:
job = client.run(
invalid_qasm, # Bad QASM
shots=-5, # Invalid shots
asynchronous=False,
)
except ValidationError as exc:
print(f"Bad request: {exc}")
try:
job = client.run(good_qasm, shots=1024, asynchronous=False)
result = job.result()
except QuotaExceededError:
print("Monthly quota exhausted - upgrade your plan")
except JobFailedError as exc:
print(f"Simulation failed: {exc.reason}")
except JobTimeoutError as exc:
print(f"Job {exc.job_id} timed out after {exc.timeout}s")
except JobNotFoundError as exc:
print(f"Job not found: {exc}")
Job Metadata and Timing¶
Every Job object carries lifecycle timestamps and resource-usage metrics.
job = client.run(
qasm,
shots=1024,
asynchronous=False,
job_name="bell-state-test",
tags={"experiment": "v3", "team": "qcomp"},
metadata={"circuit_version": "2025-03-31"},
)
# Lifecycle timestamps (all datetime, UTC)
print(job.queue_enqueued_at)
print(job.execution_started_at)
print(job.execution_finished_at)
print(job.completed_at)
# Performance metrics
print(f"Wall time: {job.execution_time_seconds:.3f}s")
print(f"CPU time: {job.cpu_seconds_total:.3f}s")
print(f"Peak RAM: {job.peak_memory_mb:.1f} MB")
# Billing telemetry
print(f"CPU millicore-s: {job.billing_cpu_millicore_seconds:.2f}")
print(f"Memory GB-s: {job.billing_memory_gb_seconds:.4f}")
# Tags and metadata round-trip
print(job.tags) # {"experiment": "v3", "team": "qcomp"}
print(job.metadata) # {"circuit_version": "2025-03-31"}
# Refresh from server (for async jobs)
job.refresh()
print(job.status.value)