Compute_decomposition() got multiple values for argument 'wires' and using qml.ctrl with functions

I’m trying to make the layers in my parameterized circuit controlled by others qubits so I’m making my own operator for it, only to find that the compute_decomposition() method throwing me errors and I don’t know how to fix it.

# Put code here
# Quantum simulator
dev = qml.device("lightning.qubit", wires=list(range(num_qubits+class_qubits+6)))
# Enable CUDA device if available
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")


class Layer(qml.operation.Operation):
    
    num_wires = qml.operation.AnyWires
    par_domain = 'R'
    grad_method = 'A'
    # control_wires = [4,5]
    def __init__(self, thetas, wires, id= None):
        # print(thetas)
        print(qml.wires.Wires(wires))
        super().__init__(thetas[0], thetas[1], thetas[2], thetas[3], wires = qml.wires.Wires(wires), id = id)

    
    @property
    def num_params(self):
        return 4
    

    @staticmethod
    def compute_decomposition(thetas, wires):
        return [qml.RX(thetas[0], wires[0]),
        qml.RZ(thetas[1], wires[1]),
        qml.RX(thetas[2], wires[2]),
        qml.CNOT(wires[:2]),
        qml.MultiRZ(thetas[3], [wires[0], wires[1], wires[2]]),
        qml.CNOT([wires[1], wires[2]]),
        qml.CY([wires[0],wires[2]])]




@qml.qnode(dev, diff_method="parameter-shift")
def quantum_circuit(noise, params):
    const = 4
    measurements = []
    # for i in range((num_qubits+num_ancilla)):
    qml.Rot(noise[0], noise[1], noise[2], wires=0)
    qml.Rot(noise[3], noise[4], noise[5], wires=1)
    qml.Rot(noise[6], noise[7], noise[8], wires=2)
    qml.Barrier()
    # Repeated layer
    qml.ctrl(Layer(params[0:4], wires = [0,1,2]), control = [4,5], control_values = (0,0))
    qml.ctrl(Layer(params[4:8], wires = [0,1,2]), control = [4,5], control_values = (0,1))
    qml.ctrl(Layer(params[8:12], wires = [0,1,2]), control = [4,5], control_values = (1,0))
    qml.ctrl(Layer(params[12:16], wires =[0,1,2]), control = [4,5], control_values = (1,1))
    qml.Barrier()
    return qml.probs(wires = list(range(num_qubits)))








I'm not really sure how the above was any different than the examples in the documents, but when I run that example there is no issue. Maybe I'm missing something. I appreciate the help!

Hi @sv2954 , welcome to the Forum! Could you please post:

  • The output of qml.about()
  • Your full error message. Even if it’s long it’s helpful to see the origin of the problem. Thanks!
  • What you’re using for noise and params (so that I can try to run your circuit)

qml.about()

# Put code here

Name: PennyLane
Version: 0.36.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /opt/homebrew/lib/python3.10/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane_Lightning

Platform info:           macOS-13.0-arm64-arm-64bit
Python version:          3.10.9
Numpy version:           1.24.1
Scipy version:           1.10.0
Installed devices:
- default.clifford (PennyLane-0.36.0)
- default.gaussian (PennyLane-0.36.0)
- default.mixed (PennyLane-0.36.0)
- default.qubit (PennyLane-0.36.0)
- default.qubit.autograd (PennyLane-0.36.0)
- default.qubit.jax (PennyLane-0.36.0)
- default.qubit.legacy (PennyLane-0.36.0)
- default.qubit.tf (PennyLane-0.36.0)
- default.qubit.torch (PennyLane-0.36.0)
- default.qutrit (PennyLane-0.36.0)
- default.qutrit.mixed (PennyLane-0.36.0)
- null.qubit (PennyLane-0.36.0)
- lightning.qubit (PennyLane_Lightning-0.36.0)


Error Message:
line 49
     46 noise = [0,0,0,0,0,0,0,0,0,0,0,0]
  47 weights = [0,1,0,1,2,3,1,2,1,2,0,1,0,1,2,3,1,2,1,2,4,4,4,4,4,4, 0,1,0,1,2,3,1,2,1,2,0,1,0,1,2,3,1,2,1,2,4,4,4,4,4,4]
---> 49 quantum_circuit(noise,weights)
     50 # qml.draw_mpl(mini_circuit)(noise, weights)
     51 # qml.draw_mpl(mini_circuit2)(weights[:3*4])
     52 # qml.draw_mpl(mini_circuit3)(weights[:3*4])
     53 # plt.show()

File /opt/homebrew/lib/python3.10/site-packages/pennylane/workflow/qnode.py:1098, in QNode.__call__(self, *args, **kwargs)
   1095 self._update_gradient_fn(shots=override_shots, tape=self._tape)
   1097 try:
-> 1098     res = self._execution_component(args, kwargs, override_shots=override_shots)
   1099 finally:
   1100     if old_interface == "auto":

File /opt/homebrew/lib/python3.10/site-packages/pennylane/workflow/qnode.py:1052, in QNode._execution_component(self, args, kwargs, override_shots)
   1049 full_transform_program.prune_dynamic_transform()
   1051 # pylint: disable=unexpected-keyword-arg
-> 1052 res = qml.execute(
   1053     (self._tape,),
   1054     device=self.device,
   1055     gradient_fn=self.gradient_fn,
   1056     interface=self.interface,
   1057     transform_program=full_transform_program,
   1058     config=config,
   1059     gradient_kwargs=self.gradient_kwargs,
   1060     override_shots=override_shots,
   1061     **self.execute_kwargs,
   1062 )
   1063 res = res[0]
   1065 # convert result to the interface in case the qfunc has no parameters

File /opt/homebrew/lib/python3.10/site-packages/pennylane/workflow/execution.py:600, in execute(tapes, device, gradient_fn, interface, transform_program, config, grad_on_execution, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform, device_vjp)
    595     if not device_batch_transform:
    596         warnings.warn(
    597             "device batch transforms cannot be turned off with the new device interface.",
    598             UserWarning,
    599         )
--> 600     tapes, post_processing = transform_program(tapes)
    601 else:
    602     # TODO: Remove once old device are removed
    603     tapes, program_post_processing = transform_program(tapes)

File /opt/homebrew/lib/python3.10/site-packages/pennylane/transforms/core/transform_program.py:509, in TransformProgram.__call__(self, tapes)
    507 if self._argnums is not None and self._argnums[i] is not None:
    508     tape.trainable_params = self._argnums[i][j]
--> 509 new_tapes, fn = transform(tape, *targs, **tkwargs)
    510 execution_tapes.extend(new_tapes)
    512 fns.append(fn)

File /opt/homebrew/lib/python3.10/site-packages/pennylane/devices/preprocess.py:340, in decompose(tape, stopping_condition, stopping_condition_shots, skip_initial_state_prep, decomposer, max_expansion, name)
    334 try:
    335     # don't decompose initial operations if its StatePrepBase
    336     prep_op = (
    337         [tape[0]] if isinstance(tape[0], StatePrepBase) and skip_initial_state_prep else []
    338     )
--> 340     new_ops = [
    341         final_op
    342         for op in tape.operations[bool(prep_op) :]
    343         for final_op in _operator_decomposition_gen(
    344             op,
    345             stopping_condition,
    346             decomposer=decomposer,
    347             max_expansion=max_expansion,
    348             name=name,
    349         )
    350     ]
    351 except RecursionError as e:
    352     raise DeviceError(
    353         "Reached recursion limit trying to decompose operations. "
    354         "Operator decomposition may have entered an infinite loop."
    355     ) from e

File /opt/homebrew/lib/python3.10/site-packages/pennylane/devices/preprocess.py:340, in <listcomp>(.0)
    334 try:
    335     # don't decompose initial operations if its StatePrepBase
    336     prep_op = (
    337         [tape[0]] if isinstance(tape[0], StatePrepBase) and skip_initial_state_prep else []
    338     )
--> 340     new_ops = [
    341         final_op
    342         for op in tape.operations[bool(prep_op) :]
    343         for final_op in _operator_decomposition_gen(
    344             op,
    345             stopping_condition,
    346             decomposer=decomposer,
    347             max_expansion=max_expansion,
    348             name=name,
    349         )
    350     ]
    351 except RecursionError as e:
    352     raise DeviceError(
    353         "Reached recursion limit trying to decompose operations. "
    354         "Operator decomposition may have entered an infinite loop."
    355     ) from e

File /opt/homebrew/lib/python3.10/site-packages/pennylane/devices/preprocess.py:62, in _operator_decomposition_gen(op, acceptance_function, decomposer, max_expansion, current_depth, name)
     60 else:
     61     try:
---> 62         decomp = decomposer(op)
     63         current_depth += 1
     64     except qml.operation.DecompositionUndefinedError as e:

File /opt/homebrew/lib/python3.10/site-packages/pennylane/devices/preprocess.py:328, in decompose.<locals>.decomposer(op)
    327 def decomposer(op):
--> 328     return op.decomposition()

File /opt/homebrew/lib/python3.10/site-packages/pennylane/operation.py:1285, in Operator.decomposition(self)
   1273 def decomposition(self) -> List["Operator"]:
   1274     r"""Representation of the operator as a product of other operators.
   1275 
   1276     .. math:: O = O_1 O_2 \dots O_n
   (...)
   1283         list[Operator]: decomposition of the operator
   1284     """
-> 1285     return self.compute_decomposition(
   1286         *self.parameters, wires=self.wires, **self.hyperparameters
   1287     )

TypeError: Layer.compute_decomposition() got multiple values for argument 'wires'

Thanks for adding this info @sv2954 .

It looks like the workflow isn’t designed to use a layer as an operator. I understand that you want to apply the controlled operation of a layer but I think there may be a better way to accomplish this.

Let me think of a solution and get back to you next week.

Hi @sv2954 ,

It looks like you were overcomplicating things a bit.
The issue was in how you were using qml.ctrl. Notice that you can apply the control to a function but then you need to pass the function arguments outside of the control. In the example below the function takes a wire as the argument. However to find the controlled version of the function you only pass the name of the function. The arguments go outside.

def func(arg):
        qml.RX(theta, wires=arg)

qml.ctrl(func, control=[control_wires])(function_argument)

Here’s and example working for what you were looking to do.

import pennylane as qml

dev = qml.device("lightning.qubit", wires=8)

def Layer(thetas, wires):
  qml.RX(thetas[0], wires[0])
  qml.RZ(thetas[1], wires[1])
  qml.RX(thetas[2], wires[2])
  qml.CNOT(wires[:2])
  qml.MultiRZ(thetas[3], [wires[0], wires[1], wires[2]])
  qml.CNOT([wires[1], wires[2]])
  qml.CY([wires[0],wires[2]])

@qml.qnode(dev, diff_method="parameter-shift")
def quantum_circuit(noise, params):
    const = 4
    measurements = []
    qml.Rot(noise[0], noise[1], noise[2], wires=0)
    qml.Rot(noise[3], noise[4], noise[5], wires=1)
    qml.Rot(noise[6], noise[7], noise[8], wires=2)
    qml.Barrier()
    # Repeated layer
    qml.ctrl(Layer, control = [4,5], control_values = (0,0))(params[0:4], wires = [0,1,2])
    qml.ctrl(Layer, control = [4,5], control_values = (0,1))(params[4:8], wires = [0,1,2])
    qml.ctrl(Layer, control = [4,5], control_values = (1,0))(params[8:12], wires = [0,1,2])
    qml.ctrl(Layer, control = [4,5], control_values = (1,1))(params[12:16], wires =[0,1,2])
    qml.Barrier()
    return qml.probs()

noise = [0,0,0,0,0,0,0,0,0,0,0,0]
weights = [0,1,0,1,2,3,1,2,1,2,0,1,0,1,2,3,1,2,1,2,4,4,4,4,4,4, 0,1,0,1,2,3,1,2,1,2,0,1,0,1,2,3,1,2,1,2,4,4,4,4,4,4]

qml.draw_mpl(quantum_circuit)(noise, weights)
quantum_circuit(noise, weights)

I hope this helps you!