Files
mxpic_forge/primitives/grating_couplers/grating_couplers.py
T

1117 lines
51 KiB
Python

import nazca as nd
import numpy as np
import math
from ..structures import *
from ..structures import _my_polygon
from ..basic import __cell_arg__
from ..routing import Route
import pandas as pd
''' Class for nanoantenna '''
class Nano_ant():
"""Class of nanoantenna for optical phased array.
This is the class of nanoantenna for optical phased array. GDS cell can be generated using this class. Simulation structure generation and simulation results analysis is going to be added in the future.
Args:
- tapeout [class] (Default: CUMEC_CSiP130Cu)
- w_wg [um] (Default: 0.5um)
Width of input waveguide
- vector [um] (Default: [0.5,..,0.5]])
Vectors to define the length of each teeth
- taper_length [um] (Default: 1um)
Length of the linear taper region
- width [um] (Default: 3um)
Width of the nanoantenna
- max_theta [degree](Default: 110)
Open degree of linear taper
- define_type [str] (Default: non-periodic)
Way to define the antenna, including: "non-periodic", "periodic"
- etch_depth [str] (Default: "DETCH")
Define the etch depth, including: "FETCH", "METCH", "SETCH"
"""
def __init__(
self,
w_wg: float = 0.41,
xs_wg: str = "strip",
define_type: str = "non-periodic",
vector: float = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
taper_length: float = 3,
width: float = 6,
max_theta: float = 110,
pitch: float = 0.6,
duty_cycle: float = 0.3,
teeth_number: float = 6,
etch_depth: str = ["METCH"],
show_pins: bool = True
):
# Init and save the input parameters
self.w_wg = w_wg
self.xs_wg = xs_wg
# Determine the etch type
if len(etch_depth)==1: self.etch_type = "single"
elif len(etch_depth)==2: self.etch_type = "dual"
if define_type=="non-periodic":
self.vector = vector
self.teeth_number = len(vector)/2
elif define_type=="periodic":
# Parameters necessary when the ant is defined by "periodic" way
if self.etch_type=="single":
self.vector = [pitch*((2*duty_cycle-1)*(index%2)+1-duty_cycle) for index in range(teeth_number*2)]
self.pitch = pitch
self.duty_cycle = duty_cycle
self.teeth_number = teeth_number
elif self.etch_type=="dual":
self.vector = [
pitch[1]*((2*duty_cycle[1]-1)*(index%2)+1-duty_cycle[1]) for index in range(teeth_number*2)
]
self.vector[0] = pitch[0]*(1-duty_cycle[0])
self.vector[1] = pitch[0]*duty_cycle[0]
self.pitch = pitch
self.duty_cycle = duty_cycle
self.teeth_number = teeth_number
self.taper_length = taper_length
self.ant_length = self.taper_length + sum(self.vector)
self.width = width
self.max_theta = max_theta
self.define_type = define_type
# Here, I should change the name-type according to the difinition in the foundry.py
self.etch_depth = []
for etch in etch_depth:
if etch=="FETCH": self.etch_depth = self.etch_depth+["STRIP"] # self.etch_depth.append("STRIP")
elif etch=="METCH": self.etch_depth = self.etch_depth+["RIB"] # self.etch_depth.append("RIB")
elif etch=="SETCH": self.etch_depth = self.etch_depth+["SRIB"] # self.etch_depth.append("SRIB")
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self, sample_step=0.1, cell_name="Nanoantenna"):
with nd.Cell(name=cell_name, instantiate=False) as nano_ant:
layer_tre = nd.get_layer("STRIP_TRE")
if layer_tre == "STRIP_TRE" :
self.generate_gds_positive(sample_step=sample_step)
else : ## TO DO
self.generate_gds_error()
# Add pins
nd.Pin(name="a0", width=self.w_wg).put(0, 0, 180)
nd.Pin(name="g1", width=self.w_wg).put(0, 0, 180)
if self.show_pins:
nd.put_stub(pinname="g1")
return nano_ant
def generate_gds_positive(self, sample_step=0.1):
"""
Generate a gds cell based on the logic of positive photoresistance.
| Positive: Define the etched region using GETCH_TRE layer.
"""
width_extra_trench = 0.1
theta_rad_max = self.max_theta * math.pi / 180
## Check if the input is appropriate or not
if math.floor(self.teeth_number) != self.teeth_number:
print("WARNNING :: Please re-check the vector of your antenna and make sure the length of vector is even.")
message = 'Inappropriate Definition of antenna.'
nd.text(text=message, height=5, layer=(96, 0), align='cc').put(0, 0)
return 0
## Build the structure
# Add input waveguide
nd.strt(length=self.taper_length-0.5, width=self.w_wg, xs=self.xs_wg).put(0, 0)
# Add the fan polygon region
radius_max = self.taper_length + sum(self.vector)
if self.width/2 > radius_max: theta_rad = theta_rad_max/2
elif math.asin(self.width/2/radius_max) > theta_rad_max/2: theta_rad = theta_rad_max/2
elif math.asin(self.width/2/radius_max) <= theta_rad_max/2: theta_rad = math.asin(self.width/2/radius_max)
theta_list = np.linspace(-theta_rad, theta_rad, math.floor(theta_rad*2*radius_max/sample_step))
fan_polygon = [(radius_max*math.cos(theta), radius_max*math.sin(theta)) for theta in theta_list]
# fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, self.w_wg/2),
# (0, -self.w_wg/2), (self.width/2/math.tan(theta_rad_max/2), -self.width/2)]
fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, 0),
(self.width/2/math.tan(theta_rad_max/2), -self.width/2)]
nd.Polygon(points=fan_polygon, layer="STRIP_COR").put(0, 0)
layer_cld = nd.get_layer("STRIP_CLD")
if layer_cld == "STRIP_CLD": # Add CLD region if necessary
nd.strt(length=self.ant_length+1, width=max(self.width+1, self.w_wg+4),
layer=layer_cld).put(0, 0)
# Add the teeth
radius_cur = self.taper_length
for teeth_index in range(0, int(self.teeth_number)):
## Determine the angular region first
if teeth_index == 0: radius_ref = radius_cur
else: radius_ref = radius_cur + self.vector[teeth_index*2-1]
if (self.width+width_extra_trench)/2 > radius_ref:
theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref
elif math.asin((self.width+width_extra_trench)/2/radius_ref) > theta_rad_max/2:
theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref
else:
theta_rad = math.asin((self.width+width_extra_trench)/2/radius_ref)
theta_step = sample_step / radius_ref
theta_list = np.linspace(-theta_rad, theta_rad, math.floor(2*theta_rad/theta_step))
## Construct the inner radius curve
if teeth_index == 0: radius_cur = radius_cur
else: radius_cur = radius_cur + self.vector[teeth_index*2-1]
inner_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list]
## Construct the outer radius curve
radius_cur = radius_cur + self.vector[teeth_index*2]
outer_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list]
outer_radius_curve.reverse()
## Add two dummy points to avoid sharp angle
offset_length = 0.015 / 2
minimum_etch = 0.2
radius_inner = radius_cur - self.vector[teeth_index*2]
radius_outer = radius_inner + self.vector[teeth_index*2]
x_1 = radius_inner * math.cos(theta_rad) + offset_length * math.cos(theta_rad)
y_1 = radius_inner * math.sin(theta_rad) + offset_length * math.sin(theta_rad)
vertical_length = math.sqrt(np.power(minimum_etch, 2) - np.power(offset_length, 2))
dummy1_x = x_1 - vertical_length * math.sin(theta_rad)
dummy1_y = y_1 + vertical_length * math.cos(theta_rad)
x_2 = radius_outer * math.cos(theta_rad) - offset_length * math.cos(theta_rad)
y_2 = radius_outer * math.sin(theta_rad) - offset_length * math.sin(theta_rad)
dummy2_x = x_2 - vertical_length * math.sin(theta_rad)
dummy2_y = y_2 + vertical_length * math.cos(theta_rad)
## Construct the teeth polygon
teeth_polygon = inner_radius_curve+[(dummy1_x,dummy1_y),(dummy2_x,dummy2_y)]+outer_radius_curve+[(dummy2_x, -dummy2_y),(dummy1_x,-dummy1_y)]
if self.etch_type == "single":
nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0)
if self.etch_type == "dual":
if teeth_index==0: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0)
else: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[1]+"_TRE").put(0, 0)
def generate_gds_error(self):
nd.text(text="This foundry is not compatiable with current device. Please check.", height=10, layer=1001).put(0, 0)
''' Class for 2D antenna array for FMF grating '''
class Taper() :
def __init__(self, width1=4, width2=0.45, length=30, type="linear", show_pins=False) -> None:
self.width1 = width1
self.width2 = width2
self.length = length
self.type = type
if self.type == "parabolic" : self.order = 2
elif self.type == "linear" : self.order = 1
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self) :
with nd.Cell(name="taper", instantiate=False) as ic :
if self.order == 1 :
strip = Route(radius=10,width=self.width1,xs='strip')
linear_taper = strip.taper(
length=self.length,width1=self.width1,width2=self.width2,patch=True).put(0,0,0)
output_strt = strip.strt(length=0.5,width=self.width2).put()
nd.Pin(name="a1",width=self.width1).put(linear_taper.pin['a0'])
nd.Pin(name="b1",width=self.width2).put(output_strt.pin['b0'])
else :
c2 = self.width1/2
c1 = (c2 - self.width2/2) / np.power(self.length, self.order)
x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2)))
taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list]
taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list]
taper_down_poly.reverse()
taper_poly = taper_up_poly + taper_down_poly
nd.Polygon(points=taper_poly, layer='STRIP_COR').put(0,0)
c2 = (self.width1+4)/2
c1 = (c2 - (self.width2+4)/2) / np.power(self.length, self.order)
x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2)))
taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list]
taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list]
taper_down_poly.reverse()
taper_poly = taper_up_poly + taper_down_poly
nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0)
width_max = np.max(np.array([self.width1, self.width2]))
taper_poly = [
(0, width_max/2+2), (0, -width_max/2-2),
(self.length, -width_max/2-2), (self.length, width_max/2+2)
]
nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0)
nd.strt(length=0.5, width=self.width2, xs='strip').put(self.length,0,0)
nd.Pin(name='a1',width=self.width1).put(0,0,180)
nd.Pin(name="b1",width=self.width2).put(self.length+0.5,0,0)
if self.show_pins :
nd.put_stub()
return ic
class Grating_2D_Hole() :
'''
This is a class for 2D Grating in IMEC.
'''
def __init__(
self,
w_wg=0.5,
w_gt=5, l_taper=30, type_taper="parabolic",
gt_vector=[0.5,0.5,0.5,0.5,0.5,], gt_diameter=0.4, gt_layer="STRIP_COR",
polysi_vector=[0.5,0.5,0.5,0.5,0.5], polysi_diameter=0.4, polysi_layer="FCW_TRE",
reflector_vector=[0.3,0.3,0.3,0.3,0.3,0.3],
l_field_center = 1
) -> None:
self.w_wg = w_wg
self.w_gt = w_gt
self.l_taper = l_taper
self.type_taper = type_taper
self.gt_vector = gt_vector
self.gt_num = len(self.gt_vector)
self.gt_diameter = gt_diameter
self.gt_layer =gt_layer
self.polysi_vector = polysi_vector
self.polysi_num = len(self.polysi_vector)
self.polysi_diameter = polysi_diameter
self.polysi_layer = polysi_layer
self.reflector_vector = reflector_vector
self.l_field_center = l_field_center
self.cell = self.generate_gds()
def generate_gds(self) :
with nd.Cell(name="2D_Grating", instantiate=False) as ic :
'''Generate the diffraction region first.'''
strip_cor_poly = [
(self.w_gt/2, self.w_gt/2), (self.w_gt/2, -self.w_gt/2),
(-self.w_gt/2, -self.w_gt/2), (-self.w_gt/2, self.w_gt/2)
]
nd.Polygon(points=strip_cor_poly, layer='STRIP_COR').put(0,0,0)
strip_cld_poly = [
(self.w_gt/2+2, self.w_gt/2+2), (self.w_gt/2+2, -self.w_gt/2-2),
(-self.w_gt/2-2, -self.w_gt/2-2), (-self.w_gt/2-2, self.w_gt/2+2)
]
nd.Polygon(points=strip_cld_poly, layer='STRIP_CLD').put(0,0,0)
'''Generate the reflection region.'''
self.reflector_num = int(len(self.reflector_vector)/2)
for index in range(self.reflector_num) :
loc = self.w_gt/2 + sum(self.reflector_vector[0:2*index+1]) + self.reflector_vector[2*index+1]/2
nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put(
-loc, -(self.w_gt)/2, 90
)
nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put(
-(self.w_gt)/2, -loc, 0
)
'''Generate the taper output.'''
taper = Taper(width1=self.w_gt, width2=self.w_wg, length=self.l_taper, type=self.type_taper)
taper_horizontal = taper.cell.put('a1', self.w_gt/2,0,0)
taper_vertical = taper.cell.put('a1',0,self.w_gt/2,90)
'''Generate the diffraction etched region.'''
theta_list = np.linspace(0, 2*np.pi, 32)
gt_ring_poly = [
(self.gt_diameter/2*np.cos(theta), self.gt_diameter/2*np.sin(theta)) for theta in theta_list
]
polysi_ring_poly = [
(self.polysi_diameter/2*np.cos(theta), self.polysi_diameter/2*np.sin(theta)) for theta in theta_list
]
self._generate_hole_array_(
polygon=gt_ring_poly, vector=self.gt_vector, layer=self.gt_layer
).put(self.w_gt/2, self.w_gt/2)
self._generate_hole_array_(
polygon=polysi_ring_poly, vector=self.polysi_vector, layer=self.polysi_layer
).put(self.w_gt/2, self.w_gt/2)
'''Put the pin location'''
nd.Pin(name='g1').put(taper_horizontal.pin['b1'])
nd.Pin(name='g2').put(taper_vertical.pin['b1'])
# nd.put_stub()
return ic
def _generate_hole_array_(self,polygon,vector,layer) :
with nd.Cell(name="diffration_"+layer, instantiate=False) as ic :
for lateral_index in range(len(vector)) :
for vertical_index in range(len(vector)) :
nd.Polygon(points=polygon,layer=layer).put(
-sum(vector[0:lateral_index+1]), -sum(vector[0:vertical_index+1])
)
return ic
class Grating_2D_Hole_4Rec() :
def __init__(self, grating_unit, mode_radius=8, cell_name=None, show_pins=False) -> None:
self.gt_2D_class = grating_unit
self.cell_unit = grating_unit.cell
self.mode_radius = mode_radius
# Calculate the field center location
# radius = np.sqrt(2)/2 * (
# self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) -
# np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2))
# )
# print("---------------------"+str(radius)+"------------------------------")
l_field_center = grating_unit.l_field_center
w_gt = grating_unit.w_gt
x0 = (
2*(w_gt/2-l_field_center)-np.sqrt(
8*mode_radius**2 - 4 * (w_gt/2 - l_field_center)**2
)
) / 4
self.field_center = (
x0 + mode_radius*np.cos(np.pi/4),
x0 + mode_radius*np.cos(np.pi/4),
180
)
self.cell_unit._put_pin(name='g0', connect=self.field_center)
self.show_pins = show_pins
self.cell_name = cell_name
self.cell = self.generate_gds()
def generate_gds(self) :
if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name
else : self.cell_name = "TwoD_Grating"
with nd.Cell(name=self.cell_name, instantiate=False) as ic :
gt_1 = self.cell_unit.put(
'g0', self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4)
)
gt_2 = self.cell_unit.put(
'g0', self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True
)
gt_3 = self.cell_unit.put(
'g0', -self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True, flop=True
)
gt_4 = self.cell_unit.put(
'g0', -self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4), flip=False, flop=True
)
'''Put OPEN and PATH region if necessary.'''
if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" :
nd.Polygon(
points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))),
layer="GC_OPEN"
).put(0,0)
if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" :
nd.Polygon(
points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))),
layer="STRIP_CLD"
).put(0,0)
''' Put Pins '''
nd.Pin(name='g1').put(gt_1.pin['g1'])
nd.Pin(name='g2').put(gt_1.pin['g2'])
nd.Pin(name='g3').put(gt_2.pin['g1'])
nd.Pin(name='g4').put(gt_2.pin['g2'])
nd.Pin(name='g5').put(gt_3.pin['g1'])
nd.Pin(name='g6').put(gt_3.pin['g2'])
nd.Pin(name='g7').put(gt_4.pin['g1'])
nd.Pin(name='g8').put(gt_4.pin['g2'])
nd.Pin(name='a0').put(gt_1.pin['g1'].x, 0, 0)
if self.show_pins :
nd.put_stub()
return ic
class Grating_2D_Hole_3Rec() :
def __init__(self, grating_unit, mode_radius=6.5, cell_name=None, show_pins=False) -> None:
self.gt_2D_class = grating_unit
self.cell_unit = grating_unit.cell
self.mode_radius = mode_radius
self.cell_name = cell_name
# Calculate the field center location
radius = np.sqrt(2)/2 * (
self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) -
np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2))
)
self.field_center = (
radius*np.cos(np.pi/4),
radius*np.cos(np.pi/4),
45
)
self.cell_unit._put_pin(name='g0', connect=self.field_center)
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self) :
if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name
else : self.cell_name = "TwoD_Grating"
with nd.Cell(name=self.cell_name, instantiate=False) as ic :
rotation_angle = 2*np.pi/3*0
gt_1 = self.cell_unit.put(
'g0',
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
180 + rotation_angle*180/np.pi
)
rotation_angle = 2*np.pi/3*1
gt_2 = self.cell_unit.put(
'g0',
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
180 + rotation_angle*180/np.pi
)
rotation_angle = 2*np.pi/3*2
gt_3 = self.cell_unit.put(
'g0',
self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle),
180 + rotation_angle*180/np.pi
)
'''Put OPEN and PATH region if necessary.'''
if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" :
nd.Polygon(
points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))),
layer="GC_OPEN"
).put(0,0)
if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" :
nd.Polygon(
points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))),
layer="STRIP_CLD"
).put(0,0)
'''Put pins'''
nd.Pin(name='g1').put(gt_1.pin['g1'])
nd.Pin(name='g2').put(gt_1.pin['g2'])
nd.Pin(name='g3').put(gt_2.pin['g1'])
nd.Pin(name='g4').put(gt_2.pin['g2'])
nd.Pin(name='g5').put(gt_3.pin['g1'])
nd.Pin(name='g6').put(gt_3.pin['g2'])
if self.show_pins :
nd.put_stub()
return ic
""" Renamed for simplification in 2023.04.02 """
class GC_STD_2D:
def __init__(self,
name=None,
etch_type :str = 'FETCH',
xs_wg:str='grating',
Dx_hole:float=0.3,
Dy_hole:float=0.3,
hole_shape :str= 'circle',
shape:str = 'circle',
xs_open:str = None,
Px:float=0.57,
Py:float=0.57,
num_x:float=25,
num_y:float=25,
Lx_taper:float = 50,
Ly_taper:float = 0,
Lx_end:float = 1,
Ly_end:float = 1,
Lx_side:float = 0.5,
Ly_side:float = 0.5,
Lx_port:float=5,
Ly_port:float=5,
w_wg:float=0.5,
show_pins:bool=False,
P_AR: float = 0.6,
L_AR: float = 1,
):
"""_summary_
Args:
etch_type (str, optional): Three etch depth for election , full-etch = "FETCH", middle etch = "METCH", shallow etch = "ETCH". Defaults to 'FETCH'.
xs_wg (str, optional): xsection of the grating and also the output waveguide. Defaults to 'grating'.
Dx_hole (float, optional): size X of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3.
Dy_hole (float, optional): size Y of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3.
hole_shape (str, 'circle' | 'rectangel'): shape of the hole. Defaults to 'circle'.
shape (str, 'circle' | 'rectangel'): shape of the grating. Defaults to 'circle'.
Px (float, optional): Period distance X. Defaults to 0.57.
Py (float, optional): Period distance Y. Defaults to 0.57.
num_x (int, optional): number of pitches. Defaults to 25.
num_y (int, optional): number of pitches. Defaults to 25.
Lx_taper (int, optional): taper connection to the port. Defaults to 50.
Ly_taper (int, optional): taper connection to the port. Defaults to 0.
Lx_end (int, optional): length arratched to the end. Defaults to 5.
Ly_end (int, optional): length arratched to the end. Defaults to 3.
Lx_side (float, optional): side expansion. Defaults to 0.5.
Ly_side (float, optional): side expansion. Defaults to 0.5.
Lx_port (int, optional): output port length expansion. Defaults to 5.
Ly_port (int, optional): output port length expansion. Defaults to 5.
w_wg (float, optional): output port width. Defaults to 0.5.
show_pins (bool, optional): _description_. Defaults to False.
Raises:
Exception: Period do not match D_hole
"""
self.name = name
if (self.name==None):
self.instantiate = False
else :
self.instantiate = True
if (isinstance(Px,int) or isinstance(Px,float)) : Px = Px * np.ones(num_x)
if (isinstance(Py,int) or isinstance(Py,float)) : Py = Py * np.ones(num_y)
if (isinstance(Dx_hole,int) or isinstance(Dx_hole,float)) : Dx_hole = Dx_hole * np.ones((num_x))
if (isinstance(Dy_hole,int) or isinstance(Dy_hole,float)) : Dy_hole = Dy_hole * np.ones((num_y))
self.num_x = len(Px)
self.num_y = len(Py)
if (len(Px)!=len(Dx_hole) or len(Py)!=len(Dy_hole)):
raise Exception("In Grating define : [Period] length not matching [D_hole] length")
self.Lx_taper = Lx_taper
self.Ly_taper = Ly_taper
self.Lx_end = Lx_end
self.Ly_end = Ly_end
self.Lx_side = Lx_side
self.Ly_side = Ly_side
self.Lx_port = Lx_port
self.Ly_port = Ly_port
self.xs_open = xs_open
self.w_wg = w_wg
self.xs_wg = xs_wg
self.etch_type = etch_type
self.shape = shape
self.hole_shape = hole_shape
self.Dx_hole = Dx_hole
self.Dy_hole = Dy_hole
self.Px = Px
self.Py = Py
self.P_AR = P_AR
self.L_AR = L_AR
self.show_pins = show_pins
if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"):
self.positive = False
if (hole_shape=='circle'):
if (etch_type=="FETCH"):
layer_etch = "STRIP_HOL"
elif (etch_type=="METCH"):
layer_etch = "RIB_HOL"
elif (etch_type=="SETCH"):
layer_etch = "SRIB_HOL"
elif (hole_shape=='rectangle'):
if (etch_type=="FETCH"):
layer_etch = "STRIP_TRE"
elif (etch_type=="METCH"):
layer_etch = "RIB_TRE"
elif (etch_type=="SETCH"):
layer_etch = "SRIB_TRE"
else :
self.positive = True
if (etch_type=="FETCH"):
layer_etch = None
elif (etch_type=="METCH"):
layer_etch = "RIB_COR"
elif (etch_type=="SETCH"):
layer_etch = "SRIB_COR"
self.layer_etch = layer_etch
if (layer_etch!=None):
if (nd.get_layer(layer_etch)!=layer_etch):
layer_etch=None
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
if (self.positive):
self.cell = self.generate_positive()
else:
self.cell = self.generate_negative()
def generate_negative(self):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
## arc shape grating
if (self.shape=='circle' or self.shape=='arc'):
print("Sorry, this function has not been built up")
## retangular grating
elif (self.shape=='rectangle'):
Lx = sum(self.Px)+self.Lx_side*2
# if (self.Ly_taper==0):
Ly = sum(self.Py)+self.Ly_side*2
# else:
# Ly = sum(self.Py)
y_offset = sum(self.Py)/2
x_offset = sum(self.Px)/2
nd.strt(length=Lx,width=Ly,xs=self.xs_wg).put(-Lx/2,0,0)
if (self.xs_open!=None):
circle(radius=max([Lx,Ly])*2/2,width=max([Lx,Ly])*2,xs=self.xs_open,
# n_points=32
).cell.put(0,0,0)
for _x_ in range(0,self.num_x):
for _y_ in range(0,self.num_y):
pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2-x_offset
pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2
if (self.hole_shape=='circle'):
circle(radius=self.Dx_hole[_x_]/4,width=self.Dx_hole[_x_]/2,layer=self.layer_etch,
# n_points=32,
sharp_patch=False).cell.put(pos_x,pos_y,0)
elif (self.hole_shape=='rectangle'):
nd.strt(length=self.Dx_hole[_x_],width=self.Dy_hole[_y_],layer=self.layer_etch).put(pos_x-self.Dx_hole[_x_]/2,pos_y,0)
else :
raise Exception("ERROR: In <mxpic::passive::GC_STD_2D>, <hole_shape> is not defined, please input [circle | rectangle]")
if (self.Ly_taper!=0):
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,Ly/2,90)
if (self.P_AR>0):
_num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1)
for _idx_ in range(0,_num_AR_):
# nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(0,self.P_AR/2+Ly/2+self.Ly_end+self.P_AR*_idx_,90)
nd.strt(xs=self.xs_wg,width=self.P_AR/2,length=Lx).put(-Lx/2,self.P_AR+Ly/2+self.Ly_end+self.P_AR*_idx_,0)
# if (self.P_AR >0 and self.L_AR>0):
# _num_AR_ = int(np.floor(Ly/self.P_AR))
# for _idx_ in range(0,_num_AR_):
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90)
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,-Ly/2,-90)
y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Ly_taper).put()
y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put()
nd.Pin(name='g2',pin=y_port.pin['b0']).put()
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx/2,0,0)
## adding anti reflection
if (self.Lx_taper!=0):
if (self.P_AR>0):
_num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1)
for _idx_ in range(0,_num_AR_):
# nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(self.P_AR/2+Lx/2+self.Lx_end+self.P_AR*_idx_,0,0)
nd.strt(xs=self.xs_wg,length=Ly,width=self.P_AR/2).put(self.P_AR+Lx/2+self.Lx_end+self.P_AR*_idx_,-Ly/2,90)
# _num_AR_ = int(np.floor(Ly/self.P_AR))
# for _idx_ in range(0,_num_AR_):
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0)
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(-Lx/2,0,180)
x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put()
x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put()
nd.Pin(name='g1',pin=x_port.pin['b0']).put()
# print("Sorry, this function has not been built up")
else :
raise Exception("In Grating define : [shape] not defined")
if (self.show_pins):
nd.put_stub(pinsize=3)
return C
def generate_positive(self):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
## arc shape grating
if (self.shape=='circle' or self.shape=='arc'):
print("Sorry, this function has not been built up")
pass
## retangular grating
elif (self.shape=='rectangle'):
Lx = sum(self.Px)
if (self.Ly_taper==0):
Ly = sum(self.Py)+self.Ly_side*2
nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,sum(self.Py)/2+self.Lx_side/2,0)
nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,-sum(self.Py)/2-self.Lx_side/2,0)
else:
Ly = sum(self.Py)
y_offset = sum(self.Py)/2
if (self.layer_etch!=None):
nd.strt(length=Lx,width=Ly,layer=self.layer_etch).put(0,0,0)
if (self.xs_open!=None):
nd.strt(length=Lx*2,width=Ly*2,xs=self.xs_open).put(-Lx/2,0,0)
for _x_ in range(0,self.num_x):
for _y_ in range(0,self.num_y):
pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2
pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2
hole(r_hole=self.Dx_hole[_x_]/2,Lx_sq=self.Px[_x_],Ly_sq=self.Py[_y_],
Dx_hole=self.Dx_hole[_x_],Dy_hole=self.Dy_hole[_y_],
# n_points=12,
xs=self.xs_wg,hole_shape=self.hole_shape).cell.put(pos_x,pos_y,0)
if (self.Ly_taper!=0):
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,Ly/2,90)
_num_AR_ = int(np.floor(Ly/self.P_AR))
for _idx_ in range(0,_num_AR_):
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90)
nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,-Ly/2,-90)
y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put()
y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put()
nd.Pin(name='g2',pin=y_port.pin['b0']).put()
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx,0,0)
## adding anti reflection
_num_AR_ = int(np.floor(Ly/self.P_AR))
for _idx_ in range(0,_num_AR_):
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0)
nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(0,0,180)
x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg).put()
x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put()
nd.Pin(name='g1',pin=x_port.pin['b0']).put()
else :
raise Exception("In Grating define : [shape] not defined")
if (self.show_pins):
nd.put_stub(pinsize=3)
return C
def generate_test_gds(self,dX_gc2gc=300):
with nd.Cell(instantiate=False) as C:
self.cell.put('g1',-dX_gc2gc/2,0,180)
self.cell.put('g1', dX_gc2gc/2,0,0)
nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0)
return C
class GC_STD_1D:
def __init__ (self,
name=None,
xs_wg : str = 'strip',
w_wg : float = 0.5,
etch_type :str = 'FETCH',
xs_open :str=None,
L_taper :float = 10,
L_end :float = 2,
A_taper :float = 30,
Period :float = 0.5,
eta_etch :float = 0.5,
num :float = 20, ### note, when Period and eta is defined as list, this is not usefull
sector_gc :bool =True,
show_pins=False,
L_tail = 2,
# n_points = 64,
P_AR: float = 1, ### adding anti reflection pitches
L_AR: float = 2,
):
self.name = name
if (self.name==None):
self.instantiate = False
else :
self.instantiate = True
if (xs_open!=None):
try: nd.get_xsection(xs_open)
except:
xs_open=None
print("WARNING:In <mxpic::passive::GC_STD_1D>, <xs_open>::",xs_open," not defined in tapeout")
self.xs_open=xs_open
self.xs_wg=xs_wg
self.w_wg=w_wg
self.L_taper=L_taper
self.L_end=L_end
self.A_taper=A_taper
self.show_pins = show_pins
self.L_tail = L_tail
if (isinstance(eta_etch,list) or isinstance(eta_etch,np.ndarray)):
num = len(eta_etch)
if (isinstance(Period,list) or isinstance(Period,np.ndarray)):
num = len(Period)
if (isinstance(Period,int) or isinstance(Period,float)):
Period = Period*np.ones(num)
if (isinstance(eta_etch,int) or isinstance(eta_etch,float)):
eta_etch = eta_etch*np.ones(num)
""" Generate ERROR """
if (len(Period)!=len(eta_etch)):
raise Exception("ERROR: In <mxpic::passive::GC_STD_1D> : [Period] length not matching [eta_etch] length")
if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"):
self.positive = False
if (etch_type=="FETCH"):
layer_etch = "STRIP_TRE"
elif (etch_type=="METCH"):
layer_etch = "RIB_TRE"
elif (etch_type=="SETCH"):
layer_etch = "SRIB_TRE"
else :
self.positive = True
if (etch_type=="FETCH"):
layer_etch = None
elif (etch_type=="METCH"):
layer_etch = "RIB_COR"
elif (etch_type=="SETCH"):
layer_etch = ["SRIB_COR","RIB_COR"]
self.Period=Period
self.eta_etch=eta_etch
self.num=len(Period)
self.sector_gc=sector_gc
# self.n_points = n_points ## revise 2022.08.18
self.L_AR = L_AR
self.P_AR = P_AR
if (layer_etch!=None):
if (isinstance(layer_etch,str)):
if (nd.get_layer(layer_etch)!=layer_etch):
layer_etch=None
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
else :
for _layer_ in layer_etch:
if (nd.get_layer(_layer_)!=_layer_):
layer_etch=None
print("WARNING: In mxpic::passive::GC_STD_1D, <layer_etch>::",layer_etch," not defined in tapeout")
self.layer_etch = layer_etch
if (self.positive):
self.cell = self.generate_positive()
else:
self.cell = self.generate_negative()
def generate_negative(self):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
## arc shape grating
if (self.sector_gc == True):
L_total = np.sum(self.Period) + self.L_taper + self.L_end
L_tail = self.L_tail
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
(a1,b1), (a2,b2),c1,c2 = growx
x_offset = -b1/np.sin(self.A_taper/2*np.pi/180)
if (self.P_AR>0 and self.L_AR>0): ## anti reflection
r_tap = L_total*1.3 - b1 - x_offset
else :
r_tap = L_total + b1 - x_offset
circle(radius=r_tap/2,width=r_tap,
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
# n_points=self.n_points,
layer=layers).cell.put(x_offset,0,0)
_L_tail_ = np.abs(x_offset)
if _L_tail_ > L_tail:
L_tail = _L_tail_
r_grat_inner = self.L_taper
for _idx_ in range(0,self.num):
d_pitch = self.Period[_idx_]*self.eta_etch[_idx_]
circle(radius=r_grat_inner + d_pitch/2,
width=d_pitch,
theta_start=-self.A_taper/2-5,theta_stop=self.A_taper/2+5,
# n_points=self.n_points,
layer=self.layer_etch).cell.put(0,0,0)
r_grat_inner = r_grat_inner + self.Period[_idx_]
L_open = 1.5*(L_total-self.L_taper)
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
x_open = (L_total+self.L_taper)/2 - L_open/2
if (self.xs_open!=None):
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
nd.strt(length=L_tail,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0)
nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180)
nd.strt(length=np.abs(self.w_wg/2/np.tan(self.A_taper/2/180*np.pi)),width=self.w_wg,xs=self.xs_wg).put(0,0,0)
## retangular grating
else:
L_total = np.sum(self.Period) + self.L_taper + self.L_end
L_grat = sum(self.Period)+self.L_end
W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2
nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0)
nd.strt(length=L_grat,width=W_grat,xs=self.xs_wg).put(self.L_taper,0,0)
### adding Anti-reflection
# if (self.P_AR>0 and self.L_AR>0):
# _num_AR_ = int(np.floor(W_grat/self.P_AR))
# for _idx_ in range(0,_num_AR_):
# nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0)
x_grat = self.L_taper
for _idx_ in range(0,self.num):
nd.strt(length=self.Period[_idx_]*self.eta_etch[_idx_],width=W_grat+2,layer=self.layer_etch).put(x_grat,0,0)
x_grat = x_grat + self.Period[_idx_]
nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0)
nd.Pin(name='g1',width=self.w_wg).put(-5,0,180)
L_open = 1.5*(L_total-self.L_taper)
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
x_open = (L_total+self.L_taper)/2 - L_open/2
if (self.xs_open!=None):
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
pass
if (self.show_pins):
nd.put_stub()
return C
def generate_positive(self):
with nd.Cell(instantiate=False) as C:
## arc shape grating
if (self.sector_gc==True):
L_tail = self.L_tail
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
(a1,b1), (a2,b2),c1,c2 = growx
x_offset = -b1/np.sin(self.A_taper/2*np.pi/180)
r_tap = self.L_taper + b1 - x_offset
circle(radius=r_tap/2,width=r_tap,
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
# n_points=self.n_points,
layer=layers).cell.put(x_offset,0,0)
r_grat_inner = self.L_taper - x_offset
_L_tail_ = np.abs(x_offset)
if _L_tail_ > L_tail:
L_tail = _L_tail_
for _idx_ in range(0,self.num):
d_pitch = self.Period[_idx_]*(1-self.eta_etch[_idx_])+b1*2
circle(radius=r_grat_inner + self.Period[_idx_]*self.eta_etch[_idx_] + d_pitch/2,
width=d_pitch,
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
# n_points=self.n_points,
layer=layers).cell.put(x_offset,0,0)
r_grat_inner = r_grat_inner + self.Period[_idx_]
d_pitch = self.L_end+b1*2
r_grat_inner = r_grat_inner + self.Period[-1]*self.eta_etch[-1]
circle(radius=r_grat_inner + d_pitch/2,
width=d_pitch,
theta_start=-self.A_taper/2,theta_stop=self.A_taper/2,
# n_points=64,
layer=layers).cell.put(x_offset,0,0)
nd.strt(length=L_tail*2,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0)
nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180)
L_total = np.sum(self.Period) + self.L_taper + self.L_end
L_open = 1.5*(L_total-self.L_taper)
W_open = 1.2*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
x_open = (L_total+self.L_taper)/2 - L_open/2
if (self.layer_etch!=None):
if (isinstance(self.layer_etch,str)):
nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0)
elif(isinstance(self.layer_etch,list)):
for _layer_ in self.layer_etch:
nd.strt(length=L_open,width=W_open,layer=_layer_).put(x_open,0,0)
if (self.xs_open!=None):
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
## retangular grating
elif (self.shape=='rectangle'):
L_grat = sum(self.Period)
W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2
nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0)
x_grat = self.L_taper
for _idx_ in range(0,self.num):
nd.strt(length=self.Period[_idx_]*(1-self.eta_etch[_idx_]),width=W_grat,xs=self.xs_wg).put(x_grat+self.Period[_idx_]*self.eta_etch[_idx_],0,0)
x_grat = x_grat + self.Period[_idx_]
nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0)
nd.Pin(name='g1',width=self.w_wg).put(-5,0,180)
### adding Anti-reflection
if (self.P_AR>0 and self.L_AR>0):
_num_AR_ = int(np.floor(W_grat/self.P_AR))
for _idx_ in range(0,_num_AR_):
nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0)
L_total = np.sum(self.Period) + self.L_taper + self.L_end
L_open = 1.5*(L_total-self.L_taper)
W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2
x_open = (L_total+self.L_taper)/2 - L_open/2
if (self.layer_etch!=None):
nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0)
if (self.xs_open!=None):
nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0)
pass
else :
raise Exception("In Grating define : [shape] not defined")
if (self.show_pins):
nd.put_stub()
return C
def generate_test_dev(self,dX_gc2gc):
with nd.Cell(instantiate=False) as C:
self.cell.put('g1',-dX_gc2gc/2,0,180)
self.cell.put('g1', dX_gc2gc/2,0,0)
nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0)
return C
class FA:
def __init__(self,fiber_coupler,pitch,number,show_pins=False):
# if (isinstance(fiber_coupler,nd.Cell)):
# fiber_cell = fiber_coupler
# elif (hasattr(fiber_coupler,'cell')):
# fiber_cell = fiber_coupler.cell
# else:
# raise Exception("ERROR: In <mxpic.passive.FA>, <fiber_coupler> not recongized, please input nazca.cell or classes that has nazca.cell")
fiber_cell = __cell_arg__(arg=fiber_coupler,arg_name="fiber_coupler",func_name="mxpic::FA")
pin_in_name = []
for name,Pin in fiber_cell.ic_pins():
pin_in_name = pin_in_name+[name]
# pin_in_name.append(name)
if ('g1' in pin_in_name):
pin_name = 'g1'
else:
pin_name = 'a0'
print("WARNING: In <mxpic.passive.FA>, <fiber_coupler> dose not contain 'g1' pin, using 'a0' in default")
self.pitch = pitch
self.number = number
with nd.Cell(instantiate=False) as C:
for idx in range(1,number+1):
port = fiber_cell.put(pin_name,0,pitch*(idx-number/2-1/2),0)
nd.Pin('g'+str(idx),pin=port.pin[pin_name]).put()
x_out = port.pin['b0'].x
nd.Pin(name='b0').put(x_out,0,180)
if (show_pins):
nd.put_stub(pinsize=3)
self.cell = C