Changelog
All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning in spirit (pre-1.0, so breaking changes can land in minor versions).
[Unreleased]
[0.5.5] - 2026-08-14
Changed
- Documentation: mathematical expressions in docstrings and on the Documenter site are now written as LaTeX — fenced
mathblocks for display equations and double-backtick inline math — instead of Unicode approximations, so formulas render as real typeset math on the docs site. Docstrings that carry TeX now use@doc raw"""where escaping would otherwise be needed. Runtime error messages and internal code comments keep their plain-text Unicode form. No API, numerical, or behavioral changes.
[0.5.4] - 2026-08-13
Added
apply_with_prob!accepts a vectorprobability— one entry per geometry element, inelements(geo, L, bc)order — so each gate location can have its own probability.
Changed
- Invalid scalar probabilities (negative,
NaN/Inf, or> 1) now throw instead of passing silently; only their sum was checked before.
[0.5.3] - 2026-07-26
Added
- Region-based entanglement entropy on the StateVector, Clifford, and Gaussian backends:
EntanglementEntropy(cut=c1:c2)(contiguous range) orEntanglementEntropy(cut=[s1, s2, ...])(arbitraryVector{Int}) computes the entropy of the reduced density matrix of any set of physical sites, including non-contiguous and periodic-boundary-wrapped regions (e.g.cut=[L-1, L, 1, 2]).renyi_index,threshold, andbasebehave identically to the existingcut::Intbipartition path on each backend (Gaussian computes real-renyi_indexRényi-n entropy for bothcut::Intand region paths, from the same covariance-spectrum formula). - Gaussian Rényi-n entanglement entropy (bipartition
cut::Intand regioncut::Vector{Int}) and Rényi mutual information, computed from the covariance-matrix eigenvalue spectrum: $S_n = \frac{1}{1-n}\sum_k \ln\!\big[((1+\lambda_k)/2)^n + ((1-\lambda_k)/2)^n\big]$ over the paired spectrum, plus $\ln(2)/2$ per unpaired zero mode on odd-sized Majorana-chain regions.TripartiteMutualInformationandEntropyProfilegain the same support by composition. - Documentation: new Custom Gates page (
docs/src/custom_gates.md) documenting theAbstractGateextension contract —support,build_operator(MPS),gate_matrix(state vector), the opt-inneeds_normalization/is_measurementtraits, and theexecute!full override — with three worked examples (a custom unitary running unchanged on both dense backends, a projective gate opting into renormalization, and anexecute!override that composes stock gates), a per-backend support table recording that the Clifford and Gaussian backends are closed whitelists while the two dense backends resolve any conforming subtype, and the verbatim rejection error. Registered in the site navigation under "Examples" and cross-linked from Custom Observables, the Gates API page, and Tutorials. - Documentation: Gaussian class-DIII monitored Majorana-chain tutorial in Tutorials — a standalone snippet running the staggered
BondParity()/GaussianHaar()circuit atsite_type="Majorana"on a periodic ring and tracking shift-averaged antipodal-quarter mutual information — plusexamples/gaussian_example.ipynbadded to the notebook table.
Changed
renyi_indexaccepts anyRealon all entropy observables (EntanglementEntropy,MutualInformation,TripartiteMutualInformation,EntropyProfile), normalized toFloat64with the normalized value required finite and> 0(was integer>= 1).n = 0(Hartley) andBoolremain rejected; each observable's docstring documents the small-n numerical caveat.- General-
nentropy kernels now evaluate in the log domain for numerical stability at extremen(e.g.n = 2048orn = floatmax(Float64)); then = 1von Neumann code path is unchanged bit-for-bit. EntanglementEntropyis now a parametric type,EntanglementEntropy{C<:Union{Int,Vector{Int}}}, dispatched on whethercutis anInt(bipartition) or aVector{Int}(region). Existing code usingEntanglementEntropy(cut=...)or annotating fields as::EntanglementEntropyis unaffected —EntanglementEntropyremains a valid UnionAll annotation — but code that wants a concrete-parameter annotation for the bipartition-only case can now write::EntanglementEntropy{Int}.- The MPS backend does not support region-based entropy:
EntanglementEntropy(cut=[...])on an MPS state throws an informativeArgumentError;cut::Intbipartition remains the only MPS interface. - Existing
cut::Intbipartition behavior is unchanged and fully backward-compatible on all four backends.
Fixed
- MPS
born_probabilitywas double-normalized:ITensorMPS.expectalready divides by $\langle \psi \vert \psi \rangle$ internally, so the additional/ inner(ψ, ψ)inflated every probability by $1/\lVert\psi\rVert^2$ whenever the state was not normalized — which is the case right after a truncating unitary layer, since those are not renormalized. Reported values could therefore exceed 1, and the inflated probabilities fed the cumulative-probability outcome sampler. Sampled trajectories on normalized states ($\lVert\psi\rVert^2 = 1$, where the extra division was a no-op) are unaffected, but MPS Born probabilities and anyMeasuresampling in an actively truncating regime now differ numerically from earlier versions. The projector is also evaluated only at the requested site instead of at all $L$ sites, removing a separate $O(L)$innercontraction from the measurement hot path.
[0.5.2] - 2026-07-15
Changed
- Reorganized the Documenter site into an ITensors.jl-style layout: the landing page is now a full Introduction (installation, quick example, citation, known limitations), the API reference is split into per-topic pages under a "Documentation" nav group, and the changelog is part of the site navigation.
- Slimmed
README.mddown to badges, pitch, installation, a quick example, and citation — the docs site is now the single source of truth for everything else.
[0.5.1] - 2026-07-15
Changed
- Applied JuliaFormatter v2.10.1 with
SciMLStyleto the package entrypoints, Gaussian backend implementation, and Gaussian test suite. This is a formatting-only maintenance release with no runtime behavior changes. - Recorded the bulk formatting commits in
.git-blame-ignore-revsso line history remains useful across the mechanical rewrite.
[0.5.0] - 2026-07-13
This release adds a fourth simulation backend: a fermionic Gaussian (free-fermion) backend for circuits built entirely out of Gaussian-preserving gates and parity measurements, along with two new gates, a Majorana-chain site granularity for class-DIII circuits, and a matching documentation and test suite.
Added
- Gaussian backend (
backend=:gaussian): a pure fermionic Gaussian state is represented as a dense $2L \times 2L$ real antisymmetric Majorana covariance matrix $\Gamma$ ($\Gamma_{ab} = (i/2)\langle[\gamma_a,\gamma_b]\rangle$, satisfying $\Gamma^2 = -I$) instead of an MPS, state vector, or stabilizer tableau;GaussianBackendholdscorr, ascratchbuffer,purify_tol(default1e-10), andmajoranas_per_site.apply!,track!,record!, andsimulate!work unchanged — only theSimulationState(...; backend=:gaussian)constructor call differs. Gate/measurement application is $O(L^3)$ per operation (Schur-complement contraction on $\Gamma$, no truncation error, exact for any circuit depth), and a re-purification step (purify!, eigen-clamping $i\Gamma$'s spectrum back to $\pm 1$) fires automatically whenever floating-point drift exceedspurify_tol. - Two new gates, dispatched only on the Gaussian backend (informative
ArgumentErrorrejection on MPS/state-vector/Clifford):GaussianHaar()— Haar-randomSO(4)rotation (fermionic-mode granularity) orSO(2)rotation (Majorana-chain granularity, $\exp(\theta\gamma_a\gamma_b)$, $\theta \sim U[0,2\pi)$), drawn from:gates_realizationand conjugated directly onto $\Gamma$.BondParity()— projective bond-parity measurement $i\gamma_{2i}\gamma_{2i+1}$ between adjacent sites (fermionic-mode) or $i\gamma_i\gamma_{i+1}$ between adjacent Majorana sites (Majorana chain), with PBC wrap support; consumes one:born_measurementdraw per bond under the same redundant-draw contract asMeasure.
PauliX(),Measure(:Z), andReset()now also work on the Gaussian backend:PauliXflips fermionic occupation parity (a single Majorana row/column sign flip),Measure(:Z)is a projective on-site occupation-parity measurement ($i\gamma_{2i-1}\gamma_{2i}$), andReset()composes the two, matching the existing semantics on every other backend.- Majorana-chain site granularity (
site_type="Majorana"): each site is one Majorana mode rather than one fermionic mode ($\Gamma$ is $L \times L$ instead of $2L \times 2L$; requires evenL), directly modeling the staggered class-DIII monitored Majorana-chain circuit family —GaussianHaar/BondParityonBricklayer(:odd)/Bricklayer(:even)reproduce the protocol with no new gate types. RandomGaussianStateinitial-state type: draws $O \in SO(2L)$ (or $SO(L)$ on the Majorana chain) from:state_initvia an exact Haar sampler,haar_orthogonal, using QR decomposition of a Ginibre matrix.- Gaussian-backend observable support:
EntanglementEntropy(von Neumann only, covariance-matrix formula;renyi_index != 1rejected),Magnetization(:Z)(fermionic-mode granularity only),BornProbability,MutualInformationandTripartiteMutualInformation(von Neumann only; the only backend that accepts non-contiguous or PBC-wrapped region pairs, since a covariance-matrix reduced state is just an index selection), andEntropyProfile.StringOrder,DomainWall,PauliString,Correlator, andMagnetizationFluctuationsare rejected with an informativeArgumentError(no fermionic-Gaussian formula exists for these). - New Documenter.jl page
docs/src/backends/gaussian.mddocumenting the backend, and a substantially expandeddocs/src/devdocs/backend_interface.mddeveloper-docs page covering the Gaussian method tables (initialize!,_apply_single!,_measure_single_site!,born_probability, observables). examples/gaussian_example.ipynb/examples/gaussian_example.jl: reproduces the class-DIII phase diagram via the staggered monitored Majorana-chain protocol.- New
test/gaussian/suite (14 files): construction and rejection checks, gate and measurement tests, an oracle-based cross-validation against an independent reference implementation, golden-value regression tests, and Majorana-chain-specific coverage.
[0.4.0] - 2026-07-07
This release is a quality/hardening pass across the whole package: a systematic audit of every backend and observable, arbitrary spin-S support, four new composed observables, a custom-observable API, a benchmark suite, a full Documenter.jl site, and CI/quality gates (Aqua, JET, ExplicitImports). It also removes one redundant gate and tightens the public export surface.
Added
PauliString(i => :P, j => :P', ...)— multi-site Pauli-string expectation value observable, on the MPS, state-vector, and Clifford backends.MutualInformation(regionA, regionB; renyi_index=1, base=ℯ)— $I(A:B) = S(A) + S(B) - S(A\cup B)$ for two disjoint contiguous site regions, on all three backends (MPS via subset-RDM contraction with a size guard, exact state vector via partial trace, Clifford via poly-time GF(2)-rank stabilizer entropies). Regions are physical sites on every backend and under both boundary conditions.Correlator(i => :P, j => :P')— connected two-point correlator $\langle P_iP_j\rangle - \langle P_i\rangle\langle P_j\rangle$, composed fromPauliString.EntropyProfile(; renyi_index=1, base=ℯ)— vector observable returning[S(cut=x) for x in 1:L-1]in one call; the first vector-valued built-in observable (see the custom-observable API below for how the vector storage is threaded throughtrack!/record!).TripartiteMutualInformation(A, B, C)— $I_3 = I(A:B) + I(A:C) - I(A:BC)$ (Gullans–Huse MIPT convention), composed fromMutualInformation.MagnetizationFluctuations(region; axis=:Z)—Var(M)for $M = \sum_{i\in\text{region}} P_i$, composed fromPauliString.- Custom-observable API:
track!now also acceptsSymbol => <any callable>(not just a built-inAbstractObservable),record_valuehas an untyped fallback (record_value(obs, state) = obs(state)), and per-key storage auto-widens fromVector{Float64}toVector{Any}the first time a non-scalar value is recorded — this is what makes vector-valued observables likeEntropyProfilework without a new trait system. Seedocs/src/custom_observables.mdfor the contract and worked examples. - Arbitrary spin-S site types (
"S=k/2"forSup to 10, half-integer steps), on the MPS and state-vector backends: initialization at any level ("Z<m>"state names, e.g."Z3/2","Z-1/2"),Sz/S+/S-operators, per-level projectors ("Proj0".."Proj2S"), categorical (2S+1)-outcome single-siteMeasure(:Z)(reduces bitwise to the old binary Born-sampling algorithm atS=1/2),Projection(k)for any levelk,MatrixGate(U; d=...)for explicit local dimension, andtotal_spin_projector(S; s=...)/verify_spin_projectors(; s=...)generalized from the hardcodedS=1case to arbitrary spin-spairs via the Lagrange/Casimir projection formula. RandomStateVector(state-vector-backend random initialization, drawing from the:state_initRNG stream) is now exported.- Benchmark suite (
benchmark/benchmarks.jl, AirspeedVelocity-compatible, 10 scope-table entries / 14 leaves) with a committed baseline (benchmark/results/baseline-5ab86ab.json) and a CI workflow (.github/workflows/benchmarks.yml,continue-on-error: trueon pull requests). - Documenter.jl site (
docs/): design philosophy, per-backend pages (MPS/state-vector/Clifford), tutorials (MIPT/CIPT/AKLT/feedback quick starts), a restructured API reference (@docsblocks per module area, plus an "Observables Catalog" and an "Arbitrary Spin-S Support" section), a private/internal API page, a developer-docs backend-interface contract page (docs/src/devdocs/backend_interface.md), and the custom-observables page. 100% ofnames(QuantumCircuitsMPS)are documented on the built site. - CI workflows:
CI.yml,format-check.yml(JuliaFormatter v2, SciMLStyle),CompatHelper.yml,TagBot.yml,docs.yml(Documenter deploy),benchmarks.yml. - Quality gates:
Aqua.test_allandExplicitImportsrun as standing tests in everyPkg.test();JET.report_packageruns opt-in underJET_TEST=truewith a documented, only-decreasing report-count ratchet. - Audit test suites (
test/audit/) systematically cross-checking every backend/observable/gate against its documented contract; several findings from this audit are theFixedentries below, and the remainder are recorded as open questions in ROADMAP.md.
Fixed
RandomMPSnow respectsRNGRegistry's:state_initstream (previously ignored it and drew from the global RNG, so random initial states were not reproducible under a fixed seed).- Calling
StringOrderorDomainWallon a Clifford-backend state no longer crashes with a rawFieldError(no field mps/no field sites); both now raise an informativeArgumentErrorexplaining that these observables are not supported on the stabilizer-tableau backend. plot_circuit's Luxor SVG rendering of non-adjacent (NNN) gates: PBC-wrap labels no longer collide pixel-exactly with unrelated gates' labels at lattice center, and non-wrap NNN gate labels no longer get overdrawn by a later gate's box in the same packed layer; open-boundary long-range gates are no longer misclassified as periodic wraps.CITATION.cffwas out of sync (version: 0.1.0, staledate-released); now tracks this release.Bricklayer(:even)at odd system sizeLunder periodic boundary conditions no longer double-touches siteLwithin a single brickwork layer (it now leaves site 1 unpaired, mirroring:odd's existing behavior of leaving siteLunpaired). In addition, usingBricklayer(:odd)/Bricklayer(:even)with oddLunderbc=:periodicnow emits a one-time warning (at circuit-build time and on immediate-modeapply!): an odd ring has no valid brickwork tiling — each layer leaves one site unpaired (:odd→ siteL,:even→ site 1) and the wrap bond(L,1)is gated by neither layer, so an alternating:odd/:evencircuit is effectively open across that bond. Enumeration is unchanged;:nnand the NNN sublayers do not warn. Verify intended patterns withprint_circuit.SpinSectorProjectiongained agate_matrixmethod, so it now works on the state-vector backend (previouslyMethodError: no method matching gate_matrix(::SpinSectorProjection)— the AKLT Quick Start could not run withbackend=:statevector).Magnetization(:Z)on"S=1"(and other arbitrary spin-S) MPS/state-vector states no longer throws; it now correctly reports $\langle S_z\rangle \in [-S, S]$ (qubit and"S=1/2"sites keep the existing $\pm 1$ Pauli convention).SimulationState(L=0, ...)(or negativeL) now throws an informativeArgumentErroron all backends (previously constructed a silent, unusable empty state).ProductState(binary_int=-1)(or any negative value) now throws an informativeArgumentError(previously accepted silently, andinitialize!produced a garbage state by parsing the-sign character as a state label).
Changed / BREAKING
- BREAKING — Clifford Born-draw contract (redundant draw): the Clifford backend's measurement primitive now consumes exactly ONE
:born_measurementdraw per measured site — always — matching MPS/state-vector. When the stabilizer tableau fixes the outcome (deterministic measurement), the draw is made anyway and its value is discarded (a deliberate redundant draw). This restores absolute cross-backend reproducibility: sameRNGRegistryseeds now produce the same measurement record on all three backends. Migration impact: seeded Clifford trajectories generated with earlier versions (which consumed zero draws for deterministic outcomes) are NOT reproducible under the new contract — a one-time break. MPS/state-vector trajectories are completely unaffected. Deterministic outcomes themselves are unchanged (still read off the tableau); only the RNG stream position differs. - BREAKING — removed: the
Measurementgate. UseMeasure(:Z)instead (drop-in replacement: same Born sampling, same one-draw-per-measurement contract, same"Meas"circuit label, bit-identical trajectories under the same seeds). For feedback, useMeasure(:Z; feedback=OnOutcome(1 => PauliX()))etc. No deprecation alias is provided. - BREAKING — export surface: 11 internal symbols are no longer exported:
advance!,get_sites,current_position,reset!,compute_site_staircase_right,compute_site_staircase_left,compute_pair_staircase,apply_op_internal!,compute_basis_mapping,physical_to_ram,ram_to_physical. They remain defined for debugging/introspection — use qualified access, e.g.QuantumCircuitsMPS.advance!(geo, L, bc). - BREAKING:
CZ/CNOT/SWAPnow reject non-qubit sites (local_dim != 2) with an informativeArgumentError, matching the existing guard onRx/Ry/Rz/Hadamard. Previously these gates silently built an undocumented, non-standard qudit generalization when applied to $S \ge 1$ sites (e.g. a 3-level "CZ" that put-1only on $\lvert 22\rangle$ instead of the standard $\omega^{jk}$ phases) — a silent-physics-trap that is now a clean error instead. born_probability(state, site, outcome)is now a documented public export (promoted out of the internal-exports block).- The
SimulationStatefield-forwardinggetpropertyshim (state.mps/state.sites/state.cutoff/state.maxdim, MPS backend only) is kept and is now explicitly documented as supported public API, not a deprecated compatibility layer — a usage census found 42 call sites intest/alone, above the removal threshold. - BREAKING: Julia compatibility floor raised from 1.11 to 1.12 (required by QuantumClifford 0.11.5, which itself requires julia≥1.12 — the previously-declared 1.11 floor did not actually resolve).
Performance
Baseline: commit 5ab86ab (benchmark/results/baseline-5ab86ab.json), Julia 1.12.6. All comparisons below are before/after on the same machine.
State-vector observable loop optimization (shared
_sv_digitstride helper + single-pass algorithms, L=12 random state): | Observable | Before | After | Speedup | |–-|–-|–-|–-| |Magnetization(:Z)| 86.7 µs | 2.62 µs | ~33x | |DomainWall(order=1)| 277.9 µs | 8.3 µs | ~33x | |DomainWall(order=2)| 277.8 µs | 7.7 µs | ~36x | |StringOrder(order=1)| 54.1 µs | 7.4 µs | ~7.3x | |StringOrder(order=2)| 53.0 µs | 14.8 µs | ~3.6x | |born_probability| 7.8 µs | 4.1 µs | ~1.9x |Corroborated by the benchmark suite:
magnetization/sv_L83.417 µs → 208 ns (16.4x). All changes verified bitwise-identical or within 1e-15 of the old formulas on 20 random states; no formula or RNG behavior changed.Vectorized
CZ/CNOT/SWAPITensor construction (reshape +ITensorconstructor instead of a 16-iteration scalarsetindex!loop): gate-construction alone went from 1.584 µs / 65 allocs to 1.167 µs / 39 allocs (1.36x faster, 40% fewer allocations); the fullapply!(state, CZ(), AdjacentPair(...))on MPS L=12 improved from 330.875 µs / 4802 allocs to 318.104 µs / 4749 allocs. Verified bitwise identical to the old operator construction on all 16 index combinations.elements()caching insimulate!(step-invariant static geometries cached per circuit run instead of recomputed every step): | Scenario | Before allocs | After allocs | Δ | |–-|–-|–-|–-| | MIPT, state vector, L=8, 50 steps | 18,703 | 16,315 | -12.8% | | MIPT, Clifford, L=100, 20 steps | 173,361 | 163,627 | -5.6% | | MIPT, MPS, L=20, 20 steps | 2,163,183 | 2,161,106 | -0.1% (MPS SVD-dominated) |Bitwise-identical trajectories verified before/after (a lazy-vs-cached equality regression suite is now permanent,
test/regression/elements_cache.jl).
[0.3.0] - 2026-07-05
Clifford (stabilizer-tableau) backend: CNOT, PhaseGate, SWAP, RandomClifford gates, and a Clifford-backend MIPT demo.
[0.2.1] - 2026-07-05
Removed a stale "Known Limitations" README entry.
[0.2.0] - 2026-07-05
State-vector backend, and periodic-boundary-condition fold alignment for the MPS backend.
[0.1.1] - 2026-07-04
Removed an orphaned imperative.jl; synced dev with main.
[0.1.0] - 2026-07-04
API refactor: MPS quantum circuit simulation core (the "v0.1" API — Gates, Geometry, Observables, the unified stochastic rule, SimulationState).
[0.0.7] - 2026-06-13
Hygiene fix.
[0.0.6] - 2026-06-11
Maintenance release.
[0.0.5] - 2026-06-10
Fixed AKLT plotting; updated the random seed for consistency across example notebooks.
[0.0.4] - 2026-06-08
Added citation metadata.
[0.0.3] - 2026-06-08
Fixed circuit plotting.
[0.0.2] - 2026-02-04
AKLT/qudit support, improved circuit visualization, comprehensive documentation.
[0.0.1] - 2026-01-31
Initial clean release, with CIPT and MIPT example notebooks.
[Unreleased]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.5...HEAD [0.5.5]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.4...v0.5.5 [0.5.4]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.2.1...v0.3.0 [0.2.1]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.7...v0.1.0 [0.0.7]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.6...v0.0.7 [0.0.6]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.5...v0.0.6 [0.0.5]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.4...v0.0.5 [0.0.4]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.3...v0.0.4 [0.0.3]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.2...v0.0.3 [0.0.2]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/compare/v0.0.1...v0.0.2 [0.0.1]: https://github.com/hainingpan/QuantumCircuitsMPS.jl/releases/tag/v0.0.1