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

Commutation Checker with PauliGate #13570

Open
MarcDrudis opened this issue Dec 16, 2024 · 3 comments · May be fixed by #13600
Open

Commutation Checker with PauliGate #13570

MarcDrudis opened this issue Dec 16, 2024 · 3 comments · May be fixed by #13600
Labels
bug Something isn't working
Milestone

Comments

@MarcDrudis
Copy link
Contributor

Environment

  • Qiskit version: 1.0, 1.2 and 1.3
  • Python version: 3.12.7
  • Operating system: Linux

What is happening?

The commutation checker always fails to commute a PauliGate with any other gate. I get this error message.

return self.cc.commute(op1, qargs1, cargs1, op2, qargs2, cargs2, max_num_qubits)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
qiskit.exceptions.QiskitError: 'Unable to hash a non-float instruction parameter.'

I think it is because the parameter in a PauliGate is the Pauli operator that it implements rather than a Parameter or a float. I realized that CommutatorChecker did not have a unittest for this particular case, and also I have tried it for different python versions 1.0, 1.2 and 1.3. 1.0 and 1.2 work fine but 1.3 does not.

2 things have changed.

  1. The way that parameters are hashed has been ported to rust (the parameter in PauliGate is a string so I don't see why we can't hash it).
  2. Before if one of the gates was parameterized commute would return False. Now it does some more invoved logic.

How can we reproduce the issue?

from qiskit.circuit.commutation_library import SessionCommutationChecker as scc
from qiskit.circuit.library.generalized_gates.pauli import PauliGate
from qiskit.circuit.library.standard_gates.rx import RXGate
from qiskit.circuit.library.standard_gates.x import XGate
from qiskit.circuit.parameter import Parameter

pauli_gate = PauliGate("XX")
# pauli_gate = XGate()
rx_gate_theta = RXGate(Parameter("Theta"))
print(scc.commute(pauli_gate, [0, 1], [], rx_gate_theta, [0], []))
print(scc.commute(rx_gate_theta, [0], [], pauli_gate, [0, 1], [])) 

What should happen?

For versions up to 1.2 we get False (which is not the correct answer). And for version 1.3 we get the error message.

Any suggestions?

I think the best option would be to fix the hashing. If there is a deeper reason why that is not possible then we need to change the logic of the function.

@MarcDrudis MarcDrudis added the bug Something isn't working label Dec 16, 2024
@aeddins-ibm
Copy link
Contributor

aeddins-ibm commented Dec 17, 2024

Thanks Marc, this issue affects me too.

In trying to find a workaround for this issue, I believe I uncovered another, somewhat related bug in CommutationChecker.

The attempted workaround was to convert the PauliGate to an equivalent PauliEvolutionGate before passing it to the CommutationChecker.

I'm not familiar with where PauliEvolutionGate stores the Pauli definition, but it's not in the list of parameters, and at first it looked like the commutation checker was processing the instruction correctly.

The problem is that two distinct PauliEvolutionGate objects can have seemingly identical Instructions, and the CommutationChecker's caching does not distinguish between them. For example:

pauli_gate = PauliEvolutionGate(qi.Pauli('XX'), time=np.pi/2)
pauli_gate2 = PauliEvolutionGate(qi.Pauli('YY'), time=np.pi/2)

If we print these, we see the instructions appear superficially identical:

Instruction(name='PauliEvolution', num_qubits=2, num_clbits=0, params=[1.5707963267948966])
Instruction(name='PauliEvolution', num_qubits=2, num_clbits=0, params=[1.5707963267948966])

CommutationChecker will correctly say that pauli_gate commutes with an RXGate on qubit 0. But if you subsequently ask it if pauli_gate2 commutes with an RXGate on qubit 0, it will wrongly identify pauli_gate2 with the cached result for pauli_gate and conclude that pauli_gate2 also commutes with an RXGate on qubit 0.

Reproducer:

from qiskit.circuit.library import PauliGate, RXGate, PauliEvolutionGate
from qiskit.circuit import Parameter
from qiskit.circuit.commutation_library import CommutationChecker
import qiskit.quantum_info as qi
import numpy as np

rx_gate_theta = RXGate(Parameter("Theta"))

pauli_gate = PauliEvolutionGate(qi.Pauli('XX'), time=np.pi/2)
pauli_gate2 = PauliEvolutionGate(qi.Pauli('YY'), time=np.pi/2)

cc = CommutationChecker()
print(cc.commute(pauli_gate, [0, 1], [], rx_gate_theta, [0], []))
print(cc.commute(pauli_gate2, [0, 1], [], rx_gate_theta, [0], []))

Output:

True  ## this is correct
True  ## this is not

So when fixing the hashing for PauliGate in this issue, we could also likewise fix how the hashing is done for PauliEvolutionGate.

@Cryoris
Copy link
Contributor

Cryoris commented Dec 18, 2024

Thanks for the reports! Indeed for the PauliEvolutionGate we'd need to do a custom caching, since the Paulis to evolve are not stored in the params. However the more sensible solution is likely just to not cache them (we already have some exceptions for this, e.g. for LinearFunction).

For the PauliGate the issue seems to be that the Python version of the commutation checker supported strings as cache, but the Rust version now doesn't anymore. If the performance cost is acceptable we could also just skip the cache here. Otherwise we'd have to support hash on strings to cache the params here.

@Cryoris Cryoris added this to the 1.3.2 milestone Dec 18, 2024
@Cryoris
Copy link
Contributor

Cryoris commented Dec 23, 2024

@aeddins-ibm: you're actually pointing at something worse -- the commutation checker will try caching a gate it does not know and only use what's inside Gate.params as key. So any gate storing information impacting the definition outside of Gate.params can lead to a wrong result.

We should probably update the commutation checker to use a cache whitelist (i.e. only our standard gates, where we know the definition is only affected by params) instead of a blacklist.

@Cryoris Cryoris linked a pull request Dec 23, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants