Repository: Qiskit/qiskit (python)

Issue:
2Q gate synthesis with fractional gates
### Environment

- **Qiskit version**: main
- **Python version**: doesn't matter
- **Operating system**: doesn't matter


### What is happening?

When I build a Qiskit `Target` that supports two qubit basis gates with arbitrary rotation angle, it cannot transpile the circuit if the added basis gate is other than `RZXGate` or `RXXGate`.

### How can we reproduce the issue?

```python
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit import quantum_info as qi, transpile, QuantumCircuit

backend = GenericBackendV2(2, basis_gates=["rz", "rx", "rzz"])
qc = QuantumCircuit(2)
qc.unitary(qi.random_unitary(4), [0, 1])
isa = transpile(qc, target=backend.target)
```
this results in an error
```
TypeError: ParameterExpression with unbound parameters (dict_keys([Parameter(ϴ)])) cannot be cast to a float.
```
because it tries to get the unitary matrix of parameterized `RZZGate(Parameter(ϴ))` instruction.

On the other hand, it transpiles circuit when I directly specify the basis gates.
```python
isa = transpile(qc, basis_gates=["rz", "rx", "rzz"])
```

### What should happen?

- Qiskit transpiler should support arbitrary fractional two qubit basis gates, e.g. `RZX, RXX, RYY, RZZ, RXY, ...`. 
- Transpiling with `Target` and giving `basis_gates` should behave identically.

This is related but might be a feature request; even if I give `RZXGate(Parameter(ϴ))` in the target, current synthesis algorithm only picks pi/4 rotation. The synthesis pass should consider another angle if there is possibility of reducing the circuit depth.

### Any suggestions?

No. I'm not familiar with synthesis passes.

Pull request:
fix issue/12311 with fractional gate in basis (backport #12511)


### Summary
This fixes an issue where an exception would be raised if transpiling a unitary gate to a backend with an RZZGate in its basis however transpiling with only `basis_gates` specified would transpile fine. 

fixes #12311 

### Details and comments
In this case, where RZZ is the only entangling gate in the basis, decomposition of a 2q unitary gate wouldn't get synthesized in the `UnitarySynthesis` pass but would instead be synthesized in the `HighLevelSynthesis` pass because this would call the instruction's `_define` method which calls `two_qubit_cnot_decompose`. A subsequent `BasisTranslator` pass ultimately maps the cx to the rzz basis.  I'm not sure whether this indirect path to unitary synthesis is intentional.

This pr resolves the specific example posed in the issue.

<hr>This is an automatic backport of pull request #12511 done by [Mergify](https://mergify.com).

Patch:
diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
index a30411d16..d2a037a50 100644
--- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
@@ -49,6 +49,7 @@ from qiskit.circuit.library.standard_gates import (
     CZGate,
     RXXGate,
     RZXGate,
+    RZZGate,
     ECRGate,
 )
 from qiskit.transpiler.passes.synthesis import plugin
@@ -744,6 +745,8 @@ class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin):
                 op = RXXGate(pi / 2)
             elif isinstance(op, RZXGate) and isinstance(op.params[0], Parameter):
                 op = RZXGate(pi / 4)
+            elif isinstance(op, RZZGate) and isinstance(op.params[0], Parameter):
+                op = RZZGate(pi / 2)
             return op
 
         try:


Test patch:
diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py
index 24e5e68ba..cd94c474d 100644
--- a/test/python/transpiler/test_basis_translator.py
+++ b/test/python/transpiler/test_basis_translator.py
@@ -16,6 +16,7 @@
 import os
 
 from numpy import pi
+import scipy
 
 from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
 from qiskit import transpile
@@ -33,13 +34,17 @@ from qiskit.circuit.library import (
     XGate,
     SXGate,
     CXGate,
+    RXGate,
+    RZZGate,
 )
 from qiskit.converters import circuit_to_dag, dag_to_circuit, circuit_to_instruction
 from qiskit.exceptions import QiskitError
+from qiskit.providers.fake_provider import GenericBackendV2
 from qiskit.quantum_info import Operator
 from qiskit.transpiler.target import Target, InstructionProperties
 from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions
+from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
 from qiskit.circuit.library.standard_gates.equivalence_library import (
     StandardEquivalenceLibrary as std_eqlib,
 )
@@ -1222,3 +1227,48 @@ class TestBasisTranslatorWithTarget(QiskitTestCase):
 
         out = BasisTranslator(eq_lib, {"my_h", "my_cx"}, target)(qc)
         self.assertEqual(out, expected)
+
+    def test_fractional_gate_in_basis_from_string(self):
+        """Test transpiling with RZZ in basis with only basis_gates option."""
+        num_qubits = 2
+        seed = 9169
+        basis_gates = ["rz", "rx", "rzz"]
+        qc = QuantumCircuit(num_qubits)
+        mat = scipy.stats.unitary_group.rvs(2**num_qubits, random_state=seed)
+        qc.unitary(mat, range(num_qubits))
+        pm = generate_preset_pass_manager(
+            optimization_level=1, basis_gates=basis_gates, seed_transpiler=134
+        )
+        cqc = pm.run(qc)
+        self.assertEqual(Operator(qc), Operator(cqc))
+
+    def test_fractional_gate_in_basis_from_backendv2(self):
+        """Test transpiling with RZZ in basis of backendv2."""
+        num_qubits = 2
+        seed = 9169
+        basis_gates = ["rz", "rx", "rzz"]
+        qc = QuantumCircuit(num_qubits)
+        mat = scipy.stats.unitary_group.rvs(2**num_qubits, random_state=seed)
+        qc.unitary(mat, range(num_qubits))
+        backend = GenericBackendV2(num_qubits, basis_gates=basis_gates)
+        target = backend.target
+        pm = generate_preset_pass_manager(optimization_level=1, target=target, seed_transpiler=134)
+        cqc = pm.run(qc)
+        self.assertEqual(Operator(qc), Operator.from_circuit(cqc))
+
+    def test_fractional_gate_in_basis_from_custom_target(self):
+        """Test transpiling with RZZ in basis of custom target."""
+        num_qubits = 2
+        seed = 9169
+        qc = QuantumCircuit(num_qubits)
+        mat = scipy.stats.unitary_group.rvs(2**num_qubits, random_state=seed)
+        qc.unitary(mat, range(num_qubits))
+        target = Target()
+        target.add_instruction(RZGate(self.theta), {(i,): None for i in range(qc.num_qubits)})
+        target.add_instruction(RXGate(self.phi), {(i,): None for i in range(qc.num_qubits)})
+        target.add_instruction(
+            RZZGate(self.lam), {(i, i + 1): None for i in range(qc.num_qubits - 1)}
+        )
+        pm = generate_preset_pass_manager(optimization_level=1, target=target, seed_transpiler=134)
+        cqc = pm.run(qc)
+        self.assertEqual(Operator(qc), Operator.from_circuit(cqc))