Step 1: PCell and Properties

Result

In this example we illustrate the core architecture of IPKISS. IPKISS is based on parametric cells (PCells) and properties through which PCells communicate with the outside world.

Illustrates

  1. how to create a parametric cell, by sub-classing from PCell.

  2. how to add parameters as properties.

  3. how to add _default_xxx methods for properties.

  4. how to validate properties.

How to run this example

To run the example, run ‘execute.py’ from the command line

Files (see: tutorials/pcell_basic/01-pcell)

There are two files contained in this step.

  • ring.py : which is a self-contained python file that contains a description of the our ringresonator. It is imported in execute.py

  • execute.py : which is the file that is ultimately executed by python. It imports the description of the ringresonator from ring.py and performs operations on it.

The very beginning

Before we do anything meaningful, we must first import the standard ipkiss api. This is done with the following line of code:

import ipkiss3.all as i3

ipkiss3.all or just i3 contains all important classes and functions. You use i3 to get access to the main functionality of ipkiss. One very important class is PCell. All your designs will need to inherit from this class. Let’s see how this works in practice.

class RingResonator(i3.PCell):

    """A generic ring resonator class.

    It is defined by a circular waveguide evanescently coupled to a straight bus waveguide.
    """
    _name_prefix = "RINGRES"  # a prefix added to the unique identifier

Here we defined our first PCell, for now it’s just a dumb placeholder but let’s take some time to analyze what is going on:

  1. class RingResonator(i3.PCell) tells us that our design is a parametric cell and that the type of the cell is ‘RingResonator’. If you know a little bit of python you’ll know that class MyClass(SomeClass): means that MyClass inherits from SomeClass.

  2. Next we give some description of what the design is about. This is not necessary but it’s highly recommended.

  3. _name_prefix = "RINGRES" Provides the name prefix, IPKISS will use this prefix to generate a unique name.

Using properties to specify the design parameters

Now that we have a base to work on, let’s gradually add more meaning. An important aspect of building a PCell is carefully choosing design parameters which will influence the behavior of your design.

Note

Choosing parameters is not an easy task and often a range of solutions exists. In reality you’ll often add or remove parameters as you add functionality to your pcell.

To define the design parameters of a PCell IPKISS uses properties. Properties allow you to define what kind of data can be assigned to the design parameter. An example will make this a bit clearer. For our ringresonator we’ve indentified the following design parameters:

  • ring_radius

  • ring_wg_width

  • bus_wg_width

  • coupler_spacing

We’ll now add these design parameters to our ringresonator, we omit the _name_prefix and documentation to keep the code concise.

class RingResonator(i3.PCell):
    # ...
    ring_radius = i3.PositiveNumberProperty(doc="radius of the ring in micrometers")
    ring_wg_width = i3.PositiveNumberProperty(default=0.5, doc="width of the ring waveguide in micrometers")
    bus_wg_width = i3.PositiveNumberProperty(default=0.5, doc="width of the bus waveguide in micrometers")
    coupler_spacing = i3.PositiveNumberProperty(doc="spacing between centerline of bus wg and ring wg")

    def _default_coupler_spacing(self):
      return self.ring_wg_width + self.bus_wg_width + 1.0

IPKISS properties have a default value, which will be used when we have not specified a value, and they have a short description or doc that gives meaning to this property. As all our design parameters correspond to physical distances, they should be positive numbers. That’s why we’ve chosen to use the PositiveNumberProperty. This will inhibit you from assigning an invalid value. In this case if you would try to assign a negative number or 0, an error would be raised, because those are not valid values for our properties.

The default value of coupler spacing is not provided directly in the property but by the method default_coupler_spacing(self). This method returns a value that is used as a default for the property coupler_spacing. The advantage of such a method is that it is possible to make the default value of a property dependent on the value of other properties.

However, it is still possible to provide a set of values that would result in an invalid ringresonator. Therefore, we want to restrict our values even more, to assure that we build a valid ringresonator. More precisely we want to enforce the following relation between properties:

  • the sum of the waveguides’ width must be smaller than the spacing between the ring and the bus waveguide. Otherwise they will touch.

  • the width of the waveguide can’t be larger than the radius of the ring it creates.

To enforce this kind of rules, you can implement the validate_properties method. Through a series of if-statements you can check if your set of parameters is valid or not. Every time the value of a property changes, this method will be called to check if the values are valid. If they are not you can raise a PropertyValidationError. If such an error is raised, the program flow stops and an error message is shown to the user so that the user can quickly correct his mistake.

Now let’s apply this to our ringresonator:

class RingResonator(i3.PCell):

    # ... properties omitted

    def validate_properties(self):
        """Check whether the combination of properties is valid."""
        if self.coupler_spacing <= 0.5 * (self.ring_wg_width + self.bus_wg_width):
            raise i3.PropertyValidationError(self, "coupler_spacing too small: waveguides will touch",
                                             {"coupler_spacing": self.coupler_spacing,
                                              "ring_wg_width": self.ring_wg_width,
                                              "bus.wg_width": self.bus_wg_width})

        if self.ring_radius < self.ring_wg_width:
            raise i3.PropertyValidationError(self, "ring_radius too small for given waveguide definition",
                                             {"ring_radius": self.ring_radius,
                                              "ring_wg_width": self.ring_wg_width})

        # The RingResonator is valid so we return True
        return True

Executing a first script

# 1. import the ring resonator
from ring import RingResonator

# 2. create a new RingResonator object
my_ring = RingResonator(ring_radius=2.0, name="my_unique_ring_name")

# 3. print the autogenerated name
print(my_ring.name)  # unique name with prefix

# 4. change property
my_ring.ring_wg_width = 0.6

# 5. The following code will throw an error because the spacing between the
# waveguides is too narrow
# my_ring.coupler_spacing = 0.1
  1. We import the RingResonator class that is contained in the ring.py class.

  2. We create an instance of the RingResonator class, which is a RingResonator object. Here we set the ring_radius to 2.0 μm. For the other properties, the default values will be used.

  3. We can check the name of my_ring by printing it. Here the name is my_unique_ring_name. The name has to be chosen as such that each instance of the RingResonator class has a different name. If no name is provided a unique name will be generated based on a name prefix. For our ring _name_prefix = "RINGRES". So, if no name is provided, pcell objects will have the name RINGRES_0, RINGRES_1 and so on.

  4. We change the wg_width property of my_ring. This can be done after instantiation.

  5. Each time a property is modified the validate_properties method is executed to ensure that the rules are respected. Here we try to create a ring with a coupler_spacing that is too small, when uncommented and executed, an error will be raised.

Recap

We’ve now created a base which we can build upon, this base on itself doesn’t implement any functionality. But still it’s an important step. Let’s repeat the main points of this first step:

  • Every design in IPKISS is a PCell, you can define your own PCells by providing your own python class that inherits from IPKISS` PCell class.

  • The design parameters of your PCell are modelled using properties, properties restrict the kind of values you can assign to the design parameter.

  • properties can have a default value that will be used when you don’t specify a value when you’re creating a cell.

  • You can model relationships between properties using the validate_properties method.

In the next step, we will learn how you can add Views to your PCells in order to start adding more functionality to your design.