Porting from Ipkiss 3.0 to Ipkiss 3.0.1

There are a small number of changes between IPKISS 3.0 (or early betas of 3.0.1) and the release of IPKISS 3.0.1 that can cause issues due to backward incompatibility.

The set method is more restrictive

In Ipkiss 3.0, the set (or _set_bulk) method on a StrongPropertyInitializer object (which includes most Ipkiss primitives such as PCell and View) would gracefully ignore keywords that did not correspond to a Property. For instance, the following code would work in Ipkiss 3.0 but will not work in Ipkiss 3.0.1:

class MyCell(StrongPropertyInitializer):
   a = IntProperty()
   b = NumberProperty()
>>> my_cell = MyCell(a=1, b=2)
>>> my_cell.a
1
>>> my_cell.set(a=2, b=3)
>>> my_cell.b
3
>>> my_cell.set(a=1, c=6) # works in Ipkiss 3.0, not in 3.0.1
>>> my_cell.a
1
>>> my_cell.c # c is not a property, but a regular python attribute
6

In the code above, c is not a property, but is assigned as a regular Python attribute. This is confusing, and also error prone. For instance, c could be just a typo, or a confusion in the property name, and it would not introduce an error but might lead to unpredictable behavior which is hard to discover.

In Ipkiss 3.0.1, set will reject any keyword that does not correspond to a property. my_cell.set(a=1, c=6) will raise an exception.

You might discover that some of your code does not work because of this. This can be

  • because you made a mistake in the keyword name. In that case, the tighter restriction uncovered a latent bug, and you should change or remove the keyword.
  • because you used set to add additional attributes to an Ipkiss object (e.g. some metadata that you want stored in the object). In that case, you should explicitly assign those attributes:
>>> my_cell.set(a=1)
>>> my_cell.a
1
>>> my_cell.c = 6 # explicitly assign c
>>> my_cell.c
6

Casting of Numbers in NumberProperties

For a more consistent behavior of numbers, NumberProperty (as well as PositiveNumberProperty, NonNegativeNumberProperty, etc…) will now cast all assigned numbers to float.

The original behavior was

class MyCell(StrongPropertyInitializer):
   a = IntProperty()
   b = NumberProperty()
>>> my_cell = MyCell(a=1, b=2)
>>> my_cell.a
1
>>> my_cell.b
2

From now on, b will be internally stored as a float:

>>> my_cell = MyCell(a=1, b=2) # b is cast to float
>>> my_cell.a
1
>>> my_cell.b
2.0

This can give some problems in existing code, where an int was assigned to a NumberProperty, and then that value was used in a division (resulting in an integer division). For instance, the following code would give different results in Ipkiss 3.0 and Ipkiss 3.0.1.

In Ipkiss 3.0, you would get

class MyCell(StrongPropertyInitializer):
   a = IntProperty()
   b = NumberProperty()

   def get_c(self):
       return self.a / self.b
>>> my_cell = MyCell(a=5, b=2)
>>> my_cell.get_c()
2

while in Ipkiss 3.0.1 you will get

>>> my_cell = MyCell(a=5, b=2)
>>> my_cell.get_c()
2.5

Also, in some cases you might get warnings that you did not get before, for instance when using the value to multiply a list:

>>> my_cell = MyCell(a=5, b=3)
>>> ['a'] * cell.b
['a', 'a', 'a']

The advice is to use more explicit casting:

  • IntProperty where you really expect an int
  • NumberProperty where you expect a float

FloatProperty is equivalent to NumberProperty, but its use will be deprecated in future versions. Use NumberProperty instead.


PortList behaves similar to TermDict

Ports (Layout) and terms (Netlist) are similar concepts. They are named object, and therefore, it is logical that they are stored in dict()-like containers, and that these containers behave similarly. This way, they can be accessed by name.

This is not the case in Ipkiss 3.0, but has been changed in Ipkiss 3.0.1. Currently, the original behavior is still supported, but it is recommended that you update your code to ensure future compatibility.

Consider the following PCell:

class MyCell(i3.PCell):
    class Layout(i3.LayoutView):
        def _generate_ports(self, prts):
            # prts is a PortList
            prts += i3.OpticalPort(name="p1")
            prts += i3.OpticalPort(name="p2")
            return prts

    class Netlist(i3.NetlistView):
        def _generate_terms(self, trms):
            # trms is a TermDict
            trms += i3.OpticalTerm(name="p1")
            trms += i3.OpticalTerm(name="p2")
            return trms

These port and term code in the views is nearly identical, but they return different types of objects:

>>> my_cell = MyCell(name="my_cell")
>>> layout = my_cell.Layout()
>>> layout.ports
[<OpticalPort 'p1': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>,
 <OpticalPort 'p2': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>]
