Repository: Qiskit/qiskit

Issue:
Opflow fails ((Z^I)@(X^X))@(Plus^Plus), but works with Aqua operators opflow
With Aqua and Terra latest versions from main branches:

Here is what I find. Using Aqua operators, just looking at the first part as we see a difference that helps understand the final failure 
```
>>> ((Z^I)@(X^X))
PauliOp(Pauli('YX'), coeff=1j) 
```
using Terra opflow
```
>>> ((Z^I)@(X^X))
PauliOp(Pauli('iYX'), coeff=1.0) 
```
Now Aqua - adding the `@(Plus^Plus)` to the expression to complete it as per the failure
```
>>> ((Z^I)@(X^X))@(Plus^Plus)
CircuitStateFn(<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7fbb1947ada0>, coeff=1j, is_measurement=False)
```
where using Terra opflow it fails
```
qiskit/opflow/primitive_ops/pauli_op.py", line 323, in to_circuit
    gate = PAULI_GATE_MAPPING[pauli_str]
KeyError: 'i'
```
this is in some to_circuit logic where the i is not managed since it seems to only expect X,Y,Z I in the mapping and in mapping each element it goes wrong. In Aqua it was just XY with the i being part of coeff. Now it moved to iXY and the to_circuit fails.


As originally reported on Qiskit Slack aqua channel here https://qiskit.slack.com/archives/CB6C24TPB/p1623208786231500




Discussion:
Thanks. I see. This is caused by `Pauli` symplectic refactoring.

Resolving patch:
diff --git a/qiskit/opflow/primitive_ops/pauli_op.py b/qiskit/opflow/primitive_ops/pauli_op.py
--- a/qiskit/opflow/primitive_ops/pauli_op.py
+++ b/qiskit/opflow/primitive_ops/pauli_op.py
@@ -12,6 +12,7 @@
 
 """PauliOp Class """
 
+from math import pi
 from typing import Dict, List, Optional, Set, Union, cast
 
 import numpy as np
@@ -20,6 +21,7 @@
 from qiskit import QuantumCircuit
 from qiskit.circuit import Instruction, ParameterExpression
 from qiskit.circuit.library import IGate, RXGate, RYGate, RZGate, XGate, YGate, ZGate
+from qiskit.circuit.library.generalized_gates import PauliGate
 from qiskit.opflow.exceptions import OpflowError
 from qiskit.opflow.list_ops.summed_op import SummedOp
 from qiskit.opflow.list_ops.tensored_op import TensoredOp
@@ -27,8 +29,6 @@
 from qiskit.opflow.primitive_ops.primitive_op import PrimitiveOp
 from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector
 
-PAULI_GATE_MAPPING = {"X": XGate(), "Y": YGate(), "Z": ZGate(), "I": IGate()}
-
 
 class PauliOp(PrimitiveOp):
     """Class for Operators backed by Terra's ``Pauli`` module."""
@@ -167,8 +167,8 @@ def compose(
             )
 
         # pylint: disable=cyclic-import
-        from .circuit_op import CircuitOp
         from ..state_fns.circuit_state_fn import CircuitStateFn
+        from .circuit_op import CircuitOp
 
         if isinstance(other, (CircuitOp, CircuitStateFn)):
             return new_self.to_circuit_op().compose(other)
