Known backwards incompatibilities in 3.7.0

Bend size calculation changes

The rounding algorithms, ShapeRound and SplineRoundingAlgorithm now use an analytical calculation of the bend size. Before, it was executing the rounding algorithm to determine the size of the bend. As as result, speed of layout generation can be improved in some cases. However, this can also lead to 1-grid snapping differences.

import ipkiss3.all as i3

wg = i3.RoundedWaveguide()
wg_layout = wg.Layout(bend_radius=5.0, shape=[(0.0, 0.0), (10.0, 0.0), (10.0, 10.0)])

Before IPKISS 3.7 this prints (5.0000000000255174, 5.0000000000255689), while on IPKISS 3.7 the slightly more accurate (4.9999999999999991, 4.9999999999999991) is returned.

When snapping a layout to grid (e.g. upon GDSII export) vertices that are near-exactly in the middle between 2 grid points may therefore snap to a different grid point than before. These 1-grid snapping differences may have an affect if you do regression testing on your layout files, but should not have an effect on the functionality.


If you defined your own rounding algorithm and inherited from ShapeRound to do so, you should override the method get_bend_size in order to calculate the correct bend size for your algorithm. This can often be done (semi-)analytically. The signature is get_bend_size(self, angle, radius, angle_step, reverse_bend) and it should return a tuple (input_length, output_length) which are the distances between the input to the center control point and the center to the output control point, respectively. See ShapeRound.get_bend_size() for an example.

RoundedWaveguide: bend radius verification

The new improvements made to i3.RoundedTrace make our verification algorithm more lenient and will allow more designs to pass. This should have no/minimal impact on existing layouts that use i3.RoundedWaveguide or any of its subclasses, while at the same time reducing the amount of false negatives that caused an exception to be thrown.

The following example demonstrates this:

import ipkiss3.all as i3

grid_step = 1. / i3.get_grids_per_unit()
max_tolerance = 0.5 * grid_step
tolerance = 0.49999 * grid_step

# Circle with bend radius = 5
s1 = i3.Shape([(0.0, 0.0), (5.0, 0.0), (5.0, 10.0), (0.0, 10.0), (-5., 10.), (-5., 0.)], closed=True)
# Apply some tolerance. This used to throw a ValueError, but should work now:
s2 = i3.Shape([(0.0, 0.0), (5.0, 0.0), (5.0, 10.0 - tolerance), (0.0, 10.0 - tolerance), (-5., 10.0 - tolerance), (-5., 0.)], closed=True)
# Apply the maximum amount of tolerance. This should fail:
s3 = i3.Shape([(0.0, 0.0), (5.0, 0.0), (5.0, 10.0 - max_tolerance), (0.0, 10.0 - max_tolerance), (-5., 10.0 - max_tolerance), (-5., 0.)], closed=True)

wg = i3.RoundedWaveguide()

Straight section lengths in TaperedWaveguide

Due to a bug, the lengths of the straight sections (straight_section_lengths) in i3.TaperedWaveguide (which was introduced in IPKISS 3.5) did not correspond with the provided values of the straight_section_lengths parameter when min_straight was not explicitly set to 0.

AWG designs based on tapered waveguides (RectangularTaperedWaveguideArray) are not affected in functionality: the delay length differences between the waveguides of the array remain the same. Their tapered sections will follow exactly the specified straight_section_lengths now.

See the example below:

from technologies import silicon_photonics
import ipkiss3.all as i3
from picazzo3.traces.rib_wg import RibWaveguideTemplate

rib_tmpl = RibWaveguideTemplate()
rib_tmpl_wide = RibWaveguideTemplate()

twg = i3.TaperedWaveguide(trace_template=rib_tmpl, straight_trace_template=rib_tmpl_wide)
lay = twg.Layout(shape=[(0, 0), (30, 0), (30, 60)],
                 straight_section_lengths=[5.0, 5.0],
                 # In IPKISS < 3.7, the correct straight_section_length would only be used if min_straight is set to 0.
                 # min_straight=0.0

Bugfix in TaperedWaveguide explained.

straight_section_length of TaperedWaveguide (introduced in IPKISS 3.5) is correctly honored in all cases in IPKISS 3.7.

Order of placed instances

The i3.place_insts and the new i3.place_and_route now return the placed instances in a reproducible order. They are now sorted by name.

Even though place_insts returns an ordered InstanceDict, running the following example before IPKISS 3.7 would return the insts in an unknown order.

import ipkiss3.all as i3

testcell = i3.LayoutCell(name="test")

# input a regular (unordered) dict
insts = {'b': testcell,
         'b2': testcell,
         'a_3': testcell,
         'a': testcell,
         'a3': testcell}

insts_placed = i3.place_insts(insts)


You can see that the order is not necessarily the order of the input (which is undefined in Python 2.7 dictionaries), but is also not sorted:

<Dict of instances: {'a':<SRef of test:layout>, 'a3':<SRef of test:layout>, 'b':<SRef of test:layout>, 'a_3':<SRef of test:layout>, 'b2':<SRef of test:layout>}

With 3.7 the output is always sorted by name:

<Dict of instances: {'a':<SRef of test:layout>, 'a3':<SRef of test:layout>, 'a_3':<SRef of test:layout>, 'b':<SRef of test:layout>, 'b2':<SRef of test:layout>}

As a result, the order of instances in a GDSII file may be different. This has no effect on tape-outs at all. If you use a file hash (e.g. md5) for tracking changes in GDSII files (in combination with OutputGdsii’s fixed_time_stamp property), then this may affect your workflow.

AWGDesigner: Straight section lengths in RectangularExpandedAWG

In the legacy RectangularExpandedAWG, the first and third expanded sections are now as close as possible to the 90 degree bends, instead of in the middle between the bends from the star coupler and the 90 degree bends. This introduces a small change in the layout but does not change any of the overall lengths of the waveguide nor the delay length. It does not change the total size of the AWG either.

The newer awg_designer.all.RectangularTaperedWaveguideArray which is the recommended way to construct rectangular AWGs was already correct and has not been modified.

Bugfix in RectangularExpandedAWG explained.

Removal internal_member_name

Before 3.7, all Properties had an attribute internal_member_name which was also the first positional argument:

from ipcore.all import StrongPropertyInitializer, DefinitionProperty

class SPI(StrongPropertyInitializer):
    a = DefinitionProperty(internal_member_name="_a", doc="some property with an internal attribute _a")

The only Property which is actually using this is SetFunctionProperty. On all other Properties, internal_member_name was removed since it was not used.

If you defined your own Property type and actively used internal_member_name then you will need to implement this functionality on your property.

If you happen to declare Properties using positional arguments (possibly by mistake), then this may affect you. For instance, the following code where the argument name doc was forgotten would in the past have assigned the documentation string to the internal_member_name attribute (and not to doc). but this error would have gone unnoticed.

from ipcore.all import StrongPropertyInitializer, PositiveNumberProperty

class SPI(StrongPropertyInitializer):
     length = PositiveNumberProperty("length of the object")