Skip to content

Pauli exponential support #134

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

Open
wants to merge 29 commits into
base: pauliexp-support
Choose a base branch
from

Conversation

radumarg
Copy link
Contributor

@radumarg radumarg commented Mar 18, 2025

The primary motivation for adding native Pauli‐exponential (pauliexp) support is performance - when we send pauliexp directly to IonQ, our compiler can optimize across a larger context (instead of first Trotterizing), which speeds up development and execution times. No existing workflows are strictly blocked, but allowing pauliexp to go through "as-is" reduces compilation overhead and yields shorter circuits.

pauliexp isn’t yet documented publicly, we already support it. The qiskit‐ionq package already includes helpers for Pauli exponentials (Qiskit‐IonQ helpers, so the hardware/runtime will accept it natively.

Copy link

codecov bot commented Mar 18, 2025

Codecov Report

Attention: Patch coverage is 63.01370% with 54 lines in your changes missing coverage. Please review.

Project coverage is 83.81%. Comparing base (5bc815f) to head (6c8fa4c).

Files with missing lines Patch % Lines
pennylane_ionq/device.py 60.00% 52 Missing ⚠️
pennylane_ionq/exceptions.py 87.50% 2 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (5bc815f) and HEAD (6c8fa4c). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (5bc815f) HEAD (6c8fa4c)
3 2
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #134       +/-   ##
===========================================
- Coverage   97.67%   83.81%   -13.86%     
===========================================
  Files           5        6        +1     
  Lines         387      519      +132     
===========================================
+ Hits          378      435       +57     
- Misses          9       84       +75     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@radumarg radumarg marked this pull request as ready for review May 9, 2025 12:35
@mudit2812 mudit2812 self-requested a review May 20, 2025 16:06
Copy link
Contributor

@mudit2812 mudit2812 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 working on this @radumarg . I'm going to do another review soon where I do an even deeper dive into your implementation. Right now, the review mostly covers stylistic and best practices suggestions.

I've also tried to address your concerns about the ParametrizedEvolution implementation, for which I'm also going to get more feedback from the team. Please don't hesitate to reach out for questions 😄

@mudit2812
Copy link
Contributor

Hi @radumarg , could you please share the API docs that show pauliexp as a valid gate in the IonQ gate set? We used this reference to get information about the gate set, but it does not mention pauliexp anywhere.

Additionally, I wanted to note that PennyLane has its own decomposition pipeline to decompose circuits into a gate set supported by the device as part of preprocessing. Before this PR, Evolution gates should have been decomposing via the Trotterization approximation automatically since it wasn't part of the device's gate set. Did executing a circuit containing the Evolution gate work before the changes in this PR?

@radumarg
Copy link
Contributor Author

radumarg commented May 28, 2025

@mudit2812 the Evolution gate does not seem to work in pennylane 0.41.1 and pennylane-ionq 0.41.0:
image

@radumarg
Copy link
Contributor Author

radumarg commented May 28, 2025

@mudit2812 Regarding the documentation for pauliexp gate, Spencer Churchill from IonQ team @splch should be able to respond

@mudit2812
Copy link
Contributor

@radumarg the preprocessing pipeline gets used under the hood when working with QNodes. If you create a qnode for the same circuit, does it work?

@Alex-Preciado
Copy link
Contributor

Alex-Preciado commented May 29, 2025

Hi @radumarg, thanks for opening this PR! 🚀 Could you please add a description to the top of the PR outlining the motivation/context behind the change and what it’s intended to do with a brief description of the changes made? This helps reviewers understand the context, keeps the project history clear, and is essential for us to determine prioritization 🙂. Let me know if you have any questions.

@radumarg
Copy link
Contributor Author

@mudit2812 I have checked and using a circuit with QNode instead of QuantumTape works when jobs are sent to IonQ simulator:
image

@mudit2812
Copy link
Contributor

@radumarg would you mind providing some details about the motivation for this PR in the description as well. Do you have any specific workflows that are blocked by the lack of this feature? What are the high-level requirements that you're hoping to accomplish with the work done in this PR?

@radumarg
Copy link
Contributor Author

@mudit2812, @Alex-Preciado I am waiting for some feedback from the IonQ team so that I can provide a better description and context for this PR. Will get back when I have something more concrete.

@splch
Copy link
Collaborator

splch commented Jun 3, 2025

The primary motivation for adding native Pauli‐exponential (pauliexp) support is performance - when we send pauliexp directly to IonQ, our compiler can optimize across a larger context (instead of first Trotterizing), which speeds up development and execution times. No existing workflows are strictly blocked, but allowing pauliexp to go through "as-is" reduces compilation overhead and yields shorter circuits.

pauliexp isn’t yet documented publicly, we already support it. The qiskit‐ionq package already includes helpers for Pauli exponentials (Qiskit‐IonQ helpers, so the hardware/runtime will accept it natively.

@radumarg
Copy link
Contributor Author

radumarg commented Jun 3, 2025

@mudit2812, @Alex-Preciado, we have received clarifications from the IonQ team, see above. PR description was updated as well.

@mudit2812
Copy link
Contributor

Hey @radumarg , I chatted with the team about the open question about ParametrizedEvolution support. ParametrizedEvolution is an operator that we use for pulse-level control. We have added support to use it with hardware in the past, but the implementation is not general and needs to be backend-specific.

With that in mind, I would suggest that we limit the scope of this PR to only add support for Evolution, not ParametrizedEvolution. We can revisit ParametrizedEvolution in the future if there is interest.

For now, feel free to tag me for review after the code relevant to ParametrizedEvolution is removed, and the remaining comments from my previous review are addressed. Thanks!

@radumarg
Copy link
Contributor Author

radumarg commented Jun 10, 2025

@mudit2812 code rework was performed, what do I need to do in order to see the code test coverage report?

@mudit2812
Copy link
Contributor

@radumarg the coverage report only gets generated if all the test workflows are successful. Tests are currently failing
image

@radumarg
Copy link
Contributor Author

radumarg commented Jun 17, 2025

@mudit2812 Thanks, the tests were fixed, now I do see test coverage reports, but these reports are not correct. For example, it says:

image

But those lines are tested by several tests using 'requires_api' argument, which means that the test is sending some circuit to IonQ simulator and it validates the job results. An example of such test is: test_evolution_object_created_from_hamiltonian test. My guess is the test coverage reports do not include test with 'requires_api' .

@mudit2812
Copy link
Contributor

mudit2812 commented Jun 18, 2025

My guess is the test coverage reports do not include test with 'requires_api' .

@radumarg This is correct. I believe that the requires_api tests are run under the "Tests / Integration-tests" job, which gets skipped because you don't have access to the secret API key that we use. We will have to use a similar strategy as #132 to merge this PR, where I will create a new branch and rebase your PR to point to that branch instead of master. Once approved and all comments are resolved, I will merge that PR and open a fresh one using that branch to master.

Since this PR's content is very non-trivial compared to #132 , it would be great if you can verify that the test coverage is good by running the tests locally. You can do so by adding the following to the pytest comment you use to invoke the tests:

python -m pytest --cov-report term-missing --cov=<path_to_folder_being_tested> <path_to_tests>

EDIT: to run the above command, you need to make sure that the pytest-cov Python package is installed.

Copy link
Contributor

@mudit2812 mudit2812 left a comment

Choose a reason for hiding this comment

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

Thanks @radumarg . I've requested just one change about the implementation itself, which is to use trotterization if a user explicitly specifies num_steps != None.

Other than that, I still see opportunity to improve tests via parametrization, but the content of the tests themselves looks great :)

Please leave some details about the inconsistency between the output of the IonQ device vs default.qubit so that the team can investigate. Thanks!

Comment on lines 328 to 340
def _apply_evolution_operation(self, operation, circuit_index, wires):
"""Applies Evolution operations to the internal device state."""
name = operation.name
terms = self._extract_evolution_pauli_terms(operation, wires)
coefficients = self._extract_evolution_coefficients(operation, wires)
terms, coefficients = self._remove_trivial_terms(terms, coefficients)
if len(terms) > 0:
gate = {"gate": self._operation_map[name]}
if len(wires) == 2:
if name in {"SWAP", "XX", "YY", "ZZ", "MS"}:
# these gates takes two targets
gate["targets"] = wires
else:
gate["control"] = wires[0]
gate["target"] = wires[1]
gate["targets"] = wires
gate["terms"] = terms
gate["coefficients"] = [-1 * float(v) for v in coefficients]
gate["time"] = operation.param
self.input["circuits"][circuit_index]["circuit"].append(gate)
Copy link
Contributor

Choose a reason for hiding this comment

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

In your previous implementation, you were doing the trotter decomposition if num_steps was specified. I think that behaviour still makes sense to me, since the user is explicitly requesting the operation to get decomposed via trotterization.

Copy link
Contributor Author

@radumarg radumarg Jun 24, 2025

Choose a reason for hiding this comment

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

After discussing with IonQ team my understanding is that totterization is handled internally by their implementation of pauliexp gates, this is why I removed this feature. I will double-check with @splch and return here with a confirmation if my assumption is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@splch would you agree with the above?

Copy link
Contributor

Choose a reason for hiding this comment

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

So the number of trotter steps specified by the user will never get used?

Copy link
Collaborator

Choose a reason for hiding this comment

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

hi! yes - pauli_exp already does a single-step product-formula trotterization on the backend, so we don’t need a num_steps option here. if users want more steps or a higher-order rule, they should trotterize the circuit in pennylane before sending it. we can add a docstring note to make this clear

Copy link
Contributor Author

@radumarg radumarg Jul 2, 2025

Choose a reason for hiding this comment

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

Update (July 2nd 2025) we are still discussing this issue .. will get back here when we reach a conclusion

Copy link
Contributor

Choose a reason for hiding this comment

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

Hey @splch, I had a discussion with the development and product teams, and our concern is the fact that the default behaviour of qml.evolve between PennyLane and IonQ is not the same. This already created a point of confusion during testing for this PR (context), and we suspect it might cause confusion for users of the plugin, as well. I agree that documentation about this is important, but we also have the impression that the default behaviour of the operation should be defined by the frontend, which in this case is PennyLane. I’m happy to continue the discussion about this, as we want to make sure this feature is as polished as possible before merging into master. Our suggestion is to add a warning message that informs the user of the plugin that the default behaviour of qml.evolve changes w.r.t. PennyLane’s.

Tagging @isaacdevlugt, our product manager, for visibility.

Copy link
Contributor Author

@radumarg radumarg Jul 9, 2025

Choose a reason for hiding this comment

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

@mudit2812 Here is some feedback from IonQ: IonQ would ideally like to ignore the number of trotter steps and trotterization strategy specified by the user so that we can employ hardware-efficient approximate compilation schemes, e.g., reducing the number of trotter steps given estimations of of the hardware noise map associated with the gate. What we settled on for Qiskit is that we would throw an error if the user sends an undecomposed PauliEvolutionGate to our backend but has not explicitly set a IONQ_RESYNTHESIS environment variable. This error explains that IonQ will be applying its own trotterization/recompilation of the PaulIEvolutionGate, and that if the user does not want this, they should explicitly decompose the circuit to base level gates before sending it to us. So there are two potential solutions:

(1) user-thrown error in cases where an undecomposed PauliEvolutionGate has been sent to IonQ, with the ability to have a user explicitly opt in to our internal compilation/trotterization by declaring an environment variable as currently done in qiskit-ionq packge.

(2) print a warning message that explains that IonQ will apply its own trotterization unless they decompose the gate further beforehand.

Which one would you prefer: (1) that requires the user to set IONQ_RESYNTHESIS env variable in order for Evolution gate to work with IonQ, or (2) that prints a warning to console when a circuit containing Evolution gate is sent to IonQ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hey @radumarg , I discussed with the team, and here are our thoughts:

  • In an ideal world, option 1 would be preferred, as it terminates execution before a user runs a potentially incorrect and paid job. But, since there is no public facing documentation for pauliexp or the IONQ_RESYNTHESIS environment variable, option 1 does not make sense.
  • Currently, option 2 seems like the best course of action. We raise a warning if a user uses qml.evolve that their specified num_steps will be ignored, and that the backend will automatically choose a compilation scheme. Note that this warning should be raised even if num_steps is not specified, since in base PennyLane, num_steps=None is equivalent to an exact time evolution, not an approximation. Along with the warning, this behaviour should be documented in the docstring of the device being updated.

Let me know if you have any questions. @isaacdevlugt feel free to chime in if I missed anything.

Choose a reason for hiding this comment

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

Yep @mudit2812 covered everything 💯!

@radumarg
Copy link
Contributor Author

@mudit2812 I have implemented most of your suggestions, increased code coverage, which should now be complete for the code modified by this ticket. I have made some comments and added some sample Qiskit code above on the issue of Pennylane apparently not returning correct simulation results. Can you please have a look with your team and let me know what you find out?

@radumarg
Copy link
Contributor Author

radumarg commented Jul 2, 2025

@mudit2812 did you have the time to test the incorrect simulation results documented above? Should we wait for you to complete your investigation, or we can proceed with this ticket?

@mudit2812 mudit2812 changed the base branch from master to pauliexp-support July 2, 2025 18:10
@mudit2812
Copy link
Contributor

@radumarg similar to #132 I've rebased this PR to point to a temporary branch. Nothing actionable, just mentioning for visibility.

Copy link
Contributor

@mudit2812 mudit2812 left a comment

Choose a reason for hiding this comment

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

Totally forgot to request you to add an entry to the changelog 😅 . Thanks

@radumarg
Copy link
Contributor Author

@mudit2812 I left a question on the only issue with is not resolved yet (see above). After consulting with IonQ team I explained why IonQ does not implement number of steps for Troterrization and suggested to solutions in order to warn the user about this nonstandard behavior. Please take a look and let me know which one you prefer. Thanks!

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.

5 participants