16
16
import itertools
17
17
from collections .abc import Sequence
18
18
19
- from qiskit .circuit import QuantumCircuit , Parameter , Gate
19
+ from qiskit .circuit import QuantumCircuit , Parameter , Gate , ParameterExpression
20
20
from qiskit .circuit .library import RXGate , RYGate , RZGate , CRXGate , CRYGate , CRZGate
21
21
22
22
@@ -90,7 +90,7 @@ def gradient_lookup(gate: Gate) -> list[tuple[complex, QuantumCircuit]]:
90
90
91
91
92
92
def derive_circuit (
93
- circuit : QuantumCircuit , parameter : Parameter
93
+ circuit : QuantumCircuit , parameter : Parameter , check : bool = True
94
94
) -> Sequence [tuple [complex , QuantumCircuit ]]:
95
95
"""Return the analytic gradient expression of the input circuit wrt. a single parameter.
96
96
@@ -114,6 +114,8 @@ def derive_circuit(
114
114
Args:
115
115
circuit: The quantum circuit to derive.
116
116
parameter: The parameter with respect to which we derive.
117
+ check: If ``True`` (default) check that the parameter is valid and that no product
118
+ rule is required.
117
119
118
120
Returns:
119
121
A list of ``(coeff, gradient_circuit)`` tuples.
@@ -124,16 +126,31 @@ def derive_circuit(
124
126
NotImplementedError: If a non-unique parameter is added, as the product rule is not yet
125
127
supported in this function.
126
128
"""
127
- # this is added as useful user-warning, since sometimes ``ParameterExpression``s are
128
- # passed around instead of ``Parameter``s
129
- if not isinstance (parameter , Parameter ):
130
- raise ValueError (f"parameter must be of type Parameter, not { type (parameter )} ." )
131
-
132
- if parameter not in circuit .parameters :
133
- raise ValueError (f"The parameter { parameter } is not in this circuit." )
134
-
135
- if len (circuit ._parameter_table [parameter ]) > 1 :
136
- raise NotImplementedError ("No product rule support yet, circuit parameters must be unique." )
129
+ if check :
130
+ # this is added as useful user-warning, since sometimes ``ParameterExpression``s are
131
+ # passed around instead of ``Parameter``s
132
+ if not isinstance (parameter , Parameter ):
133
+ raise ValueError (f"parameter must be of type Parameter, not { type (parameter )} ." )
134
+
135
+ if parameter not in circuit .parameters :
136
+ raise ValueError (f"The parameter { parameter } is not in this circuit." )
137
+
138
+ # check uniqueness
139
+ seen_parameters : set [Parameter ] = set ()
140
+ for instruction in circuit .data :
141
+ # get parameters in the current operation
142
+ new_parameters = set ()
143
+ for p in instruction .operation .params :
144
+ if isinstance (p , ParameterExpression ):
145
+ new_parameters .update (p .parameters )
146
+
147
+ if duplicates := seen_parameters .intersection (new_parameters ):
148
+ raise NotImplementedError (
149
+ "Product rule is not supported, circuit parameters must be unique, but "
150
+ f"{ duplicates } are duplicated."
151
+ )
152
+
153
+ seen_parameters .update (new_parameters )
137
154
138
155
summands , op_context = [], []
139
156
for i , op in enumerate (circuit .data ):
@@ -151,7 +168,14 @@ def derive_circuit(
151
168
c = complex (1 )
152
169
for i , term in enumerate (product_rule_term ):
153
170
c *= term [0 ]
154
- summand_circuit .data .append ([term [1 ], * op_context [i ]])
171
+ # Qiskit changed the format of the stored value. The newer Qiskit has this internal
172
+ # method to go from the older (legacy) format to new. This logic may need updating
173
+ # at some point if this internal method goes away.
174
+ if hasattr (summand_circuit .data , "_resolve_legacy_value" ):
175
+ value = summand_circuit .data ._resolve_legacy_value (term [1 ], * op_context [i ])
176
+ summand_circuit .data .append (value )
177
+ else :
178
+ summand_circuit .data .append ([term [1 ], * op_context [i ]])
155
179
gradient += [(c , summand_circuit .copy ())]
156
180
157
181
return gradient
0 commit comments