From 7dbfe263ad74dc37d419526305984063086a4fe3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 4 Feb 2025 15:34:54 +0200 Subject: [PATCH 01/36] wrote a test for rzz conversion --- qiskit_ibm_runtime/utils/utils.py | 6 ++++++ .../passes/basis/test_fold_rzz_angle.py | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index 25b2f5e07..cd62ed2bc 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -60,6 +60,12 @@ def is_simulator(backend: BackendV1 | BackendV2) -> bool: return getattr(backend, "simulator", False) +def convert_to_rzz_valid_circ_and_vals( + circ: QuantumCircuit, param_values: List[Tuple] +) -> Tuple[QuantumCircuit, List[Tuple]]: + return circ, param_values + + def _is_isa_circuit_helper(circuit: QuantumCircuit, target: Target, qubit_map: Dict) -> str: """ A section of is_isa_circuit, separated to allow recursive calls diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index a9d035e8f..21d508e1e 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -18,12 +18,14 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.parameter import Parameter +from qiskit.primitives.containers.sampler_pub import SamplerPub from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import Operator from qiskit_ibm_runtime.transpiler.passes.basis import FoldRzzAngle from qiskit_ibm_runtime.fake_provider import FakeFractionalBackend +from qiskit_ibm_runtime.utils.utils import convert_to_rzz_valid_circ_and_vals, is_valid_rzz_pub from .....ibm_test_case import IBMTestCase @@ -115,3 +117,22 @@ def test_fractional_plugin(self): self.assertEqual(isa_circ.data[0].operation.name, "global_phase") self.assertEqual(isa_circ.data[1].operation.name, "rzz") self.assertTrue(np.isclose(isa_circ.data[1].operation.params[0], 7 - 2 * pi)) + + def test_rzz_pub_conversion(self): + """Test the function `convert_to_rzz_valid_circ_and_vals`""" + p1 = Parameter("p1") + p2 = Parameter("p2") + + circ = QuantumCircuit(3) + circ.rzz(p1 + p2, 2, 1) + + param_vals = [(0.1, 0.2), (0.3, 0.4)] + + isa_circ, isa_vals = convert_to_rzz_valid_circ_and_vals(circ, param_vals) + isa_pub = SamplerPub.coerce((isa_circ, param_vals)) + + self.assertEqual(is_valid_rzz_pub(isa_pub), "") + self.assertEqual( + Operator.from_circuit(circ.assign_parameters(param_vals[0])), + Operator.from_circuit(isa_circ.assign_parameters(isa_vals[0])), + ) From 0aee8a740ec747926b3fdaefd663fefb5e9d4db1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 5 Feb 2025 17:39:33 +0200 Subject: [PATCH 02/36] switching to pubs --- qiskit_ibm_runtime/utils/utils.py | 66 +++++++++++++++++-- .../passes/basis/test_fold_rzz_angle.py | 10 ++- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index cd62ed2bc..3c1535a4d 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -20,7 +20,7 @@ import re from queue import Queue from threading import Condition -from typing import List, Optional, Any, Dict, Union, Tuple, Set +from typing import List, Optional, Any, Dict, Union, Tuple, Set, get_args from urllib.parse import urlparse from itertools import chain import numpy as np @@ -40,8 +40,8 @@ ) from qiskit.transpiler import Target from qiskit.providers.backend import BackendV1, BackendV2 -from qiskit.primitives.containers.estimator_pub import EstimatorPub -from qiskit.primitives.containers.sampler_pub import SamplerPub +from qiskit.primitives.containers.estimator_pub import EstimatorPub, EstimatorPubLike +from qiskit.primitives.containers.sampler_pub import SamplerPub, SamplerPubLike from .deprecation import deprecate_function @@ -61,9 +61,63 @@ def is_simulator(backend: BackendV1 | BackendV2) -> bool: def convert_to_rzz_valid_circ_and_vals( - circ: QuantumCircuit, param_values: List[Tuple] -) -> Tuple[QuantumCircuit, List[Tuple]]: - return circ, param_values + program_id: str, pub: SamplerPubLike | EstimatorPubLike +) -> SamplerPub | EstimatorPub: + if program_id == "sampler": + pub = SamplerPub.coerce(pub) + elif program_id == "estimator": + pub = EstimatorPub.coerce(pub) + else: + raise ValueError(f"Unknown program id {program_id}") + + #print(pub.parameter_values.as_array()) + val_data = pub.parameter_values.data + val_data[("rzz_extra_1", "rzz_extra_2", "rzz_extra_3")] = np.ones([2, 2, 3, 3]) + print(val_data) + isa_pub = SamplerPub.coerce((pub.circuit, val_data)) + + print(isa_pub.parameter_values.data) + + + + """ + pub_params = np.array(list(chain.from_iterable(pub.parameter_values.data))) + new_data = [] + + for inst in circ.data: + op = inst.operation + if op.name != "rzz": + new_data.append(inst) + + pub_params = np.array(list(chain.from_iterable(pub.parameter_values.data))) + + # first axis will be over flattened shape, second axis over circuit parameters + arr = pub.parameter_values.ravel().as_array() + + for param_exp in rzz_params: + param_names = [param.name for param in param_exp.parameters] + + col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names] + # col_indices is the indices of columns in the parameter value array that have to be checked + + # project only to the parameters that have to be checked + projected_arr = arr[:, col_indices] + + for row in projected_arr: + angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) + if angle < 0.0 or angle > np.pi / 2 + 1e-10: + vals_msg = ", ".join( + [f"{param_name}={param_val}" for param_name, param_val in zip(param_names, row)] + ) + return ( + "The instruction rzz is supported only for angles in the " + f"range [0, pi/2], but an angle of {angle} has been provided; " + f"via parameter value(s) {vals_msg}, substituted in parameter expression " + f"{param_exp}." + ) + """ + + return pub def _is_isa_circuit_helper(circuit: QuantumCircuit, target: Target, qubit_map: Dict) -> str: diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 21d508e1e..d71bdbffb 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -122,14 +122,20 @@ def test_rzz_pub_conversion(self): """Test the function `convert_to_rzz_valid_circ_and_vals`""" p1 = Parameter("p1") p2 = Parameter("p2") + p3 = Parameter("p3") + p4 = Parameter("p4") circ = QuantumCircuit(3) circ.rzz(p1 + p2, 2, 1) + circ.rzz(p3 + p4, 1, 2) param_vals = [(0.1, 0.2), (0.3, 0.4)] + val_ab = np.ones([2, 2, 3, 2]) + val_c = (-1) * np.ones([2, 2, 3]) + val_d = np.ones([2, 2, 3]) + param_vals = {("a", "b"): val_ab, "c": val_c, "d": val_d} - isa_circ, isa_vals = convert_to_rzz_valid_circ_and_vals(circ, param_vals) - isa_pub = SamplerPub.coerce((isa_circ, param_vals)) + isa_pub = convert_to_rzz_valid_circ_and_vals("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") self.assertEqual( From 1f195124c17c1285e4796699a43d41915f572c00 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 13:24:44 +0200 Subject: [PATCH 03/36] a beginning of the change in the pub --- .../transpiler/passes/basis/fold_rzz_angle.py | 87 ++++++++++++++++++- qiskit_ibm_runtime/utils/utils.py | 66 +------------- .../passes/basis/test_fold_rzz_angle.py | 15 +--- 3 files changed, 93 insertions(+), 75 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index a7996d235..8fd13bfc7 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -14,13 +14,16 @@ from typing import Tuple from math import pi +from itertools import chain from qiskit.converters import dag_to_circuit, circuit_to_dag +from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate -from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit import Qubit, ControlFlowOp from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import TransformationPass +from qiskit.primitives.containers.estimator_pub import EstimatorPub, EstimatorPubLike +from qiskit.primitives.containers.sampler_pub import SamplerPub, SamplerPubLike import numpy as np @@ -244,3 +247,85 @@ def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: check=False, ) return new_dag + + +def convert_to_rzz_valid_pub( + program_id: str, pub: SamplerPubLike | EstimatorPubLike +) -> SamplerPub | EstimatorPub: + if program_id == "sampler": + pub = SamplerPub.coerce(pub) + elif program_id == "estimator": + pub = EstimatorPub.coerce(pub) + else: + raise ValueError(f"Unknown program id {program_id}") + + """ + #print(pub.parameter_values.as_array()) + val_data = pub.parameter_values.data + val_data[("rzz_extra_1", "rzz_extra_2", "rzz_extra_3")] = np.ones([2, 3]) + print(val_data) + isa_pub = SamplerPub.coerce((pub.circuit, val_data)) + + print(isa_pub.parameter_values.data) + """ + + val_data = pub.parameter_values.data + pub_params = np.array(list(chain.from_iterable(val_data))) + # first axis will be over flattened shape, second axis over circuit parameters + arr = pub.parameter_values.ravel().as_array() + + new_data = [] + rzz_count = 0 + for instruction in pub.circuit.data: + operation = instruction.operation + if operation.name != "rzz" or not isinstance((param_exp := instruction.operation.params[0]), ParameterExpression): + new_data.append(instruction) + continue + + param_names = [param.name for param in param_exp.parameters] + + col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names] + # col_indices is the indices of columns in the parameter value array that have to be checked + + # project only to the parameters that have to be checked + projected_arr = arr[:, col_indices] + num_param_sets = len(projected_arr) + + global_phases = np.zeros(num_param_sets) + rz_angles = np.zeros(num_param_sets) + rx_angles = np.zeros(num_param_sets) + rzz_angles = np.zeros(num_param_sets) + for idx, row in enumerate(projected_arr): + angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) + + if (angle + np.pi / 2) % (2 * np.pi) >= np.pi: + global_phases[idx] += -np.pi / 2 + if angle % (2 * np.pi) >= 3 * np.pi / 2: + global_phases[idx] += np.pi + if (angle + np.pi) % (4 * np.pi) >= 2 * np.pi: + global_phases[idx] += np.pi + + if (angle + np.pi / 2) % (2 * np.pi) >= np.pi: + rz_angles[idx] = np.pi + else: + rz_angles[idx] = 0 + + if angle % np.pi >= np.pi / 2: + rx_angles[idx] = 0 + else: + rx_angles[idx] = np.pi + + rzz_angles[idx] = np.pi / 2 - (angle % np.pi - pi / 2).abs() + + rzz_count += 1 + param_prefix = f"rzz_{rzz_count}_" + qubits = instruction.qubits + + if any(not np.isclose(global_phase, 0) for global_phase in global_phases): + param_global_phase = Parameter(f"{param_prefix}global_phase") + new_data.append(CircuitInstruction(GlobalPhaseGate(param_global_phase))) + + + + + return pub \ No newline at end of file diff --git a/qiskit_ibm_runtime/utils/utils.py b/qiskit_ibm_runtime/utils/utils.py index 3c1535a4d..25b2f5e07 100644 --- a/qiskit_ibm_runtime/utils/utils.py +++ b/qiskit_ibm_runtime/utils/utils.py @@ -20,7 +20,7 @@ import re from queue import Queue from threading import Condition -from typing import List, Optional, Any, Dict, Union, Tuple, Set, get_args +from typing import List, Optional, Any, Dict, Union, Tuple, Set from urllib.parse import urlparse from itertools import chain import numpy as np @@ -40,8 +40,8 @@ ) from qiskit.transpiler import Target from qiskit.providers.backend import BackendV1, BackendV2 -from qiskit.primitives.containers.estimator_pub import EstimatorPub, EstimatorPubLike -from qiskit.primitives.containers.sampler_pub import SamplerPub, SamplerPubLike +from qiskit.primitives.containers.estimator_pub import EstimatorPub +from qiskit.primitives.containers.sampler_pub import SamplerPub from .deprecation import deprecate_function @@ -60,66 +60,6 @@ def is_simulator(backend: BackendV1 | BackendV2) -> bool: return getattr(backend, "simulator", False) -def convert_to_rzz_valid_circ_and_vals( - program_id: str, pub: SamplerPubLike | EstimatorPubLike -) -> SamplerPub | EstimatorPub: - if program_id == "sampler": - pub = SamplerPub.coerce(pub) - elif program_id == "estimator": - pub = EstimatorPub.coerce(pub) - else: - raise ValueError(f"Unknown program id {program_id}") - - #print(pub.parameter_values.as_array()) - val_data = pub.parameter_values.data - val_data[("rzz_extra_1", "rzz_extra_2", "rzz_extra_3")] = np.ones([2, 2, 3, 3]) - print(val_data) - isa_pub = SamplerPub.coerce((pub.circuit, val_data)) - - print(isa_pub.parameter_values.data) - - - - """ - pub_params = np.array(list(chain.from_iterable(pub.parameter_values.data))) - new_data = [] - - for inst in circ.data: - op = inst.operation - if op.name != "rzz": - new_data.append(inst) - - pub_params = np.array(list(chain.from_iterable(pub.parameter_values.data))) - - # first axis will be over flattened shape, second axis over circuit parameters - arr = pub.parameter_values.ravel().as_array() - - for param_exp in rzz_params: - param_names = [param.name for param in param_exp.parameters] - - col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names] - # col_indices is the indices of columns in the parameter value array that have to be checked - - # project only to the parameters that have to be checked - projected_arr = arr[:, col_indices] - - for row in projected_arr: - angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) - if angle < 0.0 or angle > np.pi / 2 + 1e-10: - vals_msg = ", ".join( - [f"{param_name}={param_val}" for param_name, param_val in zip(param_names, row)] - ) - return ( - "The instruction rzz is supported only for angles in the " - f"range [0, pi/2], but an angle of {angle} has been provided; " - f"via parameter value(s) {vals_msg}, substituted in parameter expression " - f"{param_exp}." - ) - """ - - return pub - - def _is_isa_circuit_helper(circuit: QuantumCircuit, target: Target, qubit_map: Dict) -> str: """ A section of is_isa_circuit, separated to allow recursive calls diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index d71bdbffb..eb65b7a12 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -23,9 +23,9 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import Operator -from qiskit_ibm_runtime.transpiler.passes.basis import FoldRzzAngle +from qiskit_ibm_runtime.transpiler.passes.basis import FoldRzzAngle, convert_to_rzz_valid_pub from qiskit_ibm_runtime.fake_provider import FakeFractionalBackend -from qiskit_ibm_runtime.utils.utils import convert_to_rzz_valid_circ_and_vals, is_valid_rzz_pub +from qiskit_ibm_runtime.utils.utils import is_valid_rzz_pub from .....ibm_test_case import IBMTestCase @@ -122,20 +122,13 @@ def test_rzz_pub_conversion(self): """Test the function `convert_to_rzz_valid_circ_and_vals`""" p1 = Parameter("p1") p2 = Parameter("p2") - p3 = Parameter("p3") - p4 = Parameter("p4") circ = QuantumCircuit(3) circ.rzz(p1 + p2, 2, 1) - circ.rzz(p3 + p4, 1, 2) + circ.rzz(p1 - p2, 1, 2) param_vals = [(0.1, 0.2), (0.3, 0.4)] - val_ab = np.ones([2, 2, 3, 2]) - val_c = (-1) * np.ones([2, 2, 3]) - val_d = np.ones([2, 2, 3]) - param_vals = {("a", "b"): val_ab, "c": val_c, "d": val_d} - - isa_pub = convert_to_rzz_valid_circ_and_vals("sampler", (circ, param_vals)) + isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") self.assertEqual( From 4be86664dd0463884ec9e3369a0d960eff42dc52 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 14:53:30 +0200 Subject: [PATCH 04/36] global phase done --- .../transpiler/passes/basis/fold_rzz_angle.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 8fd13bfc7..8da4be81d 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -17,7 +17,7 @@ from itertools import chain from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression +from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression, QuantumCircuit from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate from qiskit.circuit import Qubit, ControlFlowOp from qiskit.dagcircuit import DAGCircuit @@ -274,8 +274,10 @@ def convert_to_rzz_valid_pub( # first axis will be over flattened shape, second axis over circuit parameters arr = pub.parameter_values.ravel().as_array() + new_circ = QuantumCircuit(pub.circuit.qubits, pub.circuit.clbits) new_data = [] rzz_count = 0 + for instruction in pub.circuit.data: operation = instruction.operation if operation.name != "rzz" or not isinstance((param_exp := instruction.operation.params[0]), ParameterExpression): @@ -295,6 +297,7 @@ def convert_to_rzz_valid_pub( rz_angles = np.zeros(num_param_sets) rx_angles = np.zeros(num_param_sets) rzz_angles = np.zeros(num_param_sets) + for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) @@ -324,8 +327,11 @@ def convert_to_rzz_valid_pub( if any(not np.isclose(global_phase, 0) for global_phase in global_phases): param_global_phase = Parameter(f"{param_prefix}global_phase") new_data.append(CircuitInstruction(GlobalPhaseGate(param_global_phase))) + val_data[f"{param_prefix}global_phase"] = global_phases - - + new_circ.data = new_data - return pub \ No newline at end of file + if program_id == "sampler": + return SamplerPub.coerce((new_circ, val_data), pub.shots) + else: + return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.shots) \ No newline at end of file From 763da6cb8e3abd4e9d5a3a5573adaa8c2edf95a4 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 15:16:19 +0200 Subject: [PATCH 05/36] fixes --- .../transpiler/passes/basis/fold_rzz_angle.py | 16 ++++++++++++---- .../passes/basis/test_fold_rzz_angle.py | 13 ++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 8da4be81d..ebc8dd13f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -318,19 +318,27 @@ def convert_to_rzz_valid_pub( else: rx_angles[idx] = np.pi - rzz_angles[idx] = np.pi / 2 - (angle % np.pi - pi / 2).abs() + rzz_angles[idx] = np.abs(np.pi / 2 - (angle % np.pi - pi / 2)) rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits if any(not np.isclose(global_phase, 0) for global_phase in global_phases): - param_global_phase = Parameter(f"{param_prefix}global_phase") - new_data.append(CircuitInstruction(GlobalPhaseGate(param_global_phase))) - val_data[f"{param_prefix}global_phase"] = global_phases + if all(np.isclose(global_phase, global_phases[0]) for global_phase in global_phases[1:]): + new_data.append(CircuitInstruction(GlobalPhaseGate(global_phases[0]))) + else: + param_global_phase = Parameter(f"{param_prefix}global_phase") + new_data.append(CircuitInstruction(GlobalPhaseGate(param_global_phase))) + val_data[f"{param_prefix}global_phase"] = global_phases + + new_data.append(instruction) new_circ.data = new_data + print(new_circ) + print(val_data) + if program_id == "sampler": return SamplerPub.coerce((new_circ, val_data), pub.shots) else: diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index eb65b7a12..91c6170f9 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -18,12 +18,11 @@ from qiskit.circuit import QuantumCircuit from qiskit.circuit.parameter import Parameter -from qiskit.primitives.containers.sampler_pub import SamplerPub from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import Operator -from qiskit_ibm_runtime.transpiler.passes.basis import FoldRzzAngle, convert_to_rzz_valid_pub +from qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle import FoldRzzAngle, convert_to_rzz_valid_pub from qiskit_ibm_runtime.fake_provider import FakeFractionalBackend from qiskit_ibm_runtime.utils.utils import is_valid_rzz_pub from .....ibm_test_case import IBMTestCase @@ -127,11 +126,11 @@ def test_rzz_pub_conversion(self): circ.rzz(p1 + p2, 2, 1) circ.rzz(p1 - p2, 1, 2) - param_vals = [(0.1, 0.2), (0.3, 0.4)] + param_vals = [(0.2, 0.1), (0.4, 0.3)] isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") - self.assertEqual( - Operator.from_circuit(circ.assign_parameters(param_vals[0])), - Operator.from_circuit(isa_circ.assign_parameters(isa_vals[0])), - ) + #self.assertEqual( + # Operator.from_circuit(circ.assign_parameters(param_vals[0])), + # Operator.from_circuit(isa_.assign_parameters(isa_vals[0])), + #) From b2b157c0cb1ac56bcbbb94e1ed36371989d9327f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 17:12:38 +0200 Subject: [PATCH 06/36] fixed test --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 91c6170f9..0424a94ee 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -130,7 +130,8 @@ def test_rzz_pub_conversion(self): isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") - #self.assertEqual( - # Operator.from_circuit(circ.assign_parameters(param_vals[0])), - # Operator.from_circuit(isa_.assign_parameters(isa_vals[0])), - #) + for param_set_1, param_set_2 in zip(param_vals, isa_pub.parameter_values.ravel().as_array()): + self.assertEqual( + Operator.from_circuit(circ.assign_parameters(param_set_1)), + Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)), + ) From e660cb80dd9fc453865d98bf6e458954ef501164 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:39:12 +0200 Subject: [PATCH 07/36] handling rz, rx, rzz and removing global phase --- .../transpiler/passes/basis/fold_rzz_angle.py | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index ebc8dd13f..6a04d1398 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -18,7 +18,7 @@ from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression, QuantumCircuit -from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate +from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate, RXGate from qiskit.circuit import Qubit, ControlFlowOp from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.basepasses import TransformationPass @@ -293,20 +293,11 @@ def convert_to_rzz_valid_pub( projected_arr = arr[:, col_indices] num_param_sets = len(projected_arr) - global_phases = np.zeros(num_param_sets) rz_angles = np.zeros(num_param_sets) rx_angles = np.zeros(num_param_sets) - rzz_angles = np.zeros(num_param_sets) for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) - - if (angle + np.pi / 2) % (2 * np.pi) >= np.pi: - global_phases[idx] += -np.pi / 2 - if angle % (2 * np.pi) >= 3 * np.pi / 2: - global_phases[idx] += np.pi - if (angle + np.pi) % (4 * np.pi) >= 2 * np.pi: - global_phases[idx] += np.pi if (angle + np.pi / 2) % (2 * np.pi) >= np.pi: rz_angles[idx] = np.pi @@ -323,16 +314,43 @@ def convert_to_rzz_valid_pub( rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits - - if any(not np.isclose(global_phase, 0) for global_phase in global_phases): - if all(np.isclose(global_phase, global_phases[0]) for global_phase in global_phases[1:]): - new_data.append(CircuitInstruction(GlobalPhaseGate(global_phases[0]))) + + is_rz = False + if any(not np.isclose(rz_angle, 0) for rz_angle in rz_angles): + is_rz = True + if all(np.isclose(rz_angle, np.pi) for rz_angle in rz_angles): + new_data.append(CircuitInstruction(RZGate(np.pi), (qubits[0]),)) + new_data.append(CircuitInstruction(RZGate(np.pi), (qubits[1]),)) else: - param_global_phase = Parameter(f"{param_prefix}global_phase") - new_data.append(CircuitInstruction(GlobalPhaseGate(param_global_phase))) - val_data[f"{param_prefix}global_phase"] = global_phases + param_rz = Parameter(f"{param_prefix}rz") + new_data.append(CircuitInstruction(RZGate(param_rz), (qubits[0]),)) + new_data.append(CircuitInstruction(RZGate(param_rz), (qubits[1]),)) + val_data[f"{param_prefix}rz"] = rz_angles + + is_rx = False + is_x = False + if any(not np.isclose(rx_angle, 0) for rx_angle in rx_angles): + is_rx = True + if all(np.isclose(rx_angle, np.pi) for rx_angle in rx_angles): + is_x = True + new_data.append(CircuitInstruction(XGate(), (qubits[0]),)) + else: + is_x = False + param_rx = Parameter(f"{param_prefix}rx") + new_data.append(CircuitInstruction(RXGate(param_rx), (qubits[0]),)) + val_data[f"{param_prefix}rx"] = rx_angles + + if is_rz or is_rx: + rzz_angle = pi / 2 - (angle._apply_operation(mod, pi) - pi / 2).abs() + new_data.append(CircuitInstruction(RZZGate(rzz_angle), qubits)) + else: + new_data.append(instruction) - new_data.append(instruction) + if is_rx: + if is_x: + new_data.append(CircuitInstruction(XGate(), (qubits[0]),)) + else: + new_data.append(CircuitInstruction(RXGate(param_rx), (qubits[0]),)) new_circ.data = new_data From 95f60a53f488a3034ddd2d29bbaa6afb70e263c2 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:40:35 +0200 Subject: [PATCH 08/36] update test to ignore global phase --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 0424a94ee..8df761b48 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -131,7 +131,6 @@ def test_rzz_pub_conversion(self): self.assertEqual(is_valid_rzz_pub(isa_pub), "") for param_set_1, param_set_2 in zip(param_vals, isa_pub.parameter_values.ravel().as_array()): - self.assertEqual( - Operator.from_circuit(circ.assign_parameters(param_set_1)), - Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)), + self.assertTrue( + Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv(Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2))) ) From cfa60efa9e7d9642c220f20fd5092d46fd74b3d1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:41:24 +0200 Subject: [PATCH 09/36] black --- .../transpiler/passes/basis/test_fold_rzz_angle.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 8df761b48..23d99e5ab 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -22,7 +22,10 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import Operator -from qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle import FoldRzzAngle, convert_to_rzz_valid_pub +from qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle import ( + FoldRzzAngle, + convert_to_rzz_valid_pub, +) from qiskit_ibm_runtime.fake_provider import FakeFractionalBackend from qiskit_ibm_runtime.utils.utils import is_valid_rzz_pub from .....ibm_test_case import IBMTestCase @@ -130,7 +133,11 @@ def test_rzz_pub_conversion(self): isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") - for param_set_1, param_set_2 in zip(param_vals, isa_pub.parameter_values.ravel().as_array()): + for param_set_1, param_set_2 in zip( + param_vals, isa_pub.parameter_values.ravel().as_array() + ): self.assertTrue( - Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv(Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2))) + Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( + Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)) + ) ) From 7fd4b3439341ccb7902adc940741ef931cef0a0f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:44:57 +0200 Subject: [PATCH 10/36] black --- .../transpiler/passes/basis/fold_rzz_angle.py | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 6a04d1398..cfff9546c 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -271,7 +271,7 @@ def convert_to_rzz_valid_pub( val_data = pub.parameter_values.data pub_params = np.array(list(chain.from_iterable(val_data))) - # first axis will be over flattened shape, second axis over circuit parameters + # first axis will be over flattened shape, second axis over circuit parameters arr = pub.parameter_values.ravel().as_array() new_circ = QuantumCircuit(pub.circuit.qubits, pub.circuit.clbits) @@ -280,7 +280,9 @@ def convert_to_rzz_valid_pub( for instruction in pub.circuit.data: operation = instruction.operation - if operation.name != "rzz" or not isinstance((param_exp := instruction.operation.params[0]), ParameterExpression): + if operation.name != "rzz" or not isinstance( + (param_exp := instruction.operation.params[0]), ParameterExpression + ): new_data.append(instruction) continue @@ -292,7 +294,7 @@ def convert_to_rzz_valid_pub( # project only to the parameters that have to be checked projected_arr = arr[:, col_indices] num_param_sets = len(projected_arr) - + rz_angles = np.zeros(num_param_sets) rx_angles = np.zeros(num_param_sets) @@ -314,17 +316,37 @@ def convert_to_rzz_valid_pub( rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits - + is_rz = False if any(not np.isclose(rz_angle, 0) for rz_angle in rz_angles): is_rz = True if all(np.isclose(rz_angle, np.pi) for rz_angle in rz_angles): - new_data.append(CircuitInstruction(RZGate(np.pi), (qubits[0]),)) - new_data.append(CircuitInstruction(RZGate(np.pi), (qubits[1]),)) + new_data.append( + CircuitInstruction( + RZGate(np.pi), + (qubits[0]), + ) + ) + new_data.append( + CircuitInstruction( + RZGate(np.pi), + (qubits[1]), + ) + ) else: param_rz = Parameter(f"{param_prefix}rz") - new_data.append(CircuitInstruction(RZGate(param_rz), (qubits[0]),)) - new_data.append(CircuitInstruction(RZGate(param_rz), (qubits[1]),)) + new_data.append( + CircuitInstruction( + RZGate(param_rz), + (qubits[0]), + ) + ) + new_data.append( + CircuitInstruction( + RZGate(param_rz), + (qubits[1]), + ) + ) val_data[f"{param_prefix}rz"] = rz_angles is_rx = False @@ -333,11 +355,21 @@ def convert_to_rzz_valid_pub( is_rx = True if all(np.isclose(rx_angle, np.pi) for rx_angle in rx_angles): is_x = True - new_data.append(CircuitInstruction(XGate(), (qubits[0]),)) + new_data.append( + CircuitInstruction( + XGate(), + (qubits[0]), + ) + ) else: is_x = False param_rx = Parameter(f"{param_prefix}rx") - new_data.append(CircuitInstruction(RXGate(param_rx), (qubits[0]),)) + new_data.append( + CircuitInstruction( + RXGate(param_rx), + (qubits[0]), + ) + ) val_data[f"{param_prefix}rx"] = rx_angles if is_rz or is_rx: @@ -348,9 +380,19 @@ def convert_to_rzz_valid_pub( if is_rx: if is_x: - new_data.append(CircuitInstruction(XGate(), (qubits[0]),)) + new_data.append( + CircuitInstruction( + XGate(), + (qubits[0]), + ) + ) else: - new_data.append(CircuitInstruction(RXGate(param_rx), (qubits[0]),)) + new_data.append( + CircuitInstruction( + RXGate(param_rx), + (qubits[0]), + ) + ) new_circ.data = new_data @@ -360,4 +402,4 @@ def convert_to_rzz_valid_pub( if program_id == "sampler": return SamplerPub.coerce((new_circ, val_data), pub.shots) else: - return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.shots) \ No newline at end of file + return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.shots) From b4d6729a6eb45dfc23c7718d5f7d34cdc1402ab5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:49:08 +0200 Subject: [PATCH 11/36] lint --- .../transpiler/passes/basis/fold_rzz_angle.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index cfff9546c..0823e0ab1 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -14,6 +14,7 @@ from typing import Tuple from math import pi +from operator import mod from itertools import chain from qiskit.converters import dag_to_circuit, circuit_to_dag @@ -259,16 +260,6 @@ def convert_to_rzz_valid_pub( else: raise ValueError(f"Unknown program id {program_id}") - """ - #print(pub.parameter_values.as_array()) - val_data = pub.parameter_values.data - val_data[("rzz_extra_1", "rzz_extra_2", "rzz_extra_3")] = np.ones([2, 3]) - print(val_data) - isa_pub = SamplerPub.coerce((pub.circuit, val_data)) - - print(isa_pub.parameter_values.data) - """ - val_data = pub.parameter_values.data pub_params = np.array(list(chain.from_iterable(val_data))) # first axis will be over flattened shape, second axis over circuit parameters @@ -301,17 +292,15 @@ def convert_to_rzz_valid_pub( for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) - if (angle + np.pi / 2) % (2 * np.pi) >= np.pi: - rz_angles[idx] = np.pi + if (angle + pi / 2) % (2 * pi) >= pi: + rz_angles[idx] = pi else: rz_angles[idx] = 0 - if angle % np.pi >= np.pi / 2: + if angle % pi >= pi / 2: rx_angles[idx] = 0 else: - rx_angles[idx] = np.pi - - rzz_angles[idx] = np.abs(np.pi / 2 - (angle % np.pi - pi / 2)) + rx_angles[idx] = pi rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" @@ -320,16 +309,16 @@ def convert_to_rzz_valid_pub( is_rz = False if any(not np.isclose(rz_angle, 0) for rz_angle in rz_angles): is_rz = True - if all(np.isclose(rz_angle, np.pi) for rz_angle in rz_angles): + if all(np.isclose(rz_angle, pi) for rz_angle in rz_angles): new_data.append( CircuitInstruction( - RZGate(np.pi), + RZGate(pi), (qubits[0]), ) ) new_data.append( CircuitInstruction( - RZGate(np.pi), + RZGate(pi), (qubits[1]), ) ) @@ -353,7 +342,7 @@ def convert_to_rzz_valid_pub( is_x = False if any(not np.isclose(rx_angle, 0) for rx_angle in rx_angles): is_rx = True - if all(np.isclose(rx_angle, np.pi) for rx_angle in rx_angles): + if all(np.isclose(rx_angle, pi) for rx_angle in rx_angles): is_x = True new_data.append( CircuitInstruction( From aa29a7141e7e63131593a2a90121a2ab3827c64a Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 18:58:00 +0200 Subject: [PATCH 12/36] some fixes --- .../transpiler/passes/basis/fold_rzz_angle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 0823e0ab1..7bdbd44d4 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -347,7 +347,7 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( XGate(), - (qubits[0]), + (qubits[0],), ) ) else: @@ -362,7 +362,7 @@ def convert_to_rzz_valid_pub( val_data[f"{param_prefix}rx"] = rx_angles if is_rz or is_rx: - rzz_angle = pi / 2 - (angle._apply_operation(mod, pi) - pi / 2).abs() + rzz_angle = pi / 2 - (param_exp._apply_operation(mod, pi) - pi / 2).abs() new_data.append(CircuitInstruction(RZZGate(rzz_angle), qubits)) else: new_data.append(instruction) @@ -372,7 +372,7 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( XGate(), - (qubits[0]), + (qubits[0],), ) ) else: From b50a77a6f22b37585fdfd2f18987d79bab8961d0 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 19:04:53 +0200 Subject: [PATCH 13/36] bug fix --- .../transpiler/passes/basis/fold_rzz_angle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 7bdbd44d4..f38ada48f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -291,6 +291,7 @@ def convert_to_rzz_valid_pub( for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) + #rint("angle:", angle, angle % pi, pi / 2) if (angle + pi / 2) % (2 * pi) >= pi: rz_angles[idx] = pi @@ -298,10 +299,11 @@ def convert_to_rzz_valid_pub( rz_angles[idx] = 0 if angle % pi >= pi / 2: - rx_angles[idx] = 0 - else: rx_angles[idx] = pi + else: + rx_angles[idx] = 0 + #print("rx angles:", rx_angles) rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits From 64683c282cec139c44aa5d313bdbc8dfa5100880 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 20:09:04 +0200 Subject: [PATCH 14/36] preparing to test many inputs --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 23d99e5ab..a57ea0fa7 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -13,7 +13,7 @@ """Test folding Rzz angle into calibrated range.""" from math import pi -from ddt import ddt, named_data +from ddt import ddt, named_data, data, unpack import numpy as np from qiskit.circuit import QuantumCircuit @@ -120,7 +120,9 @@ def test_fractional_plugin(self): self.assertEqual(isa_circ.data[1].operation.name, "rzz") self.assertTrue(np.isclose(isa_circ.data[1].operation.params[0], 7 - 2 * pi)) - def test_rzz_pub_conversion(self): + @data([0.2, 0.1, 0.4, 0.3]) + @unpack + def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2): """Test the function `convert_to_rzz_valid_circ_and_vals`""" p1 = Parameter("p1") p2 = Parameter("p2") @@ -129,7 +131,7 @@ def test_rzz_pub_conversion(self): circ.rzz(p1 + p2, 2, 1) circ.rzz(p1 - p2, 1, 2) - param_vals = [(0.2, 0.1), (0.4, 0.3)] + param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) self.assertEqual(is_valid_rzz_pub(isa_pub), "") From a86051e36ddb29defa19c61b990f48aa6709e592 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 22:50:49 +0200 Subject: [PATCH 15/36] test more cases --- .../transpiler/passes/basis/fold_rzz_angle.py | 14 ++++++-------- .../transpiler/passes/basis/test_fold_rzz_angle.py | 5 ++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index f38ada48f..9fd3bb76b 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -291,7 +291,6 @@ def convert_to_rzz_valid_pub( for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) - #rint("angle:", angle, angle % pi, pi / 2) if (angle + pi / 2) % (2 * pi) >= pi: rz_angles[idx] = pi @@ -303,7 +302,6 @@ def convert_to_rzz_valid_pub( else: rx_angles[idx] = 0 - #print("rx angles:", rx_angles) rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits @@ -315,13 +313,13 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( RZGate(pi), - (qubits[0]), + (qubits[0],), ) ) new_data.append( CircuitInstruction( RZGate(pi), - (qubits[1]), + (qubits[1],), ) ) else: @@ -329,13 +327,13 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( RZGate(param_rz), - (qubits[0]), + (qubits[0],), ) ) new_data.append( CircuitInstruction( RZGate(param_rz), - (qubits[1]), + (qubits[1],), ) ) val_data[f"{param_prefix}rz"] = rz_angles @@ -358,7 +356,7 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( RXGate(param_rx), - (qubits[0]), + (qubits[0],), ) ) val_data[f"{param_prefix}rx"] = rx_angles @@ -381,7 +379,7 @@ def convert_to_rzz_valid_pub( new_data.append( CircuitInstruction( RXGate(param_rx), - (qubits[0]), + (qubits[0],), ) ) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index a57ea0fa7..ff4e5ed53 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -120,7 +120,10 @@ def test_fractional_plugin(self): self.assertEqual(isa_circ.data[1].operation.name, "rzz") self.assertTrue(np.isclose(isa_circ.data[1].operation.params[0], 7 - 2 * pi)) - @data([0.2, 0.1, 0.4, 0.3]) + @data([0.2, 0.1, 0.4, 0.3], # no modification in circuit + [0.2, 0.1, 0.3, 0.4], # rx + [0.1, 0.2, 0.3, 0.4] # x + ) @unpack def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2): """Test the function `convert_to_rzz_valid_circ_and_vals`""" From b059808bbc9ba835470bf204880bbddf18d5be69 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 6 Feb 2025 22:53:59 +0200 Subject: [PATCH 16/36] make the test a bit more interesting; still need to implement for dynamic circuits --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index ff4e5ed53..a5dd503e5 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -131,8 +131,8 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2): p2 = Parameter("p2") circ = QuantumCircuit(3) - circ.rzz(p1 + p2, 2, 1) - circ.rzz(p1 - p2, 1, 2) + circ.rzz(p1 + p2, 0, 1) + circ.rzz(p1 - p2, 2, 1) param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) From b29cd644196a7392ef91f1aefd37217d1da38ae3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 09:11:20 +0200 Subject: [PATCH 17/36] enhanced test --- .../passes/basis/test_fold_rzz_angle.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index a5dd503e5..7525d18d8 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -120,12 +120,14 @@ def test_fractional_plugin(self): self.assertEqual(isa_circ.data[1].operation.name, "rzz") self.assertTrue(np.isclose(isa_circ.data[1].operation.params[0], 7 - 2 * pi)) - @data([0.2, 0.1, 0.4, 0.3], # no modification in circuit - [0.2, 0.1, 0.3, 0.4], # rx - [0.1, 0.2, 0.3, 0.4] # x + @data([0.2, 0.1, 0.4, 0.3, 2], # no modification in circuit + [0.2, 0.1, 0.3, 0.4, 3], # rzz_2_rx with values 0 and pi + [0.1, 0.2, 0.3, 0.4, 2], # x + [0.2, 0.1, 0.3, 2, 5], # rzz_1_rx, rzz_1_rz, rzz_2_rz with values 0 and pi + [0.3, 2, 0.3, 2, 2] # circuit changes but no new parameters ) @unpack - def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2): + def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_num_params): """Test the function `convert_to_rzz_valid_circ_and_vals`""" p1 = Parameter("p1") p2 = Parameter("p2") @@ -137,9 +139,13 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2): param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) + isa_param_vals = isa_pub.parameter_values.ravel().as_array() + num_isa_params = len(isa_param_vals[0]) + self.assertEqual(num_isa_params, expected_num_params) + self.assertEqual(is_valid_rzz_pub(isa_pub), "") for param_set_1, param_set_2 in zip( - param_vals, isa_pub.parameter_values.ravel().as_array() + param_vals, isa_param_vals ): self.assertTrue( Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( From 2334016d671ec6e50dd785da819a2129c8e3ac6e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 11:54:27 +0200 Subject: [PATCH 18/36] beginning of a test of dynamic (still missing testing of qubit indices) --- .../transpiler/passes/basis/fold_rzz_angle.py | 2 +- .../passes/basis/test_fold_rzz_angle.py | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 9fd3bb76b..f8c0afe5a 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -391,4 +391,4 @@ def convert_to_rzz_valid_pub( if program_id == "sampler": return SamplerPub.coerce((new_circ, val_data), pub.shots) else: - return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.shots) + return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.precision) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 7525d18d8..691da3018 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -15,12 +15,13 @@ from math import pi from ddt import ddt, named_data, data, unpack import numpy as np +from itertools import chain from qiskit.circuit import QuantumCircuit from qiskit.circuit.parameter import Parameter from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager -from qiskit.quantum_info import Operator +from qiskit.quantum_info import Operator, SparsePauliOp from qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle import ( FoldRzzAngle, @@ -134,6 +135,8 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n circ = QuantumCircuit(3) circ.rzz(p1 + p2, 0, 1) + circ.rzz(0.3, 0, 1) + circ.x(0) circ.rzz(p1 - p2, 2, 1) param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] @@ -152,3 +155,27 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)) ) ) + + def test_rzz_pub_conversion_dynamic(self): + """Test the function `convert_to_rzz_valid_circ_and_vals` for dynamic circuits""" + p = Parameter("p") + observable = SparsePauliOp("ZZZ") + + circ = QuantumCircuit(3, 1) + with circ.if_test((0, 1)): + circ.rzz(p, 1, 2) + circ.rzz(p, 1, 2) + circ.rzz(p, 0, 1) + with circ.if_test((0, 1)): + circ.rzz(p, 1, 0) + circ.rzz(p, 1, 0) + circ.rzz(p, 0 ,1) + + isa_pub = convert_to_rzz_valid_pub("estimator", (circ, observable, [1, -1])) + self.assertEqual(is_valid_rzz_pub(isa_pub), "") + self.assertEqual([observable], isa_pub.observables) + + isa_pub_param_names = np.array(list(chain.from_iterable(isa_pub.parameter_values.data))) + self.assertEqual(len(isa_pub_param_names), 6) + for param_name in ["rzz_block1_rx1", "rzz_block1_rx2", "rzz_rx1", "rzz_block2_rx1", "rzz_block2_rx2", "rzz_rx2"]: + self.assertIn(param_name, isa_pub_param_names) \ No newline at end of file From 27e432abc93204f9b88f888087345c32d65d74cd Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 12:24:04 +0200 Subject: [PATCH 19/36] skip test of dynamic circuits --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 691da3018..057473a5c 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -16,6 +16,7 @@ from ddt import ddt, named_data, data, unpack import numpy as np from itertools import chain +import unittest from qiskit.circuit import QuantumCircuit from qiskit.circuit.parameter import Parameter @@ -156,6 +157,7 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n ) ) + @unittest.SkipTest def test_rzz_pub_conversion_dynamic(self): """Test the function `convert_to_rzz_valid_circ_and_vals` for dynamic circuits""" p = Parameter("p") @@ -175,6 +177,7 @@ def test_rzz_pub_conversion_dynamic(self): self.assertEqual(is_valid_rzz_pub(isa_pub), "") self.assertEqual([observable], isa_pub.observables) + # TODO: test qubit indices isa_pub_param_names = np.array(list(chain.from_iterable(isa_pub.parameter_values.data))) self.assertEqual(len(isa_pub_param_names), 6) for param_name in ["rzz_block1_rx1", "rzz_block1_rx2", "rzz_rx1", "rzz_block2_rx1", "rzz_block2_rx2", "rzz_rx2"]: From 337039d02c595cf60ff9ce1ce3fec0f21b02e36a Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 12:35:52 +0200 Subject: [PATCH 20/36] remove debug prints --- qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index f8c0afe5a..7a890443f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -385,9 +385,6 @@ def convert_to_rzz_valid_pub( new_circ.data = new_data - print(new_circ) - print(val_data) - if program_id == "sampler": return SamplerPub.coerce((new_circ, val_data), pub.shots) else: From cb0a21cfd56521f30dd7b2ae52b7af24210bb5d2 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 12:36:54 +0200 Subject: [PATCH 21/36] black --- .../passes/basis/test_fold_rzz_angle.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 057473a5c..031debf78 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -122,12 +122,13 @@ def test_fractional_plugin(self): self.assertEqual(isa_circ.data[1].operation.name, "rzz") self.assertTrue(np.isclose(isa_circ.data[1].operation.params[0], 7 - 2 * pi)) - @data([0.2, 0.1, 0.4, 0.3, 2], # no modification in circuit - [0.2, 0.1, 0.3, 0.4, 3], # rzz_2_rx with values 0 and pi - [0.1, 0.2, 0.3, 0.4, 2], # x - [0.2, 0.1, 0.3, 2, 5], # rzz_1_rx, rzz_1_rz, rzz_2_rz with values 0 and pi - [0.3, 2, 0.3, 2, 2] # circuit changes but no new parameters - ) + @data( + [0.2, 0.1, 0.4, 0.3, 2], # no modification in circuit + [0.2, 0.1, 0.3, 0.4, 3], # rzz_2_rx with values 0 and pi + [0.1, 0.2, 0.3, 0.4, 2], # x + [0.2, 0.1, 0.3, 2, 5], # rzz_1_rx, rzz_1_rz, rzz_2_rz with values 0 and pi + [0.3, 2, 0.3, 2, 2], # circuit changes but no new parameters + ) @unpack def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_num_params): """Test the function `convert_to_rzz_valid_circ_and_vals`""" @@ -148,9 +149,7 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n self.assertEqual(num_isa_params, expected_num_params) self.assertEqual(is_valid_rzz_pub(isa_pub), "") - for param_set_1, param_set_2 in zip( - param_vals, isa_param_vals - ): + for param_set_1, param_set_2 in zip(param_vals, isa_param_vals): self.assertTrue( Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)) @@ -171,7 +170,7 @@ def test_rzz_pub_conversion_dynamic(self): with circ.if_test((0, 1)): circ.rzz(p, 1, 0) circ.rzz(p, 1, 0) - circ.rzz(p, 0 ,1) + circ.rzz(p, 0, 1) isa_pub = convert_to_rzz_valid_pub("estimator", (circ, observable, [1, -1])) self.assertEqual(is_valid_rzz_pub(isa_pub), "") @@ -180,5 +179,12 @@ def test_rzz_pub_conversion_dynamic(self): # TODO: test qubit indices isa_pub_param_names = np.array(list(chain.from_iterable(isa_pub.parameter_values.data))) self.assertEqual(len(isa_pub_param_names), 6) - for param_name in ["rzz_block1_rx1", "rzz_block1_rx2", "rzz_rx1", "rzz_block2_rx1", "rzz_block2_rx2", "rzz_rx2"]: - self.assertIn(param_name, isa_pub_param_names) \ No newline at end of file + for param_name in [ + "rzz_block1_rx1", + "rzz_block1_rx2", + "rzz_rx1", + "rzz_block2_rx1", + "rzz_block2_rx2", + "rzz_rx2", + ]: + self.assertIn(param_name, isa_pub_param_names) From 1c09dc34a774ac74cafb1485681eb844eaf69ca3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 12:39:11 +0200 Subject: [PATCH 22/36] lint --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 031debf78..41fdd04b2 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -13,10 +13,10 @@ """Test folding Rzz angle into calibrated range.""" from math import pi -from ddt import ddt, named_data, data, unpack -import numpy as np from itertools import chain import unittest +import numpy as np +from ddt import ddt, named_data, data, unpack from qiskit.circuit import QuantumCircuit from qiskit.circuit.parameter import Parameter From 7a4aa09b4693fb329935b4e65ff0cd565e88f7db Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 12:49:19 +0200 Subject: [PATCH 23/36] lint --- .../transpiler/passes/basis/test_fold_rzz_angle.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index 41fdd04b2..dbb53939c 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -33,6 +33,9 @@ from .....ibm_test_case import IBMTestCase +# pylint: disable=not-context-manager + + @ddt class TestFoldRzzAngle(IBMTestCase): """Test FoldRzzAngle pass""" @@ -81,9 +84,9 @@ def test_controlflow(self): """Test non-ISA Rzz gates inside/outside a control flow branch.""" qc = QuantumCircuit(2, 1) qc.rzz(-0.2, 0, 1) - with qc.if_test((0, 1)): # pylint: disable=not-context-manager + with qc.if_test((0, 1)): qc.rzz(-0.1, 0, 1) - with qc.if_test((0, 1)): # pylint: disable=not-context-manager + with qc.if_test((0, 1)): qc.rzz(-0.3, 0, 1) pm = PassManager([FoldRzzAngle()]) @@ -93,11 +96,11 @@ def test_controlflow(self): expected.x(0) expected.rzz(0.2, 0, 1) expected.x(0) - with expected.if_test((0, 1)): # pylint: disable=not-context-manager + with expected.if_test((0, 1)): expected.x(0) expected.rzz(0.1, 0, 1) expected.x(0) - with expected.if_test((0, 1)): # pylint: disable=not-context-manager + with expected.if_test((0, 1)): expected.x(0) expected.rzz(0.3, 0, 1) expected.x(0) From 47cad78d1bf983bd1f1630f922756ad046a732ae Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 13:40:28 +0200 Subject: [PATCH 24/36] lint --- .../transpiler/passes/basis/fold_rzz_angle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 7a890443f..74ef9b168 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -12,7 +12,7 @@ """Pass to wrap Rzz gate angle in calibrated range of 0-pi/2.""" -from typing import Tuple +from typing import Tuple, Union from math import pi from operator import mod from itertools import chain @@ -251,8 +251,9 @@ def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: def convert_to_rzz_valid_pub( - program_id: str, pub: SamplerPubLike | EstimatorPubLike -) -> SamplerPub | EstimatorPub: + program_id: str, pub: Union[SamplerPubLike, EstimatorPubLike] +) -> Union[SamplerPub, EstimatorPub]: + """Return a pub which is compatible with Rzz constraints""" if program_id == "sampler": pub = SamplerPub.coerce(pub) elif program_id == "estimator": From ffa385024f54102943e1b92d3ad9ee4f94023e21 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 9 Feb 2025 13:49:04 +0200 Subject: [PATCH 25/36] mypy --- test/unit/transpiler/passes/basis/test_fold_rzz_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index dbb53939c..e43cb6af4 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -159,7 +159,7 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n ) ) - @unittest.SkipTest + @unittest.skip("") def test_rzz_pub_conversion_dynamic(self): """Test the function `convert_to_rzz_valid_circ_and_vals` for dynamic circuits""" p = Parameter("p") From 8632e32b0af39ff8111777d48ce455e25ad40416 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 13 Feb 2025 17:15:47 +0200 Subject: [PATCH 26/36] accurately copy the circuit's registers --- qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 74ef9b168..02ba649db 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -266,7 +266,7 @@ def convert_to_rzz_valid_pub( # first axis will be over flattened shape, second axis over circuit parameters arr = pub.parameter_values.ravel().as_array() - new_circ = QuantumCircuit(pub.circuit.qubits, pub.circuit.clbits) + new_circ = pub.circuit.copy_empty_like() new_data = [] rzz_count = 0 From 002f79af8750f91efc6edd06e83d79aa223172c3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 13 Feb 2025 17:23:24 +0200 Subject: [PATCH 27/36] lint --- qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 02ba649db..7f0c4db80 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -18,7 +18,7 @@ from itertools import chain from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression, QuantumCircuit +from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate, RXGate from qiskit.circuit import Qubit, ControlFlowOp from qiskit.dagcircuit import DAGCircuit From 437e23a3da93eb068caa11bb8362749897bc0913 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Feb 2025 12:09:59 +0200 Subject: [PATCH 28/36] empty commit to rerun CI From b39ba069bfbe3460586b776e26e3e86d972291cb Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 7 Jul 2025 14:06:16 +0300 Subject: [PATCH 29/36] we don't support DC --- .../transpiler/passes/basis/fold_rzz_angle.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 7f0c4db80..a9164c99f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -18,7 +18,7 @@ from itertools import chain from qiskit.converters import dag_to_circuit, circuit_to_dag -from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression +from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression, CONTROL_FLOW_OP_NAMES from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate, RXGate from qiskit.circuit import Qubit, ControlFlowOp from qiskit.dagcircuit import DAGCircuit @@ -253,7 +253,13 @@ def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: def convert_to_rzz_valid_pub( program_id: str, pub: Union[SamplerPubLike, EstimatorPubLike] ) -> Union[SamplerPub, EstimatorPub]: - """Return a pub which is compatible with Rzz constraints""" + """ + Return a pub which is compatible with Rzz constraints. + + Current limitations: + 1. Does not support dynamic circuits. + 2. Does not preserve global phase. + """ if program_id == "sampler": pub = SamplerPub.coerce(pub) elif program_id == "estimator": @@ -272,6 +278,12 @@ def convert_to_rzz_valid_pub( for instruction in pub.circuit.data: operation = instruction.operation + + if operation.name in CONTROL_FLOW_OP_NAMES: + raise ValueError( + "The function convert_to_rzz_valid_pub currently does not support dynamic instructions." + ) + if operation.name != "rzz" or not isinstance( (param_exp := instruction.operation.params[0]), ParameterExpression ): From e8d31322ea5bb4fbc67d474083d5b95c43170b24 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 7 Jul 2025 14:22:00 +0300 Subject: [PATCH 30/36] release note --- release-notes/unreleased/2126.other.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 release-notes/unreleased/2126.other.rst diff --git a/release-notes/unreleased/2126.other.rst b/release-notes/unreleased/2126.other.rst new file mode 100644 index 000000000..71760d842 --- /dev/null +++ b/release-notes/unreleased/2126.other.rst @@ -0,0 +1 @@ +A new function :func:`.convert_to_rzz_valid_pub`, to transform a PUB into equivalent PUB that is compatible with Rzz constraints. The function currently does not support dynamic circuits and does not preserve global phase. From 4200d29003d65bef8f39ee4a409a1351d421dbb1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Tue, 8 Jul 2025 16:04:03 +0300 Subject: [PATCH 31/36] not using mod operation for parameter expressions --- .../transpiler/passes/basis/fold_rzz_angle.py | 10 ++++++++-- .../transpiler/passes/basis/test_fold_rzz_angle.py | 10 +++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index a9164c99f..ed097d14e 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -301,6 +301,7 @@ def convert_to_rzz_valid_pub( rz_angles = np.zeros(num_param_sets) rx_angles = np.zeros(num_param_sets) + rzz_angles = np.zeros(num_param_sets) for idx, row in enumerate(projected_arr): angle = float(param_exp.bind(dict(zip(param_exp.parameters, row)))) @@ -315,6 +316,8 @@ def convert_to_rzz_valid_pub( else: rx_angles[idx] = 0 + rzz_angles[idx] = pi / 2 - abs(mod(angle, pi) - pi / 2) + rzz_count += 1 param_prefix = f"rzz_{rzz_count}_" qubits = instruction.qubits @@ -375,8 +378,11 @@ def convert_to_rzz_valid_pub( val_data[f"{param_prefix}rx"] = rx_angles if is_rz or is_rx: - rzz_angle = pi / 2 - (param_exp._apply_operation(mod, pi) - pi / 2).abs() - new_data.append(CircuitInstruction(RZZGate(rzz_angle), qubits)) + # param_exp * 0 to prevent an error complaining that the original parameters, + # still present in the parameter values, are missing from the circuit + param_rzz = param_exp * 0 + Parameter(f"{param_prefix}rzz") + new_data.append(CircuitInstruction(RZZGate(param_rzz), qubits)) + val_data[f"{param_prefix}rzz"] = rzz_angles else: new_data.append(instruction) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index e43cb6af4..f6f90c592 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -127,10 +127,10 @@ def test_fractional_plugin(self): @data( [0.2, 0.1, 0.4, 0.3, 2], # no modification in circuit - [0.2, 0.1, 0.3, 0.4, 3], # rzz_2_rx with values 0 and pi - [0.1, 0.2, 0.3, 0.4, 2], # x - [0.2, 0.1, 0.3, 2, 5], # rzz_1_rx, rzz_1_rz, rzz_2_rz with values 0 and pi - [0.3, 2, 0.3, 2, 2], # circuit changes but no new parameters + [0.2, 0.1, 0.3, 0.4, 4], # rzz_2_rx with values 0 and pi + [0.1, 0.2, 0.3, 0.4, 3], # x + [0.2, 0.1, 0.3, 2, 7], # rzz_1_rx, rzz_1_rz, rzz_2_rz with values 0 and pi + [0.3, 2, 0.3, 2, 4], # circuit changes but no new parameters ) @unpack def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_num_params): @@ -159,7 +159,7 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n ) ) - @unittest.skip("") + @unittest.skip("convert_to_rzz_valid_pub does not support dynamic circuits currently") def test_rzz_pub_conversion_dynamic(self): """Test the function `convert_to_rzz_valid_circ_and_vals` for dynamic circuits""" p = Parameter("p") From e019051073bb9190905c4e35c99677d76ad6481b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 24 Jul 2025 10:37:07 +0300 Subject: [PATCH 32/36] replace program_id with primitive instance --- .../transpiler/passes/basis/fold_rzz_angle.py | 16 ++++++++++------ .../passes/basis/test_fold_rzz_angle.py | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index ed097d14e..01e24bd72 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -16,6 +16,7 @@ from math import pi from operator import mod from itertools import chain +import numpy as np from qiskit.converters import dag_to_circuit, circuit_to_dag from qiskit.circuit import CircuitInstruction, Parameter, ParameterExpression, CONTROL_FLOW_OP_NAMES @@ -26,7 +27,8 @@ from qiskit.primitives.containers.estimator_pub import EstimatorPub, EstimatorPubLike from qiskit.primitives.containers.sampler_pub import SamplerPub, SamplerPubLike -import numpy as np +from qiskit_ibm_runtime import EstimatorV2, SamplerV2 +from qiskit_ibm_runtime.base_primitive import BasePrimitiveV2 class FoldRzzAngle(TransformationPass): @@ -251,7 +253,7 @@ def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: def convert_to_rzz_valid_pub( - program_id: str, pub: Union[SamplerPubLike, EstimatorPubLike] + primitive: BasePrimitiveV2, pub: Union[SamplerPubLike, EstimatorPubLike] ) -> Union[SamplerPub, EstimatorPub]: """ Return a pub which is compatible with Rzz constraints. @@ -260,12 +262,14 @@ def convert_to_rzz_valid_pub( 1. Does not support dynamic circuits. 2. Does not preserve global phase. """ - if program_id == "sampler": + if isinstance(primitive, SamplerV2): + is_sampler = True pub = SamplerPub.coerce(pub) - elif program_id == "estimator": + elif isinstance(primitive, EstimatorV2): + is_sampler = False pub = EstimatorPub.coerce(pub) else: - raise ValueError(f"Unknown program id {program_id}") + raise ValueError("Unsupported Primitive type") val_data = pub.parameter_values.data pub_params = np.array(list(chain.from_iterable(val_data))) @@ -404,7 +408,7 @@ def convert_to_rzz_valid_pub( new_circ.data = new_data - if program_id == "sampler": + if is_sampler: return SamplerPub.coerce((new_circ, val_data), pub.shots) else: return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.precision) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index f6f90c592..dd023bc4a 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -24,6 +24,7 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.quantum_info import Operator, SparsePauliOp +from qiskit_ibm_runtime import EstimatorV2, SamplerV2 from qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle import ( FoldRzzAngle, convert_to_rzz_valid_pub, @@ -145,7 +146,7 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n circ.rzz(p1 - p2, 2, 1) param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] - isa_pub = convert_to_rzz_valid_pub("sampler", (circ, param_vals)) + isa_pub = convert_to_rzz_valid_pub(SamplerV2(FakeFractionalBackend()), (circ, param_vals)) isa_param_vals = isa_pub.parameter_values.ravel().as_array() num_isa_params = len(isa_param_vals[0]) @@ -175,7 +176,9 @@ def test_rzz_pub_conversion_dynamic(self): circ.rzz(p, 1, 0) circ.rzz(p, 0, 1) - isa_pub = convert_to_rzz_valid_pub("estimator", (circ, observable, [1, -1])) + isa_pub = convert_to_rzz_valid_pub( + EstimatorV2(FakeFractionalBackend()), (circ, observable, [1, -1]) + ) self.assertEqual(is_valid_rzz_pub(isa_pub), "") self.assertEqual([observable], isa_pub.observables) From a949270898f1cf5966b42248a976115438a8a3e9 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 24 Jul 2025 10:39:52 +0300 Subject: [PATCH 33/36] Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Elena Peña Tapia <57907331+ElePT@users.noreply.github.com> --- qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 01e24bd72..5f1334ccc 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -296,8 +296,8 @@ def convert_to_rzz_valid_pub( param_names = [param.name for param in param_exp.parameters] - col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names] # col_indices is the indices of columns in the parameter value array that have to be checked + col_indices = [np.where(pub_params == param_name)[0][0] for param_name in param_names] # project only to the parameters that have to be checked projected_arr = arr[:, col_indices] From c0c08897870fcbc09fb1ebc029e5e01d6cd7a493 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 24 Jul 2025 11:03:22 +0300 Subject: [PATCH 34/36] don't allow parameters whose names start with rzz_ --- .../transpiler/passes/basis/fold_rzz_angle.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 5f1334ccc..1dce9af0f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -261,6 +261,8 @@ def convert_to_rzz_valid_pub( Current limitations: 1. Does not support dynamic circuits. 2. Does not preserve global phase. + 3. This function defines new parameters, whose names start with `rzz_`. We therefore + require that the input pub does not contain parameters whose names also start with `rzz_`. """ if isinstance(primitive, SamplerV2): is_sampler = True @@ -273,6 +275,12 @@ def convert_to_rzz_valid_pub( val_data = pub.parameter_values.data pub_params = np.array(list(chain.from_iterable(val_data))) + for p_name in pub_params: + if p_name.startswith("rzz_"): + raise ValueError( + "Original pub is not allowed to contain parameters " "whose names start with rzz_" + ) + # first axis will be over flattened shape, second axis over circuit parameters arr = pub.parameter_values.ravel().as_array() From d3715101fdc2b59ee4a79cfbfb00bcf05799133e Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 24 Jul 2025 17:43:12 +0300 Subject: [PATCH 35/36] reshape to the original shape --- .../transpiler/passes/basis/fold_rzz_angle.py | 12 ++++++++---- .../passes/basis/test_fold_rzz_angle.py | 17 ++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 1dce9af0f..948dcf184 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -273,12 +273,16 @@ def convert_to_rzz_valid_pub( else: raise ValueError("Unsupported Primitive type") + original_shape = pub.parameter_values.as_array().shape + single_param_shape = original_shape[:-1] + (1,) + val_data = pub.parameter_values.data pub_params = np.array(list(chain.from_iterable(val_data))) for p_name in pub_params: if p_name.startswith("rzz_"): raise ValueError( - "Original pub is not allowed to contain parameters " "whose names start with rzz_" + "Original pub is not allowed to contain parameters " + "whose names start with rzz_" ) # first axis will be over flattened shape, second axis over circuit parameters @@ -364,7 +368,7 @@ def convert_to_rzz_valid_pub( (qubits[1],), ) ) - val_data[f"{param_prefix}rz"] = rz_angles + val_data[f"{param_prefix}rz"] = rz_angles.reshape(single_param_shape) is_rx = False is_x = False @@ -387,14 +391,14 @@ def convert_to_rzz_valid_pub( (qubits[0],), ) ) - val_data[f"{param_prefix}rx"] = rx_angles + val_data[f"{param_prefix}rx"] = rx_angles.reshape(single_param_shape) if is_rz or is_rx: # param_exp * 0 to prevent an error complaining that the original parameters, # still present in the parameter values, are missing from the circuit param_rzz = param_exp * 0 + Parameter(f"{param_prefix}rzz") new_data.append(CircuitInstruction(RZZGate(param_rzz), qubits)) - val_data[f"{param_prefix}rzz"] = rzz_angles + val_data[f"{param_prefix}rzz"] = rzz_angles.reshape(single_param_shape) else: new_data.append(instruction) diff --git a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py index dd023bc4a..5193257f1 100644 --- a/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py +++ b/test/unit/transpiler/passes/basis/test_fold_rzz_angle.py @@ -145,15 +145,18 @@ def test_rzz_pub_conversion(self, p1_set1, p2_set1, p1_set2, p2_set2, expected_n circ.x(0) circ.rzz(p1 - p2, 2, 1) - param_vals = [(p1_set1, p2_set1), (p1_set2, p2_set2)] - isa_pub = convert_to_rzz_valid_pub(SamplerV2(FakeFractionalBackend()), (circ, param_vals)) - - isa_param_vals = isa_pub.parameter_values.ravel().as_array() - num_isa_params = len(isa_param_vals[0]) - self.assertEqual(num_isa_params, expected_num_params) + param_vals_arr = np.array([[[p1_set1, p2_set1]], [[p1_set2, p2_set2]]]) + isa_pub = convert_to_rzz_valid_pub( + SamplerV2(FakeFractionalBackend()), (circ, param_vals_arr) + ) + isa_param_vals = isa_pub.parameter_values + self.assertEqual(isa_param_vals.num_parameters, expected_num_params) self.assertEqual(is_valid_rzz_pub(isa_pub), "") - for param_set_1, param_set_2 in zip(param_vals, isa_param_vals): + + param_flat = param_vals_arr.reshape(-1, param_vals_arr.shape[-1]) + isa_flat = isa_param_vals.ravel().as_array() + for param_set_1, param_set_2 in zip(param_flat, isa_flat): self.assertTrue( Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( Operator.from_circuit(isa_pub.circuit.assign_parameters(param_set_2)) From d65e64e9ed694fafb8038ab770fccec89e3f877c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 24 Jul 2025 17:50:39 +0300 Subject: [PATCH 36/36] black, sort of --- qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py index 948dcf184..82e6e6a0f 100644 --- a/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py +++ b/qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py @@ -281,8 +281,7 @@ def convert_to_rzz_valid_pub( for p_name in pub_params: if p_name.startswith("rzz_"): raise ValueError( - "Original pub is not allowed to contain parameters " - "whose names start with rzz_" + "Original pub is not allowed to contain parameters whose names start with rzz_" ) # first axis will be over flattened shape, second axis over circuit parameters