Home Forums Nazca Bent Taper 2

Viewing 5 posts - 1 through 5 (of 5 total)
• Author
Posts
• #5849
paul
Member

Hi,

Is it possible to maintain the curvature of the bend constant with `cobra()`?

Ronald I have used your code with `viper()` (here)to write a function returning a tapered bend along an arc with specified curvature and angle.
It works well but `viper()` returns only a Polygon, which means that it does not integrate well with Interconnects or Xsections functions (i.e: I cannot easily use this tapered bend with pins, or cross-sections).
That’s why I think that using `cobra()` would be more elegant and powerful but as you can see in the example below, I haven’t figured out how to keep the curvature constant throughout. This notably gives funny results for small angles.

``````import nazca as nd
import numpy as np
import nazca as nd
from nazca.interconnects import Interconnect

def tapered_bend(w1, w2, a, r):
"""Returns a bent taper. Bent section is defined as an arc bend of angle a
and radius r. Tapered width is defined using nd.util.viper(). The width
varies linearly from w1 to w2

Args:
w1: taper start width
w2: taper end width
a: angle of the taper end edge to the horizontal
r: radius of curvature of the taper

Returns:
A Cell containing the taper centred at (0,w1/2)
"""
def x(t):
return r*np.cos(t*a/180*np.pi)

def y(t):
return r*np.sin(t*a/180*np.pi)

def w(t):
return w1-(w1-w2)*t

with nd.Cell(name='taper_%s_%s'%(a,r)) as taper:
# N is the number of points, x, y, w functions of one parameter.
xygon = nd.util.viper(x, y, w , N=200)
# shifts the polygon by y=r and rotate by -90deg to have the left edge
# positionned at x=0
nd.Polygon(points=xygon, layer=2).put(0,r,-90)

return taper
if __name__ == "__main__":
w1=10
w2=2
a=10
r=100

ic = Interconnect(width=2.0, radius=10.0)

