1616import itertools
1717from collections .abc import Sequence
1818
19- from qiskit .circuit import QuantumCircuit , Parameter , Gate
19+ from qiskit .circuit import QuantumCircuit , Parameter , Gate , ParameterExpression
2020from qiskit .circuit .library import RXGate , RYGate , RZGate , CRXGate , CRYGate , CRZGate
2121
2222
@@ -90,7 +90,7 @@ def gradient_lookup(gate: Gate) -> list[tuple[complex, QuantumCircuit]]:
9090
9191
9292def derive_circuit (
93- circuit : QuantumCircuit , parameter : Parameter
93+ circuit : QuantumCircuit , parameter : Parameter , check : bool = True
9494) -> Sequence [tuple [complex , QuantumCircuit ]]:
9595 """Return the analytic gradient expression of the input circuit wrt. a single parameter.
9696
@@ -114,6 +114,8 @@ def derive_circuit(
114114 Args:
115115 circuit: The quantum circuit to derive.
116116 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.
117119
118120 Returns:
119121 A list of ``(coeff, gradient_circuit)`` tuples.
@@ -124,16 +126,31 @@ def derive_circuit(
124126 NotImplementedError: If a non-unique parameter is added, as the product rule is not yet
125127 supported in this function.
126128 """
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 )
137154
138155 summands , op_context = [], []
139156 for i , op in enumerate (circuit .data ):
@@ -151,7 +168,14 @@ def derive_circuit(
151168 c = complex (1 )
152169 for i , term in enumerate (product_rule_term ):
153170 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 ]])
155179 gradient += [(c , summand_circuit .copy ())]
156180
157181 return gradient
0 commit comments