Skip to content

Releases: tatopenn-cell/Dense-Evolution

v8.1.21 - Chunk multi-chunk JIT fusion, QASMCircuit.__iter__

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 17:54

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-JIT apply_gate_1q/apply_gate_2q — measured 6x slower than run_circuit_jit_beast_mode on an identical workload. Replaced with a single jax.lax.scan over the whole circuit operating directly on the stacked (num_chunks, chunk_dim) representation — never materializing a (2**n_qubits,) array, preserving the anti-OOM property Chunk exists for. All 6 gate/qubit-location cases were ported formula-for-formula from the old implementation and verified case-by-case against a DenseSVSimulator reference before it was removed. Gate coverage now built via GATE_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.

PyPI: https://pypi.org/project/dense-evolution/8.1.21/

v8.1.20 - PennyLane wire-remap fix, depolarizing noise channel fix

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 14:48

Both bugs found via independent fuzz testing on Colab, verified directly before fixing.

Fixed

  • from_pennylane/run_pennylane_circuit silently renumbered qubits whenever wires weren't touched in ascending order — both qml.to_openqasm and the older tape.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 explicit wires= argument to force true wire order into the export.
  • NoiseModel.apply_to_sv's depolarizing channel (and combined's depolarizing sub-channel) picked X/Y/Z using thresholds p/3, 2p/3 against a draw uniform on the full [0,1) range, instead of the fixed 1/3, 2/3 — skewed every depolarizing circuit heavily toward Z regardless of p (verified: at p=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.

PyPI: https://pypi.org/project/dense-evolution/8.1.20/

v8.1.19 - Security fix: eval() sandbox escape in QASMParser

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 11:02

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.

PyPI: https://pypi.org/project/dense-evolution/8.1.19/

v8.1.18 - remove global warnings suppression

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 09:04

Fixed

  • Removed a global warnings.filterwarnings('ignore') from registry.py, run unconditionally on import 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 truncation UserWarnings visible throughout this project's own test output (precision silently lost under use_float32=True) would have been invisible to anyone using the package normally.

PyPI: https://pypi.org/project/dense-evolution/8.1.18/

v8.1.17 - donate_argnums on beast-mode statevector buffer

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 08:38

Added

  • donate_argnums=(0,) on run_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.sv is always rebound immediately after, verified across chunked/repeated calls and separate simulator instances). run_parametric_batch_jit (its init_sv is a vmap-broadcast closure shared across the whole batch) and circuit_to_energy_fn's VQE loop (same stato_zero reused 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).

PyPI: https://pypi.org/project/dense-evolution/8.1.17/

v8.1.16 - Public circuit_to_energy_fn, from_pennylane Python 3.10 fix

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 21 Jul 07:29

Added

  • dense_evolution.autodiff.circuit_to_energy_fn(circuit, n_qubits) — the real VQE gradient engine (jax.value_and_grad through a jax.lax.scan circuit template, verified against finite differences to ~1e-11) is now public API, independent of dashboard_core.py/Streamlit. Composes with the Qiskit/PennyLane interop bridge: circuit_to_energy_fn(from_pennylane(qnode, ...), n_qubits) gives a real, non-zero jax.grad, where run_pennylane_circuit alone silently returns 0.0.

Fixed

  • from_pennylane broke 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_pennylane now 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.

PyPI: https://pypi.org/project/dense-evolution/8.1.16/

v8.1.9 - Vector healing cleanup, JAX precision leak fix

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 20 Jul 15:45
  • Fixed: ia_utils/vector_healing.pyenhanced_dense_healing_hybrid had an unreachable third branch (a dense/blend fallback): the underlying trigger signal from evaluate_phi_trigger is 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.pyrun_simulation / run_vqe_telemetry mutated the process-wide JAX jax_enable_x64 flag 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 NoiseModel example called a nonexistent .apply() method with a wrong parameter name (n_qubits instead of n) — corrected to apply_to_sv(sv, n=..., ...). Documented QASMCircuit.to_tuples() and DenseSVSimulator.run_circuit, which already existed and work correctly but were never mentioned in the README.

PyPI: https://pypi.org/project/dense-evolution/8.1.9/

v8.1.8 - Audit fixes: entanglement, range syntax, Chunk export

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 20 Jul 15:45
  • 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 to compiler.py's documented (gate, control, target) contract, breaking entanglement for circuits run through the dashboard. The core QASMCircuit.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 Chunk raised ImportErrorChunk is now re-exported from the package root. Added get_probabilities()/get_statevector() to Chunk for parity with DenseSVSimulator.
  • Removed: dense_evolution/test2.py and stress_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.

PyPI: https://pypi.org/project/dense-evolution/8.1.8/

v8.1.14 - Qiskit/PennyLane interop bridge

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 20 Jul 21:56

Added

  • Interop bridge for Qiskit and PennyLane — from_qiskit/from_pennylane convert an existing circuit to a QASMCircuit by reusing the existing QASMParser (via qiskit.qasm2.dumps / qml.to_openqasm) instead of a bespoke gate-by-gate translator; run_qiskit_circuit/run_pennylane_circuit execute it directly on DenseSVSimulator.
  • Bit-order mismatch handled explicitly: Qiskit indexes arrays little-endian (qubit 0 = LSB), Dense-Evolution is MSB-first everywhere else, so run_qiskit_circuit reorders its output to match Qiskit's own convention (verified against Statevector(...).probabilities()). PennyLane's own wire order already matches Dense-Evolution's natively (verified the same way), so run_pennylane_circuit does 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.dumps exports composite gates (e.g. mcx) as a gate NAME params { ... } definition on a single line — the same brace-delimited block corruption already fixed for QASM3 for/if/while/def in v8.1.13, just not covered because gate wasn't in that fix's keyword set (verified: before the fix, a 4-qubit circuit using mcx silently inflated to n_qubits=5 with a ghost op). Extended the same brace-matching preprocessor to also strip gate definitions cleanly.

PyPI: https://pypi.org/project/dense-evolution/8.1.14/

v8.1.13 - Real QASM3 for-loop unrolling

Choose a tag to compare

@tatopenn-cell tatopenn-cell released this 20 Jul 20:45

Fixed

  • QASMParser declared OpenQASM 3.0 support but for/if/while/def blocks — brace-delimited, not ;-terminated — were mishandled by the naive split(';') statement splitter: a for-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 real cx silently 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.

PyPI: https://pypi.org/project/dense-evolution/8.1.13/