Skip to content

🎨 Update of IonQ and Rigetti Devices #570

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

Merged
merged 27 commits into from
May 25, 2025
Merged

Conversation

nquetschlich
Copy link
Collaborator

@nquetschlich nquetschlich commented May 22, 2025

This PR...

  • updates the IonQ device calibration data
  • introduces new devices: IonQ Forte 1 and Rigetti Ankaa 3
  • introduces a more correct gatesets for these devices

resolves #289

@nquetschlich nquetschlich added the enhancement New feature or request label May 22, 2025
@github-project-automation github-project-automation bot moved this to In Progress in MQT Applications May 22, 2025
Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

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

Thanks for the first draft here.
I just rather quickly went over everything here and added a couple of inline comments.
This is going into the right direction but, as expected, will take a little more work to get right.

Comment on lines 42 to 43
get_ionq_target("ionq_forte_1"),
get_ionq_target("ionq_aria_1"),
Copy link
Member

Choose a reason for hiding this comment

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

I believe the naming of these should be adjusted so that the last number indicates the number of qubits similar to IBM and IQM. The 1 here does not add any value

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These are the specific device names and there is also an Aria 2. Therefore, I suggest to keep the name, but this is independent of the qubit number at the end - if you prefer that, I am happy to add it to all devices.

Copy link
Member

Choose a reason for hiding this comment

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

But aria 2 still uses the same layout and the same gateset if I am not mistaken.
Given how the device data is averaged anyway, I do not think there is too much value to this number.

If the gateset should really be different between these, then I would actually add the number to the gateset name and still append an underscore with the number of qubits for the device here.

Same holds for the Rigetti device.

Copy link

codecov bot commented May 23, 2025

Codecov Report

Attention: Patch coverage is 96.96970% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mqt/bench/targets/gatesets/rigetti.py 92.8% 4 Missing ⚠️
src/mqt/bench/targets/devices/ionq.py 91.3% 2 Missing ⚠️
src/mqt/bench/targets/gatesets/__init__.py 96.2% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@nquetschlich nquetschlich changed the title 🎨 IonQ Device Update 🎨 Update of IonQ and Rigetti Devices May 23, 2025
@nquetschlich nquetschlich marked this pull request as ready for review May 23, 2025 08:02
Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

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

Thanks for all the changes. This is definitely moving in the right direction. I hope the new comments help to push this even closer to the finish line.

@@ -18,7 +18,7 @@

from qiskit.providers.fake_provider import GenericBackendV2

from ..gatesets import get_ibm_eagle_gateset, get_ibm_falcon_gateset, get_ibm_heron_gateset
from mqt.bench.targets.gatesets.ibm import get_ibm_eagle_gateset, get_ibm_falcon_gateset, get_ibm_heron_gateset
Copy link
Member

Choose a reason for hiding this comment

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

This change is unrelated to the PR and I actually would not make that change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adjusted it accordingly.

from qiskit.transpiler import InstructionProperties, Target

from mqt.bench.targets.gatesets.ionq import GPI2Gate, GPIGate, MSGate, ZZGate, add_ionq_equivalences
Copy link
Member

Choose a reason for hiding this comment

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

I would have a rather strong preference for relative imports. Tends to work better with editable installs and IDEs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Adjusted it accordingly.

return get_ionq_aria1_target()
if device_name == "ionq_harmony":
return get_ionq_harmony_target()
add_ionq_equivalences()
Copy link
Member

Choose a reason for hiding this comment

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

I am not yet quite sure this is the best place to put this. As there are multiple IonQ devices, this will inevitably get called multiple times.
I could imagine that this literally duplicates all the rules, which doesn't sound good.

I think it would be good to ensure that this is only called once. And preferably, it would only be called when the target is used, not when it's constructed. We only want to add these right before the transpile call when the target is used.

I'd also like to understand a little bit more about the lifecycle of the SessionEquivalenceLibrary. How long does it persist? Where does it enter the equation in the transpile call? Would moving to the StagedPassManager allow us to use a custom EL?

Comment on lines 82 to 85
elif gate == "fixed_angle_rx_pi_by_2":
target.add_instruction(RXPiOver2Gate())
elif gate == "fixed_angle_rx_pi_by_minus_2":
target.add_instruction(RXMinusPiOver2Gate())
Copy link
Member

Choose a reason for hiding this comment

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

Slightly more consistent naming could be useful here:

Suggested change
elif gate == "fixed_angle_rx_pi_by_2":
target.add_instruction(RXPiOver2Gate())
elif gate == "fixed_angle_rx_pi_by_minus_2":
target.add_instruction(RXMinusPiOver2Gate())
elif gate == "rxpi2":
target.add_instruction(RXPI2Gate())
elif gate == "rxpi2dg":
target.add_instruction(RXPI2DgGate())

Or something along those lines.
The PI and PI2 in "gpi" and "gpi2" stand for exactly that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I implemented that now.

@@ -13,33 +13,32 @@
import pytest
from qiskit.transpiler import Target

from mqt.bench.targets.devices.ionq import get_ionq_target
from mqt.bench.targets.devices.ionq import _build_ionq_target, get_ionq_target # noqa: PLC2701
Copy link
Member

