A Quantum Computing SDK
Documentation: https://blueqat.github.io/blueqatSDK/
Blueqat's simulator is built on PyTorch, with two selectable execution modes:
a dense statevector simulator and a memory-scalable tensornet
(tensor-network contraction) simulator, which is the default. Both are
differentiable, so circuits with torch.Tensor parameters keep their
gradients through Circuit.run().
https://github.com/Blueqat/Blueqat-tutorials
Runnable scripts in examples/:
bell_state.py-- circuit basics: statevector, single amplitude, shot samplingteleportation.py-- quantum teleportation, coherent and measured versionsgrover_search.py-- Grover's search over 8 items with oracle + diffusionqft.py-- Quantum Fourier Transform vs. the DFT matrix, period readoutvqe_ground_state.py-- VQE with a customAnsatzBase(not tied to QAOA)maxcut_qaoa.py-- QAOA for the graph Max-Cut problemnumpartition_qaoa.py-- QAOA for number partitioningexchange_only.py-- exchange-only spin qubits: logical circuits from pure exchange pulsesshor_15.py-- Shor's order finding for N=15 structured with nested named blocks
git clone https://github.com/blueqat/blueqatSDK
cd blueqatSDK
pip install -e .
from blueqat import Circuit import math #number of qubit is not specified c = Circuit() #if you want to specified the number of qubit c = Circuit(50) #50qubits
# write as chain Circuit().h[0].x[0].z[0] # write in separately c = Circuit().h[0] c.x[0].z[0]
Circuit().z[1:3] # Zgate on 1,2 Circuit().x[:3] # Xgate on (0, 1, 2) Circuit().h[:] # Hgate on all qubits Circuit().x[1, 2] # 1qubit gate with comma
Circuit().rz(math.pi / 4)[0]
from blueqat import Circuit Circuit(20).h[:].run() # returns a torch.Tensor statevector # Select the execution mode explicitly (tensornet is the default) Circuit(20).h[:].run(mode="statevector") Circuit(20).h[:].run(mode="tensornet")
Circuit(100).x[:].run(shots=1) # => Counter({'1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111': 1})
The dense statevector has 2**n_qubits entries, so for large n_qubits in
tensornet mode (the default), run() requires either shots= or
returns="amplitude" instead of materializing the full vector:
Circuit(50).h[:].run(shots=3) Circuit(50).h[:].run(returns="amplitude", amplitude="0" * 50)
# seed= fixes every random draw of a run (shot sampling, mid-circuit collapse, # large-n perfect sampling). It drives a private torch.Generator, so it does not # disturb the global RNG your program uses. c = Circuit(4).h[:] c.run(shots=200, seed=42) == c.run(shots=200, seed=42) # True # Counts keys are q_{n-1}...q_0 by default (qubit 0 rightmost). Cloud APIs use # the opposite order; bit_order= converts, always zero-padded to n_qubits. Circuit(3).x[0].run(shots=4) # => Counter({'001': 4}) Circuit(3).x[0].run(shots=4, bit_order='q0_first') # => Counter({'100': 4})
Circuit(4).h[:].run(amplitude="0101")
# reset[i] forces qubit i back to |0>. Any circuit containing reset is run # shot-by-shot with a real probabilistic collapse at each measure/reset. Circuit(2).h[0].cx[0, 1].reset[0].m[:].run(shots=100) # Measurement keys let you tag a measurement and read it back per-shot. Circuit().x[0].m(key="a")[0].run(shots=10, returns="samples") # => [{'a': [1]}, {'a': [1]}, ...]
c = Circuit(4).h[0].h[1].h[2].h[3] with c.ancilla() as a: # allocate a fresh qubit past the current width c.cx[0, a[0]] c.cx[0, a[0]] with c.ancilla(pos=6, stop=8, reset=True) as a: # or pin an explicit range c.cx[3, a[0]] # a[i] is reset back to |0> on exiting the `with` block when reset=True (the default)
from blueqat.utils import Z hamiltonian = 1*Z[0]+1*Z[1] Circuit(4).x[:].run(hamiltonian=hamiltonian) # => -2.0 # Or the equivalent convenience method (differentiable): Circuit(4).x[:].expect(hamiltonian)
from blueqat.utils import X, Y, Z # exp(-i * theta * P) for a Pauli product P, given as {qubit: letter} so there # is no bit-order ambiguity. P**2 == I, so this is exactly cos(t) - i sin(t) P. Circuit().exp_pauli({0: 'X', 1: 'X', 2: 'Z', 3: 'Y'}, 0.3) # exp(-0.3i XXZY) Circuit().exp_pauli({5: 'Z'}, 0.3) # == rz(0.6)[5] # Expectation of any Pauli expression, computed term-by-term over the # statevector (O(terms * 2**n)) instead of building a 2**n x 2**n matrix. Circuit(18).h[:].expect(1.0 * Z[0] * Z[1] - 0.5 * X[2])
c = Circuit(7) with c.block("order-finding"): with c.block("superposition"): c.h[4, 5, 6] with c.block("c-U^1"): c.cswap[4, 2, 3].cswap[4, 1, 2].cswap[4, 0, 1] c.append_block("IQFT", qft_circuit(3).dagger(), offset=4) print(c.tree()) # shows the nested structure (see examples/shor_15.py) c.run() # backends see the plain gates -- execution is unchanged c.dagger() # inverts blocks as blocks ("order-finding†") c.run(backend="draw") # blocks drawn as labeled boxes c.run(backend="draw", expand_blocks=True) # ...or expanded into their gates
Circuit(2).h[0].cx[0, 1].probs() # measurement probabilities (differentiable) Circuit(2).h[0].cx[0, 1].probs([1]) # marginal on selected qubits Circuit(2).h[0].cx[0, 1].depth() # => 2 Circuit(2).h[0].cx[0, 1].count_ops() # => Counter({'h': 1, 'cx': 1})
from blueqat.noise import depolarizing, amplitude_damping, NoiseModel # noise= switches the run onto a density-matrix simulation and applies the # channel after every gate. depolarizing(p) is the Nielsen & Chuang form, # (1-p)rho + p I/2**k, acting jointly on a two-qubit gate's qubits; # depolarizing(p, per_qubit=True) instead applies the one-qubit channel to each # of them (what papers assuming purely local noise mean -- a different map). Circuit(2).h[0].cx[0, 1].run(noise=depolarizing(0.01)) # rho Circuit(2).h[0].cx[0, 1].run(noise=depolarizing(0.01), shots=1000) # counts Circuit(2).h[0].cx[0, 1].run(noise=depolarizing(0.01), hamiltonian=h) # Tr(rho H) # Different rates per gate (real devices have worse two-qubit gates) nm = NoiseModel() nm.add(depolarizing(0.001)) nm.add(depolarizing(0.01), gates=['cx']) # noise_scale= is the zero-noise-extrapolation knob: same circuit, more noise values = [c.run(noise=nm, noise_scale=s, hamiltonian=h) for s in (1, 2, 3)] # Silicon spin qubits are dephased by noise that is *constant within a shot* # (nuclear fields, 1/f charge noise) -- not a Kraus channel, and the difference # shows: a Hahn echo refocuses this and does nothing to phase_damping. from blueqat.noise import QuasiStatic c.run(quasi_static=QuasiStatic(sigma=0.4), samples=4000, seed=1)
Density matrices have 4**n entries, so this is for small circuits: about
0.4 ms/gate at 8 qubits, 9 ms at 10, 183 ms at 12.
from blueqat.qec import repetition_code, memory_experiment, PhenomenologicalNoise code = repetition_code(5) # or rotated_surface_code(3) code.check() # generators really commute, logicals really pair code.logical_weight() # brute-forced code distance, for small codes result = memory_experiment(code, rounds=5, shots=2000, seed=1, noise=PhenomenologicalNoise(p_data=0.02, p_measure=0.02)) result.logical_error_rate # Circuit-level noise puts faults after every gate, measurement and reset, so # a single fault can ride an ancilla's later gates onto several data qubits -- # a hook error. Which faults do depends on the interaction order, which is # therefore an argument: on the d=3 surface code the order alone moves the # logical error rate by nearly a factor of two. from blueqat.qec import CircuitLevelNoise memory_experiment(code, rounds=3, shots=6000, seed=4, noise=CircuitLevelNoise(p1=0.001, p2=0.01, p_measure=0.01), order=my_schedule)
Codes, syndrome circuits, decoders and experiments are separate pieces, so a decoder can be swapped or checked against a reference. The detector graph a matching decoder needs is built by injecting each error location and observing, not by hand-writing each code's geometry.
from blueqat.clifford import Clifford, random_clifford c = Clifford.from_circuit(Circuit(2).h[0].cx[0, 1]) # a stabilizer tableau c.then(other) # compose (c first), exactly, with no 2**n matrix c.inverse() # ...and invert c.to_circuit() # back to gates random_clifford(2, seed=0) # uniform over the 11520 two-qubit Cliffords # Randomized benchmarking: a sequence plus the ONE Clifford that undoes it total = Clifford.identity(n) for i in range(m): g = random_clifford(n, seed=i) circuit += g.to_circuit() total = total.then(g) circuit += total.inverse().to_circuit() # survival is exactly 1 without noise
from blueqat.optimize import optimize optimize(Circuit(2).h[0].x[1].h[0]) # => Circuit(2).x[1] optimize(Circuit(2).rz(0.3)[0].x[1].rz(0.4)[0]) # => rz(0.7)[0] . x[1] # Every rewrite preserves the unitary exactly, global phase included: rz(2*pi) # is -I and is kept; rz(4*pi) is I and is dropped. Trainable (requires_grad) # angles are merged but never dropped, even at zero. # For exchange-only hardware the cost is the pulse count, and optimizing the # logical circuit first removes whole pulse sequences before they are emitted: import blueqat.eo logical = Circuit(2).x[0].x[0].cx[0, 1].cx[0, 1].h[1] len(logical.run(backend='eo').ops) # 65 pulses len(optimize(logical).run(backend='eo').ops) # 3
import blueqat.eo # registers the 'eo' backend from blueqat.eo import encoding, synthesize_1q # The native hardware primitive: a Heisenberg exchange pulse Circuit(2).exch(math.pi)[0, 1] # theta = pi is an exact SWAP # Transpile a logical circuit into pure exchange pulses # (3 spins per logical qubit; H = 3 pulses, Fong-Wandzura CNOT = 28 pulses) physical = Circuit(2).h[0].cx[0, 1].run(backend='eo') # Run the pulses on the encoded state and inspect the logical result init = encoding.encode_state([(1, 0), (1, 0)]) # |00>_L final = physical.run(initial=init) encoding.leakage(final, 0) # leakage out of the code space # Differentiable pulse synthesis: any SU(2) in 4 constant-amplitude pulses seq = synthesize_1q(target_2x2_unitary, n_pulses=4) # Re-calibrate a drifted 2-qubit sequence back to an exact gate from blueqat.eo import synthesize_2q, quantize_sequence, to_schedule refined = synthesize_2q(cx_4x4, pairs=pulse_pairs, initial_thetas=drifted) # Discrete pulse durations (hardware clock ticks) and time-resolved schedules seq_q = quantize_sequence(seq, step=2 * math.pi / 4096) schedule = to_schedule(physical) # ASAP-parallel, JSON-ready pulse schedule
pip install blueqat[mcp]
Register the blueqat-mcp command with an MCP client -- e.g. Claude Desktop's
config:
{ "mcpServers": { "blueqat": { "command": "blueqat-mcp" } } }Tools: run_circuit (OpenQASM in, statevector/counts out), circuit_stats,
expectation_value (Pauli-expression Hamiltonians like "1.5*Z[0]*Z[1] - 0.5*X[0]"),
draw_circuit (diagram image), eo_transpile (exchange-only pulse compilation),
blueqat_info. All inputs are parsed without eval -- safe for untrusted tool calls.
import blueqat.cloud as cloud # registers the 'cloud' backend cloud.save_api_key("YOUR_API_KEY") # get one at https://mcp.blueqat.app/login # or: export BLUEQAT_API_KEY=... c = Circuit(2).h[0].cx[0, 1] c.m[:].run(backend='cloud', shots=100) # counts (same conventions as local) c.run(backend='cloud') # statevector c.run(backend='cloud', hamiltonian=1.0 * Z[0]) # expectation value cloud.hardware_status() # real-QPU status (public) cloud.submit_hardware_job(c, shots=100, confirm=True) # real hardware, real cost
Circuit().h[0].to_qasm() #OPENQASM 2.0; #include "qelib1.inc"; #qreg q[1]; #creg c[1]; #h q[0]; from blueqat.circuit_funcs import from_qasm from_qasm(Circuit().h[0].to_qasm()) # parses back into an equivalent Circuit
from blueqat.utils import X, Y, Z, I h1 = 1.23 * Z[0] + 4.56 * X[1] * Z[2] h2 = 2.46 * Y[0] + 5.55 * Z[1] * X[2] * X[1] hamiltonian = h1 * h1 + h2 * h2 print(hamiltonian)
hamiltonian = hamiltonian.simplify() print(hamiltonian)
from blueqat.utils import qubo_bit as q hamiltonian = -3*q(0)-3*q(1)-3*q(2)-3*q(3)-3*q(4)+2*q(0)*q(1)+2*q(0)*q(2)+2*q(0)*q(3)+2*q(0)*q(4) print(hamiltonian)
import numpy as np from blueqat import Circuit from blueqat.utils import Z, X hamiltonian = [1.0*Z[0], 1.0*X[0]] a = [term.get_time_evolution() for term in hamiltonian] time_evolution = Circuit().h[0] for evo in a: evo(time_evolution, np.random.rand()) print(time_evolution)
import torch from blueqat import Circuit from blueqat.utils import Z, AnsatzBase, Vqe class MyAnsatz(AnsatzBase): def get_circuit(self, params: torch.Tensor) -> Circuit: return Circuit(1).rx(params[0])[0] hamiltonian = 1.0 * Z[0] vqe = Vqe(MyAnsatz(hamiltonian, n_params=1)) result = vqe.run(initial_params=torch.tensor([0.1])) # initial_params is optional print(result.params, result.circuit.run()) print(vqe.sampler_call_count) # 0 unless a sampler was supplied to Vqe(...) # Without initial_params the run starts from random parameters, so repeated runs # reach different local optima. seed= makes the whole run deterministic (initial # parameters and, for a seedable sampler, its draws), and loss_history records # the objective at every iteration for convergence checks. result = Vqe(MyAnsatz(hamiltonian, n_params=1), seed=42).run() print(len(result.loss_history), result.loss_history[-1])
from blueqat.utils import get_measurement_sampler # Sampling throws away the autograd graph, so a shot-based objective has no # gradient to backpropagate. Vqe notices and switches to the parameter-shift # rule, which is exact (not a finite difference) and chains correctly through # parameters that drive many gates, as QAOA's angles do. vqe = Vqe(ansatz, sampler=get_measurement_sampler(2000, seed=3), seed=42) result = vqe.run() # gradient='backprop' / 'parameter_shift' overrides the automatic choice.
from blueqat.utils import qubo_bit as q, QaoaAnsatz, Vqe hamiltonian = q(0)-q(1) step = 1 vqe = Vqe(QaoaAnsatz(hamiltonian, step)) result = vqe.run() result.circuit.run(shots=100) # => Counter({'10': 100})
Circuit().h[0].cx[0, 1].m[:].run(backend="draw") # circuit diagram Circuit().h[0].cx[0, 1].run(backend="draw_tn") # tensor-network graph
https://blueqat.github.io/blueqatSDK/ (日本語版: https://blueqat.github.io/blueqatSDK/ja/index.html)
Copyright 2026 The blueqat Developers.