Repository: Qiskit/qiskit

Issue:
`Operator.power(n)` only works for integer `n`
### Environment

- **Qiskit Terra version**: current
- **Python version**: shouldn't matter
- **Operating system**: shouldn't matter


### What is happening?

In the description of the `Operator.power(n)` method: https://github.com/Qiskit/qiskit/blob/4b305983465be7429129d5f89287ae7dc7b91952/qiskit/quantum_info/operators/operator.py#L511-L523

the argument `power` is of type `float`, however in practice this code only works for integer values of `n`.

### How can we reproduce the issue?

```
import numpy as np
from qiskit.circuit.library import CXGate
from qiskit.quantum_info import Operator

op = Operator(CXGate())
print(op)
op1 = op.power(3)
print(op1)
op2 = op.power(3.14)
print(op2)
```

### What should happen?

The `power` method in `gate.py`:

https://github.com/Qiskit/qiskit/blob/4b305983465be7429129d5f89287ae7dc7b91952/qiskit/circuit/gate.py#L64-L75

does work with floating-point powers and (as far as I can judge) could be moved to the `Operator` class.

### Any suggestions?

_No response_


Discussion:
I have assigned myself to the issue. However, any thoughts on 
(1) whether supporting general floating-point powers for `Operator` class should or should not be allowed, and
(2) whether a one-line method to raise a complex-valued matrix to a floating point already exists (and which would probably be equivalent to the code in `gate.py` anyway)
are welcome. 
The need of nth root of a operator may sound convincing if supporting general floating powers is in question
A one liner does exists in scipy => `scipy.linalg.fractional_matrix_power(A, t)` where A is a square matrix of order n and t is a fractional power, but a implementation can be made for more robustness
Yes I think it can be supported. We just have to use the same code that is used in the Gate class now, which does in fact raise a matrix to a power using `scipy.linalg.schur` and the output type is currently a UnitaryGate. I prefer natively supporting an Operator in the circuit rather than UnitaryGate (which seems like a duplication of Operator from when we could not put Operator in the circuit). In that sense I would prefer the raising to a power to return an Operator, and the code to do this to reside in the Operator.

