Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for semantic equality checking of Exprs in DAGCircuit #10367

Merged
merged 3 commits into from
Jul 19, 2023

Conversation

jakelishman
Copy link
Member

Summary

This adds support to semantically check the variables of Expr nodes that occur within DAGCircuit constructs, using similar logic to the remapping done by the existing condition checks. As a knock-on effect of how the control-flow introspection was added to allow these checks, the canonicalisation performed by qiskit.test._canonical.canonicalize_control_flow is now inherent to the DAGCircuit equality methods, largely removing the need for that explicit canonical form in tests.

The dispatch of QuantumCircuit.__eq__ to DAGCircuit.__eq__ means that direct comparisons of QuantumCircuits will also benefit from this change.

As a partial implementation detail, the semantic checking is achieved and defined by the function expr.structurally_equivalent, with key functions for mapping the variables. The naming discrepancy (semantic versus structural) is deliberate. Classical expressions are considered "equal" if and only if their tree structures match exactly; there is no attempt to move the classical expressions into some canonical form, as this is not typically simple; even mathematically symmetric operations typically impact the order of sub-expression evaluation, and we don't want to get into the weeds of that. Better to just define equality as "structurally exactly equal", and have the key function just so alpha-renaming-compatible variables can still be canonicalised into something that can be compared between expressions.

Details and comments

Close #10359. Depends on #10362 (and the rest of its chain). Changelog to come as part of #10331.

@jakelishman jakelishman added the Changelog: New Feature Include in the "Added" section of the changelog label Jun 30, 2023
@jakelishman jakelishman added this to the 0.25.0 milestone Jun 30, 2023
@jakelishman jakelishman requested a review from a team as a code owner June 30, 2023 23:34
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

@coveralls
Copy link

coveralls commented Jun 30, 2023

Pull Request Test Coverage Report for Build 5592058798

  • 128 of 143 (89.51%) changed or added relevant lines in 5 files are covered.
  • 19 unchanged lines in 4 files lost coverage.
  • Overall coverage increased (+0.005%) to 86.058%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/circuit/classical/expr/visitors.py 34 37 91.89%
qiskit/dagcircuit/dagnode.py 79 91 86.81%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 93.76%
qiskit/transpiler/passes/synthesis/unitary_synthesis.py 1 90.33%
crates/qasm2/src/lex.rs 5 91.9%
crates/qasm2/src/parse.rs 12 97.11%
Totals Coverage Status
Change from base Build 5592026163: 0.005%
Covered Lines: 72507
Relevant Lines: 84254

💛 - Coveralls

Copy link
Contributor

@atharva-satpute atharva-satpute left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the typos

This adds support to semantically check the variables of `Expr` nodes
that occur within `DAGCircuit` constructs, using similar logic to the
remapping done by the existing condition checks.  As a knock-on effect
of how the control-flow introspection was added to allow these checks,
the canonicalisation performed by
`qiskit.test._canonical.canonicalize_control_flow` is now inherent to
the `DAGCircuit` equality methods, largely removing the need for that
explicit canonical form in tests.

The dispatch of `QuantumCircuit.__eq__` to `DAGCircuit.__eq__` means
that direct comparisons of `QuantumCircuit`s will also benefit from this
change.

As a partial implementation detail, the semantic checking is achieved
and defined by the function `expr.structurally_equivalent`, with key
functions for mapping the variables.  The naming discrepancy (semantic
versus structural) is deliberate. Classical expressions are considered
"equal" if and only if their tree structures match exactly; there is no
attempt to move the classical expressions into some canonical form, as
this is not typically simple; even mathematically symmetric operations
typically impact the order of sub-expression evaluation, and we don't
want to get into the weeds of that.  Better to just define equality as
"structurally exactly equal", and have the key function just so
alpha-renaming-compatible variables can still be canonicalised into
something that can be compared between expressions.
@jakelishman
Copy link
Member Author

Rebased on top of main. Merging this unblocks three other PRs: #10375, #10392 and #10400.

self.other = other.left
if not node.left.accept(self):
return False
self.other = other.right
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, clever! You reset the reference using the value of self.other captured in the stack.

Copy link
Member Author

@jakelishman jakelishman Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah exactly - when I originally thought about this I thought I was going to need to have the visitor store a stack parameter itself, then I realised that we just get it for free through the recursive call structure. I would have liked to avoid the self.other attribute and just explicitly pass the state, but that would have involved a refactor of the visitor base class, and I'm not certain there was a huge benefit.

Comment on lines 31 to 34
qubit_order (Iterable[Qubit] or None): the ordered that the qubits should be indexed in the
output DAG. Defaults to the same order as in the circuit.
clbit_order (Iterable[Clbit] or None): the ordered that the clbits should be indexed in the
output DAG. Defaults to the same order as in the circuit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ordered => order, in both places

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7b4e791.

@@ -16,7 +16,7 @@
from qiskit.dagcircuit.dagcircuit import DAGCircuit


def circuit_to_dag(circuit, copy_operations=True):
def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_order=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is, this function could be used to create a DAG that doesn't have the same qubits as circuit, at least in the case where the bits aren't in a register or used by an instruction, right? Perhaps we should instead take iterables of indices to reorder by, or do some checking to make sure all bits from circuit are accounted for, and no extra bits were provided.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of providing indices into a foreign list because it tends to be annoying to compute and it can be confusing whether it's a forwards or backwards permutation ("put bit i in new position order[i]" or "put bit order[i] in new position i" - both make sense), and we'd still need to check for duplicate indices and the correct length of list anyway. I can put in a check that the bits are an exact permutation, though, no problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7b4e791.

Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks for the updates.

@kevinhartman kevinhartman added this pull request to the merge queue Jul 18, 2023
@jakelishman jakelishman removed this pull request from the merge queue due to a manual request Jul 19, 2023
@jakelishman jakelishman added this pull request to the merge queue Jul 19, 2023
Merged via the queue into Qiskit:main with commit f5f5852 Jul 19, 2023
@jakelishman jakelishman deleted the expr/dag-eq branch July 19, 2023 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for Expr values to DAGCircuit.__eq__
6 participants