>>> netlist = my_cell.Netlist()
>>> print netlist.terms
Terms {'p1':<OpticalTerm 'p1'>,'p2':<OpticalTerm 'p2'>}

This difference becomes very clear when iterating/looping over ports and terms:

>>> for p in layout.ports: print p    # p is a Port object
<OpticalPort 'p1': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>
<OpticalPort 'p2': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>

>>> for t in netlist.terms: print t   # t is a Term name
'p1'
'p2'

This behavior is different. To ensure compatible behavior of your future code, it is recommended to update your code wherever you use iterations over ports and terms. We have made sure that both PortList and TermDict support the same dict-like methods:

  • keys() will return a python list with all the port/term names
  • values() will return a python list with all the port/term objects
  • items() will return a python list of tuples with (name, port/term object)
>>> for p in layout.ports.keys(): print p    # p is a Port name (string)
'p1'
'p2'

>>> for p in layout.ports.values(): print p    # p is a Port object
<OpticalPort 'p1': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>
<OpticalPort 'p2': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>

>>> for p in layout.ports.items(): print p    # p is a (name, object) tuple
('p1', <OpticalPort 'p1': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>)
('p2', <OpticalPort 'p2': (0.000000, 0.000000), a=0.000000, D=<DefaultWaveguideTemplate.Layout view 'DEFAULT_WG_TEMPLATE:layout'>>)

These methods give the same behavior for terms.

If you use iterations over ports and terms in your code, you are advised to modify your code. A typical case is in PCells which contain child cells. Take this Ipkiss 3.0 example:

class MyParentCell(i3.PCell):
    child = i3.ChildCellProperty()

    class Layout(i3.LayoutView):
        def _generate_instances(self, insts):
            insts += i3.SRef(name="c", reference=self.child)
            return insts

        def _generate_ports(self, prts):
            for p in self.instances["c"].in_ports:
                prts += p.modified_copy(name="in_"+p.name)
            return prts

    class Netlist(i3.NetlistView):
        def _generate_instances(self, insts):
            insts += i3.Instance(name="c", reference=self.child)
            return insts

        def _generate_terms(self, trms):
            inst_terms = self.instances["c"].in_terms
            for t in inst_terms:
                trms += inst_terms[t].modified_copy(name="in_"+t)
            return trms

In Ipkiss 3.0.1 this becomes:

class MyParentCell(i3.PCell):
    child = i3.ChildCellProperty()

    class Layout(i3.LayoutView):
        def _generate_instances(self, insts):
            insts += i3.SRef(name="c", reference=self.child)
            return insts

        def _generate_ports(self, prts):
            for p_name, p in self.instances["c"].in_ports.items():
                prts += p.modified_copy(name="in_"+p_name)
            return prts

    class Netlist(i3.NetlistView):
        def _generate_instances(self, insts):
            insts += i3.Instance(name="c", reference=self.child)
            return insts

        def _generate_terms(self, trms):
            for t_name, t in self.instances["c"].in_terms.items():
                trms += t.modified_copy(name="in_"+t_name)
            return trms

Layer Properties in PICAZZO

If you have used one of the PICAZZO components in Ipkiss 3.0, you will find that some properties have slightly changes. Especially layer properties.

Picazzo components that had a property layer will now only have a property process and purpose. So, where you originally could use:

my_component = SomePicazzoComponent(name="my_name",
                                    layer=TECH.PPLAYER.WG.CORE
                                    )

You should now use

my_component = SomePicazzoComponent(name="my_name",
                                    process=TECH.PROCESS.WG,
                                    purpose=TECH.PURPOSE.CORE
                                    )

In some cases, components had both a layer property and a process and purpose property, creating an ambiguous situation. Therefore, the layer property has been removed.

The following components are affected:

  • Trace templates (picazzo3.traces): RibWaveguideTemplate, SlotWaveguideTemplate, DoubleSlotWaveguideTemplate, ThinnedWaveguideTemplate, CoreCladdingShapeWaveguideTemplate
  • Photonic crystals (picazzo3.phc): Photonic crystal holes, PhCLayout, W1HeteroCavity1Mirror
  • Shallow etched Multi-mode interferometers (picazzo3.filters.mmi_rib): RibMMIIdenticalTapered
  • Fiber grating couplers (picazzo3.fibcoup): UniformLineGrating, GratingUniformLine

Technology

We cleaned up the confusing hierarchy of the technology source files. To avoid breaking the si_photonics technology that is used for many Ipkiss 2.4 designs, we created a new default_ipkiss and silicon_photonics technology. For any new Ipkiss3 project, you can use the default_ipkiss technology. If you want to work with Picazzo3 devices, you should use the silicon_photonics technology.

from technologies import silicon_photonics

