From 349d07ea5680fd5186f7f8c9fb4ccb24ea60eee4 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 08:55:09 +0200 Subject: [PATCH 01/32] A function to remove parameter expressions --- qiskit_ibm_runtime/quantum_program/utils.py | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 qiskit_ibm_runtime/quantum_program/utils.py diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py new file mode 100644 index 000000000..d6ed939cd --- /dev/null +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -0,0 +1,62 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Util functions for the quantum program.""" + +from __future__ import annotations + +import numpy as np + +from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression, CircuitInstruction + + +def remove_parameter_expressions( + circ: QuantumCircuit, param_values: np.ndarray +) -> tuple[QuantumCircuit, np.ndarray]: + new_param_value_cols = [] + new_param_count = 0 + new_circ = circ.copy_empty_like() + new_data = [] + + for instruction in circ.data: + if len(instruction.operation.params) == 0 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] + circ_params = [param.name for param in circ.parameters] + + # col_indices is the indices of columns in the parameter value array that have to be checked + col_indices = [ + np.where(np.array(circ_params) == param_name)[0][0] for param_name in param_names + ] + + # project only to the parameters that have to be checked + projected_arr = param_values[:, col_indices] + + new_param_values = np.zeros(param_values.shape[:-1] + (len(param_names),)) + for (idx,) in np.ndindex(projected_arr.shape[:-1]): + to_bind = projected_arr[idx] + new_param_values[idx] = param_exp.bind(dict(zip(param_exp.parameters, to_bind))) + + new_param_count += 1 + param_name = f"yael_{new_param_count}" + + new_gate = instruction.operation.copy() + new_gate.params = [Parameter(param_name)] + new_data.append(CircuitInstruction(new_gate, instruction.qubits)) + new_param_value_cols.append(new_param_values) + + new_circ.data = new_data + return new_circ, np.concatenate(new_param_value_cols) From b5c555308d985db5b3a4fd325069a41eb6ce8091 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 12:09:15 +0200 Subject: [PATCH 02/32] wrote a test --- test/unit/quantum_program/__init__.py | 11 ++++++ test/unit/quantum_program/test_utils.py | 48 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 test/unit/quantum_program/__init__.py create mode 100644 test/unit/quantum_program/test_utils.py diff --git a/test/unit/quantum_program/__init__.py b/test/unit/quantum_program/__init__.py new file mode 100644 index 000000000..60e93d8a9 --- /dev/null +++ b/test/unit/quantum_program/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py new file mode 100644 index 000000000..4aba28021 --- /dev/null +++ b/test/unit/quantum_program/test_utils.py @@ -0,0 +1,48 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2025. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test utility functions of the quantum program.""" + +import numpy as np + +from qiskit.circuit import QuantumCircuit, Parameter +from qiskit.quantum_info import Operator + +from qiskit_ibm_runtime.quantum_program.utils import remove_parameter_expressions + +from ...ibm_test_case import IBMTestCase + + +class TestRemoveParameterExpressions(IBMTestCase): + """Test the function :func:`~remove_parameter_expressions`.""" + + def test_remove_parameter_expressions(self): + p1 = Parameter("p1") + p2 = Parameter("p2") + param_values = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]], [[9, 10], [11, 12], [13, 14], [15, 16]], [[17, 18], [19, 20], [21, 22], [23, 24]]]) + + circ = QuantumCircuit(2) + circ.h(0) + circ.rz(p1, 0) + circ.rx(p1 + p2, 1) + + new_circ, new_values = remove_parameter_expressions(circ, param_values) + + self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) + param_values_flat = param_values.reshape(-1, param_values.shape[-1]) + new_values_flat = new_values.reshape(-1, param_values.shape[-1]) + for param_set_1, param_set_2 in zip(param_values_flat, new_values_flat): + self.assertTrue( + Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( + Operator.from_circuit(new_circ.circuit.assign_parameters(param_set_2)) + ) + ) \ No newline at end of file From 8afc7868ceb47e3f8c3ecd0412afd47d07adeb06 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 12:59:07 +0200 Subject: [PATCH 03/32] fixes --- qiskit_ibm_runtime/quantum_program/utils.py | 13 +++++-------- test/unit/quantum_program/test_utils.py | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index d6ed939cd..203eddaa7 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -42,13 +42,10 @@ def remove_parameter_expressions( np.where(np.array(circ_params) == param_name)[0][0] for param_name in param_names ] - # project only to the parameters that have to be checked - projected_arr = param_values[:, col_indices] - - new_param_values = np.zeros(param_values.shape[:-1] + (len(param_names),)) - for (idx,) in np.ndindex(projected_arr.shape[:-1]): - to_bind = projected_arr[idx] - new_param_values[idx] = param_exp.bind(dict(zip(param_exp.parameters, to_bind))) + new_param_values = np.zeros(param_values.shape[:-1] + (1,)) + for idx in np.ndindex(param_values.shape[:-1]): + to_bind = param_values[idx] + new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) new_param_count += 1 param_name = f"yael_{new_param_count}" @@ -59,4 +56,4 @@ def remove_parameter_expressions( new_param_value_cols.append(new_param_values) new_circ.data = new_data - return new_circ, np.concatenate(new_param_value_cols) + return new_circ, np.concatenate(new_param_value_cols, axis=-1) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 4aba28021..8b08cc324 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -43,6 +43,6 @@ def test_remove_parameter_expressions(self): for param_set_1, param_set_2 in zip(param_values_flat, new_values_flat): self.assertTrue( Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( - Operator.from_circuit(new_circ.circuit.assign_parameters(param_set_2)) + Operator.from_circuit(new_circ.assign_parameters(param_set_2)) ) ) \ No newline at end of file From 756bee5f865bd02421a94ebda8ef3706872d12f7 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 13:27:03 +0200 Subject: [PATCH 04/32] parameter table --- qiskit_ibm_runtime/quantum_program/utils.py | 34 ++++++++++++--------- test/unit/quantum_program/test_utils.py | 3 ++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 203eddaa7..913ca237f 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -22,8 +22,8 @@ def remove_parameter_expressions( circ: QuantumCircuit, param_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: + parameter_table = {} new_param_value_cols = [] - new_param_count = 0 new_circ = circ.copy_empty_like() new_data = [] @@ -34,26 +34,30 @@ def remove_parameter_expressions( new_data.append(instruction) continue - param_names = [param.name for param in param_exp.parameters] - circ_params = [param.name for param in circ.parameters] + if str(param_exp) in parameter_table: + new_param = parameter_table[str(param_exp)] + else: + param_names = [param.name for param in param_exp.parameters] + circ_params = [param.name for param in circ.parameters] - # col_indices is the indices of columns in the parameter value array that have to be checked - col_indices = [ - np.where(np.array(circ_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(np.array(circ_params) == param_name)[0][0] for param_name in param_names + ] - new_param_values = np.zeros(param_values.shape[:-1] + (1,)) - for idx in np.ndindex(param_values.shape[:-1]): - to_bind = param_values[idx] - new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) + new_param_values = np.zeros(param_values.shape[:-1] + (1,)) + for idx in np.ndindex(param_values.shape[:-1]): + to_bind = param_values[idx] + new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) - new_param_count += 1 - param_name = f"yael_{new_param_count}" + new_param_value_cols.append(new_param_values) + new_param = Parameter(str(param_exp)) + parameter_table[str(param_exp)] = new_param new_gate = instruction.operation.copy() - new_gate.params = [Parameter(param_name)] + new_gate.params = [new_param] new_data.append(CircuitInstruction(new_gate, instruction.qubits)) - new_param_value_cols.append(new_param_values) + new_circ.data = new_data return new_circ, np.concatenate(new_param_value_cols, axis=-1) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 8b08cc324..91bc4102c 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -34,9 +34,12 @@ def test_remove_parameter_expressions(self): circ.h(0) circ.rz(p1, 0) circ.rx(p1 + p2, 1) + circ.rx(p1 + p2, 0) new_circ, new_values = remove_parameter_expressions(circ, param_values) + self.assertEqual(len(new_circ.parameters), 2) + self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) param_values_flat = param_values.reshape(-1, param_values.shape[-1]) new_values_flat = new_values.reshape(-1, param_values.shape[-1]) From 712ef049430f1301f302d3e8d073bf7418e018c1 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 14:46:49 +0200 Subject: [PATCH 05/32] gates with multiple parameters --- qiskit_ibm_runtime/quantum_program/utils.py | 43 ++++++++++----------- test/unit/quantum_program/test_utils.py | 6 ++- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 913ca237f..6ead32d48 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -28,34 +28,31 @@ def remove_parameter_expressions( new_data = [] for instruction in circ.data: - if len(instruction.operation.params) == 0 or not isinstance( - param_exp := instruction.operation.params[0], ParameterExpression - ): + + param_exps = [op_param for op_param in instruction.operation.params if isinstance(op_param, ParameterExpression)] + if len(param_exps) == 0: new_data.append(instruction) continue - if str(param_exp) in parameter_table: - new_param = parameter_table[str(param_exp)] - else: - param_names = [param.name for param in param_exp.parameters] - circ_params = [param.name for param in circ.parameters] - - # col_indices is the indices of columns in the parameter value array that have to be checked - col_indices = [ - np.where(np.array(circ_params) == param_name)[0][0] for param_name in param_names - ] - - new_param_values = np.zeros(param_values.shape[:-1] + (1,)) - for idx in np.ndindex(param_values.shape[:-1]): - to_bind = param_values[idx] - new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) - - new_param_value_cols.append(new_param_values) - new_param = Parameter(str(param_exp)) - parameter_table[str(param_exp)] = new_param + new_op_params = [] + for param_exp in param_exps: + + if str(param_exp) in parameter_table: + new_param = parameter_table[str(param_exp)] + else: + new_param_values = np.zeros(param_values.shape[:-1] + (1,)) + for idx in np.ndindex(param_values.shape[:-1]): + to_bind = param_values[idx] + new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) + + new_param_value_cols.append(new_param_values) + new_param = Parameter(str(param_exp)) + parameter_table[str(param_exp)] = new_param + + new_op_params.append(new_param) new_gate = instruction.operation.copy() - new_gate.params = [new_param] + new_gate.params = new_op_params new_data.append(CircuitInstruction(new_gate, instruction.qubits)) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 91bc4102c..f1894dcf4 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -15,6 +15,7 @@ import numpy as np from qiskit.circuit import QuantumCircuit, Parameter +from qiskit.circuit.library import U2Gate from qiskit.quantum_info import Operator from qiskit_ibm_runtime.quantum_program.utils import remove_parameter_expressions @@ -35,14 +36,15 @@ def test_remove_parameter_expressions(self): circ.rz(p1, 0) circ.rx(p1 + p2, 1) circ.rx(p1 + p2, 0) + circ.append(U2Gate(p1 - p2, p1 + p2), [1]) new_circ, new_values = remove_parameter_expressions(circ, param_values) - self.assertEqual(len(new_circ.parameters), 2) + self.assertEqual(len(new_circ.parameters), 3) self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) param_values_flat = param_values.reshape(-1, param_values.shape[-1]) - new_values_flat = new_values.reshape(-1, param_values.shape[-1]) + new_values_flat = new_values.reshape(-1, new_values.shape[-1]) for param_set_1, param_set_2 in zip(param_values_flat, new_values_flat): self.assertTrue( Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( From d3383a320b53e576d3507ddd73373ee58f7d6626 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 17:24:26 +0200 Subject: [PATCH 06/32] a test for dynamic circuits --- test/unit/quantum_program/test_utils.py | 94 ++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index f1894dcf4..fc6416ba1 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -26,7 +26,11 @@ class TestRemoveParameterExpressions(IBMTestCase): """Test the function :func:`~remove_parameter_expressions`.""" - def test_remove_parameter_expressions(self): + def test_remove_parameter_expressions_static_circuit(self): + """ + Test the function :func:`~remove_parameter_expressions` for static circuits. + The static property allows a rigorous check using operator equivalence. + """ p1 = Parameter("p1") p2 = Parameter("p2") param_values = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]], [[9, 10], [11, 12], [13, 14], [15, 16]], [[17, 18], [19, 20], [21, 22], [23, 24]]]) @@ -50,4 +54,92 @@ def test_remove_parameter_expressions(self): Operator.from_circuit(circ.assign_parameters(param_set_1)).equiv( Operator.from_circuit(new_circ.assign_parameters(param_set_2)) ) + ) + + def test_remove_parameter_expressions_dynamic_circuit(self): + """ + Test the function :func:`~remove_parameter_expressions` for dynamic circuits. + """ + p1 = Parameter("p1") + p2 = Parameter("p2") + param_values = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]], [[9, 10], [11, 12], [13, 14], [15, 16]], [[17, 18], [19, 20], [21, 22], [23, 24]]]) + param_values_flat = param_values.reshape(-1, param_values.shape[-1]) + + circ = QuantumCircuit(2, 1) + circ.h(0) + circ.rz(p1, 0) + with circ.box(): + circ.rx(p1 + p2, 1) + circ.append(U2Gate(p1 - p2, p1 + p2), [1]) + circ.rz(p1, 1) + circ.rx(p1 + p2, 0) + circ.measure(0, 0) + with circ.if_test((0, 1)): + with circ.if_test((0, 0)) as else_: + circ.h(0) + with else_: + circ.rz(p1 + 3, 0) + circ.rx(p1 * p2, 1) + + new_circ, new_values = remove_parameter_expressions(circ, param_values) + + # parameter names: p1, p1 + p2, p1 - p2, p1 + 3, p1 * p2 + self.assertEqual(len(new_circ.parameters), 5) + self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) + + outer_circ_1 = QuantumCircuit(2) + outer_circ_1.data = [circ.data[i] for i in (0, 1, 3, 4, 6)] + outer_circ_2 = QuantumCircuit(2) + outer_circ_2.data = [new_circ.data[i] for i in (0, 1, 3, 4, 6)] + + outer_params_2 = new_values[:, [0, 1, 4]] + outer_2_flat = outer_params_2.reshape(-1, outer_params_2.shape[-1]) + for param_set_1, param_set_2 in zip(param_values_flat, outer_2_flat): + self.assertTrue( + Operator.from_circuit(outer_circ_1.assign_parameters(param_set_1)).equiv( + Operator.from_circuit(outer_circ_2.assign_parameters(param_set_2)) + ) + ) + + self.assertEqual(new_circ.data[2].operation.name, "box") + box_circ_1 = QuantumCircuit(2) + box_circ_1.data = circ.data[2].operation.blocks[0] + box_circ_2 = QuantumCircuit(2) + box_circ_2.data = new_circ.data[2].operation.blocks[0] + + box_params_2 = new_values[:, [1, 2, 0]] + box_2_flat = box_params_2.reshape(-1, box_params_2.shape[-1]) + for param_set_1, param_set_2 in zip(param_values_flat, box_2_flat): + self.assertTrue( + Operator.from_circuit(box_circ_1.assign_parameters(param_set_1)).equiv( + Operator.from_circuit(box_circ_2.assign_parameters(param_set_2)) + ) + ) + + self.assertEqual(new_circ.data[5].operation.name, "if_test") + self.assertEqual(new_circ.data[5].operation.blocks[0], "if_test") + if_circ_1 = QuantumCircuit(2) + if_circ_1.data = circ.data[5].operation.blocks[0].blocks[0] + if_circ_2 = QuantumCircuit(2) + if_circ_2.data = new_circ.data[5].operation.blocks[0].blocks[0] + + self.assertTrue( + Operator.from_circuit(if_circ_1).equiv( + Operator.from_circuit(if_circ_2) + ) + ) + + else_circ_1 = QuantumCircuit(2) + else_circ_1.data = circ.data[5].operation.blocks[0].blocks[1] + else_circ_2 = QuantumCircuit(2) + else_circ_2.data = new_circ.data[5].operation.blocks[0].blocks[1] + + else_1_flat = param_values_flat[:, 0] + else_params_2 = new_values[:, 3] + else_2_flat = else_params_2.reshape(-1, else_params_2.shape[-1]) + for param_set_1, param_set_2 in zip(else_1_flat, else_2_flat): + self.assertTrue( + Operator.from_circuit(else_circ_1.assign_parameters(param_set_1)).equiv( + Operator.from_circuit(else_circ_2.assign_parameters(param_set_2)) + ) ) \ No newline at end of file From 9e2a547571fc6f57fa1f767c81ee9f0d0be6007b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 16 Nov 2025 19:36:12 +0200 Subject: [PATCH 07/32] more accurate duplication of the instruction --- qiskit_ibm_runtime/quantum_program/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 6ead32d48..f0eac40df 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -53,7 +53,7 @@ def remove_parameter_expressions( new_gate = instruction.operation.copy() new_gate.params = new_op_params - new_data.append(CircuitInstruction(new_gate, instruction.qubits)) + new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) new_circ.data = new_data From a422ae652ff7997bd0828de09e73d39334812217 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 10:37:34 +0200 Subject: [PATCH 08/32] dynamic circuits --- qiskit_ibm_runtime/quantum_program/utils.py | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index f0eac40df..8c6b247b8 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -19,15 +19,16 @@ from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression, CircuitInstruction -def remove_parameter_expressions( - circ: QuantumCircuit, param_values: np.ndarray -) -> tuple[QuantumCircuit, np.ndarray]: - parameter_table = {} - new_param_value_cols = [] +def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: np.ndarray, + parameter_table: dict[str, Parameter], new_param_value_cols: list[np.ndarray]): new_circ = circ.copy_empty_like() new_data = [] for instruction in circ.data: + if instruction.is_control_flow(): + new_blocks = [_remove_parameter_expressions_in_blocks(block, param_values, parameter_table, new_param_value_cols) for block in instruction.blocks] + new_data.append(instruction.replace_blocks(new_blocks)) + continue param_exps = [op_param for op_param in instruction.operation.params if isinstance(op_param, ParameterExpression)] if len(param_exps) == 0: @@ -53,8 +54,17 @@ def remove_parameter_expressions( new_gate = instruction.operation.copy() new_gate.params = new_op_params - new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) - + new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) new_circ.data = new_data + return new_circ + + +def remove_parameter_expressions( + circ: QuantumCircuit, param_values: np.ndarray +) -> tuple[QuantumCircuit, np.ndarray]: + parameter_table = {} + new_param_value_cols = [] + + new_circ = _remove_parameter_expressions_in_blocks(circ, param_values, parameter_table, new_param_value_cols) return new_circ, np.concatenate(new_param_value_cols, axis=-1) From 51499225ba558b525bba3194be53ffc2c752b8c6 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 11:52:04 +0200 Subject: [PATCH 09/32] fixes --- qiskit_ibm_runtime/quantum_program/utils.py | 6 ++-- test/unit/quantum_program/test_utils.py | 35 +++++++++------------ 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 8c6b247b8..b6dcffd2d 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -26,8 +26,9 @@ def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: for instruction in circ.data: if instruction.is_control_flow(): - new_blocks = [_remove_parameter_expressions_in_blocks(block, param_values, parameter_table, new_param_value_cols) for block in instruction.blocks] - new_data.append(instruction.replace_blocks(new_blocks)) + new_blocks = [_remove_parameter_expressions_in_blocks(block, param_values, parameter_table, new_param_value_cols) for block in instruction.operation.blocks] + new_gate = instruction.operation.replace_blocks(new_blocks) + new_data.append(instruction.replace(params=new_gate.params, operation=new_gate)) continue param_exps = [op_param for op_param in instruction.operation.params if isinstance(op_param, ParameterExpression)] @@ -37,7 +38,6 @@ def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: new_op_params = [] for param_exp in param_exps: - if str(param_exp) in parameter_table: new_param = parameter_table[str(param_exp)] else: diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index fc6416ba1..2e15bd756 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -83,16 +83,16 @@ def test_remove_parameter_expressions_dynamic_circuit(self): new_circ, new_values = remove_parameter_expressions(circ, param_values) - # parameter names: p1, p1 + p2, p1 - p2, p1 + 3, p1 * p2 + # parameter names: 3 + p1, p1, p1 + p2, p1 - p2, p1*p2 self.assertEqual(len(new_circ.parameters), 5) self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) outer_circ_1 = QuantumCircuit(2) - outer_circ_1.data = [circ.data[i] for i in (0, 1, 3, 4, 6)] + outer_circ_1.data = [circ.data[i] for i in (0, 1, 3, 6)] outer_circ_2 = QuantumCircuit(2) - outer_circ_2.data = [new_circ.data[i] for i in (0, 1, 3, 4, 6)] + outer_circ_2.data = [new_circ.data[i] for i in (0, 1, 3, 6)] - outer_params_2 = new_values[:, [0, 1, 4]] + outer_params_2 = new_values[..., [0, 1, 4]] outer_2_flat = outer_params_2.reshape(-1, outer_params_2.shape[-1]) for param_set_1, param_set_2 in zip(param_values_flat, outer_2_flat): self.assertTrue( @@ -105,9 +105,9 @@ def test_remove_parameter_expressions_dynamic_circuit(self): box_circ_1 = QuantumCircuit(2) box_circ_1.data = circ.data[2].operation.blocks[0] box_circ_2 = QuantumCircuit(2) - box_circ_2.data = new_circ.data[2].operation.blocks[0] + box_circ_2.data = new_circ.data[2].operation.blocks[0] - box_params_2 = new_values[:, [1, 2, 0]] + box_params_2 = new_values[..., [0, 1, 2]] box_2_flat = box_params_2.reshape(-1, box_params_2.shape[-1]) for param_set_1, param_set_2 in zip(param_values_flat, box_2_flat): self.assertTrue( @@ -116,12 +116,10 @@ def test_remove_parameter_expressions_dynamic_circuit(self): ) ) - self.assertEqual(new_circ.data[5].operation.name, "if_test") - self.assertEqual(new_circ.data[5].operation.blocks[0], "if_test") - if_circ_1 = QuantumCircuit(2) - if_circ_1.data = circ.data[5].operation.blocks[0].blocks[0] - if_circ_2 = QuantumCircuit(2) - if_circ_2.data = new_circ.data[5].operation.blocks[0].blocks[0] + self.assertEqual(new_circ.data[5].operation.name, "if_else") + self.assertEqual(new_circ.data[5].operation.blocks[0].data[0].operation.name, "if_else") + if_circ_1 = circ.data[5].operation.blocks[0].data[0].operation.blocks[0] + if_circ_2 = new_circ.data[5].operation.blocks[0].data[0].operation.blocks[0] self.assertTrue( Operator.from_circuit(if_circ_1).equiv( @@ -129,17 +127,14 @@ def test_remove_parameter_expressions_dynamic_circuit(self): ) ) - else_circ_1 = QuantumCircuit(2) - else_circ_1.data = circ.data[5].operation.blocks[0].blocks[1] - else_circ_2 = QuantumCircuit(2) - else_circ_2.data = new_circ.data[5].operation.blocks[0].blocks[1] + else_circ_1 = circ.data[5].operation.blocks[0].data[0].operation.blocks[1] + else_circ_2 = new_circ.data[5].operation.blocks[0].data[0].operation.blocks[1] else_1_flat = param_values_flat[:, 0] - else_params_2 = new_values[:, 3] - else_2_flat = else_params_2.reshape(-1, else_params_2.shape[-1]) + else_2_flat = new_values[..., 3].ravel() for param_set_1, param_set_2 in zip(else_1_flat, else_2_flat): self.assertTrue( - Operator.from_circuit(else_circ_1.assign_parameters(param_set_1)).equiv( - Operator.from_circuit(else_circ_2.assign_parameters(param_set_2)) + Operator.from_circuit(else_circ_1.assign_parameters([param_set_1])).equiv( + Operator.from_circuit(else_circ_2.assign_parameters([param_set_2])) ) ) \ No newline at end of file From 9b14e8cd84db0aef16736d40692459405108dd0f Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 12:28:19 +0200 Subject: [PATCH 10/32] save the binding if the parameter expression is a parameter --- qiskit_ibm_runtime/quantum_program/utils.py | 15 ++++++++++----- test/unit/quantum_program/test_utils.py | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index b6dcffd2d..f3c142667 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -41,13 +41,18 @@ def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: if str(param_exp) in parameter_table: new_param = parameter_table[str(param_exp)] else: - new_param_values = np.zeros(param_values.shape[:-1] + (1,)) - for idx in np.ndindex(param_values.shape[:-1]): - to_bind = param_values[idx] - new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) + if isinstance(param_exp, Parameter): + location = next(i for i, param in enumerate(circ.parameters) if param.name == param_exp.name) + new_param_values = param_values[..., [location]] + new_param = param_exp + else: + new_param_values = np.zeros(param_values.shape[:-1] + (1,)) + for idx in np.ndindex(param_values.shape[:-1]): + to_bind = param_values[idx] + new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) + new_param = Parameter(str(param_exp)) new_param_value_cols.append(new_param_values) - new_param = Parameter(str(param_exp)) parameter_table[str(param_exp)] = new_param new_op_params.append(new_param) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 2e15bd756..e1aeca0ed 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -45,6 +45,7 @@ def test_remove_parameter_expressions_static_circuit(self): new_circ, new_values = remove_parameter_expressions(circ, param_values) self.assertEqual(len(new_circ.parameters), 3) + self.assertEqual(new_circ.parameters[0], p1) self.assertEqual(param_values.shape[:-1], new_values.shape[:-1]) param_values_flat = param_values.reshape(-1, param_values.shape[-1]) From 714344e0f4ec6bebf8bc6eb39206656f626efc01 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 14:29:30 +0200 Subject: [PATCH 11/32] added a test --- test/unit/quantum_program/test_utils.py | 40 ++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index e1aeca0ed..61fd6b52d 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -138,4 +138,42 @@ def test_remove_parameter_expressions_dynamic_circuit(self): Operator.from_circuit(else_circ_1.assign_parameters([param_set_1])).equiv( Operator.from_circuit(else_circ_2.assign_parameters([param_set_2])) ) - ) \ No newline at end of file + ) + + + def test_remove_parameter_expressions_one_parameter(self): + """ + Test the function :func:`~remove_parameter_expressions` in the edge + case where the circuit contains one parameter. + """ + p = Parameter("p") + + circ = QuantumCircuit(1) + circ.h(0) + circ.rz(p + 1, 0) + + param_values = np.array([5]) + _, new_values = remove_parameter_expressions(circ, param_values) + self.assertTrue(np.array_equal(new_values, np.array([6]))) + + circ = QuantumCircuit(1) + circ.h(0) + circ.rz(p, 0) + + param_values = np.array([5]) + _, new_values = remove_parameter_expressions(circ, param_values) + self.assertTrue(np.array_equal(new_values, np.array([5]))) + + circ = QuantumCircuit(1) + circ.h(0) + circ.rz(p + 1, 0) + circ.rz(p, 0) + + param_values = np.array([5]) + _, new_values = remove_parameter_expressions(circ, param_values) + print(new_values) + self.assertTrue(np.array_equal(new_values, np.array([6, 5]))) + + param_values = np.array([[5], [10]]) + _, new_values = remove_parameter_expressions(circ, param_values) + self.assertTrue(np.array_equal(new_values, np.array([[6, 5], [11, 10]]))) From 3ad7227742a9c4340e945a8e0f70ec09eea203f5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 14:30:25 +0200 Subject: [PATCH 12/32] black --- qiskit_ibm_runtime/quantum_program/utils.py | 39 +++++++++++++++------ test/unit/quantum_program/test_utils.py | 25 ++++++++----- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index f3c142667..ec02a57bd 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -19,19 +19,32 @@ from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression, CircuitInstruction -def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: np.ndarray, - parameter_table: dict[str, Parameter], new_param_value_cols: list[np.ndarray]): +def _remove_parameter_expressions_in_blocks( + circ: QuantumCircuit, + param_values: np.ndarray, + parameter_table: dict[str, Parameter], + new_param_value_cols: list[np.ndarray], +): new_circ = circ.copy_empty_like() new_data = [] for instruction in circ.data: if instruction.is_control_flow(): - new_blocks = [_remove_parameter_expressions_in_blocks(block, param_values, parameter_table, new_param_value_cols) for block in instruction.operation.blocks] + new_blocks = [ + _remove_parameter_expressions_in_blocks( + block, param_values, parameter_table, new_param_value_cols + ) + for block in instruction.operation.blocks + ] new_gate = instruction.operation.replace_blocks(new_blocks) new_data.append(instruction.replace(params=new_gate.params, operation=new_gate)) continue - - param_exps = [op_param for op_param in instruction.operation.params if isinstance(op_param, ParameterExpression)] + + param_exps = [ + op_param + for op_param in instruction.operation.params + if isinstance(op_param, ParameterExpression) + ] if len(param_exps) == 0: new_data.append(instruction) continue @@ -42,24 +55,28 @@ def _remove_parameter_expressions_in_blocks(circ: QuantumCircuit, param_values: new_param = parameter_table[str(param_exp)] else: if isinstance(param_exp, Parameter): - location = next(i for i, param in enumerate(circ.parameters) if param.name == param_exp.name) + location = next( + i for i, param in enumerate(circ.parameters) if param.name == param_exp.name + ) new_param_values = param_values[..., [location]] new_param = param_exp else: new_param_values = np.zeros(param_values.shape[:-1] + (1,)) for idx in np.ndindex(param_values.shape[:-1]): to_bind = param_values[idx] - new_param_values[idx] = param_exp.bind_all(dict(zip(circ.parameters, to_bind))) + new_param_values[idx] = param_exp.bind_all( + dict(zip(circ.parameters, to_bind)) + ) new_param = Parameter(str(param_exp)) new_param_value_cols.append(new_param_values) parameter_table[str(param_exp)] = new_param - + new_op_params.append(new_param) new_gate = instruction.operation.copy() new_gate.params = new_op_params - new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) + new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) new_circ.data = new_data return new_circ @@ -71,5 +88,7 @@ def remove_parameter_expressions( parameter_table = {} new_param_value_cols = [] - new_circ = _remove_parameter_expressions_in_blocks(circ, param_values, parameter_table, new_param_value_cols) + new_circ = _remove_parameter_expressions_in_blocks( + circ, param_values, parameter_table, new_param_value_cols + ) return new_circ, np.concatenate(new_param_value_cols, axis=-1) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 61fd6b52d..df37f0256 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -33,7 +33,13 @@ def test_remove_parameter_expressions_static_circuit(self): """ p1 = Parameter("p1") p2 = Parameter("p2") - param_values = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]], [[9, 10], [11, 12], [13, 14], [15, 16]], [[17, 18], [19, 20], [21, 22], [23, 24]]]) + param_values = np.array( + [ + [[1, 2], [3, 4], [5, 6], [7, 8]], + [[9, 10], [11, 12], [13, 14], [15, 16]], + [[17, 18], [19, 20], [21, 22], [23, 24]], + ] + ) circ = QuantumCircuit(2) circ.h(0) @@ -63,7 +69,13 @@ def test_remove_parameter_expressions_dynamic_circuit(self): """ p1 = Parameter("p1") p2 = Parameter("p2") - param_values = np.array([[[1, 2], [3, 4], [5, 6], [7, 8]], [[9, 10], [11, 12], [13, 14], [15, 16]], [[17, 18], [19, 20], [21, 22], [23, 24]]]) + param_values = np.array( + [ + [[1, 2], [3, 4], [5, 6], [7, 8]], + [[9, 10], [11, 12], [13, 14], [15, 16]], + [[17, 18], [19, 20], [21, 22], [23, 24]], + ] + ) param_values_flat = param_values.reshape(-1, param_values.shape[-1]) circ = QuantumCircuit(2, 1) @@ -106,7 +118,7 @@ def test_remove_parameter_expressions_dynamic_circuit(self): box_circ_1 = QuantumCircuit(2) box_circ_1.data = circ.data[2].operation.blocks[0] box_circ_2 = QuantumCircuit(2) - box_circ_2.data = new_circ.data[2].operation.blocks[0] + box_circ_2.data = new_circ.data[2].operation.blocks[0] box_params_2 = new_values[..., [0, 1, 2]] box_2_flat = box_params_2.reshape(-1, box_params_2.shape[-1]) @@ -122,11 +134,7 @@ def test_remove_parameter_expressions_dynamic_circuit(self): if_circ_1 = circ.data[5].operation.blocks[0].data[0].operation.blocks[0] if_circ_2 = new_circ.data[5].operation.blocks[0].data[0].operation.blocks[0] - self.assertTrue( - Operator.from_circuit(if_circ_1).equiv( - Operator.from_circuit(if_circ_2) - ) - ) + self.assertTrue(Operator.from_circuit(if_circ_1).equiv(Operator.from_circuit(if_circ_2))) else_circ_1 = circ.data[5].operation.blocks[0].data[0].operation.blocks[1] else_circ_2 = new_circ.data[5].operation.blocks[0].data[0].operation.blocks[1] @@ -140,7 +148,6 @@ def test_remove_parameter_expressions_dynamic_circuit(self): ) ) - def test_remove_parameter_expressions_one_parameter(self): """ Test the function :func:`~remove_parameter_expressions` in the edge From 93481a8011d7de43faed7ce30e7bf4cc5a28254b Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 14:31:39 +0200 Subject: [PATCH 13/32] lint --- qiskit_ibm_runtime/quantum_program/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index ec02a57bd..51879a0c1 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -16,7 +16,7 @@ import numpy as np -from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression, CircuitInstruction +from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression def _remove_parameter_expressions_in_blocks( From 95ed68c7b7c00bbd87302e6bc3c941b11d4980ef Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 14:41:08 +0200 Subject: [PATCH 14/32] mypy --- qiskit_ibm_runtime/quantum_program/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 51879a0c1..c30366aba 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -24,7 +24,7 @@ def _remove_parameter_expressions_in_blocks( param_values: np.ndarray, parameter_table: dict[str, Parameter], new_param_value_cols: list[np.ndarray], -): +) -> QuantumCircuit: new_circ = circ.copy_empty_like() new_data = [] @@ -85,8 +85,8 @@ def _remove_parameter_expressions_in_blocks( def remove_parameter_expressions( circ: QuantumCircuit, param_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: - parameter_table = {} - new_param_value_cols = [] + parameter_table: dict[str, Parameter] = {} + new_param_value_cols: list[np.ndarray] = [] new_circ = _remove_parameter_expressions_in_blocks( circ, param_values, parameter_table, new_param_value_cols From f75d001d5600f90dfff5dddef7d487e5afbf09e5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Mon, 17 Nov 2025 14:57:30 +0200 Subject: [PATCH 15/32] lint --- qiskit_ibm_runtime/quantum_program/utils.py | 2 ++ test/unit/quantum_program/test_utils.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index c30366aba..cc64e9b1e 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -85,6 +85,8 @@ def _remove_parameter_expressions_in_blocks( def remove_parameter_expressions( circ: QuantumCircuit, param_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: + """Create an input to the quantum program that's + free from parameter expressions.""" parameter_table: dict[str, Parameter] = {} new_param_value_cols: list[np.ndarray] = [] diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index df37f0256..004b9c4ac 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -23,6 +23,9 @@ from ...ibm_test_case import IBMTestCase +# pylint: disable=not-context-manager + + class TestRemoveParameterExpressions(IBMTestCase): """Test the function :func:`~remove_parameter_expressions`.""" From b333bc0dc9f36086dc2024f068cc6d9bd6a8d8a5 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 19 Nov 2025 11:13:05 +0200 Subject: [PATCH 16/32] use samplomatic parameter table --- qiskit_ibm_runtime/quantum_program/utils.py | 43 +++++++-------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index cc64e9b1e..2466aeaae 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -18,12 +18,13 @@ from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression +from samplomatic.samplex import ParameterExpressionTable + def _remove_parameter_expressions_in_blocks( circ: QuantumCircuit, - param_values: np.ndarray, - parameter_table: dict[str, Parameter], - new_param_value_cols: list[np.ndarray], + parameter_table: ParameterExpressionTable, + parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] ) -> QuantumCircuit: new_circ = circ.copy_empty_like() new_data = [] @@ -31,9 +32,7 @@ def _remove_parameter_expressions_in_blocks( for instruction in circ.data: if instruction.is_control_flow(): new_blocks = [ - _remove_parameter_expressions_in_blocks( - block, param_values, parameter_table, new_param_value_cols - ) + _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_to_new_parameters_map) for block in instruction.operation.blocks ] new_gate = instruction.operation.replace_blocks(new_blocks) @@ -51,27 +50,15 @@ def _remove_parameter_expressions_in_blocks( new_op_params = [] for param_exp in param_exps: - if str(param_exp) in parameter_table: - new_param = parameter_table[str(param_exp)] + if param_exp in parameter_expressions_to_new_parameters_map: + new_param = parameter_expressions_to_new_parameters_map[param_exp] else: if isinstance(param_exp, Parameter): - location = next( - i for i, param in enumerate(circ.parameters) if param.name == param_exp.name - ) - new_param_values = param_values[..., [location]] - new_param = param_exp + new_param = param_exp else: - new_param_values = np.zeros(param_values.shape[:-1] + (1,)) - for idx in np.ndindex(param_values.shape[:-1]): - to_bind = param_values[idx] - new_param_values[idx] = param_exp.bind_all( - dict(zip(circ.parameters, to_bind)) - ) new_param = Parameter(str(param_exp)) - - new_param_value_cols.append(new_param_values) - parameter_table[str(param_exp)] = new_param - + parameter_table.append(param_exp) + parameter_expressions_to_new_parameters_map[param_exp] = new_param new_op_params.append(new_param) new_gate = instruction.operation.copy() @@ -87,10 +74,8 @@ def remove_parameter_expressions( ) -> tuple[QuantumCircuit, np.ndarray]: """Create an input to the quantum program that's free from parameter expressions.""" - parameter_table: dict[str, Parameter] = {} - new_param_value_cols: list[np.ndarray] = [] + parameter_table = ParameterExpressionTable() + parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} - new_circ = _remove_parameter_expressions_in_blocks( - circ, param_values, parameter_table, new_param_value_cols - ) - return new_circ, np.concatenate(new_param_value_cols, axis=-1) + new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_to_new_parameters_map) + return new_circ, parameter_table.evaluate(param_values) From e23f19cce73832112c280650c67b691aa8b0b451 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 19 Nov 2025 18:52:09 +0200 Subject: [PATCH 17/32] correct usage of evaluate --- qiskit_ibm_runtime/quantum_program/utils.py | 23 +++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 2466aeaae..c0a854be2 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -24,7 +24,7 @@ def _remove_parameter_expressions_in_blocks( circ: QuantumCircuit, parameter_table: ParameterExpressionTable, - parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] + parameter_expressions_metadata: dict[ParameterExpression, tuple[Parameter, int]] ) -> QuantumCircuit: new_circ = circ.copy_empty_like() new_data = [] @@ -32,7 +32,7 @@ def _remove_parameter_expressions_in_blocks( for instruction in circ.data: if instruction.is_control_flow(): new_blocks = [ - _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_to_new_parameters_map) + _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_metadata) for block in instruction.operation.blocks ] new_gate = instruction.operation.replace_blocks(new_blocks) @@ -50,15 +50,15 @@ def _remove_parameter_expressions_in_blocks( new_op_params = [] for param_exp in param_exps: - if param_exp in parameter_expressions_to_new_parameters_map: - new_param = parameter_expressions_to_new_parameters_map[param_exp] + if param_exp in parameter_expressions_metadata: + new_param = parameter_expressions_metadata[param_exp][0] else: if isinstance(param_exp, Parameter): new_param = param_exp else: new_param = Parameter(str(param_exp)) - parameter_table.append(param_exp) - parameter_expressions_to_new_parameters_map[param_exp] = new_param + param_exp_index = parameter_table.append(param_exp) + parameter_expressions_metadata[param_exp] = (new_param, param_exp_index) new_op_params.append(new_param) new_gate = instruction.operation.copy() @@ -75,7 +75,12 @@ def remove_parameter_expressions( """Create an input to the quantum program that's free from parameter expressions.""" parameter_table = ParameterExpressionTable() - parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} + parameter_expressions_metadata: dict[ParameterExpression, (Parameter, int)] = {} - new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_to_new_parameters_map) - return new_circ, parameter_table.evaluate(param_values) + new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_metadata) + + new_values = np.zeros(param_values.shape[:-1] + (len(new_circ.parameters),)) + for idx in np.ndindex(param_values.shape[:-1]): + new_values[idx] = parameter_table.evaluate(param_values[*idx, :]) + + return new_circ, new_values From 6e4d8701d0cd0eab9fe7848a3a0e2c22348abb03 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 19 Nov 2025 23:08:42 +0200 Subject: [PATCH 18/32] no need to store the table index --- qiskit_ibm_runtime/quantum_program/utils.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index c0a854be2..76f97fa85 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -24,7 +24,7 @@ def _remove_parameter_expressions_in_blocks( circ: QuantumCircuit, parameter_table: ParameterExpressionTable, - parameter_expressions_metadata: dict[ParameterExpression, tuple[Parameter, int]] + parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] ) -> QuantumCircuit: new_circ = circ.copy_empty_like() new_data = [] @@ -32,7 +32,7 @@ def _remove_parameter_expressions_in_blocks( for instruction in circ.data: if instruction.is_control_flow(): new_blocks = [ - _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_metadata) + _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_to_new_parameters_map) for block in instruction.operation.blocks ] new_gate = instruction.operation.replace_blocks(new_blocks) @@ -50,15 +50,15 @@ def _remove_parameter_expressions_in_blocks( new_op_params = [] for param_exp in param_exps: - if param_exp in parameter_expressions_metadata: - new_param = parameter_expressions_metadata[param_exp][0] + if param_exp in parameter_expressions_to_new_parameters_map: + new_param = parameter_expressions_to_new_parameters_map[param_exp] else: if isinstance(param_exp, Parameter): new_param = param_exp else: new_param = Parameter(str(param_exp)) - param_exp_index = parameter_table.append(param_exp) - parameter_expressions_metadata[param_exp] = (new_param, param_exp_index) + parameter_table.append(param_exp) + parameter_expressions_to_new_parameters_map[param_exp] = new_param new_op_params.append(new_param) new_gate = instruction.operation.copy() @@ -75,9 +75,9 @@ def remove_parameter_expressions( """Create an input to the quantum program that's free from parameter expressions.""" parameter_table = ParameterExpressionTable() - parameter_expressions_metadata: dict[ParameterExpression, (Parameter, int)] = {} + parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} - new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_metadata) + new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_to_new_parameters_map) new_values = np.zeros(param_values.shape[:-1] + (len(new_circ.parameters),)) for idx in np.ndindex(param_values.shape[:-1]): From 28b4c785cc9d69d1de4369c8a0a4c6aab30dc353 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 19 Nov 2025 23:09:19 +0200 Subject: [PATCH 19/32] remove a print --- test/unit/quantum_program/test_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 004b9c4ac..60dccae0b 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -181,7 +181,6 @@ def test_remove_parameter_expressions_one_parameter(self): param_values = np.array([5]) _, new_values = remove_parameter_expressions(circ, param_values) - print(new_values) self.assertTrue(np.array_equal(new_values, np.array([6, 5]))) param_values = np.array([[5], [10]]) From c6aba00a8ea4a61ff5fafd52fec4aa0f28044cf0 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Wed, 19 Nov 2025 23:12:01 +0200 Subject: [PATCH 20/32] make some names longer --- qiskit_ibm_runtime/quantum_program/utils.py | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 76f97fa85..17d6fbff2 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -22,14 +22,14 @@ def _remove_parameter_expressions_in_blocks( - circ: QuantumCircuit, + circuit: QuantumCircuit, parameter_table: ParameterExpressionTable, parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] ) -> QuantumCircuit: - new_circ = circ.copy_empty_like() + new_circuit = circuit.copy_empty_like() new_data = [] - for instruction in circ.data: + for instruction in circuit.data: if instruction.is_control_flow(): new_blocks = [ _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_to_new_parameters_map) @@ -65,22 +65,22 @@ def _remove_parameter_expressions_in_blocks( new_gate.params = new_op_params new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) - new_circ.data = new_data - return new_circ + new_circuit.data = new_data + return new_circuit def remove_parameter_expressions( - circ: QuantumCircuit, param_values: np.ndarray + circuit: QuantumCircuit, parameter_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: """Create an input to the quantum program that's free from parameter expressions.""" parameter_table = ParameterExpressionTable() parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} - new_circ = _remove_parameter_expressions_in_blocks(circ, parameter_table, parameter_expressions_to_new_parameters_map) + new_circuit = _remove_parameter_expressions_in_blocks(circuit, parameter_table, parameter_expressions_to_new_parameters_map) - new_values = np.zeros(param_values.shape[:-1] + (len(new_circ.parameters),)) - for idx in np.ndindex(param_values.shape[:-1]): - new_values[idx] = parameter_table.evaluate(param_values[*idx, :]) + new_values = np.zeros(parameter_values.shape[:-1] + (len(new_circuit.parameters),)) + for idx in np.ndindex(parameter_values.shape[:-1]): + new_values[idx] = parameter_table.evaluate(parameter_values[*idx, :]) - return new_circ, new_values + return new_circuit, new_values From 4aa4a6b6d5bdcfa103dc15ca3c83689497927d61 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 20 Nov 2025 14:06:28 +0200 Subject: [PATCH 21/32] more doc --- qiskit_ibm_runtime/quantum_program/utils.py | 41 ++++++++++++++++++--- test/unit/quantum_program/test_utils.py | 14 +++---- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 17d6fbff2..dc5aa91f5 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -21,7 +21,7 @@ from samplomatic.samplex import ParameterExpressionTable -def _remove_parameter_expressions_in_blocks( +def _replace_parameter_expressions( circuit: QuantumCircuit, parameter_table: ParameterExpressionTable, parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] @@ -32,7 +32,7 @@ def _remove_parameter_expressions_in_blocks( for instruction in circuit.data: if instruction.is_control_flow(): new_blocks = [ - _remove_parameter_expressions_in_blocks(block, parameter_table, parameter_expressions_to_new_parameters_map) + _replace_parameter_expressions(block, parameter_table, parameter_expressions_to_new_parameters_map) for block in instruction.operation.blocks ] new_gate = instruction.operation.replace_blocks(new_blocks) @@ -69,15 +69,44 @@ def _remove_parameter_expressions_in_blocks( return new_circuit -def remove_parameter_expressions( +def replace_parameter_expressions( circuit: QuantumCircuit, parameter_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: - """Create an input to the quantum program that's - free from parameter expressions.""" + """ + A helper to replace a circuit's parameter expressions with parameters. + + The function tranverses the circuit and collects all the parameters and parameter expressions. + A new parameter is created for every parameter expression that is not a parameter. + The function builds a new circuit, where each parameter expression is replaced by the + corresponding new parameter. + In addition, the function creates a new array of parameter values, which matches the parameters + of the new circuit. Values for new parameters are obtained by evaluating the original + expressions over the original parameter values. + + Example: + + .. code-block:: python + + import numpy as np + from qiskit.circuit import QuantumCircuit, Parameter + from qiskit_ibm_runtime.quantum_program.utils import replace_parameter_expressions + + circuit = QuantumCircuit(1) + circuit.rx(a := Parameter("a"), 0) + circuit.rx(b := Parameter("b"), 0) + circuit.rx(a + b, 0) + + values = np.array([[1, 2], [3, 4]]) + + # ``new_circuit`` will incorporate a new parameter, which replaces the parameter + # expression ``a + b`` + # ``new_values`` will be ``np.array([[1, 2, 3], [3, 4, 7]])`` + new_circuit, new_values = replace_parameter_expressions(circuit, values) + """ parameter_table = ParameterExpressionTable() parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} - new_circuit = _remove_parameter_expressions_in_blocks(circuit, parameter_table, parameter_expressions_to_new_parameters_map) + new_circuit = _replace_parameter_expressions(circuit, parameter_table, parameter_expressions_to_new_parameters_map) new_values = np.zeros(parameter_values.shape[:-1] + (len(new_circuit.parameters),)) for idx in np.ndindex(parameter_values.shape[:-1]): diff --git a/test/unit/quantum_program/test_utils.py b/test/unit/quantum_program/test_utils.py index 60dccae0b..b43806873 100644 --- a/test/unit/quantum_program/test_utils.py +++ b/test/unit/quantum_program/test_utils.py @@ -18,7 +18,7 @@ from qiskit.circuit.library import U2Gate from qiskit.quantum_info import Operator -from qiskit_ibm_runtime.quantum_program.utils import remove_parameter_expressions +from qiskit_ibm_runtime.quantum_program.utils import replace_parameter_expressions from ...ibm_test_case import IBMTestCase @@ -51,7 +51,7 @@ def test_remove_parameter_expressions_static_circuit(self): circ.rx(p1 + p2, 0) circ.append(U2Gate(p1 - p2, p1 + p2), [1]) - new_circ, new_values = remove_parameter_expressions(circ, param_values) + new_circ, new_values = replace_parameter_expressions(circ, param_values) self.assertEqual(len(new_circ.parameters), 3) self.assertEqual(new_circ.parameters[0], p1) @@ -97,7 +97,7 @@ def test_remove_parameter_expressions_dynamic_circuit(self): circ.rz(p1 + 3, 0) circ.rx(p1 * p2, 1) - new_circ, new_values = remove_parameter_expressions(circ, param_values) + new_circ, new_values = replace_parameter_expressions(circ, param_values) # parameter names: 3 + p1, p1, p1 + p2, p1 - p2, p1*p2 self.assertEqual(len(new_circ.parameters), 5) @@ -163,7 +163,7 @@ def test_remove_parameter_expressions_one_parameter(self): circ.rz(p + 1, 0) param_values = np.array([5]) - _, new_values = remove_parameter_expressions(circ, param_values) + _, new_values = replace_parameter_expressions(circ, param_values) self.assertTrue(np.array_equal(new_values, np.array([6]))) circ = QuantumCircuit(1) @@ -171,7 +171,7 @@ def test_remove_parameter_expressions_one_parameter(self): circ.rz(p, 0) param_values = np.array([5]) - _, new_values = remove_parameter_expressions(circ, param_values) + _, new_values = replace_parameter_expressions(circ, param_values) self.assertTrue(np.array_equal(new_values, np.array([5]))) circ = QuantumCircuit(1) @@ -180,9 +180,9 @@ def test_remove_parameter_expressions_one_parameter(self): circ.rz(p, 0) param_values = np.array([5]) - _, new_values = remove_parameter_expressions(circ, param_values) + _, new_values = replace_parameter_expressions(circ, param_values) self.assertTrue(np.array_equal(new_values, np.array([6, 5]))) param_values = np.array([[5], [10]]) - _, new_values = remove_parameter_expressions(circ, param_values) + _, new_values = replace_parameter_expressions(circ, param_values) self.assertTrue(np.array_equal(new_values, np.array([[6, 5], [11, 10]]))) From 5efaf883fe561b74ed7b0043c3ad05dc6b424ebd Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 20 Nov 2025 14:08:07 +0200 Subject: [PATCH 22/32] black --- qiskit_ibm_runtime/quantum_program/utils.py | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index dc5aa91f5..49a67f001 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -72,18 +72,18 @@ def _replace_parameter_expressions( def replace_parameter_expressions( circuit: QuantumCircuit, parameter_values: np.ndarray ) -> tuple[QuantumCircuit, np.ndarray]: - """ - A helper to replace a circuit's parameter expressions with parameters. - - The function tranverses the circuit and collects all the parameters and parameter expressions. - A new parameter is created for every parameter expression that is not a parameter. - The function builds a new circuit, where each parameter expression is replaced by the - corresponding new parameter. - In addition, the function creates a new array of parameter values, which matches the parameters - of the new circuit. Values for new parameters are obtained by evaluating the original - expressions over the original parameter values. - - Example: + """ + A helper to replace a circuit's parameter expressions with parameters. + + The function tranverses the circuit and collects all the parameters and parameter expressions. + A new parameter is created for every parameter expression that is not a parameter. + The function builds a new circuit, where each parameter expression is replaced by the + corresponding new parameter. + In addition, the function creates a new array of parameter values, which matches the parameters + of the new circuit. Values for new parameters are obtained by evaluating the original + expressions over the original parameter values. + + Example: .. code-block:: python @@ -102,7 +102,7 @@ def replace_parameter_expressions( # expression ``a + b`` # ``new_values`` will be ``np.array([[1, 2, 3], [3, 4, 7]])`` new_circuit, new_values = replace_parameter_expressions(circuit, values) - """ + """ parameter_table = ParameterExpressionTable() parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} From 52b71efee9b3d996173a3adc501803e0ee305c4a Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 20 Nov 2025 14:08:16 +0200 Subject: [PATCH 23/32] black --- qiskit_ibm_runtime/quantum_program/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 49a67f001..37000b2b0 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -24,7 +24,7 @@ def _replace_parameter_expressions( circuit: QuantumCircuit, parameter_table: ParameterExpressionTable, - parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] + parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter], ) -> QuantumCircuit: new_circuit = circuit.copy_empty_like() new_data = [] @@ -32,7 +32,9 @@ def _replace_parameter_expressions( for instruction in circuit.data: if instruction.is_control_flow(): new_blocks = [ - _replace_parameter_expressions(block, parameter_table, parameter_expressions_to_new_parameters_map) + _replace_parameter_expressions( + block, parameter_table, parameter_expressions_to_new_parameters_map + ) for block in instruction.operation.blocks ] new_gate = instruction.operation.replace_blocks(new_blocks) @@ -54,7 +56,7 @@ def _replace_parameter_expressions( new_param = parameter_expressions_to_new_parameters_map[param_exp] else: if isinstance(param_exp, Parameter): - new_param = param_exp + new_param = param_exp else: new_param = Parameter(str(param_exp)) parameter_table.append(param_exp) @@ -82,7 +84,7 @@ def replace_parameter_expressions( In addition, the function creates a new array of parameter values, which matches the parameters of the new circuit. Values for new parameters are obtained by evaluating the original expressions over the original parameter values. - + Example: .. code-block:: python @@ -106,7 +108,9 @@ def replace_parameter_expressions( parameter_table = ParameterExpressionTable() parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} - new_circuit = _replace_parameter_expressions(circuit, parameter_table, parameter_expressions_to_new_parameters_map) + new_circuit = _replace_parameter_expressions( + circuit, parameter_table, parameter_expressions_to_new_parameters_map + ) new_values = np.zeros(parameter_values.shape[:-1] + (len(new_circuit.parameters),)) for idx in np.ndindex(parameter_values.shape[:-1]): From c45d701d299298750c8f1ad6e2c2129cdb2906ce Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 20 Nov 2025 14:25:02 +0200 Subject: [PATCH 24/32] lint --- qiskit_ibm_runtime/quantum_program/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 37000b2b0..d51f15f57 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -114,6 +114,6 @@ def replace_parameter_expressions( new_values = np.zeros(parameter_values.shape[:-1] + (len(new_circuit.parameters),)) for idx in np.ndindex(parameter_values.shape[:-1]): - new_values[idx] = parameter_table.evaluate(parameter_values[*idx, :]) + new_values[idx] = parameter_table.evaluate(parameter_values[idx + (slice(None),)]) return new_circuit, new_values From 8867ecc68fd43dda505e920f4400a30c89a8aae6 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 23 Nov 2025 08:21:00 +0200 Subject: [PATCH 25/32] Update qiskit_ibm_runtime/quantum_program/utils.py Co-authored-by: Samuele Ferracin --- qiskit_ibm_runtime/quantum_program/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index d51f15f57..9d998f675 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -77,8 +77,7 @@ def replace_parameter_expressions( """ A helper to replace a circuit's parameter expressions with parameters. - The function tranverses the circuit and collects all the parameters and parameter expressions. - A new parameter is created for every parameter expression that is not a parameter. + The function tranverses the circuit and replaces all the parameters and parameter expressions with new parameters. The function builds a new circuit, where each parameter expression is replaced by the corresponding new parameter. In addition, the function creates a new array of parameter values, which matches the parameters From 19532c32a3846c6e6d8cfe80944161f532b66057 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 23 Nov 2025 08:33:45 +0200 Subject: [PATCH 26/32] revert last commit --- qiskit_ibm_runtime/quantum_program/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 9d998f675..d51f15f57 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -77,7 +77,8 @@ def replace_parameter_expressions( """ A helper to replace a circuit's parameter expressions with parameters. - The function tranverses the circuit and replaces all the parameters and parameter expressions with new parameters. + The function tranverses the circuit and collects all the parameters and parameter expressions. + A new parameter is created for every parameter expression that is not a parameter. The function builds a new circuit, where each parameter expression is replaced by the corresponding new parameter. In addition, the function creates a new array of parameter values, which matches the parameters From c9083589f3d4b5b59acfa98ab06bc7520ebb76f3 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 27 Nov 2025 19:26:00 +0200 Subject: [PATCH 27/32] Update qiskit_ibm_runtime/quantum_program/utils.py Co-authored-by: Samuele Ferracin --- qiskit_ibm_runtime/quantum_program/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index d51f15f57..ccd4407f5 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -63,9 +63,8 @@ def _replace_parameter_expressions( parameter_expressions_to_new_parameters_map[param_exp] = new_param new_op_params.append(new_param) - new_gate = instruction.operation.copy() - new_gate.params = new_op_params - new_data.append(instruction.replace(params=new_op_params, operation=new_gate)) + new_op = type(op := instruction.operation)(new_op_params) + new_circuit.append(new_op, op.qubits, op.clbits) new_circuit.data = new_data return new_circuit From ddb72fef5c53383e395a0731fb19bee4c90aaccc Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 27 Nov 2025 19:29:41 +0200 Subject: [PATCH 28/32] update function doc --- qiskit_ibm_runtime/quantum_program/utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index ccd4407f5..9dce59ca5 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -103,6 +103,14 @@ def replace_parameter_expressions( # expression ``a + b`` # ``new_values`` will be ``np.array([[1, 2, 3], [3, 4, 7]])`` new_circuit, new_values = replace_parameter_expressions(circuit, values) + + .. note: + + The instructions of the new circuit are the same as in the original circuit, in terms of + operation types, qubits, and classical bits. Other instruction attributes, such as + ``label``, are not copied. Instruction operations are assumed to be one of + ``global_phase``, ``p``, ``r``, ``rx``, ``rxx``, ``ry``, ``ryy``, ``rz``, ``rzx``, ``rzz``, + ``u``, ``u1``, ``u2``, ``u3``. Other operations may yield unexpected behavior. """ parameter_table = ParameterExpressionTable() parameter_expressions_to_new_parameters_map: dict[ParameterExpression, Parameter] = {} From c2300559aa6bbd29f77f343fdfb65143f28f1896 Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 27 Nov 2025 19:59:50 +0200 Subject: [PATCH 29/32] fix --- qiskit_ibm_runtime/quantum_program/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 9dce59ca5..cf41f5ba1 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -63,7 +63,7 @@ def _replace_parameter_expressions( parameter_expressions_to_new_parameters_map[param_exp] = new_param new_op_params.append(new_param) - new_op = type(op := instruction.operation)(new_op_params) + new_op = type(op := instruction.operation)(*new_op_params) new_circuit.append(new_op, op.qubits, op.clbits) new_circuit.data = new_data From cc64abf5f651016d621f418592282c282e50d0eb Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Thu, 27 Nov 2025 20:58:22 +0200 Subject: [PATCH 30/32] fix --- qiskit_ibm_runtime/quantum_program/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index cf41f5ba1..f9729d7a8 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -16,7 +16,7 @@ import numpy as np -from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression +from qiskit.circuit import Parameter, QuantumCircuit, ParameterExpression, CircuitInstruction from samplomatic.samplex import ParameterExpressionTable @@ -63,8 +63,8 @@ def _replace_parameter_expressions( parameter_expressions_to_new_parameters_map[param_exp] = new_param new_op_params.append(new_param) - new_op = type(op := instruction.operation)(*new_op_params) - new_circuit.append(new_op, op.qubits, op.clbits) + new_op = type(instruction.operation)(*new_op_params) + new_circuit.append(CircuitInstruction(new_op, instruction.qubits, instruction.clbits)) new_circuit.data = new_data return new_circuit From 7305d0dad9479e097efc23fb5a0f85afdab9f3ae Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 30 Nov 2025 08:29:07 +0200 Subject: [PATCH 31/32] fix --- qiskit_ibm_runtime/quantum_program/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index f9729d7a8..385a13a4c 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -64,7 +64,7 @@ def _replace_parameter_expressions( new_op_params.append(new_param) new_op = type(instruction.operation)(*new_op_params) - new_circuit.append(CircuitInstruction(new_op, instruction.qubits, instruction.clbits)) + new_data.append(CircuitInstruction(operation=new_op, qubits=instruction.qubits, clbits=instruction.clbits)) new_circuit.data = new_data return new_circuit From 4234f9ea1c20bac872249c8041be21364564682c Mon Sep 17 00:00:00 2001 From: Yael Ben-Haim Date: Sun, 30 Nov 2025 08:29:38 +0200 Subject: [PATCH 32/32] black --- qiskit_ibm_runtime/quantum_program/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit_ibm_runtime/quantum_program/utils.py b/qiskit_ibm_runtime/quantum_program/utils.py index 385a13a4c..37f2a79c6 100644 --- a/qiskit_ibm_runtime/quantum_program/utils.py +++ b/qiskit_ibm_runtime/quantum_program/utils.py @@ -64,7 +64,11 @@ def _replace_parameter_expressions( new_op_params.append(new_param) new_op = type(instruction.operation)(*new_op_params) - new_data.append(CircuitInstruction(operation=new_op, qubits=instruction.qubits, clbits=instruction.clbits)) + new_data.append( + CircuitInstruction( + operation=new_op, qubits=instruction.qubits, clbits=instruction.clbits + ) + ) new_circuit.data = new_data return new_circuit