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 "direct" Instruction -> SparsePauliOp conversion #11892

Open
mrossinek opened this issue Feb 26, 2024 · 5 comments
Open

Support "direct" Instruction -> SparsePauliOp conversion #11892

mrossinek opened this issue Feb 26, 2024 · 5 comments
Labels
type: feature request New feature or request

Comments

@mrossinek
Copy link
Member

mrossinek commented Feb 26, 2024

What should we add?

I have recently found myself having to convert an Instruction object into a SparsePauliOp.
This is currently possible via:

from qiskit.quantum_info import Operator, SparsePauliOp

operator = SparsePauliOp.from_operator(Operator(instruction))

While this works, internally this will convert the instruction to a matrix and subsequently decompose that Matrix into Pauli terms. Another limitation is that this does not work for parameterized instructions.

Thus, I would like to propose a new method be added to the gate objects (not sure which exact class this would end up on). This method should:

  • convert the gate into the decomposed Pauli terms
  • add the correct coefficients
  • and handle parameters if the coefficients happen to be such

As an initial naming suggestion I propose to_symbolic (or something along these lines), to indicate that this can handle parameters. At the same time this also hints at the "symbolic" form in terms of Paulis.

The example at the top could be used as a fallback implementation which would gracefully handle encountering a parameter.


For this method to be truly useful, we might need #11891 in order to retain the gate qubit indices as part of the SparsePauliOp. But I think an initial implementation can already be done without this logic.


I am happy to contribute a PR for this. I would require minimal guidance on the interplay of the different gate classes (e.g. Gate, Instruction, etc.).

@mrossinek mrossinek added the type: feature request New feature or request label Feb 26, 2024
@jakelishman
Copy link
Member

I think a from_instruction constructor for all the Pauli-like operators in quantum_info is a fine direction to go, and in line with other things we have there.

All existing from_instruction-type syntheses (see Clifford.from_instruction and basically everything in qiskit.synthesis) do this by methods that inspect the instructions and dispatch on them, and I'd suggest that that's a more consistent way to go rather than adding more magic decomposition methods to Instruction and Gate, which would be interface liabilities for us to change the internal representations of them. We might want to improve the way that user-defined classes hook into these in the future, but I think it's probably best to do that in bulk, if/when we look at it. It also rather simplifies the naming of the method: Pauli.from_instruction, SparsePauliOp.from_instruction and Clifford.from_instruction all are absolutely clear what they do, but should XGate.to_symbolic return a Pauli, and SparsePauliOp or a Clifford? It's also easier to add new bits to the interface (new keyword arguments, etc) if it's a "from" method on a class we entirely control than a "to" method on classes that are intended to be subclassed.

@jakelishman
Copy link
Member

jakelishman commented Feb 26, 2024

Actually, having just written that, two more thoughts:

  • Pauli.from_instruction and SparsePauliOp.from_instruction already actually exist in spirit, it's just embedded in the default constructor. You can do Pauli(QuantumCircuit) or Pauli(Instruction) (or with SparsePauliOp) already. I wouldn't be opposed to exposing that formally in an explicit constructor if there's more to add to the interface.
  • It's not clear to me that SparsePauliOp.from_instruction offers any benefit over Pauli.from_instruction as opposed to SparsePauliOp(Pauli.from_instruction(...)) (which is give-or-take what happens internally already), just because I don't think we have many use-cases where an Instruction represents several terms in a Pauli sum right now, except for use-cases that already wrap SparsePauliOp internally (like PauliEvolutionGate).

@mrossinek
Copy link
Member Author

Thanks for the input! I will do some more reading up on those existing paths.
From what I see, however, these also do not currently support parameterized Instruction objects.

@jakelishman
Copy link
Member

In what situations are you expecting to have a parametric Instruction object that can be converted to SparsePauliOp? The Clifford methods have some ways of checking if a value for a rotation gate is a multiple of $\pi/2$, so if that's all you need, then those can potentially be re-used for the Pauli constructors.

@mrossinek
Copy link
Member Author

I can see a use-case where one might want to convert between a circuit operation (i.e. Gate or Instruction) and an operator in Pauli form. Since we have the ability to represent parameterized SparsePauliOp objects, I don't think it is too far fetched that this can also be of interest.
Here is a very simple example for the case of a single parameterized RXGate:

from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import Operator, SparsePauliOp

qc = QuantumCircuit(1)
qc.rx(1.57, 0)

rx_instruction = qc[0].operation
print(rx_instruction)
sop = SparsePauliOp.from_operator(Operator(rx_instruction))
print(sop)

a = Parameter("a")

qc_p = QuantumCircuit(1)
qc_p.rx(1.57 * a, 0)

rx_instruction_p = qc_p[0].operation
print(rx_instruction_p)
print(a * sop)

This outputs:

Instruction(name='rx', num_qubits=1, num_clbits=0, params=[1.57])
SparsePauliOp(['I', 'X'],
              coeffs=[0.70738827+0.j        , 0.        -0.70682518j])
Instruction(name='rx', num_qubits=1, num_clbits=0, params=[ParameterExpression(1.57*a)])
SparsePauliOp(['I', 'X'],
              coeffs=[ParameterExpression(0.7073882691672*a),
 ParameterExpression(-0.706825181105366*I*a)])

Trying sop = SparsePauliOp.from_operator(Operator(rx_instruction_p)) will fail because the unbound parameter a cannot be cast to a float:

Traceback (most recent call last):
  File "/home/oss/Files/Dev/Qiskit/qiskit/main/tmp-sparse-pauli-gate.py", line 21, in <module>
    sop_p = SparsePauliOp.from_operator(Operator(rx_instruction_p))
  File "/home/oss/Files/Dev/Qiskit/qiskit/main/qiskit/quantum_info/operators/operator.py", line 97, in __init__
    self._data = self._init_instruction(data).data
  File "/home/oss/Files/Dev/Qiskit/qiskit/main/qiskit/quantum_info/operators/operator.py", line 700, in _init_instruction
    return Operator(np.array(instruction, dtype=complex))
  File "/home/oss/Files/Dev/Qiskit/qiskit/main/qiskit/circuit/library/standard_gates/rx.py", line 125, in __array__
    cos = math.cos(self.params[0] / 2)
  File "/home/oss/Files/Dev/Qiskit/qiskit/main/qiskit/circuit/parameterexpression.py", line 415, in __float__
    raise TypeError(
TypeError: ParameterExpression with unbound parameters (dict_keys([Parameter(a)])) cannot be cast to a float.

I am aware that my proposal has quite some caveats and I also understand that a user may be facing a possibly exponential blow-up of parameter expressions when doing the above repeatedly and composing the results. Nonetheless, I think that a "symbolic" interpretation of a gate in terms of Pauli terms can have value.
By that I mean a way to convert standard gates such as RX(a) to a form like this: cos(a / 2) * I - i * sin(a / 2) * X.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants