Circuit
The lazy-mode do-block circuit builder, its expansion into a flat operation list, and the eager/lazy gate-application entry points.
QuantumCircuitsMPS.apply! — Function
apply!(state::SimulationState, gate::AbstractGate, geo::AbstractGeometry)Apply a gate to the state at sites specified by geometry. Modifies state.backend.mps in-place.
Geometry dispatch resolves the target region(s); each region is executed through the uniform execute!(state, gate, region) protocol.
Normalization dispatch (Contract 3.5) is trait-based:
needs_normalization(gate) == false(unitaries): NO normalize after applyneeds_normalization(gate) == true(projective gates): normalize + truncate
apply!(state::SimulationState, gate::AbstractGate, sites::Vector{Int})Apply a gate to specific physical sites. Direct site specification. Routes through the uniform execute! protocol (so measurement-like gates such as Measure/Reset work with explicit site vectors too).
apply!(builder::CircuitBuilder, gate, geometry)Record a deterministic gate operation in the circuit builder.
Stores operation as: (type=:deterministic, gate=gate, geometry=geometry)
A Bricklayer(:odd)/Bricklayer(:even) geometry with odd L under bc=:periodic emits a one-time warning at this recording step (no valid brickwork tiling exists — see _warn_bricklayer_odd_pbc); the operation is still recorded unchanged.
Example
Circuit(L=4, bc=:periodic) do c
apply!(c, Hadamard(), SingleSite(1))
apply!(c, CNOT(), StaircaseRight(1))
endapply!(state::SimulationState, pg::ProductGate)Apply a ProductGate eagerly with its geometry omitted (recommended form): equivalent to apply!(state, pg, Sites(u)) with u the union of elements(pg.region_geometry, state.L, state.bc).
apply!(builder::CircuitBuilder, pg::ProductGate)Record a deterministic ProductGate operation with its geometry omitted (recommended form): the canonical region Sites(union) is filled in from the builder's L/bc.
apply!(builder::CircuitBuilder, pg::ProductGate, geometry)Explicit form of recording a deterministic ProductGate: geometry MUST be Sites(u) with u exactly the union of the product's elements (validated at BUILD time; any other geometry throws an ArgumentError).
QuantumCircuitsMPS.apply_with_prob! — Function
apply_with_prob!(state::SimulationState; outcomes)Eagerly execute ONE stochastic operation on state under the v0.1 unified stochastic rule — identical semantics to recording the same outcomes in a Circuit and running one step of simulate!, but applied immediately.
Each outcome is a NamedTuple with fields:
probability(required): a scalarReal, which broadcasts identically across every element.probabilitymay also be anAbstractVector{<:Real}with one entry per geometry element (inelements(geo, L, bc)order), so each gate location gets its own probability. Scalar and vector outcomes may be freely mixed within one call.gate::AbstractGate(required)geometry::AbstractGeometry(required)
Semantics (v0.1 unified stochastic rule)
Each outcome's geometry expands to elements (elements(geo, L, bc) for broadcast geometries; set geometries are a single element); all outcomes must expand to the SAME element count K. At element k, $\sum p$ is the sum of every outcome's probability value AT THAT ELEMENT — a scalar outcome contributes the same value to every element, a vector outcome contributes its k-th entry. For each element $k = 1, \ldots, K$, exactly ONE scalar coin is drawn from the :gates_spacetime stream — unchanged whether probabilities are scalar or per-element vectors — and a categorical selection is made among the outcomes via select_outcome_index (the engine's single source of truth); the remainder $1 - \sum p$ selects identity (nothing applied, staircases not advanced). The winning outcome's gate is executed at its k-th element via the uniform execute! protocol.
A selected staircase advances after application and other staircase geometries in outcomes are synced to its position (sync_staircase_positions!) — exactly as in simulate!. Unlike simulate!, the eager form never resets geometry positions: the caller owns staircase/Pointer state across calls.
Call-time validation (all ArgumentError, thrown BEFORE any coin is drawn
or the state is touched — the lazy form runs the same checks at build time)
outcomesmust be non-empty- $\sum p$ must be $\le 1$ (tolerance
1e-10) - Equal-K: every outcome's geometry must expand to the same element count (the error names each outcome's geometry and K)
- Staircase/
Pointerphysics guard: if any outcome uses a staircase orPointergeometry, $\sum p$ must equal $1$ (an identity remainder would silently stall the random walk — see the builder docstring for the CIPT rationale) - The removed
rng=keyword (or any other keyword) throws with a migration message: all coins come from:gates_spacetimein v0.1
Event log
When the state was constructed with log_events=true, each applied gate emits a GateApplied event. Eager calls happen outside an engine run, so step and op_idx are the documented 0 sentinels; element_idx is the real element index k.
Example
apply_with_prob!(state;
outcomes = [
(probability=p, gate=Measure(:Z), geometry=AllSites())
]
)Example ($\sum p < 1$: remaining 0.3 = identity)
apply_with_prob!(state;
outcomes = [
(probability=0.3, gate=PauliX(), geometry=site),
(probability=0.4, gate=PauliY(), geometry=site)
]
)See Also
apply_with_prob!(c::CircuitBuilder; outcomes): lazy form (records into aCircuitforsimulate!)apply!(state, gate, geometry): deterministic, unconditional application
apply_with_prob!(builder::CircuitBuilder; outcomes)Record a stochastic operation in the circuit builder (v0.1 unified rule).
Stores operation as: (type=:stochastic, rng=:gates_spacetime, outcomes=collect(outcomes))
All stochastic coins are drawn from the :gates_spacetime stream — the pre-v0.1 rng= keyword was REMOVED (passing it throws an ArgumentError with a migration message).
Arguments
outcomes: Vector of NamedTuples with keys(:probability, :gate, :geometry). Eachprobabilitymay be a scalarReal, broadcast identically across every element;probabilitymay also be anAbstractVector{<:Real}with one entry per geometry element (inelements(geo, L, bc)order), so each gate location gets its own probability. Scalar and vector outcomes may be freely mixed within one call. The operation records the caller's ORIGINALprobabilityvalues unchanged — this builder never stores a derived probability matrix.
Semantics (v0.1 unified stochastic rule)
Each outcome's geometry expands to elements (elements(geo, L, bc)); all outcomes must expand to the SAME element count K. At element k, $\sum p$ is the sum of every outcome's probability value AT THAT ELEMENT — a scalar outcome contributes the same value to every element, a vector outcome contributes its k-th entry. Per element $k = 1, \ldots, K$, the engine draws ONE :gates_spacetime coin — unchanged whether probabilities are scalar or per-element vectors — and makes a categorical selection among the outcomes; the remainder $1 - \sum p$ selects identity (nothing applied).
Build-time validations (all ArgumentError)
All of the following are checked — and any violation is thrown — before any RNG draw or state mutation; the builder never touches :gates_spacetime or pushes a partial operation onto builder.operations.
outcomesmust be non-empty- Equal-K: every outcome's geometry must expand to the same element count (the error names each outcome's geometry and K)
- Per outcome,
probabilityis validated (finite, in[0,1]) and every element's total probability (summed across outcomes) must be ≤ 1 (tolerance1e-10) — via the sharedresolve_probability_scheduleresolver (src/Circuit/probabilities.jl) - Staircase/Pointer physics guard: if any outcome uses a staircase or
Pointergeometry, $\sum p$ at that element must equal $1$ (tolerance1e-10). The CIPT random walk advances via the selected staircase every step; an identity remainder ($\sum p < 1$) would silently stall the walk. - The removed
rng=keyword (or any other keyword) throws with a migration message
Example
Circuit(L=4, bc=:periodic) do c
apply_with_prob!(c; outcomes=[
(probability=0.3, gate=PauliX(), geometry=SingleSite(1)),
(probability=0.2, gate=PauliZ(), geometry=SingleSite(1))
])
endExample (per-element vector probability)
Circuit(L=4, bc=:periodic) do c
apply_with_prob!(c; outcomes=[
(probability=[0.1, 0.2, 0.3, 0.1], gate=Measure(:Z), geometry=AllSites())
])
endQuantumCircuitsMPS.Circuit — Type
Circuit(; L::Int, bc::Symbol, operations=NamedTuple[])Lazy representation of a quantum circuit as a sequence of symbolic operations.
Circuits are currently one-dimensional: L sites on a chain with :open/:periodic boundary conditions. Higher-dimensional (2D+) circuit geometries are a planned future direction (see ROADMAP.md).
A Circuit does NOT execute immediately - it stores operations symbolically and expands them to concrete gate applications only when passed to simulate!.
A Circuit always represents ONE time step. Repeat execution is controlled by simulate!(circuit, state; n_steps=...).
Fields
L::Int: Number of physical sites in the systembc::Symbol: Boundary conditions (:periodicor:open)operations::Vector{NamedTuple}: Internal symbolic operation listparams::Dict{Symbol,Any}: User-defined parameters (default: empty Dict)
Operation Representation
Operations are stored as NamedTuples with different formats:
Deterministic gates:
(type = :deterministic, gate = gate, geometry = geometry)Stochastic outcomes (apply_with_prob!):
(type = :stochastic, rng = :gates_spacetime, outcomes = [(probability=p, gate=g, geometry=geo), ...])Each outcome's probability (p above) is a scalar Real, which broadcasts identically across every element; probability may also be an AbstractVector{<:Real} with one entry per geometry element (in elements(geo, L, bc) order), so each gate location gets its own probability. Scalar and vector outcomes may be freely mixed within one operation's outcomes list.
Construction
Users construct circuits via the do-block API (see CircuitBuilder):
circuit = Circuit(L=10, bc=:periodic) do c
apply!(c, Hadamard(), SingleSite(1))
apply!(c, CNOT(), StaircaseRight(1), steps=5)
endExecution
Pass the circuit to simulate! to execute:
state = SimulationState(...)
simulate!(circuit, state; n_steps=50) # Runs the circuit 50 timesSee Also
expand_circuit: Expands symbolic operations to concrete site listssimulate!: Executes a circuit on a simulation state
QuantumCircuitsMPS.expand_circuit — Function
expand_circuit(circuit::Circuit; n_steps::Int=1, seed::Int=0) -> Vector{Vector{ExpandedOp}}Expand a symbolic circuit to a flat list of concrete gate operations per timestep.
This is the backward-compatible flat version. For visualization, prefer expand_circuit_grouped which preserves operation group boundaries.
Stochastic operations are expanded with the engine's shared selection function select_outcome_index (v0.1 unified rule) — see expand_circuit_grouped for details. Record markers appear as gate-less pseudo-ops (see is_record_mark).
Arguments
circuit::Circuit: The circuit to expand (one time step)n_steps::Int: Number of times to repeat the circuit step (default: 1)seed::Int: RNG seed for stochastic branch selection (default: 0)
Returns
Vector{Vector{ExpandedOp}}: Outer vector has lengthn_steps, inner vectors contain all operations for that timestep (flattened across groups). Inner vectors may be empty if the identity remainder is selected for all stochastic elements.
See Also
expand_circuit_grouped: Grouped version for visualization
QuantumCircuitsMPS.expand_circuit_grouped — Function
expand_circuit_grouped(circuit::Circuit; n_steps::Int=1, seed::Int=0) -> Vector{Vector{Vector{ExpandedOp}}}Expand a symbolic circuit to concrete gate operations, preserving operation groups.
Each apply! / apply_with_prob! / record! call in the circuit definition becomes (at most) one group. This grouping is essential for visualization: gates within the same group (e.g., all pairs in a Bricklayer) can be rendered on the same row, while different groups (e.g., a Bricklayer followed by AllSites measurements) get separate rows.
Stochastic selection (v0.1 unified rule)
Stochastic operations are expanded with the ENGINE's single-source selection function select_outcome_index — one scalar coin per element $k = 1, \ldots, K$, categorical selection among the outcomes, remainder $1 - \sum p$ = identity (element omitted). This is the exact rule simulate! executes: expanding with seed=X shows the same selections the engine makes when its :gates_spacetime stream is seeded with X.
Per-element probability schedules
probability may also be an AbstractVector{<:Real} with one entry per geometry element (in elements(geo, L, bc) order), so each gate location gets its own probability — resolved via the same resolve_probability_schedule simulate! uses.
Record markers
(type=:record_mark, ...) pseudo-ops (from record!(c[, names...])) become gate-less marker ExpandedOps (see is_record_mark) in their own group. Markers consume no RNG. Unknown operation types are skipped (never an error), keeping expansion forward-compatible.
Arguments
circuit::Circuit: The circuit to expand (one time step)n_steps::Int: Number of times to repeat the circuit step (default: 1)seed::Int: RNG seed for stochastic branch selection (default: 0)
Returns
Vector{Vector{Vector{ExpandedOp}}}: steps → groups → ops.- Outer vector: one entry per timestep (length
n_steps) - Middle vector: one entry per circuit call that produced ops
- Inner vector: concrete gate operations from that call
- Outer vector: one entry per timestep (length
Groups with no operations (stochastic all-identity selections) are omitted.
See Also
expand_circuit: Flat version (no grouping) for backward compatibilityselect_outcome_index: The shared engine/visualization selection rule
QuantumCircuitsMPS.simulate! — Function
simulate!(circuit::Circuit, state::SimulationState; n_steps::Int=1, record_when::Union{Symbol,Function}=:every_step)Execute a circuit on a simulation state, applying gates and recording observables.
A Circuit represents ONE time step. This function runs that step n_steps times, processing all operations in circuit.operations on each iteration.
Arguments
circuit::Circuit: The circuit to execute (symbolic operations)state::SimulationState: The state to modify in-placen_steps::Int: Number of times to execute the circuit step (default: 1)record_when::Union{Symbol,Function}: Controls when observables are recorded (default::every_step)
Unified stochastic rule (v0.1)
Every stochastic operation (apply_with_prob!) is executed as follows:
- All outcomes expand to the SAME number of elements K (validated at build time; broadcast geometries expand via
elements(geo, L, bc), set geometries are a single element). - For each element $k = 1, \ldots, K$: exactly ONE scalar coin is drawn from the
:gates_spacetimestream — unchanged whether probabilities are scalar or per-element vectors — and a categorical selection is made among the outcomes viaselect_outcome_index(remainder $1 - \sum p$ = identity). - The winning outcome's gate is executed at its k-th element; identity applies nothing (and does NOT advance staircases).
Each outcome's probability may be a scalar Real (broadcast identically across every element) or a per-element AbstractVector{<:Real} of exact length K, indexed in the SAME canonical elements(geo, L, bc) order as element k above — see apply_with_prob!(::CircuitBuilder; outcomes) for the full per-element probability contract (canonical ordering including the Bricklayer PBC wrap-bond position, and K = 1 vector support). Scalar and vector outcomes may be freely mixed; at element k, $\sum p$ is the sum of every outcome's value at that element. Every stochastic op's schedule — scalar or vector alike — is validated once (length, range, and per-element totals) before geometry reset, RNG construction, or any gate execution.
Coin consumption is therefore data-independent: K draws per stochastic op per step, always — see expected_draws.
Recording Options
The record_when parameter accepts:
:every_step(default): Record once per step, after the last operation. This fires STRUCTURALLY every step — even when every stochastic op selects the identity ("do nothing") branch.:every_gate: Record after every actual gate execution:final_only: Record only after the final step completes:marks: Record exactly at the circuit'srecord!(c[, names...])marker positions, in the op stream (NOT at the structural step boundary). A circuit with M markers yields exactlyM * n_stepsrecords per (matching) observable, deterministically — independent of stochastic outcomes. Selective markers (record!(c, :entropy)) grow only the named observables' vectors. Requires the circuit to contain markers (ArgumentErrorotherwise).- Custom function
(ctx::RecordingContext) -> Bool: evaluated once per element slot (withis_step_boundary=false), once per marker (withat_mark=true,mark_indexset), and once at the structural end of each step (withis_step_boundary=true,gate_type=nothing); if any evaluation returns true, ONE record is taken at the end of the step.
Marker/policy conflict (ArgumentError)
If the circuit contains record!(c) markers, record_when MUST be :marks or a custom predicate. Passing (or defaulting to) :every_step, :every_gate, or :final_only throws an ArgumentError — those policies would silently ignore the markers.
RecordingContext Fields
Custom recording functions receive a RecordingContext with:
step_idx::Int: Current step index (1 to n_steps)gate_idx::Int: Cumulative element-slot count across all steps (never resets). Advances once per element slot REGARDLESS of the stochastic outcome (identity included), so gate_idx-based recording schedules are trajectory-independent. Markers do NOT advance it.op_idx::Int: Position of the current op incircuit.operations(0 for the step-boundary evaluation)element_idx::Int: Element index within the current op (0 for step-boundary and marker evaluations)gate_type::Any: The gate applied at this slot (nothingfor identity slots, marker evaluations, and the structural step-boundary evaluation)is_step_boundary::Bool: True only for the structural step-boundary evaluation after the op loopat_mark::Bool/mark_index::Int: Marker evaluations carryat_mark=trueand the marker's 1-based ordinal among the circuit's markers (0/false everywhere else)
Examples
# Basic execution with 5 steps
circuit = Circuit(L=4, bc=:periodic) do c
apply_with_prob!(c; outcomes=[
(probability=0.5, gate=Reset(), geometry=StaircaseRight(1)),
(probability=0.5, gate=HaarRandom(), geometry=StaircaseLeft(4))
])
end
rng = RNGRegistry(gates_spacetime=42, gates_realization=44, born_measurement=45)
state = SimulationState(L=4, bc=:periodic, rng=rng)
initialize!(state, ProductState(binary_int=1))
track!(state, :dw => DomainWall(order=1, i1_fn=() -> 1))
# Record after every step (default)
simulate!(circuit, state; n_steps=5)
# Record after every gate
simulate!(circuit, state; n_steps=5, record_when=:every_gate)
# Record only at the end
simulate!(circuit, state; n_steps=5, record_when=:final_only)
# Custom: record every 10 element slots
simulate!(circuit, state; n_steps=5, record_when=every_n_gates(10))
# Custom: record every 2 steps
simulate!(circuit, state; n_steps=10, record_when=every_n_steps(2))RNG Contract
- All stochastic coins come from
:gates_spacetime(the builder no longer accepts anrng=keyword). - Scalar draws only; element order = documented enumeration order of the outcome geometries (
elements) — the SAME order a per-element vector probability's entries are indexed in (seeapply_with_prob!(::CircuitBuilder; outcomes)for the canonical ordering, including theBricklayerPBC wrap-bond position). - Consumption is data-independent:
expected_draws(circuit, n_steps)coins, exactly one per element per stochastic op per step — unchanged whether an outcome's probability is a scalar or a per-element vector.
Thread safety
A Circuit holding staircase/Pointer geometries is MUTABLE (positions advance during simulate!). For per-trajectory parallelism use copy(circuit) (a deepcopy preserving intra-circuit geometry aliasing) so each trajectory owns private geometry state:
Threads.@threads for seed in seeds
c = copy(circuit)
st = SimulationState(...; rng=RNGRegistry(gates_spacetime=seed, ...))
initialize!(st, ProductState(binary_int=0))
simulate!(c, st; n_steps=n)
endValidation
- Throws
ArgumentErrorifn_steps < 1 - Throws
ArgumentErrorfor unknown symbol presets
See Also
expand_circuit: Visualize which gates will be appliedrecord!: Manual observable recordingRecordingContext: Context passed to custom recording functionsevery_n_gates,every_n_steps: Helper functions for common patterns
QuantumCircuitsMPS.ExpandedOp — Type
ExpandedOpRepresents a concrete gate operation at specific sites for a specific timestep.
Fields
step::Int: Circuit timestep (1-indexed)gate::Union{AbstractGate, Nothing}: The gate to apply.nothingmarks arecord!marker pseudo-op (no gate, no sites — seeis_record_mark).sites::Vector{Int}: Physical sites for this operation (empty for markers)label::String: Short label for visualization (e.g., "Rst", "Haar", "CZ";"[R]"/"[R:names]"for record markers)
Usage
Produced by expand_circuit for visualization and manual execution.
QuantumCircuitsMPS.RecordingContext — Type
RecordingContextContext information passed to recording predicate functions during circuit execution.
Fields
step_idx::Int: Current step index (1 to n_steps passed to simulate!)gate_idx::Int: Cumulative element-slot count across all steps (never resets). Advances once per element slot regardless of stochastic outcome;record!(c)markers do NOT advance it.op_idx::Int: 1-based position of the current operation withincircuit.operations(0 for the structural step-boundary evaluation)element_idx::Int: 1-based element index within the current operation (0 for step-boundary and marker evaluations)gate_type::Any: The gate applied at this slot (nothingfor identity slots, marker evaluations, and the step-boundary evaluation)is_step_boundary::Bool: True only for the structural step-boundary evaluation after the op loopat_mark::Bool: True when this evaluation happens at arecord!(c)marker pseudo-opmark_index::Int: 1-based ordinal of the marker among the circuit's markers (stable across steps; 0 whenat_mark == false)
Example
# Custom recording function
function my_recorder(ctx::RecordingContext)
return ctx.gate_idx > 10 && ctx.is_step_boundary
end
# Record only at the second marker of each step
simulate!(circuit, state; record_when = ctx -> ctx.at_mark && ctx.mark_index == 2)QuantumCircuitsMPS.every_n_gates — Function
every_n_gates(n::Int)Create a recording predicate that triggers every n gates.
Arguments
n::Int: Record every n gates (based on cumulative gate_idx)
Returns
Function that takes a RecordingContext and returns Bool
Example
simulate!(state, circuit; record_when=every_n_gates(5))QuantumCircuitsMPS.every_n_steps — Function
every_n_steps(n::Int)Create a recording predicate that triggers every n steps at step boundaries.
Records once per n steps, only when is_step_boundary is true (after all gates in the step have been executed).
Arguments
n::Int: Record every n steps (based on step_idx)
Returns
Function that takes a RecordingContext and returns Bool
Example
simulate!(state, circuit; record_when=every_n_steps(2))QuantumCircuitsMPS.print_circuit — Function
print_circuit(circuit::Circuit; n_steps::Int=1, gates_spacetime::Int=0, io::IO=stdout, unicode::Bool=true)Print an ASCII visualization of a quantum circuit realization.
Shows the stochastic realization determined by gates_spacetime RNG seed, matching what expand_circuit(circuit; seed=gates_spacetime) produces.
Arguments
circuit::Circuit: Circuit to visualizen_steps::Int=1: Number of circuit steps to visualize (default: 1)gates_spacetime::Int=0: RNG seed controlling which stochastic branches fire.io::IO: Output stream (default: stdout)unicode::Bool: Use Unicode box-drawing characters (default: true)
Character Sets
- Unicode mode (default): Uses
─(wire),┤(left box),├(right box) - ASCII mode: Uses
-(wire),|(both box edges)
Record Markers
record!(c[, names...]) markers appear as their own row with the marker glyph on every wire: ▽ in Unicode mode, [R] in ASCII mode. Named markers print their names after the wires (e.g. ▽ ... ▽ [R:entropy]).
Layout Algorithm
- Expands the circuit with
expand_circuit_grouped(one group perapply!call) - For each group, packs ops into non-overlapping layers with
pack_ops_into_layers; each layer becomes one visual row, and the group label is printed only on the first row of the group - Letters identify groups (not sub-rows): single-group steps are labeled
"1:", multi-group steps"1a:","1b:", etc.; empty steps → one wire-only row - Calculates fixed column width based on longest gate label (+2 for box chars)
- Renders header row with qubit labels (e.g., "q1", "q2", "q3", "q4")
- Renders time step rows with gate boxes or wire segments
Multi-Qubit Gates
For gates spanning multiple sites (e.g., CZ on sites [2, 3]):
- Gate label appears ONCE in a box on the minimum site
- Other sites show continuation boxes (box edges without label)
Examples
# Basic usage
circuit = Circuit(L=4, bc=:periodic) do c
apply!(c, Reset(), StaircaseRight(1))
end
print_circuit(circuit; n_steps=4)
# Output to file
open("circuit.txt", "w") do io
print_circuit(io, circuit; n_steps=4)
end
# ASCII mode (no Unicode)
print_circuit(circuit; n_steps=4, unicode=false)See Also
expand_circuit: Get a concrete stochastic realizationExpandedOp: Concrete operation representation
print_circuit(circuit::Circuit; n_steps::Int=1, gates_spacetime::Int=0, io::IO=stdout, unicode::Bool=true)Convenience form. Calls print_circuit(io, circuit; ...).
QuantumCircuitsMPS.plot_circuit — Function
plot_circuit(circuit::Circuit; n_steps=1, gates_spacetime=0, filename=nothing)Export an SVG diagram of circuit's deterministic template (all stochastic branches, with probability annotations), resolved for n_steps repetitions using the gates_spacetime seed for stochastic-branch layout — matching expand_circuit(circuit; seed=gates_spacetime).
Requires using Luxor to be loaded first (the implementation lives in the Luxor package extension, ext/QuantumCircuitsMPSLuxorExt.jl); calling this without Luxor loaded throws an ArgumentError telling you to load Luxor. When filename is nothing, the SVG is written to a temporary file and its path returned; otherwise it is written to filename.
See also print_circuit for a Luxor-free ASCII/Unicode terminal visualization of the same circuit template.
Example
using QuantumCircuitsMPS, Luxor
circuit = Circuit(L=4, bc=:periodic) do c
apply!(c, Reset(), StaircaseRight(1))
end
plot_circuit(circuit; gates_spacetime=42, filename="diagram.svg")