QUBASIC
Quantum computing in a BASIC interpreter.
] 10 H 0
] 20 CX 0, 1
] 30 MEASURE
] RUN
RAN 3 lines, 2 qubits, 1024 shots in 0.02s [depth=3, gates=4, method=stabilizer, CNOT=1]
bit order q1 q0 (qubit 0 = rightmost)
State Count % Distribution
|11⟩ 521 50.9% ███████████████████████████████████
|00⟩ 503 49.1% █████████████████████████████████
QUBASIC is a BASIC interpreter whose programs build and run quantum circuits.
The language is QBASIC's: typed variables, records, arrays with bounds,
SUB and FUNCTION, labels, SELECT CASE, PRINT USING, sequential, random and
binary files, graphics on QBASIC's SCREEN modes, PLAY and SOUND, timer, key
and music event traps and error trapping, so QBASIC programs run unchanged,
with the graphics screen drawn in the terminal and sound played through the
speaker. Gates are statements
(H 0, CX 0, 1), measurements feed back into the program, and RUN simulates
the result on Qiskit Aer or on QUBASIC's own engines. Around that sit noise
models and error mitigation, quantum algorithms, error-correcting codes with
circuit-level noise, and analysis, benchmarking and visualization commands.
It runs as an interactive REPL, as a script interpreter, as a Python library,
as a Jupyter kernel and as a browser REPL.
Documentation: tutorial (from a first circuit to error
correction, every example tested), reference (every
command, statement, function and gate, generated from the code), and
Development below for how the interpreter is built. At the
prompt, HELP lists the commands and HELP <name> describes one.
Install
pip install qubasic
Requires Python 3.10 or newer; Qiskit, Qiskit Aer, NumPy, SciPy and rustworkx
come with it. Optional extras: qubasic[qec] (stim and pymatching, for the
STIM and MWPM paths of the error-correction commands), qubasic[jupyter]
(the notebook kernel), qubasic[qasm3] (OpenQASM 3 import) and
qubasic[charts] (sweep charts) and qubasic[ibm] (Qiskit Runtime, for IBM
quantum hardware). For development, pip install -e ".[charts]".
Running it
qubasic interactive REPL
qubasic program.qb run a QUBASIC script (numbered lines plus commands)
qubasic program.bas run QBASIC source; unnumbered lines are numbered as loaded
qubasic --quiet program.qb results only, no banner
qubasic --json program.qb the result as JSON (counts, variables, state, method)
qubasic --seed 7 program.qb reproducible sampling
qubasic --spec a JSON description of every command, statement and function
qubasic --web [port] browser REPL on localhost, token-gated
qubasic --install-kernel install the Jupyter kernelspec (then pick "QUBASIC")
python -m qubasic_core the same without the console script
From Python, run executes a program and returns its result, and a Session
keeps one interpreter across calls:
from qubasic_core import Session, run
result = run('''
10 H 0
20 CX 0, 1
30 MEASURE
''', qubits=2, shots=1000, seed=7)
result['counts'] # {'00': 503, '11': 497}
result['output'] # what the program printed
result['errors'] # the ?... lines, if any
s = Session(qubits=1, seed=3)
s.execute('10 RY theta, 0')
s.execute('20 SAVE_EXPECT Z 0 -> z')
s.execute('theta = 0.5')
s.run()['variables']['z'] # cos(0.5)
run and Session take qubits, shots, seed, method, precision,
noise (a NOISE specification) and inputs (answers for INPUT). Source with
no numbered line is read as QBASIC source.
The language
Numbered lines form the program; anything else runs at once (gates typed at
the prompt act on a fresh |0…0⟩ and show the state they make). LIST, EDIT,
DELETE, RENUM (which rewrites every line reference), UNDO, SAVE and
LOAD manage it, and LOAD reads .bas files as QBASIC source.
10 TYPE Planet
20 name AS STRING * 8
30 moons AS INTEGER
40 END TYPE
50 DIM p(1 TO 3) AS Planet
60 FOR i = 1 TO 3: READ p(i).name, p(i).moons: NEXT i
70 FOR i = 1 TO 3
80 PRINT USING "\ \ ##"; p(i).name; p(i).moons
90 NEXT i
100 DATA Earth, 1, Mars, 2, Jupiter, 95
The dialect follows QBASIC: % & ! # $ suffixes, DEFINT-style letter
ranges and DIM ... AS, with OVERFLOW and TYPE MISMATCH errors on assignment;
records (nested up to 16 deep), fixed-length strings, arrays with lower bounds and
LBOUND/UBOUND; SUB and FUNCTION with local variables, STATIC,
SHARED, by-reference arguments and typed or whole-array parameters; labels as
jump targets; block IF/ELSEIF, SELECT CASE, DO/LOOP,
WHILE/WEND, FOR with QBASIC's zero-trip rule; ON ERROR GOTO and
RESUME with QBASIC's error codes; INPUT and LINE INPUT, PRINT USING
with the full field set, and the string, math and conversion functions. An
unassigned variable reads as 0 inside a program. Expressions are parsed with
QBASIC precedence and evaluated without Python's eval.
Files: OPEN for INPUT, OUTPUT, APPEND, RANDOM and BINARY, with PRINT #,
WRITE #, INPUT #, LINE INPUT #, FIELD, LSET/RSET, GET #/PUT #
of records, SEEK, LOF, LOC, EOF, INPUT$, MKI$/CVI and their
relatives, and KILL, NAME, MKDIR, RMDIR, CHDIR and FILES.
Graphics: SCREEN 1, 2 and 7 to 13 open QBASIC's graphics modes, with PSET,
LINE (boxes, styles), CIRCLE (arcs, ellipses), PAINT, DRAW, VIEW,
WINDOW, PALETTE, GET/PUT of images, POINT and PMAP, and PRINT,
LOCATE, COLOR, CLS and INPUT put text on the screen in QBASIC's
character cells (40 or 80 columns, 25 or 30 rows, scrolling at the bottom).
The picture is drawn in the terminal in colour cells, or as shading off a
terminal. At a
terminal it is redrawn in place while the program runs, from the first
SLEEP, INKEY$, GET or INPUT or after half a second, so animations and
games show as they run; otherwise it appears when the program ends. SHOW
redraws it and SAVEPNG file.png SCREEN saves it. At an interactive terminal a
program runs until it ends or Ctrl+C stops it; off a terminal and in agent mode
a run stops after 100,000 statements (POKE $D006 changes the limit), and a
circuit build stops at a million instructions everywhere.
Sound: PLAY takes QBASIC's music macro language and SOUND a frequency and
duration. At an interactive terminal they play through the speaker, foreground
music holding the program until it ends and MB music queueing in the
background, as in QBASIC; AUDIO OFF keeps them silent, and SAVEWAV file.wav writes everything played. Events: ON TIMER(n), ON KEY(n) (F1 to
F12, the arrow keys and keys a program defines with KEY 15-25), ON PLAY(n)
(the background music queue running low) and ON MEASURE call their handlers
with GOSUB, each armed, disarmed or held by TIMER, KEY(n) or PLAY with
ON, OFF or STOP; INKEY$ reads a key without a character as CHR$(0)
and its scan code.
The PC underneath: after DEF SEG = &HA000, PEEK and POKE reach SCREEN
13's pixels, and after DEF SEG = &HB800 the text screen's characters and
colours; BSAVE and BLOAD save and load those bytes; OUT and INP drive
the VGA palette ports (&H3C7 to &H3C9) and read the vertical retrace at
&H3DA, which WAIT &H3DA, 8 paces animation by at 70 Hz. RUN and CLEAR
inside a program, CSRLIN, SCREEN(row, col), VIEW PRINT, FILEATTR,
LPOS, ENVIRON$, SHELL (off in agent mode) and the Microsoft Binary Format
conversions behave as in QBASIC, while CALL ABSOLUTE and VARPTR report that
there is no machine code or memory address to reach. Without DEF SEG,
PEEK and POKE address QUBASIC's own memory map (below).
Quantum programs
| Gates | |
|---|---|
| one qubit | H X Y Z S T SDG TDG SX SXDG ID |
| one qubit, angles | RX RY RZ P (one angle), U (three) |
| two qubits | CX CZ CY CH SWAP DCX ISWAP ECR CS CSDG CSX, CRX CRY CRZ CP RXX RYY RZZ RZX (one angle) |
| three qubits | CCX CSWAP CCZ |
Angles come first: RX PI/4, 0, CRZ theta, 0, 1. CTRL and INV modify any
gate, UNITARY NAME = [[...]] defines one from a matrix, DEF NAME = gates
defines a macro, and QFT, IQFT, DIFFUSE, MCX, MCZ, MCP, QADD,
QPE, GRAPHSTATE, FEATUREMAP and EVOLVE (Trotterized time evolution)
are statements that expand into gates. Bitstrings put qubit 0 on the right, as
Qiskit does; OPTION ENDIAN BIG reverses every displayed bitstring.
MEASURE measures every qubit (or a list) into the histogram. MEAS q -> bit
measures in the middle of the program: an IF on the bit that applies gates
compiles to a dynamic circuit, and a program that uses a measured bit any
other way (prints it, computes with it, loops on it) runs live, one shot at a
time, with every statement seeing the real outcome:
10 H 0
20 MEAS 0 -> m
30 IF m = 0 THEN RESET 0: GOTO 10 ' repeat until the qubit reads 1
40 MEASURE
SAVE_EXPECT, SAVE_PROBS and SAVE_AMPS record values into variables when
RUN finishes; STATE, PROBS, BLOCH, DENSITY, CIRCUIT, EXPECT,
ENTROPY, FIDELITY and HEATMAP inspect the last state; STEP runs a
program a line at a time and REWIND/FORWARD move through the states it
passed.
Simulation
METHOD automatic (the default) runs noiseless Clifford circuits (ECR, SXDG
and RZ at multiples of π/2 included) on the stabilizer simulator at any
width, other circuits on a statevector while one fits in the memory available
(at double precision, then single), and wider ones as matrix product states;
the summary line names the method used.
METHOD also selects statevector, density_matrix, stabilizer,
matrix_product_state, extended_stabilizer, unitary or superop, and
PRECISION SINGLE halves statevector memory. Each run is sized against the
memory free at that moment, and one that cannot fit is refused with what it
needs and what to change; RAM reports what fits. QUBASIC_MEMORY_LIMIT=4GB
caps what QUBASIC treats as available. COUPLING, BASIS and DEVICE
constrain transpilation to a hardware topology and native gates.
A live run holds a statevector per shot while one fits. Past that, or under
METHOD stabilizer, each shot holds a stabilizer tableau (Aaronson and
Gottesman's, bit-packed), so a Clifford program that loops and branches on
its measurements runs at hundreds or thousands of qubits: 500 qubits take
about 50 ms a shot. Pauli noise channels, NOISE depolarizing and
phase_flip among them, run on the tableau as sampled Paulis; SAVE_EXPECT
and SAVE_PROBS read it exactly; STATE lists the stabilizer generators of
the first shot and BLOCH reads one qubit from them. A non-Clifford gate,
a non-Pauli channel or SAVE_AMPS stops such a run with the reason it holds
a tableau.
Noise and error mitigation
NOISE depolarizing 0.001 0.01; readout 0.02 0.03 channels joined with ;
NOISE thermal 50e-6 70e-6 50e-9 T1, T2 and gate time
NOISE ADD amplitude_damping 0.01 extend the active model
NOISE lindblad X 0.001 ZZ 0.0005 Pauli-Lindblad generators and rates
NOISE DEFECT 0 80 25 0.5 a switching two-level defect on qubit 0
MITIGATE READOUT ON invert readout error on every RUN
ZNE 1.0 ZZ 0 1 SCALES 1 3 5 FIT EXP zero-noise extrapolation
MITIGATE READOUT calibrates each qubit's flip probabilities and shows every
following histogram with them inverted. ZNE measures an observable with the
gate noise amplified by folding and extrapolates to zero noise with a linear,
quadratic, Richardson or exponential fit, printing the noiseless value beside
it.
NOISE DEFECT q kHz tau [p] attaches a two-level defect to a qubit: while
the qubit idles in a DELAY its frequency is kHz higher whenever the
defect is excited, the defect switching with time constant tau
microseconds and excited a fraction p of the time. The defect keeps its
state from one DELAY to the next, so a Ramsey experiment beats and an echo
undoes what the defect did unless it switched in between, which no Markov
channel reproduces. Aer carries each defect as one extra qubit, applying the
exact joint channel of the qubit and its defect over each DELAY; the defect
follows its qubit through routing and composes with a device model.
IBM quantum hardware
pip install "qubasic[ibm]"
QPU LIST devices, queues and status
DEVICE IBM ibm_fez simulate that device as calibrated now
QPU ibm_fez SHOTS 4000 run the program on the device
QPU ibm_fez SWEEP T 0 40 21 the program at 21 values of T, in one job
QPU JOB <id> collect a job after an interrupted wait
QPU and DEVICE IBM use the account that qiskit-ibm-runtime saved with
QiskitRuntimeService.save_account. DEVICE IBM builds Aer's noise model
from the device's current calibration (gate and readout errors, and T1 and T2
relaxation during gates and idle time) and routes every run onto its coupling
map and native gates. QPU compiles the program the same way, runs it through
the Sampler primitive and shows the histogram with the QPU time it spent.
QPU ... SWEEP builds the program at each value of a variable, as SWEEP
does on the simulator, and sends every point in one job (up to 300), since
each job carries a fixed overhead beyond its shots. A program that reads
measured bits as numbers runs shot by shot and stays on the simulator. DELAY q, microseconds leaves qubits idle: an ideal simulation
ignores it, while a device model or a QPU run decoheres them.
Algorithms
GROVER, SIMON, DJ (Deutsch-Jozsa), BV (Bernstein-Vazirani), QPE
(energy of a Hamiltonian by phase estimation), QCOUNT (quantum counting),
IQPE, AMPEST (maximum-likelihood amplitude estimation), SHOR
(Beauregard's circuit for N < 256), HHL (any size), QAOA (MaxCut graphs or
a diagonal Hamiltonian), VQE (with EXCITED k for excited states),
QWALK and QKERNEL. MINIMIZE and GRADIENT optimize a program's own
parameters (Nelder-Mead, SPSA or parameter-shift gradient descent), compiling
the circuit once with bound parameters when the program allows.
HAMILTONIAN H = MOLECULE LIH FREEZE builds a molecule's qubit Hamiltonian
with no chemistry package: STO-3G integrals over s and p Gaussians
(McMurchie-Davidson), restricted Hartree-Fock, a frozen core or ACTIVE
space, and the Jordan-Wigner transform, for H2, HeH+, LiH, BeH2, H2O, NH3,
N2 and H4 by name or any geometry of atoms from H to Ne, reporting the
Hartree-Fock and exact energies. VQE H ANSATZ UCCSD then runs unitary
coupled cluster from the Hartree-Fock state and PROGRAM writes its circuit.
ISING, HEISENBERG, HUBBARD and RYDBERG build model Hamiltonians;
LINDBLAD evolves open systems. Benchmarks: RB, RBINT, XEB, QVOLUME, MIRROR, GST,
TOMOGRAPHY and PTOMOGRAPHY.
Error correction
QEC SURFACE 5 a code and its stabilizers
LOGICAL_ERROR_RATE SURFACE 7 0.05 MATCH code capacity, matching decoder
LOGICAL_ERROR_RATE SURFACE 5 0.002 CIRCUIT circuit-level noise
LOGICAL_ERROR_RATE BB 0.01 bivariate-bicycle code, BP+OSD
THRESHOLD SURFACE 0.002 0.012 5 CIRCUIT threshold from crossing curves
LQUBITS 2 CODE STEANE PHYS 0.001 run the program encoded
LATTICE CNOT 0 1 lattice-surgery CNOT
Codes: repetition, Steane, Shor, rotated surface and bivariate-bicycle qLDPC.
Decoders: minimum-weight lookup, exact minimum-weight matching, union-find
(Delfosse and Nickerson, weighted on space-time graphs), pymatching and
BP+OSD. PHENOM adds measurement errors over d rounds, and CIRCUIT makes
every gate, measurement and reset of the syndrome circuit noisy, building the
detector error model itself (CIRCUIT STIM uses stim instead). LQUBITS
runs a program on logical qubits, simulated encoded or on a modeled logical
channel, and DISTILL and RESOURCES estimate magic-state distillation and
fault-tolerant costs.
More models
LOCC 4 4 splits the machine into registers that share only classical bits
(SEND) or Bell pairs (SHARE, in LOCC JOINT). QUDIT simulates d-level
systems and BOSONIC Fock-space modes with displacement, squeezing, cat
states and beamsplitters. A memory map (PEEK, POKE, SYS, MAP,
MONITOR) exposes qubit state, simulator settings and per-qubit noise as
addresses.
Development
python -m pytest tests/ the test suite
ruff check qubasic_core tests tools lint
mypy qubasic_core types (strict)
python tools/generate.py regenerate host.py's stubs and docs/reference.md
The interpreter is one object, QBasicTerminal (terminal.py), assembled
from a state container and 60 mixins, one per concern, in six layers. Its state
is the program, its variables and the run settings, plus one state object per
concern, each a dataclass beside the mixin that owns it: files, screen
(the text screen), graphics, sound, events (event traps), errors
(ON ERROR), debug, data (DATA and READ), procs (SUB and FUNCTION),
symbols (declarations and types), editor, noise, aer (Aer's tuning
options), machine (the memory map and profiler), qudit and bosons. A module imports only from its own
layer or the layers below it, and the same holds for calls through self:
each layer has a host class in host.py declaring what its mixins may use, the
attributes and methods of its own layer and the ones below, and each mixin
derives from its layer's host, so mypy rejects a call into a higher layer. The
few places where a lower layer must reach up, such as LOAD entering lines as if
typed or PEEK reading the memory map, are hooks: a method declared with a
docstring at the lowest layer that calls it and implemented only above it. RUN
is a presentation command: the simulation layer opens and runs the program and
returns a RunReport saying what is left to show (the histogram, a note,
DISPLAY's view, the logical-qubit trailer), and the RUN command shows it.
tools/generate.py writes the method stubs and places each attribute at the
lowest layer that uses it.
| Layer | Modules |
|---|---|
| core | host (the layer table and hosts), engine (limits, result codes, errors, the I/O protocol, output and path services) |
| language | parser (statement parser, compiler, catalog), expression (expressions, variables, formatting), interpreter (control flow, procedures, statements), devices (graphics, sound, screen, files), editing |
| simulation | simtools (capacity, the numpy statevector and stabilizer engines, backends), circuits (circuit build, quantum statements), simulator (results and how they print, RUN, live runs), noise, locc, physics (dynamics, chemistry, qudits, bosonic modes), machine (settings, memory map, profiler, demos) |
| applications | algorithms, benchmarking (benchmarks, resources, IBM hardware), qec (codes, decoders, circuit-level noise), logical (logical qubits, lattice surgery) |
| presentation | display, analysis |
| composition | terminal, api, cli (with the browser REPL), jupyter_kernel |
process(line) stores a numbered line, parsing it once into a typed
statement, and passes anything else to dispatch, which calls the matching
cmd_* method or runs the line as an immediate statement. RUN compiles the
program, resolving labels and matching blocks, and reports a missing target or
an unmatched block before anything runs. It then executes the program as a
Qiskit circuit on the Aer method the memory allows, live and shot by shot on
the numpy engine when the program reads a measured bit, as LOCC registers, or
encoded on logical qubits.
A command is a method cmd_<name>(self, rest) on the mixin of its concern
whose docstring opens with its syntax, registered in _CMD_WITH_ARG or
_CMD_NO_ARG (terminal.py) and placed under a topic in COMMAND_TOPICS
(parser.py), after which tools/generate.py declares it in its layer's host
and lists it in the reference. A statement is a handler in its layer's table
(_LANGUAGE_STATEMENTS in interpreter.py, _QUANTUM_STATEMENTS in
circuits.py, _CIRCUIT_STATEMENTS in algorithms.py), which the terminal
merges into _STMT_KEYWORDS, with its line in the catalog's STATEMENTS.
tests/test_project.py checks the layering of imports and of calls, the
hooks, the stubs, the docstrings, the catalog's coverage of every dispatched
keyword and the generated reference.
Continuous integration runs the suite on Python 3.10 to 3.13 across Linux,
Windows and macOS with coverage, the lint and type checks,
tools/generate.py --check, and a build of the wheel that installs it and
runs a program.
License
MIT
Metadata
Release files for qubasic 0.34.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qubasic-0.34.0.tar.gz | 518.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qubasic-0.34.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 919.9 kB
Release files / qubasic-0.34.0.tar.gz
| Download URL | qubasic-0.34.0.tar.gz |
|---|---|
| Size | 518.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
adbd95b79007e917db177bb144acc99a57ecb06cd56c07ec1c0cd6352d0e0f3b
|
|
BLAKE2b-256 checksum How to use checksums |
a3aa9182488e99f8bf4afd5f55cce9f5bf022411638004865c90a89f34bc66f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / qubasic-0.34.0-py3-none-any.whl
| Download URL | qubasic-0.34.0-py3-none-any.whl |
|---|---|
| Size | 401.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3b268ec4195617cf180202cc486dd1b22014b82d7f625eedf9bf839d024915dc
|
|
BLAKE2b-256 checksum How to use checksums |
ddfde37a3ffafbe1fbdf4d2e688648f6e0ee121f482667b06de1802330e5a0a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log