qvivid provides circuit construction, exact statevector
simulation, terminal measurement sampling, tidy inspection, and
publication-oriented graphics in one R workflow. The simulation core
does not require Python. This vignette introduces the workflow with a
Bell state and documents the data structures that remain stable in
qvivid 0.1.x.
Gate functions take a circuit as their first argument, so they work naturally with R’s base pipe. Qubit indices start at one.
bell <- quantum_circuit(2, name = "Bell state") |>
gate_h(1) |>
gate_cx(control = 1, target = 2) |>
measure_all()
bell
#> <qv_circuit: Bell state>
#> 2 qubits | 2 classical bits | 3 operations | depth 3
#> 1 H q1
#> 2 CX q1,q2
#> 3 M q1,q2 -> c1,c2simulate_quantum() always computes the exact final
state. Set shots to also sample terminal measurements,
seed to make those samples reproducible, and
record = TRUE to retain the state after every
operation.
result <- simulate_quantum(
bell,
shots = 256,
seed = 2026,
record = TRUE
)
result
#> <qv_result>
#> 2 qubits | native backend | 0.000000 seconds
#> Exact state probabilities:
#> |00> 0.500000
#> |11> 0.500000
#> 256 shots | seed 2026
#> 00 141
#> 11 115
result$counts
#> basis count probability
#> 1 00 141 0.5507812
#> 2 11 115 0.4492188Measurement in qvivid 0.1.x is terminal: it samples the final state but does not perform mid-circuit collapse, reset, or conditional execution. If a circuit has no explicit measurement operation, shot sampling uses the default mapping from each available qubit to the matching classical bit.
Statevector memory grows exponentially with the number of qubits.
Recorded trajectories retain an additional state for every unitary
operation; terminal measurement frames reuse the preceding state. Before
allocating the state, qvivid estimates peak memory and compares it with
memory_limit_gib (2 GiB by default, configurable with the
qvivid.memory_limit_gib option). Raise that guard only
after confirming that the machine has enough memory; use
record = FALSE for large circuits.
The R API uses one-based qubits. In the statevector, qubit 1 is the
least-significant bit (LSB). Displayed basis strings are written from
the highest qubit down to qubit 1. Consequently, applying X to qubit 1
in a two-qubit register populates |01>, not
|10>.
lsb_result <- quantum_circuit(2, name = "LSB example") |>
gate_x(1) |>
simulate_quantum()
state_data(lsb_result, include_zero = FALSE)
#> index basis real imaginary magnitude probability phase
#> 1 1 01 1 0 1 1 0For two-qubit custom unitaries, the first qubit supplied to
gate_unitary() is the most-significant qubit in the
matrix’s local basis order
|00>, |01>, |10>, |11>.
Use state_data() instead of reconstructing labels or
phases from the raw statevector. Its stable 0.1.x columns are:
| Column | Meaning |
|---|---|
index |
Zero-based integer statevector index |
basis |
High-to-low binary basis label, ending in qubit 1 |
real, imaginary |
Cartesian components of the amplitude |
magnitude |
Absolute value of the amplitude |
probability |
Squared magnitude |
phase |
Complex phase in radians; NA for a numerically zero
amplitude |
state_data(result, include_zero = FALSE)
#> index basis real imaginary magnitude probability phase
#> 1 0 00 0.7071068 0 0.7071068 0.5 0
#> 2 3 11 0.7071068 0 0.7071068 0.5 0result$counts is always a data frame with
basis, count, and probability
columns. It has zero rows when shots = NULL. Count labels
are classical-bit strings ordered from the highest classical bit down to
bit 1; the circuit’s terminal measurement mapping determines which qubit
supplies each bit.
When record = TRUE, trajectory_data()
returns the state-data columns above plus step and
label. Step 0 is the initial state, and each subsequent
step corresponds to one circuit operation.
trajectory <- trajectory_data(result, include_zero = FALSE)
trajectory[, c("step", "label", "basis", "probability")]
#> step label basis probability
#> 1 0 Initial 00 1.0
#> 2 1 H 00 0.5
#> 3 1 H 01 0.5
#> 4 2 CX 00 0.5
#> 5 2 CX 11 0.5
#> 6 3 M 00 0.5
#> 7 3 M 11 0.5
trajectory_bloch(result, qubit = 1)
#> step label qubit n_qubits x y z radius purity
#> 1 0 Initial 1 2 0 0 1 1 1.0
#> 2 1 H 1 2 1 0 0 1 1.0
#> 3 2 CX 1 2 0 0 0 0 0.5
#> 4 3 M 1 2 0 0 0 0 0.5A qv_result is a named list with class
qv_result. The following named fields and their meanings
are stable through the 0.1.x series:
| Field | Meaning |
|---|---|
circuit |
The simulated qv_circuit |
state |
Normalized complex vector of length 2^n in LSB
order |
probabilities |
Numeric vector aligned with state |
counts |
The three-column terminal-shot table described above |
shots, seed |
Requested values, or NULL when omitted |
backend |
Backend actually used: "native" or
"reference" |
elapsed |
Simulation elapsed time in seconds |
trajectory |
Recorded frames, or NULL when
record = FALSE |
schema_version |
Integer version of the result schema; currently 1L |
A qv_circuit has stable name,
n_qubits, n_clbits, operations,
and schema_version fields. Each operation has stable
type, name, label,
qubits, clbits, matrix, and
parameters fields. Use constructors and gate functions to
create or modify circuits; treat these named fields as read-only
interoperability data.
Each raw result$trajectory frame has stable
step, label, operation, and
state fields. Prefer trajectory_data() for
tabular analysis. Its stable columns are the seven
state_data() columns followed by step and
label. trajectory_bloch() returns stable
step, label, qubit,
n_qubits, x, y, z,
radius, and purity columns.
Minor 0.1.x releases may append named fields, columns, optional arguments, or new exported functions. They will not remove or rename the fields and columns listed above, change their meaning, or reorder existing tabular columns. Code should select fields and columns by name rather than depend on list position.
plot() dispatches a circuit to
plot_circuit() and a result to plot_state().
Explicit plotting functions expose the same visual presets:
"nature", "npj", "colorblind",
"dark", "light", and "mono". Plot
behavior does not change with installed packages:
plot_state() uses base graphics by default and returns the
plotted state data invisibly. engine = "auto" is a
compatibility alias for base graphics. An explicit
engine = "ggplot2" requires the suggested
ggplot2 package and returns a ggplot object for printing or
composition. plot_circuit() returns its circuit invisibly,
and plot_execution() returns its result invisibly.
Recorded runs can synchronize the circuit playhead and exact state at any execution step.
save_quantum_plot() supports PDF, SVG, PNG, and TIFF.
Always provide a path you control; package examples and vignettes should
write only to R’s temporary directory.
GIF export requires the suggested gifski package. The
following chunk is evaluated only when gifski is installed
and the environment variable
QVIVID_BUILD_GIF=true is set. It is skipped during ordinary
package and CRAN vignette builds, and its output is confined to
tempdir().
The exported functions, required arguments, S3 classes, and data
structures documented above will remain compatible throughout qvivid
0.1.x. New optional arguments, functions, fields, and columns may be
added. If an interface must be replaced, qvivid will issue a warning for
at least one 0.1.x release and keep the old interface available
throughout the series. Removal can occur no earlier than 0.2.0. A
serious correctness or security defect may require an earlier change;
such changes will be recorded in NEWS.md.
Unexported names (including names beginning with .qv_),
exact timing values, warning text, and pixel-level plot geometry are
implementation details. Plot engine and return-value semantics, theme
names, basis ordering, and data meanings remain documented public
behavior.