@@ -207,10 +207,10 @@ def eval(
             return self.to_matrix_op()
 
         # pylint: disable=cyclic-import
-        from ..state_fns.state_fn import StateFn
-        from ..state_fns.dict_state_fn import DictStateFn
-        from ..state_fns.circuit_state_fn import CircuitStateFn
         from ..list_ops.list_op import ListOp
+        from ..state_fns.circuit_state_fn import CircuitStateFn
+        from ..state_fns.dict_state_fn import DictStateFn
+        from ..state_fns.state_fn import StateFn
         from .circuit_op import CircuitOp
 
         new_front = None
@@ -312,15 +312,25 @@ def exp_i(self) -> OperatorBase:
             return EvolvedOp(self)
 
     def to_circuit(self) -> QuantumCircuit:
-        # If Pauli equals identity, don't skip the IGates
-        is_identity = sum(self.primitive.x + self.primitive.z) == 0
-
-        # Note: Reversing endianness!!
-        qc = QuantumCircuit(len(self.primitive))
-        for q, pauli_str in enumerate(reversed(self.primitive.to_label())):
-            gate = PAULI_GATE_MAPPING[pauli_str]
-            if not pauli_str == "I" or is_identity:
-                qc.append(gate, qargs=[q])
+
+        pauli = self.primitive.to_label()[-self.num_qubits :]
+        phase = self.primitive.phase
+
+        qc = QuantumCircuit(self.num_qubits)
+        if pauli == "I" * self.num_qubits:
+            qc.global_phase = -phase * pi / 2
+            return qc
+
+        if self.num_qubits == 1:
+            gate = {"I": IGate(), "X": XGate(), "Y": YGate(), "Z": ZGate()}[pauli]
+        else:
+            gate = PauliGate(pauli)
+        qc.append(gate, range(self.num_qubits))
+
+        if not phase:
+            return qc
+
+        qc.global_phase = -phase * pi / 2
         return qc
 
     def to_instruction(self) -> Instruction:
@@ -329,7 +339,7 @@ def to_instruction(self) -> Instruction:
         # (Reduce removes extra IGates).
         # return PrimitiveOp(self.primitive.to_instruction(), coeff=self.coeff).reduce()
 
-        return self.to_circuit().to_instruction()
+        return self.primitive.to_instruction()
 
     def to_pauli_op(self, massive: bool = False) -> "PauliOp":
         return self


Test patch:
diff --git a/test/python/opflow/test_op_construction.py b/test/python/opflow/test_op_construction.py
--- a/test/python/opflow/test_op_construction.py
+++ b/test/python/opflow/test_op_construction.py
@@ -13,50 +13,49 @@
 """ Test Operator construction, including OpPrimitives and singletons. """
 
 
+import itertools
 import unittest
+from math import pi
 from test.python.opflow import QiskitOpflowTestCase
-import itertools
+
+import numpy as np
 import scipy
+from ddt import data, ddt
 from scipy.stats import unitary_group
-import numpy as np
-from ddt import ddt, data
 
 from qiskit import QiskitError
-from qiskit.circuit import QuantumCircuit, QuantumRegister, Instruction, Parameter, ParameterVector
-
-from qiskit.extensions.exceptions import ExtensionError
-from qiskit.quantum_info import Operator, Pauli, Statevector
+from qiskit.circuit import Instruction, Parameter, ParameterVector, QuantumCircuit, QuantumRegister
 from qiskit.circuit.library import CZGate, ZGate
-
+from qiskit.extensions.exceptions import ExtensionError
 from qiskit.opflow import (
-    X,
-    Y,
-    Z,
-    I,
     CX,
-    T,
-    H,
-    Minus,
-    PrimitiveOp,
-    PauliOp,
     CircuitOp,
-    MatrixOp,
-    EvolvedOp,
-    StateFn,
     CircuitStateFn,
-    VectorStateFn,
+    ComposedOp,
     DictStateFn,
-    OperatorStateFn,
+    EvolvedOp,
+    H,
+    I,
     ListOp,
-    ComposedOp,
-    TensoredOp,
-    SummedOp,
+    MatrixOp,
+    Minus,
     OperatorBase,
-    Zero,
+    OperatorStateFn,
     OpflowError,
+    PauliOp,
+    PrimitiveOp,
     SparseVectorStateFn,
+    StateFn,
+    SummedOp,
+    T,
+    TensoredOp,
+    VectorStateFn,
+    X,
+    Y,
+    Z,
+    Zero,
 )
-
+from qiskit.quantum_info import Operator, Pauli, Statevector
 
 # pylint: disable=invalid-name
 
@@ -495,7 +494,7 @@ def test_permute_on_primitive_op(self):
         expected_circuit_op = expected_pauli_op.to_circuit_op()
 
         self.assertEqual(
-            permuted_circuit_op.primitive.__str__(), expected_circuit_op.primitive.__str__()
+            Operator(permuted_circuit_op.primitive), Operator(expected_circuit_op.primitive)
         )
 
         # MatrixOp
@@ -867,6 +866,43 @@ def test_composed_op_to_circuit(self):
 
         self.assertTrue(Operator(unitary).equiv(circuit))
 
+    def test_pauli_op_to_circuit(self):
+        """Test PauliOp.to_circuit()"""
+        with self.subTest("single Pauli"):
+            pauli = PauliOp(Pauli("Y"))
+            expected = QuantumCircuit(1)
+            expected.y(0)
+            self.assertEqual(pauli.to_circuit(), expected)
+
+        with self.subTest("single Pauli with phase"):
+            pauli = PauliOp(Pauli("-iX"))
+            expected = QuantumCircuit(1)
+            expected.x(0)
+            expected.global_phase = -pi / 2
+            self.assertEqual(Operator(pauli.to_circuit()), Operator(expected))
+
+        with self.subTest("two qubit"):
+            pauli = PauliOp(Pauli("IX"))
+            expected = QuantumCircuit(2)
+            expected.pauli("IX", range(2))
+            self.assertEqual(pauli.to_circuit(), expected)
+            expected = QuantumCircuit(2)
+            expected.x(0)
+            expected.id(1)
+            self.assertEqual(pauli.to_circuit().decompose(), expected)
+
+        with self.subTest("two qubit with phase"):
+            pauli = PauliOp(Pauli("iXZ"))
+            expected = QuantumCircuit(2)
+            expected.pauli("XZ", range(2))
+            expected.global_phase = pi / 2
+            self.assertEqual(pauli.to_circuit(), expected)
+            expected = QuantumCircuit(2)
+            expected.z(0)
+            expected.x(1)
+            expected.global_phase = pi / 2
+            self.assertEqual(pauli.to_circuit().decompose(), expected)
+
     def test_op_to_circuit_with_parameters(self):
         """On parameterized SummedOp, to_matrix_op returns ListOp, instead of MatrixOp. To avoid
         the infinite recursion, OpflowError is raised."""