Choose a reason for hiding this comment

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

Not quite sure whether I like that this is importing a private function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I removed the test.

Comment on lines 42 to 43
get_ionq_target("ionq_forte_1"),
get_ionq_target("ionq_aria_1"),
Copy link
Member

Choose a reason for hiding this comment

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

But aria 2 still uses the same layout and the same gateset if I am not mistaken.
Given how the device data is averaged anyway, I do not think there is too much value to this number.

If the gateset should really be different between these, then I would actually add the number to the gateset name and still append an underscore with the number of qubits for the device here.

Same holds for the Rigetti device.

[0],
)
u_gate.append(GPI2Gate(0.5 + phi_param / (2 * np.pi)), [0])
SessionEquivalenceLibrary.add_equivalence(UGate(theta_param, phi_param, lambda_param), u_gate)
Copy link
Member

Choose a reason for hiding this comment

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

I believe it would be better if these methods would take an equivalence library as a parameter instead of unconditionally adding to the SEL.
Gives a bit more flexibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I followed your hint. Additionally, I add the library now only immediately before the transpile method is called. I played around a bit with the EquivalenceLibrary and you can get all available decompositions for a gate but would need to compare the existing ones with the one that might be added to check if it is already in there - which is also not ideal.

SessionEquivalenceLibrary.add_equivalence(UGate(theta, phi, lam), qc)


def cx_gate_equivalence() -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Is it really true that a single CX gate takes two iSWAP gates? Seems counterintuitive to me.
Furthermore, the iSWAP gate is a Qiskit standard gate, so this equivalence should not really be needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I thinks so, also considering this reference implementation (https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.iSwapGate). Although it is the other way round there. Anyways, I removed this equivalence because it is not necessary.

self.definition = qc


def u_gate_equivalence() -> None:
Copy link
Member

Choose a reason for hiding this comment

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

I think it could be worth to add a couple more equivalences here that take fewer gates.
Equivalences for the real (parametrized) RX gate come to mind.
Pretty sure there are others.

Copy link
Collaborator Author

@nquetschlich nquetschlich May 23, 2025

Choose a reason for hiding this comment

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

I added one for that, although it still has 5 gates (but at least without parameters - not sure how much it improves though). Let me know if you have specific other ones in mind as well.

@nquetschlich nquetschlich requested a review from burgholzer May 23, 2025 14:23
Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

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

One final comment, then I believe this should be good to go here 🙌

Comment on lines +139 to +151
def rx_gate_equivalence(sel: EquivalenceLibrary) -> None:
"""Add RX(θ) gate equivalence using RX(±π/2) and RZ gates."""
theta = Parameter("θ")
qr = QuantumRegister(1, "q")
qc = QuantumCircuit(qr)

qc.rz(-np.pi / 2, qr[0])
qc.append(RXPI2Gate(), [qr[0]])
qc.rz(theta, qr[0])
qc.append(RXPI2DgGate(), [qr[0]])
qc.rz(np.pi / 2, qr[0])

sel.add_equivalence(RXGate(theta), qc)
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't really help much I suppose.
What I actually meant with my other comment was to add equivalences from
RXGate(pi/2) to RXPI2Gate and so on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is this what you would like to see here?

def rx_gate_equivalences(sel: EquivalenceLibrary) -> None:
    """Add RX(θ) gate equivalence using RX(±π/2) and RZ gates."""
    qr = QuantumRegister(1, "q")
    qc = QuantumCircuit(qr)
    qc.append(RXPI2Gate(), [qr[0]])
    sel.add_equivalence(RXGate(np.pi / 2), qc)

    qr = QuantumRegister(1, "q")
    qc = QuantumCircuit(qr)
    qc.append(RXPI2DgGate(), [qr[0]])
    sel.add_equivalence(RXGate(-np.pi / 2), qc)

    qr = QuantumRegister(1, "q")
    qc = QuantumCircuit(qr)
    qc.append(RXPIGate(), [qr[0]])
    sel.add_equivalence(RXGate(np.pi), qc)

If yes: Apparently it is not possible to add these equivalences only for specific angles.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. Something like that is what I would have expected.
How's that not supported? Or what is the error you are seeing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It leads to an internal Qiskit Error in qiskit.transpiler.passes.basis.basis_translator.BasisTranslator.run: AttributeError: 'float' object has no attribute '_uuid' because for a parameterized gate, Qiskit apparently expects a parameter instead of just a specific angle.

Copy link
Member

Choose a reason for hiding this comment

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

Hm.. unfortunate..
That's something that I would consider opening up an issue on the Qiskit tracker for.
Such equivalences could be extremely helpful.

Any how.. I suppose this PR can be merged then 😌

@nquetschlich nquetschlich merged commit cb0c53b into main May 25, 2025
17 of 18 checks passed
@nquetschlich nquetschlich deleted the update_ionq_devices branch May 25, 2025 16:44
@github-project-automation github-project-automation bot moved this from In Progress to Done in MQT Applications May 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🐛 Rigetti Device's RX gate only supports specific angles and not arbitrary ones
2 participants