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 DropNegligible transpiler pass #12384

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

kevinsung
Copy link
Contributor

@kevinsung kevinsung commented May 9, 2024

Summary

Fixes #8654.

Draft PR because I would like feedback before adding more code.

Details and comments

The pass removes a gate if:

  • The gate type is explicitly listed in DROP_NEGLIGIBLE_GATE_CLASSES.
  • All of the gate parameters are close to zero up to the specified absolute tolerance (default 1e-8).

DROP_NEGLIGIBLE_GATE_CLASSES should list gates with the property that if all of their parameters are close to zero, then the gate's effect is small. It does not guarantee any error bounds based on the tolerance, so in principle this pass can drop gates whose effect is not actually negligible. In practice, I don't expect it to be an issue.

So far, I've only added a few gates to DROP_NEGLIGIBLE_GATE_CLASSES. If people agree with this approach, I'll go through the rest of the gate library and add the appropriate ones.

@mtreinish @ajavadia thoughts?

@coveralls
Copy link

coveralls commented May 9, 2024

Pull Request Test Coverage Report for Build 9038654167

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 27 of 27 (100.0%) changed or added relevant lines in 3 files are covered.
  • 8 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.02%) to 89.649%

Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 2 92.88%
crates/qasm2/src/parse.rs 6 97.61%
Totals Coverage Status
Change from base Build 9032084393: 0.02%
Covered Lines: 62240
Relevant Lines: 69426

💛 - Coveralls

Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

I think having a pass like this in the library makes a lot of sense. We don't want to run it by default, but if you know it makes sense in your use case then having this available is useful. We could even look at adding it to the end of the init stage in the preset pass managers and tying the atol value to the approximation_degree value somehow.

Comment on lines 64 to 65
if not isinstance(node.op, DROP_NEGLIGIBLE_GATE_CLASSES):
continue
Copy link
Member

Choose a reason for hiding this comment

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

Since you're using a fixed set of gates in this PR one thing you should do here is before looping is check if the names of the gate classes intersect with dag.count_ops(). That'll be much faster in the negative case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't dag.count_ops also need to iterate through the nodes of the dag?

Copy link
Member

Choose a reason for hiding this comment

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

DAGCircuit maintains a dictionary of counts that it updates whenever an op is added or removed from the circuit. This is explicitly to support cases like this to do constant time checks for the instructions presence in the circuit. The only exceptions are if you set recurse=True (which is the default) and there are any control flow ops in the circuit.

Copy link
Contributor Author

@kevinsung kevinsung May 10, 2024

Choose a reason for hiding this comment

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

dag.count_ops() stores strings, but I'm using the gate class itself to check the gate type. Any advice? Should I also just use strings? That doesn't seem robust because any gate can have any arbitrary name. But I guess there's many places in Qiskit which assume to know the type of a gate based on its name.

Comment on lines 68 to 69
if np.allclose(node.op.params, 0, atol=self.atol):
dag.remove_op_node(node)
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we want to generalize this for any Gate object, we could do something similar to this but instead compare node.op.to_matrix() against an identity matrix with the tolerance.

That being said doing this will obviously be slower though. If we're concerned about runtime performance I'd probably suggest moving this away from using np.allclose() and just do something like:

Suggested change
if np.allclose(node.op.params, 0, atol=self.atol):
dag.remove_op_node(node)
if all(math.isclose(x, 0, atol=self.atol) for x in node.op.params):
dag.remove_op_node(node)

because I expect the numpy overhead of converting params to an array and all the checking involved with that will be slower that just checking each value in params manually, especially as in general there won't be very many parameters for a gate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea to use the built-ins instead of Numpy, done.

I don't think we should try to construct a gate's matrix, due to the performance overhead, and also because it's not feasible for all gates (cost is exponential in the number of qubits). However, I do think we should let the user pass additional gate classes to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added an argument for passing additional gate types.

test/python/transpiler/test_drop_negligible.py Outdated Show resolved Hide resolved
@levbishop
Copy link
Member

I like the general idea to drop negligible gates but think this implementation is not optimal. TwoQubitBasisDecomposer will already drop all negligible 2q gates, not just the ones in a hardcoded list; will do it with a controlled approximation (the average-gate-fidelity of the approximation, rather than just a parameter value); and will optimize away 2q gates while keeping an optimal 1q approximation. Because of how the default passmanagers work, this TwoQubitBasisDecomposer only runs after routing, losing most of the benefit of dropping negligible 2q gates: I've been meaning for a while to write up some notes on a better default passmanager flow - I'll see if I can get something down for discussion over the weekend.

@mtreinish
Copy link
Member

The concern I have with just relying on the TwoQubitBasisDecomposer for this is the same issue we had with trying to do something similar with the 1q case in #11354 . As soon as we added Optimize1qGatesDecomposition to the init stage there users testing against main (mainly qiskit-experiments) reported issues that we were breaking transpilation for targets with a discrete basis. So we very quickly reverted this in #11360. In general I really like the idea of doing the peephole optimization early in the pipeline but we just have to be careful around how we do it. Regardless I look forward to seeing your proposal on an improved pipeline.

@kevinsung
Copy link
Contributor Author

TwoQubitBasisDecomposer is not appropriate for some of the uses cases I have in mind. As an example, ffsim defines a high-level gate DiagonalCoulombJW. This gate is defined by a matrix, and it has a gate decomposition into CPhaseGates based on that matrix. Sometimes you know that some entries are close to zero, so you just want to get rid of them. Or, you might want to get rid of some gates based on a threshold just to make it easier to map to hardware. This workflow would not use TwoQubitBasisDecomposer and you might want to do this independently from the standard "preset staged pass manager" transpilation workflow.

@levbishop
Copy link
Member

try:
    decomp = TwoQubitWeylDecomposition(node.op, fidelity=fid,  _specialization=_specializations.IdEquiv)
except QiskitError:
    pass
else:
    <replace dag node with 1q gates from decomp>

@kevinsung
Copy link
Contributor Author

try:
    decomp = TwoQubitWeylDecomposition(node.op, fidelity=fid,  _specialization=_specializations.IdEquiv)
except QiskitError:
    pass
else:
    <replace dag node with 1q gates from decomp>

Doing linear algebra is too expensive when the aim is just to drop gates with small angles.

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.

Transpiler pass to remove gates with negligible effects
4 participants