How can I use qml.select to make an overall selection of ansatz functions?

given an ansatz:

def ansatz(case, para):
    if case == 1:
        qml.RX(para[0], wires=[0])
        qml.RY(para[1], wires=[1])
        qml.CNOT(wires=[0,1])
    elif case == 2:
        qml.RY(para[0], wires=[0])
        qml.RX(para[1], wires=[1])
        qml.CNOT(wires=[0,1])
    elif case == 3:
        qml.RY(para[0], wires=[0])
        qml.RY(para[1], wires=[1])
        qml.CNOT(wires=[0,1])
    elif case == 4:
        qml.RX(para[0], wires=[0])
        qml.RX(para[1], wires=[1])
        qml.CNOT(wires=[0,1])


wire = 4
dev = qml.device("default.qubit", wires=wire)
@qml.qnode(dev)
def my_model():
    ops = [ansatz(1, [1,2]), ansatz(2, [1,2]), ansatz(3, [1,2]), ansatz(4, [1,2])]
    qml.Select(ops, control=[2,3])
    return qml.expval(qml.PauliZ(1))
print(qml.draw(my_model, expansion_strategy='device')())

the result is:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_24286/534970757.py in <module>
     25     qml.Select(ops, control=[2,3])
     26     return qml.expval(qml.PauliZ(1))
---> 27 print(qml.draw(my_model, expansion_strategy='device')())

/opt/conda/lib/python3.9/site-packages/pennylane/drawer/draw.py in wrapper(*args, **kwargs)
    245         try:
    246             qnode.expansion_strategy = expansion_strategy or original_expansion_strategy
--> 247             tapes = qnode.construct(args, kwargs)
    248         finally:
    249             qnode.expansion_strategy = original_expansion_strategy

/opt/conda/lib/python3.9/site-packages/pennylane/qnode.py in construct(self, args, kwargs)
    870             self.interface = qml.math.get_interface(*args, *list(kwargs.values()))
    871 
--> 872         self._tape = make_qscript(self.func, shots)(*args, **kwargs)
    873         self._qfunc_output = self.tape._qfunc_output
    874 

/opt/conda/lib/python3.9/site-packages/pennylane/tape/qscript.py in wrapper(*args, **kwargs)
   1529     def wrapper(*args, **kwargs):
   1530         with AnnotatedQueue() as q:
-> 1531             result = fn(*args, **kwargs)
   1532 
   1533         qscript = QuantumScript.from_queue(q, shots)

/tmp/ipykernel_24286/534970757.py in my_model()
     23 def my_model():
     24     ops = [ansatz(1, [1,2]), ansatz(2, [1,2]), ansatz(3, [1,2]), ansatz(4, [1,2])]
---> 25     qml.Select(ops, control=[2,3])
     26     return qml.expval(qml.PauliZ(1))
     27 print(qml.draw(my_model, expansion_strategy='device')())

/opt/conda/lib/python3.9/site-packages/pennylane/templates/subroutines/select.py in __init__(self, ops, control, id)
     84             )
     85 