This loads all the constructs needed for Ipkiss3 and Picazzo3. The backward-compatibility entries needed to run design code based on Picazzo2 have been deliberately omitted to keep this technology folder pure. If you want to combine Picazzo2 and Picazzo3 devices within the same Ipkiss3 design, you can use the compat24 technology together with the silicon_photonics technology:

from technologies import silicon_photonics
from technologies.compat24 import picazzo24

If you are running pure Ipkiss 2.4 scripts, you can also use the ‘old’ si_photonics technology:

from technologies.si_photonics.picazzo import *

When creating custom technologies, we recommend basing them on silicon_photonics or base_ipkiss, and invoke the compat24 for managing the backward compatibility.

The new technology has a clearer folder structure:

  • default_waveguide.py: The default waveguide template
  • traces.py: Default settings for waveguides and traces
  • transitions.py: The auto transition database
  • blocks.py: default settings for Columns and blocks
  • io.py: Default adapter for columns
  • admin.py: administrative tools, such as the automatic name generation
  • metrics.py: Grid, unit and discretisation
  • rules.py: Default design rules (line widths, spacings, …)
  • process.py: Processes, purposes and layers
  • display.py: Display styles
  • mask.py: Default settings for the mask fabrication
  • gdsii.py: GDSII import and export settings

When building a new technology, you can either copy-paste these files into your own technology, or just import them.


Explicit Evaluations of child views are no longer required

In Ipkiss 3.0, it was often needed to explicitly evaluate views of Child views, to make sure that the parameters were properly passed from the parent to the child. This is no longer needed.

For instance, in the following code from Ipkiss 3.0

my_ring = RingRect180DropFilter(name="my_ring")
cp = dict(cross_coupling1=0.1j,      # The coupling from bus to ring and back
          straight_coupling1=0.96,   # Straight coupling
          )
my_ring_cm = my_ring.CapheModel(ring_length=40.0,             # we can manually specify the ring length
                                coupler_parameters=[cp, cp]) # 2 couplers
my_ring_cm.couplers # this line makes sure the coupler models are properly evaluated

we can now remove the explicit evaluations of the couplers in the last line.

The origin of this issue was that in a PCell with a child cell XYZ, the _default_XYZ methods in the view was not always executed. For example, consider the following Parent and Child class with a Layout view:

class SomeChildCell(i3.PCell):
    class Layout(i3.LayoutView):
        some_property = IntProperty(default=1)

class MyCell(i3.PCell):
    XYZ = i3.ChildCellProperty()

    def _default_XYZ(self):
        return SomeChildCell(name=self.name + "_child")

    class Layout(i3.LayoutView):
        a = IntProperty(default=5)

        def _default_XYZ(self):
            lo = self.cell.XYZ.get_default_view(i3.LayoutView)
            lo.set(some_property=self.a)
            return lo

In Ipkiss 3.0, the following code would give a wrong result:

>>> my_cell = MyCell(name="my_cell")
>>> parent_layout = my_cell.Layout(a=6)
>>> child = my_cell.XYZ
>>> child_layout = child.get_default_view("Layout")
>>> child_layout.some_property
1

This is clearly wrong, as you would expect the child layout to get its parameters from the parent. In Ipkiss 3.0, this could be solved by explicitly evaluating the child view, either by calling it, or by running a visualize or GDSII export:

>>> my_cell = MyCell(name="my_cell")
>>> parent_layout = my_cell.Layout(a=6)
>>> parent_layout.XYZ            # Explicitly evaluate Child View
>>> child = my_cell.XYZ
>>> child_layout = child.get_default_view("Layout")
>>> child_layout.some_property
6

or

>>> my_cell = MyCell(name="my_cell")
>>> parent_layout = my_cell.Layout(a=6)
>>> parent_layout.visualize()    # Visualization will evaluate all the child views
>>> child = my_cell.XYZ
>>> child_layout = child.get_default_view("Layout")
>>> child_layout.some_property
6

In Ipkiss 3.0.1, this intermediate evaluation is no longer needed:

>>> my_cell = MyCell(name="my_cell")
>>> parent_layout = my_cell.Layout(a=6)
>>> child = my_cell.XYZ
>>> child_layout = child.get_default_view("Layout")
>>> child_layout.some_property
6

The parameter a is now properly passed to the child cell.


Notebooks are now using IPython 3

the Notebooks have been ported to the IPython 3 engine (Do not confuse to Python 3.x - Ipkiss is still based on Python 2.7). This engine is also the one bundled with the Luceda installation. Normally, you notebooks v2 should still run, but you might need to add the following line at the start of your notebook:

%pylab inline

After saving the notebooks they will be upgraded to v3.

If you want to convert a notebook from the command line, you can use:

ipython nbconvert --to notebook {} --output {}

We have been diligent in documenting possible backward incompatibilities. If by any chance you encounter other inconsistencies, do not hesitate to use the feedback button.