# Example of a Disk Resonator with Wraparound waveguide¶

Result

In this example, we construct a PCell for a parametric disk resonator with a bus waveguide which wraps partially around the disk.

Fig. 70 Disk resonator with wraparound bus waveguide

Illustrates

1. how to define a pcell
2. how to define properties
3. how to use geometric primitives to draw a disk
4. how to use a rounded waveguide
5. how to use IPKISS shape operations to quickly build a control path

Files (see: samples/ipkiss3/samples/wrapped_disk)

There are two files contained in this step.

How to run this example

To run the example, run ‘execute.py’.

## Defining the Parametric Cell¶

We define a parametric cell (or PCell) as a python class. Each view of the PCell (a Layout, a Netlist, a Model …) is defined as a new class within the PCell class:

class DiskResonator(i3.PCell):

# code of the PCell

class Layout(i3.LayoutView):

# code of the Layout view


PCells and views are defined by parameters, also called properties. These need to be listed in the class definition. It si a good practice to identify which parameters are global to the complete PCell, and which only apply to one particular view.

For our disk resonator, as shown in the figure above, we can identify the following parameters

• The disk radius
• The spacing between the disk and the waveguide
• The angle over which the bus waveguide is coupled to the disk

These are very clearly layout-specific parameters, so we can add them to the Layout view:

class DiskResonator(i3.PCell):

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")


Because waveguides in ipkiss are stand-alone components (also PCells), we should define the bus waveguide as a child cell of our class. Child cells should be defined at cell level, so we can add the bus waveguide there.

class DiskResonator(i3.PCell):

bus_wg = i3.ChildCellProperty(doc="the bus waveguide")

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")


This is already a good start for our PCell.

## Generating the Disk Layout using geometric primitives¶

We can now start generating the disk by drawing a circle on the layout. The dimensions of the circle are generated using the properties we defined in our Layout View:

class DiskResonator(i3.PCell):

# ...

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")

def _generate_elements(self, elems):
# define the disk as pure geometric elements
elems += i3.Circle(layer=i3.TECH.PPLAYER.WG.CORE,
)
)

return elems


Geometric primitives are added using the _generate_elements methods, which we have to add to our Layout view. We add two Circle elements:

• the first circle is the disk itself, which is drawn on the waveguide layer
• the second circle defines a trench around the disk, as some technologies require that the trench is explicitly provided.

## Creating a Waveguide child cell¶

Drawing a circle is easy. It is more difficult to calculate the waveguide, as this consists of circular segments that need to be correctly positioned. IPKISs provides a number of constructs for this. First, let’s define a waveguide for the bus. This we can do by adding a _default_bus_wg method on the PCell which creates a waveguide cell, and another method (with the same name) in the Layout view, that sets all the correct layout parameters.

class DiskResonator(i3.PCell):

bus_wg = i3.ChildCellProperty(doc="the bus waveguide")

def _default_bus_wg(self):
return i3.Waveguide(name=self.name+"bus_wg")

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")

def _default_bus_wg(self):
# set the layout parameters of the waveguide

r = self.radius + self.spacing

bus_layout = self.cell.bus_wg.get_default_view(i3.LayoutView)
bus_layout.set(shape=[(-r, -r), (+r, -r)])
return bus_layout

def _generate_instances(self, insts):
insts += i3.SRef(name="bus", reference=self.bus_wg)  # Adding an instance of the bus as a hierarchical component
return insts


In the second _default_bus_wg method, we assign a shape to the bus waveguide layout. But for now we have just used a straight shape. Now we could go and calculate a shape with circular segments, for which IPKISS provides the i3.ShapeArc <ipkiss.geometry.shapes.basic.ShapeArc>. But it is not that easy to to all those calculations by hand.

The _generate_instances method places a reference to the waveguide child cell in the layout.

### Creating a Rounded Waveguide¶

IPKISS provides rounded waveguides, which automatically add bends. This means you don;t have to specify the complete shape of the waveguide path, but you can provide a list of waypoints, or routes. The i3.RoundedWaveguide <ipkiss3.pcell.photonics.rounded_waveguide.RoundedWaveguide> will automatically add bends with the specified radius at every waypoint. Using a RoundedWaveguide instead of a waveguide is fairly simple:

class DiskResonator(i3.PCell):
bus_wg = i3.ChildCellProperty(doc="the bus waveguide")

def _default_bus_wg(self):
return i3.RoundedWaveguide(name=self.name+"_bus")

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")

def _default_bus_wg(self):
# set the layout parameters of the waveguide
r = self.radius + self.spacing
s = i3.TECH.WG.SHORT_STRAIGHT

# Bus waveguide layout
bus_layout = self.cell.bus_wg.get_default_view(i3.LayoutView) # default bus layout view
shape=[(-r, r), (-r, -r), (+r, -r), (r, r)],
draw_control_shape=True)

return bus_layout


To incorporate the RoundedWaveguide cell, we just changed the _default_bus_wg in the PCell to use RoundedWaveguide instead of Waveguide, and added the additional parameters in the _default_bus_wg method in the Layout view:

