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.

1 Like

Awesome! Thanks for getting back to us :grin: