Adding custom gate to Pennylane

I am trying to implement a custom gate. The gate have a matrix like this:

def matrix(theta):

a = np.cos(theta)
b = np.sin(theta)
A = [[1, 0, 0, 0],
[0, a, b, 0],
[0, -b, a, 0],
[0, 0, 0, 1]]

return A

i checked your link about how to add custom gates and templates to Pennylane here: How to add custom gates and templates to PennyLane
Here is my implementation:
import pennylane as qml
from pennylane import numpy as np

from pennylane.operation import Operation, AnyWires

class MyGate(Operation):
num_params = 1 # The template takes one parameters.
num_wires = AnyWires # The template works for any number of wires.
par_domain = “R”

def expand(self):
    
    with qml.tape.QuantumTape() as tape:
        # Iterate over all qubit pairs
        for i in range(len(self.wires)):
            qml.Hadamard(wires=[self.wires[i]])
            qml.Hadamard(wires = [self.wires[i + 1]])
            qml.CZ(wires=[self.wires[i], self.wires[i+1]])
            qml.RY(self.data[0]/2, self.wires[i])
            qml.RY(-self.data[0]/2, self.wires[i+1])
            qml.CZ(wires=[self.wires[i], self.wires[i+1]])
            qml.Hadamard(wires=[self.wires[i]])
            qml.Hadamard(wires = [self.wires[i + 1]])

    return tape   

above, i decomposed the gate corresponding to the matrix A.

dev = qml.device(‘default.qubit’, wires=4)
dev.operations.add(“MyGate”)

and then

@qml.qnode(dev)
def circuit(theta):

MyGate(theta[0], wires=[0, 2])
MyGate(theta[1], wires=[0, 1])
MyGate(theta[2], wires=[2, 3])


return qml.state()

theta = [0.42053434, 2.03444394, 3.14159265]

print(circuit(theta))

. I encountered this error:

MatrixUndefinedError Traceback (most recent call last)
/tmp/ipykernel_58390/1607498551.py in
1 theta = [0.42053434, 2.03444394, 3.14159265]
2
----> 3 print(circuit(theta))

~/anaconda3/lib/python3.7/site-packages/pennylane/qnode.py in call(self, *args, **kwargs)
666 gradient_kwargs=self.gradient_kwargs,
667 override_shots=override_shots,
→ 668 **self.execute_kwargs,
669 )
670

~/anaconda3/lib/python3.7/site-packages/pennylane/interfaces/execution.py in execute(tapes, device, gradient_fn, interface, mode, gradient_kwargs, cache, cachesize, max_diff, override_shots, expand_fn, max_expansion, device_batch_transform)
370 qml.interfaces.cache_execute(
371 batch_execute, cache, return_tuple=False, expand_fn=expand_fn
→ 372 )(tapes)
373 )
374

~/anaconda3/lib/python3.7/site-packages/pennylane/interfaces/execution.py in wrapper(tapes, **kwargs)
195 else:
196 # execute all unique tapes that do not exist in the cache
→ 197 res = fn(execution_tapes.values(), **kwargs)
198
199 final_res =

~/anaconda3/lib/python3.7/site-packages/pennylane/interfaces/execution.py in fn(tapes, **kwargs)
120 def fn(tapes, **kwargs): # pylint: disable=function-redefined
121 tapes = [expand_fn(tape) for tape in tapes]
→ 122 return original_fn(tapes, **kwargs)
123
124 @wraps(fn)

~/anaconda3/lib/python3.7/contextlib.py in inner(*args, **kwds)
72 def inner(*args, **kwds):
73 with self._recreate_cm():
—> 74 return func(*args, **kwds)
75 return inner
76

~/anaconda3/lib/python3.7/site-packages/pennylane/_qubit_device.py in batch_execute(self, circuits)
584
585 # TODO: Insert control on value here
→ 586 res = self.execute(circuit)
587 results.append(res)
588

~/anaconda3/lib/python3.7/site-packages/pennylane/_qubit_device.py in execute(self, circuit, **kwargs)
313
314 # apply all circuit operations
→ 315 self.apply(circuit.operations, rotations=circuit.diagonalizing_gates, **kwargs)
316
317 # generate computational basis samples

~/anaconda3/lib/python3.7/site-packages/pennylane/devices/default_qubit.py in apply(self, operations, rotations, **kwargs)
255 self._debugger.snapshots[len(self._debugger.snapshots)] = state_vector
256 else:
→ 257 self._state = self._apply_operation(self._state, operation)
258
259 # store the pre-rotated state

~/anaconda3/lib/python3.7/site-packages/pennylane/devices/default_qubit.py in _apply_operation(self, state, operation)
283 return self._apply_ops[operation.base_name](state, axes, inverse=operation.inverse)
284
→ 285 matrix = self._asarray(self._get_unitary_matrix(operation), dtype=self.C_DTYPE)
286
287 if operation in diagonal_in_z_basis:

~/anaconda3/lib/python3.7/site-packages/pennylane/devices/default_qubit.py in _get_unitary_matrix(self, unitary)
617 return unitary.eigvals()
618
→ 619 return unitary.matrix()
620
621 @classmethod

~/anaconda3/lib/python3.7/site-packages/pennylane/operation.py in matrix(self, wire_order)
1389
1390 def matrix(self, wire_order=None):
→ 1391 canonical_matrix = self.compute_matrix(*self.parameters, **self.hyperparameters)
1392
1393 if self.inverse:

~/anaconda3/lib/python3.7/site-packages/pennylane/operation.py in compute_matrix(*params, **hyperparams)
458 tensor_like: matrix representation
459 “”"
→ 460 raise MatrixUndefinedError
461
462 # pylint: disable=no-self-argument, comparison-with-callable

MatrixUndefinedError:

many thanks in advance for helping me.

Hey @sassan_moradi! Welcome back! :smiley:

Your code is pretty close. This works for me:

import pennylane as qml
from pennylane import numpy as np

from pennylane.operation import Operation, AnyWires


class YourGate(Operation):
    num_wires = AnyWires  

    def __init__(self, theta, wires, do_queue=True, id=None):
        all_wires = qml.wires.Wires(wires)
        super().__init__(theta, wires=all_wires, do_queue=do_queue, id=id)

    @staticmethod
    def compute_decomposition(theta, wires):
        decomp = []
        for i in range(len(wires) - 1):
            decomp.append(
                op
                for op in [
                    qml.Hadamard(wires=wires[i]),
                    qml.Hadamard(wires=wires[i + 1]),
                    qml.CZ(wires=[wires[i], wires[i + 1]]),
                    qml.RY(theta / 2, wires[i]),
                    qml.RY(-theta / 2, wires[i + 1]),
                    qml.CZ(wires=[wires[i], wires[i + 1]]),
                    qml.Hadamard(wires=wires[i]),
                    qml.Hadamard(wires=wires[i + 1]),
                ]
            )
        return decomp

Note that I made a small modification to your decomposition method in that I looped over len(wires) - 1, otherwise you’ll be accessing indices that are out of range!

Sorry for replying you late and many thanks for your useful answer. it worked.

Awesome! Thanks for getting back to us :grin: