Releases: tatopenn-cell/Dense-Evolution
Release list
v8.1.21 - Chunk multi-chunk JIT fusion, QASMCircuit.__iter__
Added
QASMCircuit.__iter__— duck-types a parsed circuit as an iterable of the same tuples.to_tuples()returns, so it works anywhere a plain circuit list is expected (Chunk.run_chunk,QuantumTranspiler.transpile, ...) without remembering to call.to_tuples()first.
Fixed / Performance
Chunk's multi-chunk dispatch (num_chunks > 1) used to apply every gate through a Python loop calling non-JITapply_gate_1q/apply_gate_2q— measured 6x slower thanrun_circuit_jit_beast_modeon an identical workload. Replaced with a singlejax.lax.scanover the whole circuit operating directly on the stacked(num_chunks, chunk_dim)representation — never materializing a(2**n_qubits,)array, preserving the anti-OOM propertyChunkexists for. All 6 gate/qubit-location cases were ported formula-for-formula from the old implementation and verified case-by-case against aDenseSVSimulatorreference before it was removed. Gate coverage now built viaGATE_IDS, aligning with beast-mode. Measured speedup: 2.2s → 0.42s (~5x) on the benchmark that surfaced the problem — now close to beast-mode's 0.37s on the same non-chunked workload. Closes #5.
v8.1.20 - PennyLane wire-remap fix, depolarizing noise channel fix
Both bugs found via independent fuzz testing on Colab, verified directly before fixing.
Fixed
from_pennylane/run_pennylane_circuitsilently renumbered qubits whenever wires weren't touched in ascending order — bothqml.to_openqasmand the oldertape.to_openqasm()number exported QASM qubits by first-touch order, not actual wire index. Fuzz test (20 random circuits, 4 wires touched in random order): 9/20 matched PennyLane's own result before this fix, 20/20 after. Fixed by passing an explicitwires=argument to force true wire order into the export.NoiseModel.apply_to_sv'sdepolarizingchannel (andcombined's depolarizing sub-channel) picked X/Y/Z using thresholdsp/3,2p/3against a draw uniform on the full[0,1)range, instead of the fixed1/3,2/3— skewed every depolarizing circuit heavily toward Z regardless ofp(verified: atp=0.3,P(Z|fire)=80%instead of the documented 33.3%, confirmed both via isolated branch-logic trace and full statevector simulation).
Tracked, not fixed here
- Unrecognized gate names are silently dropped everywhere in the simulator instead of raising — a breaking-change decision, tracked in #4.
v8.1.19 - Security fix: eval() sandbox escape in QASMParser
Security
Fixed a code-execution vulnerability affecting every previously published version.
QASMParser's gate-parameter evaluator (_eval_param, used for expressions like rx(...), p(...)) called eval() with {'__builtins__': {}} as its only protection. That blocks direct builtin names (open, len, __import__, ...) but does not block attribute/dunder traversal of the live Python object graph (().__class__.__bases__[0].__subclasses__()...), which needs no builtin name at all — from there, any class loaded in the process is reachable, including ones whose __globals__ reference os/subprocess.
Verified directly: a crafted gate-parameter expression, passed through the public QASMParser.parse() entry point — the primary entry point of the whole library (dashboard, Qiskit/PennyLane interop bridge, direct usage) — executed successfully. Anyone parsing untrusted QASM text was affected.
Fixed by replacing eval() with an AST node-type whitelist evaluator (_eval_ast_node) — only literals, +-*/%** arithmetic, and calls/lookups restricted to the documented math environment (pi, sin, sqrt, ...) are ever evaluated; an attribute access is structurally impossible to reach, not blocklisted. _resolve_int_expr (QASM3 for-loop bounds) used the same pattern but was already protected by a separate pre-filter — verified safe before this fix — now migrated to the same evaluator for consistency, so no raw eval()/exec() remains anywhere in the codebase.
No behavior change for legitimate expressions — every previously-supported parameter syntax evaluates identically.
If you parse QASM text from any source you don't fully trust, upgrade immediately.
v8.1.18 - remove global warnings suppression
Fixed
- Removed a global
warnings.filterwarnings('ignore')fromregistry.py, run unconditionally onimport dense_evolution. It silenced every Python warning process-wide for the importing user's whole session — not just this package's, but their own code's and every other library's too. Inherited unchanged from the original Colab notebook (added in v8.0.6, never reconsidered once this became a real pip package). Concretely masked real signal: the JAX float64→float32 truncationUserWarnings visible throughout this project's own test output (precision silently lost underuse_float32=True) would have been invisible to anyone using the package normally.
v8.1.17 - donate_argnums on beast-mode statevector buffer
Added
donate_argnums=(0,)onrun_circuit_jit_beast_mode's statevector buffer — the only one of_compile_and_run_circuit_jit's four call sites where it's safe (self.svis always rebound immediately after, verified across chunked/repeated calls and separate simulator instances).run_parametric_batch_jit(itsinit_svis avmap-broadcast closure shared across the whole batch) andcircuit_to_energy_fn's VQE loop (samestato_zeroreused every epoch) are deliberately left un-donated — donating there would make JAX raise on the second use instead of helping.- Verified with a real measurement: RSS growth on a 22-qubit/300-gate circuit drops from +89.4MB to +4.5MB on the dev machine. Independently re-verified in a second environment (direction confirmed at a smaller scale, exact numbers differ as expected across hardware — RSS benchmarks are inherently environment-dependent, documented as such in the test itself).
v8.1.16 - Public circuit_to_energy_fn, from_pennylane Python 3.10 fix
Added
dense_evolution.autodiff.circuit_to_energy_fn(circuit, n_qubits)— the real VQE gradient engine (jax.value_and_gradthrough ajax.lax.scancircuit template, verified against finite differences to ~1e-11) is now public API, independent ofdashboard_core.py/Streamlit. Composes with the Qiskit/PennyLane interop bridge:circuit_to_energy_fn(from_pennylane(qnode, ...), n_qubits)gives a real, non-zerojax.grad, whererun_pennylane_circuitalone silently returns0.0.
Fixed
from_pennylanebroke on Python 3.10 — CI caught it (3.10 job red, 3.11/3.12 green). Newer PennyLane releases dropped Python 3.10 support, so pip resolves an older PennyLane (0.42.3) there;qml.to_openqasm(tape)behaves incompatibly between versions for a bare tape/QuantumScript input. Verified against both versions directly.from_pennylanenow picks whichever serialization path the installed PennyLane version actually supports.
Note
- v8.1.15's published PyPI package does not contain this fix, despite its own changelog entry describing it — the fix landed in the repo before the PyPI upload, but the actual uploaded wheel/sdist were built from an earlier commit, and PyPI doesn't allow re-uploading files under an already-published version. No git tag/GitHub Release was ever created for 8.1.15. If you're on 8.1.15, upgrade to 8.1.16.
v8.1.9 - Vector healing cleanup, JAX precision leak fix
- Fixed:
ia_utils/vector_healing.py—enhanced_dense_healing_hybridhad an unreachable third branch (a dense/blend fallback): the underlyingtriggersignal fromevaluate_phi_triggeris strictly binary (0.0/1.0), so the branch could never execute. Collapsed to the genuine 2-state logic (pass-through vs. median fallback); runtime output is unchanged since the branch never ran. - Fixed:
dashboard_core.py—run_simulation/run_vqe_telemetrymutated the process-wide JAXjax_enable_x64flag without ever restoring it, so running one float32 simulation silently downgraded numerical precision for unrelated code later in the same process (e.g. the Vector Healing page, which sets no precision of its own). Both now save/restore the flag around their own execution. - Docs: README's
NoiseModelexample called a nonexistent.apply()method with a wrong parameter name (n_qubitsinstead ofn) — corrected toapply_to_sv(sv, n=..., ...). DocumentedQASMCircuit.to_tuples()andDenseSVSimulator.run_circuit, which already existed and work correctly but were never mentioned in the README.
v8.1.8 - Audit fixes: entanglement, range syntax, Chunk export
- Fixed:
parser.py— controlled two-qubit gates (cx/cy/cz/cp/crz) parsed from QASM in the dashboard layer had control and target swapped relative tocompiler.py's documented(gate, control, target)contract, breaking entanglement for circuits run through the dashboard. The coreQASMCircuit.to_tuples()path was already correct. - Fixed:
parser.py— range syntax (q[0:3]) on single-qubit gates only applied to the first qubit in the range, silently dropping the rest. Now expands into one gate application per qubit, matching the parser's own documented contract. - Fixed:
from dense_evolution import ChunkraisedImportError—Chunkis now re-exported from the package root. Addedget_probabilities()/get_statevector()toChunkfor parity withDenseSVSimulator. - Removed:
dense_evolution/test2.pyandstress_test.py— byte-identical, assertion-free debug scripts that shipped inside every install with 0% test coverage. Their one real check (Kraus noise is genuinely stochastic across independent runs) is now a real regression test.
v8.1.14 - Qiskit/PennyLane interop bridge
Added
- Interop bridge for Qiskit and PennyLane —
from_qiskit/from_pennylaneconvert an existing circuit to aQASMCircuitby reusing the existingQASMParser(viaqiskit.qasm2.dumps/qml.to_openqasm) instead of a bespoke gate-by-gate translator;run_qiskit_circuit/run_pennylane_circuitexecute it directly onDenseSVSimulator. - Bit-order mismatch handled explicitly: Qiskit indexes arrays little-endian (qubit 0 = LSB), Dense-Evolution is MSB-first everywhere else, so
run_qiskit_circuitreorders its output to match Qiskit's own convention (verified againstStatevector(...).probabilities()). PennyLane's own wire order already matches Dense-Evolution's natively (verified the same way), sorun_pennylane_circuitdoes not reorder — kept as two separate code paths on purpose. - New optional extras:
dense-evolution[qiskit],dense-evolution[pennylane].
Fixed
- Found while building the Qiskit bridge:
qiskit.qasm2.dumpsexports composite gates (e.g.mcx) as agate NAME params { ... }definition on a single line — the same brace-delimited block corruption already fixed for QASM3for/if/while/defin v8.1.13, just not covered becausegatewasn't in that fix's keyword set (verified: before the fix, a 4-qubit circuit usingmcxsilently inflated ton_qubits=5with a ghost op). Extended the same brace-matching preprocessor to also stripgatedefinitions cleanly.
v8.1.13 - Real QASM3 for-loop unrolling
Fixed
QASMParserdeclared OpenQASM 3.0 support butfor/if/while/defblocks — brace-delimited, not;-terminated — were mishandled by the naivesplit(';')statement splitter: afor-loop's body was never extracted, and its closing}merged into whatever real statement followed on the same line, corrupting it too. Verified:for int i in [0:2] { h q[i]; } cx q[0],q[1];produced a single ghost op named'}', with the loop body lost and the realcxsilently dropped — executed circuit stayed|000⟩at 100% probability, no error.
Needed for writing VQE ansätze with a loop over qubits instead of one line per qubit.
Added _process_block_constructs, run before the ;-split: for-loops with resolvable integer bounds (literals, or int/const int variables declared earlier in the source — QASM3's inclusive-end range semantics) are now genuinely unrolled by substituting the loop variable into the body per iteration; if/while/def blocks and for-loops with unresolvable bounds are cleanly stripped instead of corrupting the source that follows them.