-
Notifications
You must be signed in to change notification settings - Fork 83
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
transformations: add qref/qssa conversions #2770
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #2770 +/- ##
==========================================
+ Coverage 89.79% 89.80% +0.01%
==========================================
Files 376 378 +2
Lines 48079 48181 +102
Branches 7370 7372 +2
==========================================
+ Hits 43172 43269 +97
- Misses 3757 3762 +5
Partials 1150 1150 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love to see docstrings on the patterns and/or passes that explain what they do!
xdsl/dialects/qref.py
Outdated
@property | ||
@abstractmethod | ||
def is_gate(self) -> bool: | ||
""" | ||
Is this operation a gate | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a docstring to explain why you need this helper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, also a more explicit description of what a gate is in this context.
# Create replacement operation | ||
new_op: QssaBase = op.ssa_op() | ||
# For gates there are no results to replace | ||
new_results = () if new_op.is_gate else new_op.results |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this check be replaced with a check of "has the new_op results that are a qubit"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is_gate
should be equivalent to "The qubit-typed operands match one-to-one with the qubit-typed results".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property could be removed and replaced by an actual check of this. Essentially the gates
are the things we actually need to modify in the transform as they switch between having results and having no results depending on the dialect. Alloc and measure on the other hand are basically unchanged between dialects (apart from their types)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I'd suggest to separate out dialect and transform code
xdsl/dialects/qref.py
Outdated
@property | ||
def is_gate(self) -> bool: | ||
return False | ||
|
||
res: VarOpResult = var_result_def(qubit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style comment: Can you move res
before the methods please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AntonLydike I maybe incorrectly believed that this would cause some style check to fail, since it is done this way across all xdsl ops I've seen.
xdsl/dialects/qref.py
Outdated
@@ -70,6 +94,17 @@ def print(self, printer: Printer): | |||
class HGateOp(QRefBase): | |||
name = "qref.h" | |||
|
|||
def ssa_op(self) -> qssa.HGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best to keep dialect conversion functions in the converter rather than on the dialect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure if this is desirable, as I don't see a way around having a big if isinstance
chain in the converter without doing it this way. Another advantage of this method is that if a new operation is added, then the implementation of these two functions is forced
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could match on the specific op, create a new one, and move any shared code to a helper function., e.g. something along the lines of:
class ConvertHGateToQssaPattern(RewritePattern):
@op_type_rewrite_pattern
def match_and_rewrite(self, op: qref.HGateOp, rewriter: PatternRewriter):
replace_with_ssa(
op,
qssa.HGateOp.create(
operands=op.operands,
result_types=(qssa.qubit,),
attributes=op.attributes,
),
rewriter,
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still runs into the problem of not forcing the conversion to be written for new ops, though I guess a test could be written that ensures this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of like the current setup. The only thing I might suggest is to have a QuantumGate
trait instead of is_gate()
but even that seems like a nit, and like less good a way to guarantee that each op will have to implement these methods when it's added. It also feels like the two dialects are coupled in a pretty fundamental way, which seems like an ok design decision, making the abstract method style of conversion quite natural. I'm not sure in what situation in the future we would regret merging this PR as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still runs into the problem of not forcing the conversion to be written for new ops, though I guess a test could be written that ensures this
This could also be a feature. Forcing a conversion to be made also implies an unwritten guarantee that there will always be a valid 1:1 conversion, and that neither of these dialects will ever hold concepts that are not directly convertible, now or in the future. I just wanted to draw attention to the fact that this tight coupling might be a rather committal decision. I don't fully know the plans for these dialects, so if you're happy then please go ahead and merge.
xdsl/dialects/qref.py
Outdated
@@ -85,6 +120,17 @@ def __init__(self, input: SSAValue): | |||
class CNotGateOp(QRefBase): | |||
name = "qref.cnot" | |||
|
|||
def ssa_op(self) -> qssa.CNotGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qref.py
Outdated
@@ -102,6 +148,17 @@ def __init__(self, in1: SSAValue, in2: SSAValue): | |||
class CZGateOp(QRefBase): | |||
name = "qref.cz" | |||
|
|||
def ssa_op(self) -> qssa.CZGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qref.py
Outdated
@@ -119,6 +176,17 @@ def __init__(self, in1: SSAValue, in2: SSAValue): | |||
class MeasureOp(QRefBase): | |||
name = "qref.measure" | |||
|
|||
def ssa_op(self) -> qssa.MeasureOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qssa.py
Outdated
name = "qssa.alloc" | ||
|
||
def ref_op(self) -> qref.QRefAllocOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qssa.py
Outdated
name = "qssa.h" | ||
|
||
def ref_op(self) -> qref.HGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qssa.py
Outdated
name = "qssa.cnot" | ||
|
||
def ref_op(self) -> qref.CNotGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qssa.py
Outdated
name = "qssa.cz" | ||
|
||
def ref_op(self) -> qref.CZGateOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
xdsl/dialects/qssa.py
Outdated
name = "qssa.measure" | ||
|
||
def ref_op(self) -> qref.MeasureOp: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise
Co-authored-by: Sasha Lopoukhine <superlopuh@gmail.com>
08fbe7e
to
45ea1f4
Compare
# For gates we replace any other occurences of the original operands | ||
# with the results of the new operation | ||
|
||
old_operands = tuple(new_op.operands) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@math-fehr I requested a review to see if you knew a better way to do this section, but feel free to ignore if you're too busy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff!
""" | ||
Is this operation a gate? | ||
Qref gates represent standard quantum logic gates. | ||
They should have no results. | ||
The results of the generated gate must be rewired when converting to the qssa dialect. | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Nice and concise docstring!
class QubitBase(IRDLOperation, ABC): | ||
pass | ||
class QssaBase(IRDLOperation, ABC): | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this renaming, big fan 💪
|
||
def apply(self, ctx: MLContext, op: builtin.ModuleOp) -> None: | ||
PatternRewriteWalker( | ||
ConvertQRefToQssaPattern(), apply_recursively=False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Supernit: I'm 99% sure you don't need to specify apply_recursively=False
here as it should not impact the result at all.
Every non-standard option specified unnecessarily introduces reviewer overhead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree, it has a performance impact, and should be added whenever the rewrites don't insert operations that could be recursively matched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are probably right, think I was worried about things being converted in the "wrong" order which could maybe cause verification problems (I'm not actually sure when the verifier is run)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests still succeed without it, so I guess it's just a performance thing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're happy with a tight coupling of these dialects (see discussion above) I'm happy to approve.
Built on top of #2768
Adds two passes which convert back and forth between the qssa and qref dialects. Also adds filecheck tests that test each pass and roundtrip of the passes
I think the
convert-qref-to-qssa
is particularly hacky, with the saving the old operands of the operation into a tuple, running replace on the operands and then inserting back the old ones. Would be keen to hear better ways to achieve the same behaviour