tapered_bend(w1, w2, a, r).put(0,0)
ic.cobra_p2p(pin1=(0, 0, 0),
pin2=(r*np.sin(np.pi*a/180), r*(1-np.cos(np.pi*a/180)), a),
width1=w1,
width2=w2,
nd.export_gds()``````

Of course when I refer to cobra, I mean either cobra itself or cobra_p2p.

Thanks,

Paul

#5852
Ronald
Keymaster

Dear Paul,

There are infinite ways to draw curves (waveguides) between two points. The cobra_p2p is one that specifically does not use a constant bend radius but smoothly goes from a straight waveguide to a curved waveguide and finds an optimal path according to some strategy, as described inside the function.

Hence, the Viper is a good approach to do what you want, but it is not yet an Interconnect. A proper Nazca Interconnect set up has a number of levels to make it work as effortlessly as possible when utilizing it as a designer. It uses a xsection and stores a number of properties that are reused when creating mask_elements, like strt, bend or cobra_p2p.

The following structure gives a short overview on what an Interconnect definition is built upon:

A- xsection:
– add layers and their growth values

B- mask_element template function:
– provide a xsection (e.g. get from Interconnect (C))
– provide default values (e.g. get from Interconnect (C))

– – the mask_element is a “closure”, i.e. the function and its environment are saved when creating it.
– – loop over all xsection layers:
– – – apply grow
– – – create and discretize the polygon based on the layer
– – add pins and other properties

C- Interconnect (class):
– add a xsection, see (A)
– add default values (get from xsection where possible)
– Interconnect methods:
– – create an interconnect cell from a mask_element cell (B) and neither cell will be instantiated (by choice).
– – add pins to the interconnect cell

Note that mask_elements exist without Interconnects. In other words, an Interconnect object is just a wrapper around mask_elements to group these elements together inside a single technology definition.

If we now turn to the Viper we find that it creates a spine or path [x(t), y(t)]. From that it creates a polygon by adding a width w(t) to the spine. The viper still needs proper pin directions and polygon geometry at the begin and end points, proper discretization w.r.t. to the mask resolution, loop over all layers in a xsection, etc.

From the user perspective the layout code for a bent waveguide as described in your example simplifies to something as in the example below. It generates the layout shown after the code.
Note that the radius, and width1 and width2, are nicely incorporated as keywords for the tapered_bend() to make it possible overrule defaults.

``````tapered_bend().put(0)
ic.strt().put()
tapered_bend(angle=90, width2=20, N=1000).put()
tapered_bend(angle=-300, radius=60, width1=20, width2=0.5, N=2000).put()
nd.export_gds()``````

The complete code to set it up is shown below. The viper functions x, y, w can be adapted to more or less any sensible set of functions for drawing a waveguide. The example demonstrates part A and B in the above explained structure. Putting it in Interconnect C is straight forward after this step and omitted in this example.

Note that the keywords “anglei” and “angleo” in viper() require Nazca-0.5.8 or up. You can run the code without them in earlier Nazca versions (with the viper) and notice that the viper polygons then do not connect “good enough” in case of a curvature in the begin and/or end point, because the polygon angles are no longer matching the pins their “exact” angle values in those points.

A next step, not in this example, could be to calculate the minimum N for a specific maximum resolution. Here you can take N “large enough”. If this function is called from an Interconnect object you would not use `nd.get_xsection(xs).width` but get the Interconnect’s settings.

``````import numpy as np
import math as m
from functools import partial
import nazca as nd
from nazca.interconnects import Interconnect

def Tp_tapered_bend(x, y, w, radius=100.0, angle=10.0, width1=None, width2=None,
xs=None, layer=None, N=200):
"""Template for a specific Viper implementation.

args:
x (function): function in at least t, t in [0,1]
y (function): function in at least t, t in [0,1]
w (function): function in at least t, t in [0,1]

Returns:
function: Cell generating function
"""

def tapered_bend(radius=radius, angle=angle, width1=None, width2=None, xs=xs, layer=layer, N=N):
"""Specific Viper implementation.

Returns:
Cell: Cell based on a Viper
"""
N = N # discretization steps should be "large enough" for mask resolution
name = 'viper_bend'

# housekeeping for default values (add as needed):
if width1 is None:
width1 = nd.get_xsection(xs).width
if width2 is None:
width2 = nd.get_xsection(xs).width

# Fill in all x, y, w function parameters except t:
W = partial(w, width1=width1, width2=width2)

# Store begin and end points of the viper.
# Note-1: begin and end angle of the viper spine should be taken
#     infinitesimally close to the begin and end.
# Note-2: the polygon's begin and end edge should be perpendicular
#     to the local angles from Note-1
xa, ya = X(0), Y(0)
xb, yb = X(1), Y(1)
d = 1e-8
aa = m.degrees(m.atan2( Y(0)-Y(d), X(0)-X(d)))
ab = m.degrees(m.atan2( Y(1)-Y(1-d), X(1)-X(1-d)))
AB = (ab-aa-90)
if AB < -180:
AB += 180

# create the Cell:
with nd.Cell(name=name, cnt=True) as C:
for lay, grow, acc, line in nd.layeriter(xs, layer):
(a1, b1), (a2, b2), c1, c2 = grow
xygon = nd.util.viper(
X,
Y,
lambda t: W(t) + b1-b2, # add the layer growth
N=N,
anglei = -aa, # remove for <0.5.8
angleo = AB   # remove for <0.5.8
)
nd.Polygon(points=xygon, layer=lay).put(0)
nd.Pin(name='a0', type=0, width=width1, xs=xs, show=True).put(xa, ya, aa)
nd.Pin(name='b0', type=1, width=width2, xs=xs, show=True).put(xb, yb, ab)
nd.put_stub(length=0)
return C
return tapered_bend

if __name__ == "__main__":
# add a technology:
XS.width = 1.0
ic = Interconnect(xs='xs')

# create a specific viper-based mask_element:
def x(t, radius, angle):
return radius * np.cos(t * angle/180 * np.pi)
def y(t, radius, angle):
return radius * np.sin(t * angle/180 * np.pi)
def w(t, width1=None, width2=None):
return width1 + (width2 - width1) * t
tapered_bend = Tp_tapered_bend(x, y, w, xs=ic.xs)

# put waveguides:
tapered_bend().put(0)
ic.strt().put()
tapered_bend(angle=90, width2=20, N=1000).put()
tapered_bend(angle=-300, radius=60, width1=20, width2=0.5, N=2000).put()
nd.export_gds()``````

Ronald

#5853
paul
Member

Dear Ronald,

Thanks for the detailed answer. This is exactly the sort of function I was after and I think that this post will be helpful when it comes to creating custom Interconnect shapes.

With 0.5.4 this line
`for lay, grow, acc, line in nd.layeriter(xs, layer):`
returned a ValueError: not enough values to unpack (expected 4, got 3), so I commented the `line` parameter out since it wasn’t being used anywhere in the loop.

Thanks,
Paul

#5854
Ronald
Keymaster

Dear Paul,

The “line” variable yielded by layeriter was added in Nazca-0.5.7, based on among others a request for a polyline option in a xsection rather than polygons: creating paths. See also xsections and layersfor the polyline usage.

For Nazca<0.5.7, indeed use `for lay, grow, acc in nd.layeriter(xs, layer):` to loop over all layers in a xsection, as you did. Note that the Viper would need a minor upgrade for “edge” defined layers, rather than ‘width’ defined layers, but that is not relevant in this example.

Note that 0.5.8 needs to be released at the time of writing of this post, so you have to remark out anglei and angleo for now.

Ronald

#5898
Ronald
Keymaster

Dear Paul,

For more information on the viper integration as a mask element this tutorial may be helpful: Free form curves

Ronald

Viewing 5 posts - 1 through 5 (of 5 total)
• You must be logged in to reply to this topic.