Skip to content

PyQASM 1.1.0

Latest

Choose a tag to compare

@TheGupta2012 TheGupta2012 released this 17 Aug 11:57
· 9 commits to main since this release
c31b070

Release 1.1.0 (August 17, 2026)

Summary

Added

  • Added support for #pragma statements, which previously raised ValidationError: Unsupported statement of type <class 'openqasm3.ast.Pragma'> and blocked any program carrying one. Pragmas are now passed through loads/validate/unroll/dumps unchanged, and are printed in their #pragma form (the upstream printer emits the bare pragma keyword). A #pragma braket verbatim additionally marks the box that immediately follows it: gates inside a verbatim box are emitted as written instead of being decomposed, so verbatim submissions to Braket QPUs keep the native gates they were built with. (#341)

Improved / Modified

  • Added / updated type hinting for base.py, qasm2.py, qasm3.py, and visitor.py signatures, plus the visit_statement / visit_basic_block signatures in pulse/visitor.py. Fixed grammatical typos in base.py, qasm2.py, qasm3.py, and visitor.py docstrings. Fixes incorrect return types in the docstrings of visitor.py. (#346)
  • Added / updated type hinting for pulse/visitor.py signatures and fixes incorrect return types in the docstrings of pulse/visitor.py. (#365)
  • Breaking: loads() and load() now raise TypeError on an unrecognised keyword argument instead of silently ignoring it, so a misspelling like loads(src, devise_qubits=5) fails at the call site rather than being dropped. Any caller currently passing a stray or misspelled kwarg will break on upgrade. (#356)

Deprecated

Removed

Fixed

  • Fixed loads() silently discarding a falsy kwarg value. The seven documented kwargs were stored behind a walrus truthiness test, so a caller value of 0, 0.0, False, {} or [] was treated as though the kwarg had never been passed. Each kwarg is now tested for presence, with an explicit None still meaning "not passed". What that changes per kwarg: extern_functions={} / [] is now stored rather than dropped; a non-positive device_qubits / device_cycle_time / compiler_angle_type_size / frame_limit_per_port is now rejected with a ValueError at the call site rather than silently dropped, along with a non-numeric or bool value (TypeError); and frame_in_def_cal=None / play_in_cal_block=None now mean "not passed" — previously an explicit None was stored and read as falsy, so passing None disabled the check that the True default enables. Pass False for the old None behaviour. (#356)
  • Fixed unroll(consolidate_qubits=True) emitting two unrelated address spaces for a program mixing declared registers with physical qubits — a consolidated register plus as-written $n references. Such programs now raise a ValidationError naming the physical qubits, and a program using only physical qubits no longer receives an internal register declaration nothing references. (#353)
  • Fixed external and verbatim-box gates counting the depth of the decomposition they skipped: unroll(external_gates=["crz"]) on a single crz reported depth() == 12 while emitting one statement. An external gate now records its own depth, matching how a single-level external custom gate is already handled. (#352)
  • Fixed has_measurements() / remove_measurements() and has_barriers() / remove_barriers() missing occurrences inside for / while / switch bodies on a module that has not been unrolled — e.g. a measurement inside a for loop was invisible and remove_measurements() was a no-op. The statement walker now descends into loop and switch bodies. (#354)
  • Fixed unrolling of rzz/rxx in an OpenQASM 2 program emitting an invalid gphase(...) statement — syntax QASM 2 does not have — so the output was not a loadable QASM 2 program. Global phase is unobservable, so unroll-emitted phases are now dropped for a QASM 2 target; a user-written gphase is still rejected. A conditional left with no body by the drop is removed too, since QASM 2 has no form for an if without a qop. (#351)
  • Fixed the negctrl @ expansion emitting the same x QuantumGate object at both the leading and trailing position, so in-place transformations mutated its operands twice — crashing reverse_qubit_order() (KeyError) and unroll(consolidate_qubits=True) (KeyError: '__PYQASM_QUBITS__') on any unrolled negctrl gate. The two x gates are now distinct statements with fresh operand nodes. (#350)
  • Fixed inaccurate device_qubits entry in QasmModule.unroll() docstring (#349)
  • Fixed remove_idle_qubits() and reverse_qubit_order() ignoring statements nested inside box and if blocks. Top-level operands were rewritten while nested ones kept their old indices, so the result silently addressed the wrong qubits — and when a nested index fell outside the shrunken register, the output was not a loadable program at all. Both passes now walk nested bodies, as do has_measurements() / remove_measurements() and has_barriers() / remove_barriers(); a box left empty by a removal is dropped, since pyqasm rejects a box with no statements. Two consequences of the same blind spot are fixed alongside: a qubit operated on only inside an if block no longer counts as idle, and remove_idle_qubits() no longer raises AssertionError on a program that mixes physical qubits with declared registers. (#345)
  • Fixed unroll(consolidate_qubits=True) raising AttributeError: 'str' object has no attribute 'name' for any gate applied to a physical qubit, e.g. h $1;. Consolidation assumed every gate operand was an IndexedIdentifier, but a physical qubit survives unrolling as Identifier("$1"). Physical qubits are absolute hardware indices belonging to no declared register, so they are now left as written — matching how measure, reset and barrier already treat them. (#344)
  • Fixed unroll() and rebase() emitting statements that share operand AST nodes: gate decompositions passed the same IndexedIdentifier objects into every statement they emitted, so transformations that rewrite qubit indices in place mutated a shared node once per referencing statement. This crashed reverse_qubit_order() (KeyError: -1) and remove_idle_qubits() (KeyError, #331) on any decomposed gate (e.g. crz) whenever the remap was not the identity. Statement constructors in maps/gates.py and Decomposer now copy their qubit operands so every emitted statement owns its nodes. (#333)
  • Fixed statements that OpenQASM 2 cannot condition being accepted as the body of a classical conditional. The QASM 2 grammar admits only a <qop> — a gate application, a measurement or a reset — as the body of an if. barrier is a separate production, and delay/box have no QASM 2 syntax at all, yet if(m==1) barrier q;, if(m==1) delay[10ns] q; and if(m==1) box {...} all validated and were emitted unchanged, producing output that QASM 2 parsers reject. Conditional bodies are now filtered against the <qop> whitelist at any nesting depth, raising a ValidationError that names the offending keyword and its source span. (#339)
  • Fixed remove_idle_qubits(in_place=False) updating the qubit count on the wrong module: the original module's num_qubits was decremented while the returned copy kept the stale pre-removal count. The copy's AST was already correct; only the counters were swapped. (#336)
  • Fixed remove_idle_qubits() raising KeyError when the unrolled AST contains operand nodes shared across multiple statements (e.g. the crz decomposition) and an idle lower-indexed qubit shifts the register indices. _remap_qubits now remaps each operand node exactly once instead of once per statement that references it. (#332)
  • Fixed box duration validation summing delay durations across all qubits instead of tracking each qubit's timeline. Delays on disjoint qubits run in parallel, so box[300ns] { delay[200ns] q[0]; delay[200ns] q[1]; } was rejected while the identical schedule written as a broadcast delay (delay[200ns] q;) was accepted. Delays are now accumulated per qubit and the box is validated against the busiest single timeline; the error message names the offending qubit. Nested boxes now also contribute their declared duration to the enclosing box's timelines (previously the accumulator was reset when an inner box closed, dropping all inner delay accounting). (#330)

Dependencies

  • Updated docutils requirement from <0.23 to <0.24. (#319)
  • Bumped codecov/codecov-action from 6.0.0 to 7.0.0. (#321)
  • Bumped actions/checkout from 6 to 7. (#323)
  • Updated pillow requirement from <12.3.0 to <12.4.0. (#324)
  • Bumped actions/setup-python from 6 to 7. (#328)

New Contributors

Full Changelog: v1.0.4...v1.1.0