-
Notifications
You must be signed in to change notification settings - Fork 182
Update parameters to accommodate with RZZ constraints #2126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 37 commits
7dbfe26
0aee8a7
1f19512
4be8666
763da6c
b2b157c
e660cb8
95f60a5
cfa60ef
7fd4b34
b4d6729
aa29a71
b50a77a
64683c2
a86051e
b059808
b29cd64
2334016
27e432a
337039d
cb0a21c
1c09dc3
7a4aa09
f873e69
47cad78
ffa3850
8632e32
002f79a
6aa30f4
437e23a
aeb7a90
a3f9839
514eaa0
aacd61f
b39ba06
e8d3132
4200d29
e019051
a949270
c0c0889
d371510
d65e64e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -12,15 +12,19 @@ | |||||
|
||||||
"""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 | ||||||
|
||||||
from qiskit.converters import dag_to_circuit, circuit_to_dag | ||||||
from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate | ||||||
from qiskit.circuit.parameterexpression import 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 | ||||||
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 +248,163 @@ def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: | |||||
check=False, | ||||||
) | ||||||
return new_dag | ||||||
|
||||||
|
||||||
def convert_to_rzz_valid_pub( | ||||||
program_id: str, pub: Union[SamplerPubLike, EstimatorPubLike] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is intended to be user-facing, I feel like it would be smoother to allow the function to accept a primitive instance instead of a
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in e019051. There is a problem though, that defining a primitive instance requires |
||||||
) -> Union[SamplerPub, EstimatorPub]: | ||||||
""" | ||||||
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": | ||||||
pub = EstimatorPub.coerce(pub) | ||||||
else: | ||||||
raise ValueError(f"Unknown program id {program_id}") | ||||||
|
||||||
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_circ = pub.circuit.copy_empty_like() | ||||||
new_data = [] | ||||||
rzz_count = 0 | ||||||
|
||||||
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 | ||||||
): | ||||||
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 | ||||||
yaelbh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
# 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) | ||||||
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 + pi / 2) % (2 * pi) >= pi: | ||||||
rz_angles[idx] = pi | ||||||
else: | ||||||
rz_angles[idx] = 0 | ||||||
|
||||||
if angle % pi >= pi / 2: | ||||||
rx_angles[idx] = pi | ||||||
else: | ||||||
rx_angles[idx] = 0 | ||||||
|
||||||
rzz_angles[idx] = pi / 2 - abs(mod(angle, pi) - pi / 2) | ||||||
Comment on lines
+324
to
+334
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the target allows to store angle bound information, this could be extended to read the bounds from the target. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me know when it happens. |
||||||
|
||||||
rzz_count += 1 | ||||||
param_prefix = f"rzz_{rzz_count}_" | ||||||
wshanks marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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, pi) for rz_angle in rz_angles): | ||||||
new_data.append( | ||||||
CircuitInstruction( | ||||||
RZGate(pi), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this case and the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know. It will happen if all the angles belong to the same quad. |
||||||
(qubits[0],), | ||||||
) | ||||||
) | ||||||
new_data.append( | ||||||
CircuitInstruction( | ||||||
RZGate(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],), | ||||||
) | ||||||
) | ||||||
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, 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: | ||||||
# 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") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure where the error comes in. It might be worth checking with the Qiskit team if this is the best way to do this. I just worry that a future change could make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yaelbh do you have an error trace we can look at? I am also not sure what error this is referring to, but I agree that we should take a look. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error is correct. A pub must not contain values for a parameter that's not in the circuit. So, for p = Parameter("p")
circ = QuantumCircuit(1, 1)
circ.measure(0, 0)
sampler.run([(circ, {"p": 1})]).result() we rightfully obtain
In the context of this PR, we replace |
||||||
new_data.append(CircuitInstruction(RZZGate(param_rzz), qubits)) | ||||||
val_data[f"{param_prefix}rzz"] = rzz_angles | ||||||
else: | ||||||
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 | ||||||
|
||||||
if program_id == "sampler": | ||||||
return SamplerPub.coerce((new_circ, val_data), pub.shots) | ||||||
wshanks marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
else: | ||||||
return EstimatorPub.coerce((new_circ, pub.observables, val_data), pub.precision) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is not a transpiler pass that will be run automatically, do you want to expose it in a more public way by including it in the documentation and possibly providing a higher level import path than
qiskit_ibm_runtime.transpiler.passes.basis.fold_rzz_angle
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ElePT