Dear Pennylane Team,
I am currently trying to implement a quantum circuit composed of various Operations and Operations of Operations. However, I face trouble as soon as I want to include the command qml.ctrl() in any of my Operator-definitions.
Let me explain the issue with a minimal example:
If I define the Operator like this:
class controlled_H_gate(Operation):
num_wires = AnyWires
grad_method = "A"
def __init__(self,wires, id=None):
super().__init__(wires=wires, id=id) # calls the init of the operator class
@staticmethod
def compute_decomposition(wires):
n_wires_1 = wires[0:1]
n_aux_1 = wires[1:2]
return qml.ctrl(qml.H, control = n_aux_1, control_values=[0])(wires=n_wires_1[0:1])
and want to call it within a quantum circuit
n = 2
dev_test = qml.device("default.qubit", wires=n)
@qml.qnode(dev_test)
def test_circuit():
w = dev_test.wires
controlled_H_gate(wires=w)
return qml.state()
it will run without an error but the operator is apperently not applied as the output-state and a verification with
fig, ax = qml.draw_mpl(test_circuit, level=“device”)()
plt.show()
shows.
If I modify my circuit, by returning a list (as I would usually do), i.e.:
class controlled_H_gate(Operation):
num_wires = AnyWires
grad_method = "A"
def __init__(self,wires, id=None):
super().__init__(wires=wires, id=id) # calls the init of the operator class
@staticmethod
def compute_decomposition(wires):
n_wires_1 = wires[0:1]
n_aux_1 = wires[1:2]
return [qml.ctrl(qml.H, control = n_aux_1, control_values=[0])(wires=n_wires_1[0:1])]
and then try ot run the quantum circuit I get the error message:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[110], line 10
7 controlled_H_gate(wires=w)
8 return qml.state()
---> 10 print( test_circuit())
11 fig, ax = qml.draw_mpl(test_circuit, level="device")() #, level="device"
12 plt.show()
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\workflow\qnode.py:882, in QNode.__call__(self, *args, **kwargs)
879 from ._capture_qnode import capture_qnode # pylint: disable=import-outside-toplevel
881 return capture_qnode(self, *args, **kwargs)
--> 882 return self._impl_call(*args, **kwargs)
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\workflow\qnode.py:855, in QNode._impl_call(self, *args, **kwargs)
852 # Calculate the classical jacobians if necessary
853 self._transform_program.set_classical_component(self, args, kwargs)
--> 855 res = qml.execute(
856 (tape,),
857 device=self.device,
858 diff_method=self.diff_method,
859 interface=self.interface,
860 transform_program=self._transform_program,
861 gradient_kwargs=self.gradient_kwargs,
862 **self.execute_kwargs,
863 )
864 res = res[0]
866 # convert result to the interface in case the qfunc has no parameters
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\workflow\execution.py:239, in execute(tapes, device, diff_method, interface, transform_program, grad_on_execution, cache, cachesize, max_diff, device_vjp, postselect_mode, mcm_method, gradient_kwargs, mcm_config, config, inner_transform)
234 transform_program, inner_transform = _setup_transform_program(
235 transform_program, device, config, cache, cachesize
236 )
238 #### Executing the configured setup #####
--> 239 tapes, post_processing = transform_program(tapes)
241 if transform_program.is_informative:
242 return post_processing(tapes)
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\transforms\core\transform_program.py:643, in TransformProgram.__call__(self, *args, **kwargs)
641 if type(args[0]).__name__ == "Jaxpr":
642 return self.__call_jaxpr(*args, **kwargs)
--> 643 return self.__call_tapes(*args, **kwargs)
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\transforms\core\transform_program.py:580, in TransformProgram.__call_tapes(self, tapes)
578 if argnums is not None:
579 tape.trainable_params = argnums[j]
--> 580 new_tapes, fn = transform(tape, *targs, **tkwargs)
581 execution_tapes.extend(new_tapes)
583 fns.append(fn)
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\devices\preprocess.py:401, in decompose(tape, stopping_condition, stopping_condition_shots, skip_initial_state_prep, decomposer, name, error)
398 return (tape,), null_postprocessing
399 try:
--> 401 new_ops = [
402 final_op
403 for op in tape.operations[len(prep_op) :]
404 for final_op in _operator_decomposition_gen(
405 op,
406 stopping_condition,
407 decomposer=decomposer,
408 name=name,
409 error=error,
410 )
411 ]
412 except RecursionError as e:
413 raise error(
414 "Reached recursion limit trying to decompose operations. "
415 "Operator decomposition may have entered an infinite loop."
416 ) from e
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\devices\preprocess.py:401, in <listcomp>(.0)
398 return (tape,), null_postprocessing
399 try:
--> 401 new_ops = [
402 final_op
403 for op in tape.operations[len(prep_op) :]
404 for final_op in _operator_decomposition_gen(
405 op,
406 stopping_condition,
407 decomposer=decomposer,
408 name=name,
409 error=error,
410 )
411 ]
412 except RecursionError as e:
413 raise error(
414 "Reached recursion limit trying to decompose operations. "
415 "Operator decomposition may have entered an infinite loop."
416 ) from e
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\devices\preprocess.py:72, in _operator_decomposition_gen(op, acceptance_function, decomposer, max_expansion, current_depth, name, error)
67 raise error(
68 f"Operator {op} not supported with {name} and does not provide a decomposition."
69 ) from e
71 for sub_op in decomp:
---> 72 yield from _operator_decomposition_gen(
73 sub_op,
74 acceptance_function,
75 decomposer=decomposer,
76 max_expansion=max_expansion,
77 current_depth=current_depth,
78 name=name,
79 error=error,
80 )
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\devices\preprocess.py:60, in _operator_decomposition_gen(op, acceptance_function, decomposer, max_expansion, current_depth, name, error)
58 if max_expansion is not None and max_expansion <= current_depth:
59 max_depth_reached = True
---> 60 if acceptance_function(op) or max_depth_reached:
61 yield op
62 else:
File ~\Desktop\TrainingProject\jax-env\lib\site-packages\pennylane\devices\default_qubit.py:61, in stopping_condition(op)
59 def stopping_condition(op: qml.operation.Operator) -> bool:
60 """Specify whether or not an Operator object is supported by the device."""
---> 61 if op.name == "QFT" and len(op.wires) >= 6:
62 return False
63 if op.name == "GroverOperator" and len(op.wires) >= 13:
AttributeError: 'list' object has no attribute 'name'
If I instead remove the ctrl, everything works as expected.
I am well aware, that for this simple example I do not need to include the control in the operator itself, but in my more complicated-quite-nested circuit it would be very helpful.
Is there a way how to include ctrl in an Operation-definition correctly?
Many thanks and best regards,
Pia