add classical control / feedforward support - #403
Conversation
167b86a to
f53e8da
Compare
|
Thanks David!! I met with the group yesterday and I received some feedback. Regarding gFlow: we have an internal algorithm for finding Pauli webs thanks to Tianyi Hao. This algorithm is faster and leverages our viewpoint that operations with measurements that are not explicitly specified with feedforward by the user are either T gates or using discards. Therefore, all of our input ZX graphs must have gFlow, and we do not need to check for this when finding Pauli webs. I think this means we can put any bugs I was encountering about finding gFlow using PyZX methods aside. In case it's helpful, the following is a high-level description of the variety of circuits we want to be able to parse eventually:
Apologies the earlier PR got merged before I had the chance to tell relay this to you. I should've noticed it earlier.
The example circuit we have been working with was inputted by hacking the QASM specification to PyZX into something which provided a ZX graph which we knew represented the Steane encoding through calculation by hand. There was a different hack involving padding the file that worked that I think J uses, but I can't remember it right now. Regardless, it would be nice if a Steane encoding circuit intuitively specified with resets and measurements, like this one would now be parseable into something like:
There are some techniques, like fallback rotation synthesis, which rely on conditional operations at an algorithmic level, i.e. repeat-until-success. Representing these would be our most-likely usage of feedforward recognition from the pyZX parser, but we have a long ways to go to support conditional operations or large algorithms in our software architecture, so this is far from immediately needed. Thanks again! |
Not originally my trick. The ones that figured it out were @Zhaoyilunnn and Purva (can't tag, GH won't offer preview suggestion) That said, it's a semi-general trick I've used in several circuits. For the Steane in @KabirDubey 's comment: For a GHZ: And for the two-qubit HTHTHT, to get rid of the now-famous lonely spider.
Ps. Nothing was actually done to the QASM file. We just designed the circuits without the type of operations that would result in a QASM line not supported by previous parser, then added the remainder of things manually once in PyZX. |
f53e8da to
741c371
Compare
|
@KabirDubey Thanks very much for the feedback. I have added a commit to this PR with tests based on your comments. |
There was a problem hiding this comment.
Pull request overview
Adds OpenQASM classical control / feedforward support end-to-end (parse → ZX graph → circuit extraction) and expands regression coverage around resets/measurements and conditional operations, targeting tqec/pyzx interop and fixing #345.
Changes:
- Parse OpenQASM 2/3
if (creg == val) ...statements, including braced blocks, into a newConditionalGate. - Represent conditional single-qubit rotations in ZX graphs using boolean symbolic (
Poly) phases and extract (Z-type) conditionals back from graphs. - Extend QASM serialization to declare classical registers needed by measurements and conditionals; add extensive tests for parsing/round-trips.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_qasm.py |
Adds new regression + round-trip tests for reset/measure circuits and conditional parsing/graph behavior. |
pyzx/circuit/qasmparser.py |
Expands braced if blocks and parses if(reg==val) into ConditionalGate. |
pyzx/circuit/graphparser.py |
Extracts Z-type conditional gates from Poly phases; falls back to symbolic phase gates otherwise. |
pyzx/circuit/gates.py |
Introduces ConditionalGate; adjusts QASM output to attempt emitting symbolic phases; improves measurement symbolic naming. |
pyzx/circuit/__init__.py |
Exports ConditionalGate and updates QASM docstrings + creg/bit declaration generation. |
Comments suppressed due to low confidence (3)
pyzx/circuit/gates.py:290
- Gate.to_qasm() now falls back to emitting symbolic (Poly) phases by interpolating
self.phaseinto the QASM parameter string. The resulting text is not valid OpenQASM for this codebase (e.g., Poly.str uses the Unicode '⋅' operator, and QASMParser.parse_phase_arg cannot parse symbolic expressions at all), so aCircuit.to_qasm()output containing such phases will not round-trip and may confuse users. Consider raising a clear TypeError for symbolic phases (or implementing a dedicated, parseable serialization + matching parser support) instead of emitting an invalid QASM expression.
try:
param = "({}*pi)".format(float(self.phase))
except (TypeError, ValueError):
# Symbolic (Poly) phase — emit as-is.
param = "({}*pi)".format(self.phase)
pyzx/circuit/graphparser.py:204
- graph_to_circuit() falls back to emitting raw ZPhase/XPhase gates with a symbolic Poly phase when it can't interpret the Poly as a ConditionalGate. Downstream code paths (e.g., Circuit.split_phase_gates(), Gate.to_qasm(), and QASMParser.parse_phase_arg) assume numeric phases and will either throw (Poly.denominator) or output non-parseable QASM. Consider either (a) ensuring symbolic-phase gates never reach those exporters/splitting routines, (b) introducing a dedicated gate type for symbolic phases, or (c) updating phase-splitting/export to explicitly handle Poly by leaving it unsplit and raising on QASM export with a clear message.
if isinstance(phase, Poly):
# Only extract Z-type vertices as conditional gates.
# X-type vertices with boolean phases are ambiguous:
# measurement outcomes (from Measurement.to_graph_symbolic_boolean)
# produce the same X spider structure as conditional NOT/XPhase
# gates, so we cannot distinguish them here. Conditional
# Z rotations are unambiguous because measurements never
# create Z spiders with boolean phases.
cgate = None
if t == VertexType.Z:
cgate = _poly_phase_to_conditional_gate(phase, t, int(q))
if cgate is not None:
c.add_gate(cgate)
elif phase != 0:
gate_name = "ZPhase" if t == VertexType.Z else "XPhase"
c.add_gate(gate_name, q, phase=phase)
elif phase!=0 and not split_phases:
pyzx/circuit/graphparser.py:83
- _poly_phase_to_conditional_gate() uses exhaustive evaluation over all bit assignments (O(2^n)) and only emits a warning for register sizes > 16, but still proceeds to evaluate. For larger classical registers this can easily become a practical hang in graph_to_circuit(). Consider adding a hard cutoff / configurable limit that returns None (i.e., skip extraction) beyond some size, or an alternative non-exponential recognition strategy for the specific condition-polynomial form produced by ConditionalGate.
if reg_size > 16:
warnings.warn(
"Conditional gate extraction is O(2^n) in register size; "
"register '{}' has {} bits ({} evaluations).".format(
reg_name, reg_size, 1 << reg_size))
cond_value: Optional[int] = None
inner_phase_value: Optional[Fraction] = None
for val in range(1 << reg_size):
var_map: Dict[Var, Union[float, complex, Fraction]] = {}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@jvdwetering Is there any way to distinguish measurements from conditional X gates in a ZX graph? For example, is there an order in which I can process the vertices such that the first X-spider I see with a boolean symbolic phase, say Alternatively, would it make sense to tag measurement vertices (similar to the |
|
I don't there is, unless you tag things yourself. Maybe you can use the vertex_data field for that? |
|
@jvdwetering This PR is ready to be merged, pending your review/approval. I think we can leave the conditional X gate ambiguity / inability to round-trip for later. |
Fixed in #430. |



Fixes #345. Relates to tqec/tqec#708.
if (creg == val) gate args;statements (including braced blocks)