Also I think the "control" method should also work for operators. It amounts to increasing the dimension and making the new matrix a direct sum of Identity and U (if control is the lower qubit according to qiskit's little endian convention, otherwise a permutation of this matrix).
Fractional-power `Operator` seems completely reasonable.

To speak to Ali's other point: I think in the higher-abstraction-level world `QuantumCircuit` lives in now, there is still a need for both `Operator` and `UnitaryGate`.  `Operator` would represent an abstract high-level quantum operator, where `UnitaryGate` would be a low-level primitive `unitary` for backends that support it (e.g. Aer).  `qiskit.quantum_info` would exclusively deal with `Operator`, which would be then be lowered by high-level gate decompositions to the target ISA, either by full synthesis (a place we currently use `UnitaryGate` as a temporary, but `Operator` could/should replace it), or by the trivial synthesis `Operator -> UnitaryGate` if the backend ISA includes the variadic `unitary` instruction (as Aer does).

The `Instruction` interface (which `UnitaryGate` satisfies and `Operator` doesn't) is more constraining than the `Operation` interface (which `Operator` does satisfy), and I think it's best to keep both concepts of low- and high-level descriptions, so that backends, and consequently the `Target` ISA descriptor, etc, need only deal with the low level.

(As a very much side note, can we stop referring to an "endianness" convention?  It's at best misleading terminology, and it's not strictly related to the concepts here; the convention for "which qubit is the control?" is a separate question to "in which order do we perform the Kronecker product?" and "in which order do we label qubits?", and none of those are strictly about endianness.)
I think all I'm saying for the Operator vs. UnitaryGate thing is that we should make it similar to how Cliffords are done now:
```py
>>>c = QuantumCircuit(2)
>>>c.append(random_clifford(2), [0, 1])
>>>c.append(random_unitary(4), [0, 1])
>>>print(type(c[0]))
>>>print(type(c[1]))
qiskit.quantum_info.operators.symplectic.clifford.Clifford
qiskit.circuit.library.generalized_gates.unitary.UnitaryGate
```
The first one just uses Clifford, which is far better in my opinion as it gives access to the machinery we have around analyzing Cliffords and doesn't create unnecessary classes. The second one converts  `Operator -> UnitaryGate` under the hood, which is unnecessary IMHO. If we allow CircuitInstructions to point to some quantum_info object, then we immediately allow very general mathematical oeprators to be dropped into circuits, and then teach the transpiler, simulators, etc. to deal with them. The fact that Aer knows about UnitaryGate right now isn't fundamental, it can be adapted to work with `CircuitInstruction(operation=Operator(...), ...)`


Ok we can call it something other than endianness :)  I was trying to say that `U.control()` should become `I \otimes |0><0| + U \otimes |1><1|` which is counter-intuitive but makes sense if we consider the original operation as `U(t1, t2, t3)` and the new one as `controlled-U(c, t1, t2, t3)`, i.e. the 0th qubit is the control, i.e. the least-significant-bit. This is a combination of kronecker product ordering and how we order the qargs of an instruction.
Oh yeah, I totally agree with all that - these to me are examples of a user interacting with the objects in an abstract mathematical way, so it's preferable that they stay as `quantum_info` objects.

`Operator` doesn't have a `control` method yet, so we can give it whatever semantics we like when we add it, right?  I totally agree that we should cause `Operator.control` and `UnitaryGate.control` to produce the same control-qubit ordering as each other in their matrix forms - it'd be super confusing to get them reversed.

Resolving patch:
diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py
--- a/qiskit/circuit/gate.py
+++ b/qiskit/circuit/gate.py
@@ -77,23 +77,7 @@ def power(self, exponent: float):
         from qiskit.quantum_info.operators import Operator
         from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate
 
-        from scipy.linalg import schur
-
-        # Should be diagonalized because it's a unitary.
-        decomposition, unitary = schur(Operator(self).data, output="complex")
-        # Raise the diagonal entries to the specified power
-        decomposition_power = []
-
-        decomposition_diagonal = decomposition.diagonal()
-        # assert off-diagonal are 0
-        if not np.allclose(np.diag(decomposition_diagonal), decomposition):
-            raise CircuitError("The matrix is not diagonal")
-
-        for element in decomposition_diagonal:
-            decomposition_power.append(pow(element, exponent))
-        # Then reconstruct the resulting gate.
-        unitary_power = unitary @ np.diag(decomposition_power) @ unitary.conj().T
-        return UnitaryGate(unitary_power, label=f"{self.name}^{exponent}")
+        return UnitaryGate(Operator(self).power(exponent), label=f"{self.name}^{exponent}")
 
     def __pow__(self, exponent: float) -> "Gate":
         return self.power(exponent)
diff --git a/qiskit/quantum_info/operators/operator.py b/qiskit/quantum_info/operators/operator.py
--- a/qiskit/quantum_info/operators/operator.py
+++ b/qiskit/quantum_info/operators/operator.py
@@ -21,6 +21,7 @@
 from numbers import Number
 from typing import TYPE_CHECKING
 
+import scipy.linalg
 import numpy as np
 
 from qiskit.circuit.instruction import Instruction
@@ -524,7 +525,16 @@ def power(self, n: float) -> Operator:
         if self.input_dims() != self.output_dims():
             raise QiskitError("Can only power with input_dims = output_dims.")
         ret = copy.copy(self)
-        ret._data = np.linalg.matrix_power(self.data, n)
+        if isinstance(n, int):
+            ret._data = np.linalg.matrix_power(self.data, n)
+        else:
+            # Experimentally, for fractional powers this seems to be 3x faster than
+            # calling scipy.linalg.fractional_matrix_power(self.data, n)
+            decomposition, unitary = scipy.linalg.schur(self.data, output="complex")
+            decomposition_diagonal = decomposition.diagonal()
+            decomposition_power = [pow(element, n) for element in decomposition_diagonal]
+            unitary_power = unitary @ np.diag(decomposition_power) @ unitary.conj().T
+            ret._data = unitary_power
         return ret
 
     def tensor(self, other: Operator) -> Operator:


Test patch:
diff --git a/test/python/quantum_info/operators/test_operator.py b/test/python/quantum_info/operators/test_operator.py
--- a/test/python/quantum_info/operators/test_operator.py
+++ b/test/python/quantum_info/operators/test_operator.py
@@ -498,6 +498,18 @@ def test_power(self):
         self.assertEqual(op.power(4), Operator(-1 * np.eye(2)))
         self.assertEqual(op.power(8), Operator(np.eye(2)))
 
+    def test_floating_point_power(self):
+        """Test handling floating-point powers."""
+        circuit = QuantumCircuit(2)
+        circuit.crz(np.pi, 0, 1)
+        op = Operator(circuit)
+
+        expected_circuit = QuantumCircuit(2)
+        expected_circuit.crz(np.pi / 4, 0, 1)
+        expected_op = Operator(expected_circuit)
+
+        self.assertEqual(op.power(0.25), expected_op)
+
     def test_expand(self):
         """Test expand method."""
         mat1 = self.UX