Open mrossinek opened 8 months ago
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
, andSparsePauliOp
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.
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.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
).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.
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.
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
.
What should we add?
I have recently found myself having to convert an
Instruction
object into aSparsePauliOp
. This is currently possible via: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:
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.).