Building a plugin

Hey everyone,

I started building my own plugin, since I want to support a new gate consisting of already existing PennyLane gates, so that if I want to apply this gate I don’t have to always apply all operations one after another and can just call one gate. I had quite some difficulties getting to the point where I am right now, but now I am stuck.

I created my own device and followed the PennyLane documentation[https://pennylane.readthedocs.io/en/user-docs-refactor/introduction/plugins.html] and added it into the setup.py file. The device installation works fine and I can also import my custom gate. If I try to run it though it just returns a Key Error with the name of my gate. No additional information. Now I am quite confused. What am I doing wrong?

This is the code I have so far:

from pennylane.operation import CVOperation
from pennylane_sf.fock import StrawberryFieldsFock
import pennylane as qml



class CustomDevice(StrawberryFieldsFock):
    name = 'My custom device'
    short_name = 'example.mydevice'
    pennylane_requires = '0.15.0'
    version = '0.0.1'
    author = 'Noah Wach'

    operations = {"FockState", "Beamsplitter", "Test_Gate"}
    observables = {"NumberOperator"}


    def apply(self, operations, wires, par, **kwargs):
        return StrawberryFieldsFock.apply(self, operations, wires, par, **kwargs)


    def var(self, operations, wires, par, **kwargs):
        return StrawberryFieldsFock.var(self, operations, wires, par, **kwargs)


    def expval(self, operations, wires, par, **kwargs):
        return StrawberryFieldsFock.expval(self, operations, wires, par, **kwargs)



class Test_Gate(CVOperation):

    num_params = 1
    num_wires = 2
    par_domain = 'R'
    grad_method = 'A'
    grad_recipe = None

    @staticmethod
    def decomposition(*params, wires):
        return [qml.Kerr(params[0], wires=wires[0]), qml.Kerr(params[0], wires=wires[1]), qml.CrossKerr(-2*params[0], wires=wires[0, 1])]

If I run the circuit I get the following error:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-5-b578624aadda> in <module>
----> 1 result = Kerr_test()

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/qnode.py in __call__(self, *args, **kwargs)
    553 
    554         # execute the tape
--> 555         res = self.qtape.execute(device=self.device)
    556 
    557         if original_shots is not None:

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/tape/tape.py in execute(self, device, params)
   1187             params = self.get_parameters()
   1188 
-> 1189         return self._execute(params, device=device)
   1190 
   1191     def execute_device(self, params, device):

~/venvs/HiWi/lib/python3.7/site-packages/autograd/tracer.py in f_wrapped(*args, **kwargs)
     46             return new_box(ans, trace, node)
     47         else:
---> 48             return f_raw(*args, **kwargs)
     49     f_wrapped.fun = f_raw
     50     f_wrapped._is_autograd_primitive = True

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/interfaces/autograd.py in _execute(self, params, device)
    163         # evaluate the tape
    164         self.set_parameters(self._all_params_unwrapped, trainable_only=False)
--> 165         res = self.execute_device(params, device=device)
    166         self.set_parameters(self._all_parameter_values, trainable_only=False)
    167 

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/tape/tape.py in execute_device(self, params, device)
   1220             res = device.execute(self)
   1221         else:
-> 1222             res = device.execute(self.operations, self.observables, {})
   1223 
   1224         # Update output dim if incorrect.

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/_device.py in execute(self, queue, observables, parameters, **kwargs)
    394 
    395             for operation in queue:
--> 396                 self.apply(operation.name, operation.wires, operation.parameters)
    397 
    398             self.post_apply()

~/Desktop/Uni/HiWi/pennylane_NaLi/pennylane/pennylane/devices/NaLi.py in apply(self, operations, wires, par, **kwargs)
     21 
     22     def apply(self, operations, wires, par, **kwargs):
---> 23         return StrawberryFieldsFock.apply(self, operations, wires, par, **kwargs)
     24 
     25 

~/venvs/HiWi/lib/python3.7/site-packages/pennylane_sf/simulator.py in apply(self, operation, wires, par)
    112         par = [p.unwrap() if hasattr(p, "unwrap") else p for p in par]
    113 
--> 114         op = self._operation_map[operation](*sf_par)
    115         op | [self.q[i] for i in device_wires.labels]  # pylint: disable=pointless-statement
    116 

KeyError: 'Test_Gate'

What is the problem here? Thanks in advance.

Cheers,

Noah

Hi @nlwach,

Thank you for your question! :slightly_smiling_face:

It’s great seeing a new CV device being built on top of the existing ones, very exciting!

There are two avenues that can be taken when adding a new operation for a CV device that inherits from StrawberryFieldsFock:

  1. Supporting the device natively in the device by adding it to the operations set, defining a fitting function that will compute the transformation equivalent to applying the gate and adding this new function to the _operations_map dictionary.
  2. Rather than supporting the gate explicitly, falling back to its decomposition. The device should support all operations used in the decomposition.

The error is raised because parts for option 1. are missing. The underlying StrawberryFieldsFock device doesn’t have an entry for Test_Gate in the _operations_map, so it doesn’t know how to natively support it (it lacks the dedicated function for "Test_Gate"). The reason why it tries to do that is because Test_Gate was defined in operations.

The most straightforward solution would be to remove "Test_Gate" from operations and adding the "Kerr" and the "CrossKerr" entries such that the decomposition is valid and can be enforced by applying "Test_Gate". So in that regard going with option 2.

Small note: it seems that the qml.CrossKerr(-2*params[0], wires=wires[0, 1]) -> qml.CrossKerr(-2*params[0], wires=wires) change will need to be applied in the code.

With these changes the following worked well for me locally:

dev = CustomDevice(wires=3, cutoff_dim=5)

wires = qml.wires.Wires([0,1])

@qml.qnode(dev)
def circuit():
    Test_Gate(0.3, wires=wires)
    return qml.expval(qml.NumberOperator(0))

circuit()

Hope this helps, let us know, how it goes! :slight_smile:

2 Likes

Hi @antalszava,

thank you so much for your help. I tried it and it works perfectly.

I just wanted to shout out the entire Xanadu PennyLane/strawberryfields community for being absolutely awesome!! I asked a lot of questions the past couple of weeks (here and on slack) and I always got an answer in a day. Just amazing. Thank you so much! :smiley:

Cheers,

Noah

2 Likes