---> 86         if any(
     87             control_wire in qml.wires.Wires.all_wires([op.wires for op in ops])
     88             for control_wire in control

/opt/conda/lib/python3.9/site-packages/pennylane/templates/subroutines/select.py in <genexpr>(.0)
     85 
     86         if any(
---> 87             control_wire in qml.wires.Wires.all_wires([op.wires for op in ops])
     88             for control_wire in control
     89         ):

/opt/conda/lib/python3.9/site-packages/pennylane/templates/subroutines/select.py in <listcomp>(.0)
     85 
     86         if any(
---> 87             control_wire in qml.wires.Wires.all_wires([op.wires for op in ops])
     88             for control_wire in control
     89         ):

AttributeError: 'NoneType' object has no attribute 'wires'

​
@Maria_Schuld @Guillermo_Alonso @CatalinaAlbornoz

Hey @RX1 , thanks for asking!
(And there’s no need to tag a lot of different people in a post — somebody will be able to answer you without getting direct notifications. :slight_smile:)

When you’re using a circuit ansatz that applies multiple gates, what you actually have to do is properly define it as a new operator, because qml.Select expects to be called with an operator. :slight_smile: You can find a nice guide for how to do that in our documentation: Adding new operators — PennyLane 0.32.0 documentation
I hope this helps you set up exactly what you need! Let us know if anything doesn’t work as expected.

Sorry, that’s not the answer I was looking for. My code means how to be controlled for an anatz function, tried qml.select and controlled functions but both failed. I hope you can help me by giving me an example. Let me know how to control a whole ansatz function.

from pennylane.ops.op_math import *
def ansatz(case, para):
    if case == 1:
        qml.RX(para[0], wires=0)
        qml.RY(para[1], wires=1)
        qml.CNOT(wires=[0,1])
    elif case == 2:
        qml.RY(para[0], wires=0)
        qml.RX(para[1], wires=1)
        qml.CNOT(wires=[0,1])
    elif case == 3:
        qml.RY(para[0], wires=0)
        qml.RY(para[1], wires=1)
        qml.CNOT(wires=[0,1])
    elif case == 4:
        qml.RX(para[0], wires=0)
        qml.RX(para[1], wires=1)
        qml.CNOT(wires=[0,1])
    

wire = 4
dev = qml.device("default.qubit", wires=wire)
@qml.qnode(dev)
def my_model():
    #ops = [ansatz(1, [1,2]), ansatz(2, [1,2]), ansatz(3, [1,2]), ansatz(4, [1,2])]
    #qml.Select(ops, control=[2,3])
    Controlled(ansatz(1, [1,2]), control_wires=[2, 3], control_values=[True, True])
    return qml.expval(qml.PauliZ(1))
print(qml.draw(my_model)())

result is:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_24286/189351434.py in <module>
     27     Controlled(ansatz(1, [1,2]), control_wires=[2, 3], control_values=[True, True])
     28     return qml.expval(qml.PauliZ(1))
---> 29 print(qml.draw(my_model)())

/opt/conda/lib/python3.9/site-packages/pennylane/drawer/draw.py in wrapper(*args, **kwargs)
    245         try:
    246             qnode.expansion_strategy = expansion_strategy or original_expansion_strategy
--> 247             tapes = qnode.construct(args, kwargs)
    248         finally:
    249             qnode.expansion_strategy = original_expansion_strategy

/opt/conda/lib/python3.9/site-packages/pennylane/qnode.py in construct(self, args, kwargs)
    870             self.interface = qml.math.get_interface(*args, *list(kwargs.values()))
    871 
--> 872         self._tape = make_qscript(self.func, shots)(*args, **kwargs)
    873         self._qfunc_output = self.tape._qfunc_output
    874 

/opt/conda/lib/python3.9/site-packages/pennylane/tape/qscript.py in wrapper(*args, **kwargs)
   1529     def wrapper(*args, **kwargs):
   1530         with AnnotatedQueue() as q:
-> 1531             result = fn(*args, **kwargs)
   1532 
   1533         qscript = QuantumScript.from_queue(q, shots)

/tmp/ipykernel_24286/189351434.py in my_model()
     25     #ops = [ansatz(1, [1,2]), ansatz(2, [1,2]), ansatz(3, [1,2]), ansatz(4, [1,2])]
     26     #qml.Select(ops, control=[2,3])
---> 27     Controlled(ansatz(1, [1,2]), control_wires=[2, 3], control_values=[True, True])
     28     return qml.expval(qml.PauliZ(1))
     29 print(qml.draw(my_model)())

/opt/conda/lib/python3.9/site-packages/pennylane/ops/op_math/controlled.py in __init__(self, base, control_wires, control_values, work_wires, id)
    282                 raise ValueError("control_values should be the same length as control_wires")
    283 
--> 284         if len(Wires.shared_wires([base.wires, control_wires])) != 0:
    285             raise ValueError("The control wires must be different from the base operation wires.")
    286 

AttributeError: 'NoneType' object has no attribute 'wires'

​

Hey @RX1 , the syntax you’re using doesn’t work, and that’s your problem. :slight_smile:
For qml.Select you need an operator (see here: qml.Select — PennyLane 0.32.0 documentation).

The ansatz definition you have first of all doesn’t return anything explicitly, and isn’t an operator. In extension, ops also isn’t an operator, which means qml.Select can’t be run properly. If you’re defining it outside of the circuit, you would need to make your ansatz function into a new operator in PennyLane (see here for instructions, templates and examples: Adding new operators — PennyLane 0.32.0 documentation).

The code you’ve attached here is using another function, Controlled — but this function (see here: qml.ops.op_math.Controlled — PennyLane 0.32.0 documentation) also needs to be called with an operator or operators. :sweat_smile: This means you need to define your ansatz as an operator.
Currently, if you run ansatz(1, [1,2]), for example, it returns None — it’s not an operator; so if you want to use functions that get called with operators, you should define your ansatz as an operator.

Does this make sense to you? Depending on what exactly you want your ansatz to do, you can go ahead and define it as such. :smiley: So, once again, you need to define ansatz as an operator before you can use it as an operator. :slight_smile:

Here I am including the first example from the instructions on adding new operators:

import pennylane as qml


class FlipAndRotate(qml.operation.Operation):

    # Define how many wires the operator acts on in total.
    # In our case this may be one or two, which is why we
    # use the AnyWires Enumeration to indicate a variable number.
    num_wires = qml.operation.AnyWires

    # This attribute tells PennyLane what differentiation method to use. Here
    # we request parameter-shift (or "analytic") differentiation.
    grad_method = "A"

    def __init__(self, angle, wire_rot, wire_flip=None, do_flip=False, id=None):

        # checking the inputs --------------

        if do_flip and wire_flip is None:
            raise ValueError("Expected a wire to flip; got None.")

        # note: we use the framework-agnostic math library since
        # trainable inputs could be tensors of different types
        shape = qml.math.shape(angle)
        if len(shape) > 1:
            raise ValueError(f"Expected a scalar angle; got angle of shape {shape}.")

        #------------------------------------

        # do_flip is not trainable but influences the action of the operator,
        # which is why we define it to be a hyperparameter
        self._hyperparameters = {
            "do_flip": do_flip
        }

        # we extract all wires that the operator acts on,
        # relying on the Wire class arithmetic
        all_wires = qml.wires.Wires(wire_rot) + qml.wires.Wires(wire_flip)

        # The parent class expects all trainable parameters to be fed as positional
        # arguments, and all wires acted on fed as a keyword argument.
        # The id keyword argument allows users to give their instance a custom name.
        super().__init__(angle, wires=all_wires, id=id)

    @property
    def num_params(self):
        # if it is known before creation, define the number of parameters to expect here,
        # which makes sure an error is raised if the wrong number was passed
        return 1

    @staticmethod
    def compute_decomposition(angle, wires, do_flip):  # pylint: disable=arguments-differ
        # Overwriting this method defines the decomposition of the new gate, as it is
        # called by Operator.decomposition().
        # The general signature of this function is (*parameters, wires, **hyperparameters).
        op_list = []
        if do_flip:
            op_list.append(qml.PauliX(wires=wires[1]))
        op_list.append(qml.RX(angle, wires=wires[0]))
        return op_list

    def adjoint(self):
        # the adjoint operator of this gate simply negates the angle
        return FlipAndRotate(-self.parameters[0], self.wires[0], self.wires[1], do_flip=self.hyperparameters["do_flip"])
1 Like

By the way, @RX1 , if you just want to take a bunch of existing operators and make another operator out of them, you could try using qml.ops.op_math.CompositeOp — PennyLane 0.32.0 documentation :slight_smile:
But if you’re trying to build a more specific project, you’re probably fine just defining a new operator. It all depends on what exactly you’re hoping to have it do. :grin:
Good luck and have fun!

Hi! Indeed, what my colleague says is very important :slight_smile: You are not returning an operator and qml.Select needs it. A trick to be able to apply what you want is to put the decorator @qml.prod on top of your ansatz. I hope it helps!

@qml.prod
def ansatz(case, para):
1 Like

As a Pennylane user of 2 years, I apologize for the non-stop harassment, but wish Pennylane was as easy to use for controlled operations as QISKIT, which would be more conducive to my own research.

This kind of approach is very simple to realize the problem of controlled ansatz functions, but the problem of training latency also occurs. I am interested in understanding the controlled ansatz decomposition into a collection of elementary quantum gates. If I decompose the controlled ansatz function into elementary quantum gates beforehand, will the training speed be improved?

Could you explain what do you mean by “controlled ansatz decomposition”, the one with if/else? When you are calling to the ansatz you add the gates to the circuit, not creating a larger block. Therefore the difference in speed should not be noticeable.

1 Like