• the bend radius, which is slightly larger than that of the disk itself (adding the spacing between the disk edge and the centerline of the waveguide)
• the control shape: with 4 control points we created a U-shaped waveguide that wraps around the disk
• we also switch on the displaying of the control shape itself

### Calculating the correct control shape¶

One of the nice things of using rounded waveguides, is that these components are aware of their own bend size. This means you can use them to calculate the points of the control shape. In our case, we need a control shape to generate the wraparond bend:

Fig. 71 Control shape of the bus waveguide

The control shape consists of only 6 waypoints, and RoundedWaveguide will create the bends for these. The distance between the control points can be calculated using the get_bend_size method of RoundedWaveguide: this gives the distances between the control shape and the two end points of a bend.

class DiskResonator(i3.PCell):
bus_wg = i3.ChildCellProperty(doc="the bus waveguide")

def _default_bus_wg(self):
return i3.RoundedWaveguide(name=self.name+"_bus")

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")

def _default_bus_wg(self):
# set the layout parameters of the waveguide
r = self.radius + self.spacing
a = 0.5* self.bus_angle
s = i3.TECH.WG.SHORT_STRAIGHT

# Bus waveguide layout
bus_layout = self.cell.bus_wg.get_default_view(i3.LayoutView) # default bus layout view

b1, b2 = bus_layout._get_bend_size(a) # calculates the size of the waveguide bend

# control shape for the bus waveguide (one half)
# the RoundedWaveguide will automatically generate smooth bends
s1 = i3.Shape([(-b1, -r)])        # point 1
s1.add_polar(2* b2, 180.0 - a)    # point 2
s1.add_polar(b1 + s, 180.0)       # point 3

# stitching 2 halves together
bus_shape = s1.reversed() + s1.h_mirror_copy()

# assigning the shape to the bus
bus_layout.set(shape=bus_shape,
draw_control_shape=True) # will also draw the control shape on a doc layer
return bus_layout


We first set the bend radius of the RoundedWaveguide. Based on that value, we can then query the size of the bends. We then use IPKISS’s shape construction methods to build a shape of 3 points (as indicated in the figure above). The add_polar method adds a point to the shape that is a given distance away at a given angle (in degrees).

The full shape is built from a reversed copy and a mirrored copy of the shape. That full shape is then assigned as control shape for the waveguide.

### Using Waveguide Templates¶

We now use a standard waveguide. However, IPKISS supports the use of waveguide templates. These are predefined PCells that describe how a waveguide is constructed (layers, widths, …). We can add a waveguide template to our PCell, and that way we can use different waveguides as bus waveguides:

class DiskResonator(i3.PCell):

wg_template = i3.WaveguideTemplateProperty(default=i3.TECH.PCELLS.WG.DEFAULT, doc="trace template used for the bus")
bus_wg = i3.ChildCellProperty(doc="the bus waveguide")

def _default_bus_wg(self):
# waveguide cell
bus_wg = i3.RoundedWaveguide(name=self.name+"_bus",
trace_template=self.wg_template)

return bus_wg

class Layout(i3.LayoutView):
radius = i3.PositiveNumberProperty(default=10.0, doc="radius of the disk")
spacing = i3.PositiveNumberProperty(default=0.6, doc="spacing between centerline of bus waveguide and edge of the disk")
bus_angle = i3.PositiveNumberProperty(default=60.0, doc="angular span (in degrees) of the bus waveguide around the disk")

def _default_bus_wg(self):
# set the layout parameters of the waveguide
r = self.radius + self.spacing
a = 0.5* self.bus_angle
s = i3.TECH.WG.SHORT_STRAIGHT

# Bus waveguide layout
bus_layout = self.cell.bus_wg.get_default_view(i3.LayoutView) # default bus layout view
trace_template=self.wg_template)

b1, b2 = bus_layout._get_bend_size(a) # calculates the size of the waveguide bend

# control shape for the bus waveguide (one half)
# the RoundedWaveguide will automatically generate smooth bends
s1 = i3.Shape([(-b1, -r)])
s1.add_polar(2* b2, 180.0 - a)
s1.add_polar(b1 + s, 180.0)

# stitching 2 halves together
bus_shape = s1.reversed() + s1.h_mirror_copy()

# assigning the shape to the bus
bus_layout.set(shape=bus_shape,
draw_control_shape=True)
return bus_layout


We see three changes when adding the support for waveguide templates:

• we add a property wg_template to the PCell
• we have to pass the wg_template as a parameter to RoundedWaveguide
• we have to set the wg_template in the Layout view as well.

When we want to incorporate the disk in a circuit, it is important that the input and output are correctly defined. In this case, these correspond to the inputs and outputs of the bus waveguide. Still we have to explicitly define them in the Layout view:

class DiskResonator(i3.PCell):

# ...

class Layout(i3.LayoutView):

# ...

def _generate_ports(self, ports):
# the connection to the outside world
ports += self.instances["bus"].ports
return ports


We just pass on the ports from the “bus” instance.

### Recap¶

In this examples we built up a Disk resonator from the ground up, using a combination of geometric primitives and the built-in rounded waveguides.