Skip to content

Stop emitting empty quantum registers and validate sizes in the direct PyPHIR reader - #895

Open
ciaranra wants to merge 1 commit into
devfrom
phir-json-empty-register-regression
Open

ciaranra wants to merge 1 commit into
devfrom
phir-json-empty-register-regression

Conversation

@ciaranra

Copy link
Copy Markdown
Member

Follow-up to #892, which merged before this fix round landed. #892 introduced a live regression on dev and this closes it, plus two gaps its independent review found.

1. Regression: PECOS's own empty circuit no longer round-trips

#892 made a quantum declaration's size required and strictly positive, matching specification/v0.1/spec.md:141 and upstream phir.model.QVarDefine (size: int with Gt(gt=0)). Correct rule — but three PECOS producers emit size: 0 for an empty register, so PECOS generates documents its own readers now reject.

On dev at c0d311267, to_phir_dict(QuantumCircuit()) emits:

[{"data": "qvar_define", "data_type": "qubits", "variable": "q", "size": 0}]

and the converter, AST deserialization, the Rust interpreter and the engine all reject it. With this change the same call emits [] and round-trips through every entry point.

The rule is not relaxed. The producers are fixed:

  • python/quantum-pecos/src/pecos/circuits/qc2phir.py — skips empty quantum and classical declarations, and no longer emits the implicit qvar_spec register when the circuit has no qubits
  • crates/pecos-qasm/src/qasm_to_phir_json.rs — skips empty quantum and classical registers, and drops them from automatic exports so an export cannot name an undeclared register
  • exp/zlup/src/codegen/phir.rs — skips zero-capacity allocators

Swept for others: the five examples/phir programs, the PHIR and v0.1 READMEs, the specification examples and all thirteen example notebooks contain no zero-size declarations.

2. The direct Python reader did not enforce the rule

#892's agreement table claimed Python matched. It did not — PyPHIR.from_phir validated only the type. Measured on that commit: size absent raised a bare KeyError, size 0 was accepted with count 0, size -1 accepted with count −1, and size true accepted as 1 (because bool is an int subclass). It now requires a positive integer and rejects bool explicitly, with the register named in the message. Python's duplicate-overwrite behaviour is untouched.

3. Test coverage the earlier suite missed

Three independent mutations passed all six of #892's new Rust tests: removing from_json header registration, the processor silently coercing size zero to one, and skipping engine runtime declarations. The table test never reached the block executor or engine command execution, and one test discarded the engine without asserting its qubit count.

Added: a direct processor zero-size rejection test, engine assertions on qubit count, resolved ids and emitted command ids, and execution of declarations through both the block executor and engine commands. All three mutations now fail, along with the three from #892 (inferring a missing size, accepting a wrong type, rejecting an identical redeclaration).

Python and Rust share one checked-in fixture, crates/pecos-phir-json/tests/fixtures/empty_quantum_circuit.phir.json: Python asserts the generator's exact output against it, Rust consumes that same document through all four entry points. No cross-language dependency is added in either direction.

Verification

cargo clippy --locked --workspace --all-targets --all-features -- -D warnings, cargo test -p pecos-phir-json -p pecos-qasm -p zlup --no-fail-fast (1689 passed), cargo fmt --check, just python-ci-build-test, just pytest-ci-core-shard rest (6752 passed), just python-ci-lint. Branch is level with dev.

How this was produced

Implemented by OpenAI gpt-6-astra (Codex CLI 0.154.0) from findings I wrote after an independent Codex review arm, blind to the implementation session, attacked #892 with build access. That arm found all three items here; I reproduced the regression myself before acting on it, and it also confirmed that no existing validation was lost at any entry point and that #892's removal of the engine size scan is safe. The producer sweep beyond qc2phir.py — the QASM emitter and ZLUP — came from asking whether the same defect existed elsewhere rather than fixing only the reported instance. Claude reviewed every hunk, re-ran the named lanes, and mutation-checked the producer fix independently. Posted at the maintainer's request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant