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

Support for externally linked gates #59

Merged
merged 14 commits into from
Nov 12, 2024
Merged

Support for externally linked gates #59

merged 14 commits into from
Nov 12, 2024

Conversation

Sola85
Copy link
Contributor

@Sola85 Sola85 commented Nov 5, 2024

This is my preliminary PR for externally linked gates, as suggested here qBraid/qbraid-qir#167.

This is probably not ready to merge, but I figured it's easier if I get some feedback first. I will follow up with a PR for qbraid-qir shortly. With both changes, external gates work in qbraid-qir, at least for my use-case.

With this change pyqasm accepts a list of custom gates, that should not be unrolled into their definition.

e.g. with qasm3_module.unroll(external_gates=["mygate"]), the following QASM would be unrolled into an AST that contains a "mygate" node, rather than the nodes in the definiton of "mygate".

OPENQASM 3.0;
include "stdgates.inc";
gate mygate(p0) _gate_q_0, _gate_q_1 {
  h _gate_q_1;
  cx _gate_q_0, _gate_q_1;
  rz(p0) _gate_q_1;
  cx _gate_q_0, _gate_q_1;
  h _gate_q_1;
}
bit[2] c;
qubit[2] q;
ry(pi/2) q[0];
mygate(pi/2) q[0], q[1];

pyqasm/visitor.py Outdated Show resolved Hide resolved
pyqasm/visitor.py Outdated Show resolved Hide resolved
@TheGupta2012
Copy link
Contributor

TheGupta2012 commented Nov 5, 2024

Hey, thanks for the changes! I've given some comments on this and the linked PR.

The only change I see is making the acceptable external_gates more generic and not confined to user defined custom gates. There may very well be a use case when someone wants to stop the decomposition of, say, a crz gate and pass that in the external_gates list. Since we support that gate natively in our unroller (thus, not requiring a gate definition), it would not be possible to specify that in the external_gates variable with the current logic.

@TheGupta2012
Copy link
Contributor

Hey, @Sola85 just a heads up - we added some new commits to main so you'll probably need to rebase the PR against it

@Sola85
Copy link
Contributor Author

Sola85 commented Nov 7, 2024

Hi @TheGupta2012, thanks for the feedback! I hadn't realized that pyqasm unrolls some gates into other gates and therefore I thought it didn't make sense to mark non-custom gates as external.

With my last commit, it should be possible to mark any gate as external (I've added a test that tests this for the u3 gate). What is still missing is the unrolling of gates with multiple targets (e.g. u3(0.5, 0.5, 0.5) q[0], q[1];). Currently this doesn't work for external gates (and I think also not for custom gates).
I was wondering if it made sense to offload this unrolling logic into somewhere outside of _visit_*_gate_operation(), since it is identical for all types of gates.
Maybe this could be placed inside a new function and called from _visit_generic_gate_operation()?

pyqasm/visitor.py Outdated Show resolved Hide resolved
@TheGupta2012
Copy link
Contributor

TheGupta2012 commented Nov 8, 2024

I agree, the unrolling of gates with multiple targets is not handled for custom operations. This is because of the assumption that any custom gate will eventually be broken down into simpler gates with either the user supplied definition or the one present in the maps.py.

We can have something like a _broadcast_gate_operation which will essentially use the same logic present in _visit_basic_gate_operation for decomposing multiple targets but, as you suggested, make it more generic so that it can be used on every gate. This method would return a list of QuantumGates with the qubit arguments transformed according to the op_count of gate.

@TheGupta2012
Copy link
Contributor

Another thing to note is about the qubit depths. We recently added the support for calculating the depth of a quantum program in pyqasm and with this change, there would be some impact on the depth calculation logic.

Eg, if we are unrolling with an external gate custom_1 -

OPENQASM 3.0;
qubit[3] q;
gate custom_1 a { 
     h a;
     x a;
} 
custom_1 q[0];
custom_1 q[1];

the final unrolled qasm would look like -

OPENQASM 3.0;
qubit[3] q;
custom_1 q[0];
custom_1 q[1];

Inside the depth calculation, we are making a call to unroll before calculating the depth using individual qubits. A couple of points to note here -

  1. Since the final unrolled_qasm only contains custom_1 op, which might very well be the basis gate of a QC, the depth of the program should come out to be 1. But the depth we get is 2 as internally we visit the custom_1 definition and update the qubit depths to 2 -
In [16]: import pyqasm

In [17]: qasm_str = """
    ...: OPENQASM 3.0;
    ...: qubit[3] q;
    ...: gate custom_1 a { 
    ...:      h a;
    ...:      x a;
    ...: } 
    ...: custom_1 q[0];
    ...: custom_1 q[1];"""

In [18]: mod = pyqasm.load(qasm_str)

In [19]: mod.unroll(external_gates = ["custom_1"])

In [20]: mod.unrolled_qasm.splitlines()
Out[20]: 
['OPENQASM 3.0;',
 'include "stdgates.inc";',
 'qubit[3] q;',
 'custom_1 q[0];',
 'custom_1 q[1];']

In [21]: mod.depth()
Out[21]: 2

This is incorrect as we want to get the depth of the current unrolled program and not unroll it yet again.

  1. The _visit_external_gate_operation should also update the qubit depths before returning the resulting gate. Moreover, when it is calling the _visit_basic_gate_operation, qubit depths are implicitly getting updated which is not correct.

This might expand the scope a bit and I think for now you can add a test in tests/qasm3/test_depth.py with a @pytest.mark.skip decorator highlighting this bug. Once this PR merges, I'll create a follow up issue to tackle it.

@Sola85
Copy link
Contributor Author

Sola85 commented Nov 11, 2024

Changes since my last comment:

  • I have isolated the broadcasting logic into a separate function which works for basic gates and external gates
  • There is an additional test for broadcasting external gates
  • I have added a test for depth-calculation of external gates, but this is currently skipped (and, as you said, could be addressed in a separate issue)

I have not yet extended the broadcasting logic to custom gates i.e. something like

gate mygate() _gate_q_0,  {
  h _gate_q_0;
}
qubit[2] q;
mygate() q;

still does not work. But I think this is also not related to this PR and could be addressed separately

pyqasm/visitor.py Outdated Show resolved Hide resolved
pyqasm/visitor.py Outdated Show resolved Hide resolved
@TheGupta2012
Copy link
Contributor

Hi @Sola85 , thank you for the updates! Besides some minor suggestions, I think the changes look good. Should be good to merge soon.

@TheGupta2012
Copy link
Contributor

Thanks for working on this @Sola85 , the changes look good!

@TheGupta2012 TheGupta2012 merged commit 0cb941f into qBraid:main Nov 12, 2024
6 checks passed
@Sola85
Copy link
Contributor Author

Sola85 commented Nov 12, 2024

Thanks for the feedback and thanks for merging!

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.

2 participants