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 commaCircuit().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 gatesCircuit(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 noisefrom 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) # 3import 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 schedulepip 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 costCircuit().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 Circuitfrom 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 graphhttps://blueqat.github.io/blueqatSDK/ (日本語版: https://blueqat.github.io/blueqatSDK/ja/index.html)
Copyright 2026 The blueqat Developers.