Files
mxpic_forge/mxpic/components/primitives/pic/gratings.py
T

1519 lines
67 KiB
Python

from typing import Any, Optional
import nazca as nd
import numpy as np
import math
from ...geometry import *
from ...geometry 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.
Parameters
----------
w_wg : float, optional
Width parameter in microns. Default is 0.41.
xs_wg : str, optional
Layer or cross-section name used by the device. Default is 'strip'.
define_type : str, optional
Value for the define_type parameter. Default is 'non-periodic'.
vector : list, optional
Value for the vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5, 0.5].
taper_length : float, optional
Value for the taper_length parameter. Default is 3.
width : float, optional
Width parameter in microns. Default is 6.
max_theta : float, optional
Value for the max_theta parameter. Default is 110.
pitch : float, optional
Spacing or gap parameter in microns. Default is 0.6.
duty_cycle : float, optional
Value for the duty_cycle parameter. Default is 0.3.
teeth_number : float, optional
Value for the teeth_number parameter. Default is 6.
etch_depth : list, optional
Value for the etch_depth parameter. Default is ['METCH'].
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is True.
"""
def __init__(
self,
w_wg: float = 0.41,
xs_wg: str = "strip",
define_type: str = "non-periodic",
vector: list = [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: list = ["METCH"],
show_pins: bool = True
) -> None:
# 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() :
"""
Taper primitive component.
This component builds the Taper layout cell.
Parameters
----------
width1 : float, optional
Width parameter in microns. Default is 4.
width2 : float, optional
Width parameter in microns. Default is 0.45.
length : float, optional
Length parameter in microns. Default is 30.
type : str, optional
Value for the type parameter. Default is 'linear'.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
"""
def __init__(self, width1: float=4, width2: float=0.45, length: float=30, type: str="linear", show_pins: bool=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()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name="a1",width=self.width1).put(linear_taper.pin['a0'])
nd.Pin(name="opt_a1",width=self.width1,type="optical:").put(linear_taper.pin['a0'])
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name="b1",width=self.width2).put(output_strt.pin['b0'])
nd.Pin(name="opt_b1",width=self.width2,type="optical:").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)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',width=self.width1).put(0,0,180)
nd.Pin(name='opt_a1',width=self.width1,type="optical:").put(0,0,180)
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name="b1",width=self.width2).put(self.length+0.5,0,0)
nd.Pin(name="opt_b1",width=self.width2,type="optical:").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.
Parameters
----------
w_wg : float, optional
Width parameter in microns. Default is 0.5.
w_gt : float, optional
Width parameter in microns. Default is 5.
l_taper : float, optional
Value for the l_taper parameter. Default is 30.
type_taper : str, optional
Value for the type_taper parameter. Default is 'parabolic'.
gt_vector : list, optional
Value for the gt_vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5].
gt_diameter : float, optional
Value for the gt_diameter parameter. Default is 0.4.
gt_layer : str, optional
Value for the gt_layer parameter. Default is 'STRIP_COR'.
polysi_vector : list, optional
Value for the polysi_vector parameter. Default is [0.5, 0.5, 0.5, 0.5, 0.5].
polysi_diameter : float, optional
Value for the polysi_diameter parameter. Default is 0.4.
polysi_layer : str, optional
Value for the polysi_layer parameter. Default is 'FCW_TRE'.
reflector_vector : list, optional
Value for the reflector_vector parameter. Default is [0.3, 0.3, 0.3, 0.3, 0.3, 0.3].
l_field_center : float, optional
Value for the l_field_center parameter. Default is 1.
"""
def __init__(
self,
w_wg: float=0.5,
w_gt: float=5, l_taper: float=30, type_taper: str="parabolic",
gt_vector: list=[0.5,0.5,0.5,0.5,0.5,], gt_diameter: float=0.4, gt_layer: str="STRIP_COR",
polysi_vector: list=[0.5,0.5,0.5,0.5,0.5], polysi_diameter: float=0.4, polysi_layer: str="FCW_TRE",
reflector_vector: list=[0.3,0.3,0.3,0.3,0.3,0.3],
l_field_center: float = 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)
## revised in 2026.06.07 by Qin Yue
# legacy: taper_horizontal = taper.cell.put('a1', self.w_gt/2,0,0)
taper_horizontal = taper.cell.put('opt_a1', self.w_gt/2,0,0)
## revised in 2026.06.07 by Qin Yue
# legacy: taper_vertical = taper.cell.put('a1',0,self.w_gt/2,90)
taper_vertical = taper.cell.put('opt_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'''
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='g1').put(taper_horizontal.pin['b1'])
nd.Pin(name='g1').put(taper_horizontal.pin['opt_b1'])
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='g2').put(taper_vertical.pin['b1'])
nd.Pin(name='g2').put(taper_vertical.pin['opt_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() :
"""
Grating 2D Hole 4Rec primitive component.
This component builds the Grating 2D Hole 4Rec layout cell.
Parameters
----------
grating_unit : Any
Grating unit cell or component used by this wrapper.
mode_radius : int, optional
Value for the mode_radius parameter. Default is 8.
cell_name : Optional[str], optional
Optional generated cell name. Default is None.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
"""
def __init__(self, grating_unit: Any, mode_radius: int=8, cell_name: Optional[str]=None, show_pins: bool=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() :
"""
Grating 2D Hole 3Rec primitive component.
This component builds the Grating 2D Hole 3Rec layout cell.
Parameters
----------
grating_unit : Any
Grating unit cell or component used by this wrapper.
mode_radius : float, optional
Value for the mode_radius parameter. Default is 6.5.
cell_name : Optional[str], optional
Optional generated cell name. Default is None.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
"""
def __init__(self, grating_unit: Any, mode_radius: float=6.5, cell_name: Optional[str]=None, show_pins: bool=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:
"""
GC STD 2D primitive component.
This component builds the GC STD 2D layout cell.
Parameters
----------
name : Optional[str], optional
Unique identifier for the device cell. Default is None.
etch_type : str, optional
Value for the etch_type parameter. Default is 'FETCH'.
xs_wg : str, optional
Layer or cross-section name used by the device. Default is 'grating'.
Dx_hole : float, optional
Value for the Dx_hole parameter. Default is 0.3.
Dy_hole : float, optional
Value for the Dy_hole parameter. Default is 0.3.
hole_shape : str, optional
Value for the hole_shape parameter. Default is 'circle'.
shape : str, optional
Value for the shape parameter. Default is 'circle'.
xs_open : str, optional
Layer or cross-section name used by the device. Default is None.
Px : float, optional
Value for the Px parameter. Default is 0.57.
Py : float, optional
Value for the Py parameter. Default is 0.57.
num_x : float, optional
Count or repetition parameter. Default is 25.
num_y : float, optional
Count or repetition parameter. Default is 25.
Lx_taper : float, optional
Length parameter in microns. Default is 50.
Ly_taper : float, optional
Length parameter in microns. Default is 0.
Lx_end : float, optional
Length parameter in microns. Default is 1.
Ly_end : float, optional
Length parameter in microns. Default is 1.
Lx_side : float, optional
Length parameter in microns. Default is 0.5.
Ly_side : float, optional
Length parameter in microns. Default is 0.5.
Lx_port : float, optional
Length parameter in microns. Default is 5.
Ly_port : float, optional
Length parameter in microns. Default is 5.
w_wg : float, optional
Width parameter in microns. Default is 0.5.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
P_AR : float, optional
Value for the P_AR parameter. Default is 0.6.
L_AR : float, optional
Length parameter in microns. Default is 1.
"""
def __init__(self,
name: Optional[str]=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,
) -> None:
"""_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:
"""
GC STD 1D primitive component.
This component builds the GC STD 1D layout cell.
Parameters
----------
name : Optional[str], optional
Unique identifier for the device cell. Default is None.
xs_wg : str, optional
Layer or cross-section name used by the device. Default is 'strip'.
w_wg : float, optional
Width parameter in microns. Default is 0.5.
etch_type : str, optional
Value for the etch_type parameter. Default is 'FETCH'.
xs_open : str, optional
Layer or cross-section name used by the device. Default is None.
L_taper : float, optional
Length parameter in microns. Default is 10.
L_end : float, optional
Length parameter in microns. Default is 2.
A_taper : float, optional
Angle parameter in degrees. Default is 30.
Period : float, optional
Value for the Period parameter. Default is 0.5.
eta_etch : float, optional
Value for the eta_etch parameter. Default is 0.5.
num : float, optional
Count or repetition parameter. Default is 20.
sector_gc : bool, optional
Value for the sector_gc parameter. Default is True.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
L_tail : int, optional
Length parameter in microns. Default is 2.
P_AR : float, optional
Value for the P_AR parameter. Default is 1.
L_AR : float, optional
Length parameter in microns. Default is 2.
"""
def __init__ (self,
name: Optional[str]=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: bool=False,
L_tail: int = 2,
# n_points = 64,
P_AR: float = 1, ### adding anti reflection pitches
L_AR: float = 2,
) -> None:
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.sector_gc==False):
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 GC_SiN_Si_Dual_Layer:
"""
GC SiN Si Dual Layer primitive component.
This component builds the GC SiN Si Dual Layer layout cell.
Parameters
----------
name : str, optional
Unique identifier for the device cell. Default is None.
w_teeth_SiN : list or float, optional
Width parameter in microns. Default is 0.5.
gap_teeth_SiN : list or float, optional
Spacing or gap parameter in microns. Default is 0.5.
w_teeth_Si : list or float, optional
Width parameter in microns. Default is 0.5.
gap_teeth_Si : list or float, optional
Spacing or gap parameter in microns. Default is 0.5.
ori_teeth_offset : float, optional
Value for the ori_teeth_offset parameter. Default is 5.0.
n_teeth_Si : float, optional
Value for the n_teeth_Si parameter. Default is 30.
n_teeth_SiN : float, optional
Value for the n_teeth_SiN parameter. Default is 30.
A_gc_taper : float, optional
Angle parameter in degrees. Default is 25.0.
R_teeth_ori_SiN : float, optional
Radius parameter in microns. Default is 40.0.
R_teeth_ori_Si : float, optional
Radius parameter in microns. Default is 40.0.
L_end_Si : float, optional
Length parameter in microns. Default is 0.2.
L_end_SiN : float, optional
Length parameter in microns. Default is 5.0.
w_port : float, optional
Width parameter in microns. Default is 0.9.
A_anti_rfl : float, optional
Angle parameter in degrees. Default is 4.0.
layer_SiN_slab : str, optional
Layer or cross-section name used by the device. Default is None.
layer_Si_slab : str, optional
Layer or cross-section name used by the device. Default is None.
layer_Si_teeth : str, optional
Layer or cross-section name used by the device. Default is None.
layer_SiN_teeth : str, optional
Layer or cross-section name used by the device. Default is None.
layer_SiN_etch : str, optional
Layer or cross-section name used by the device. Default is None.
layer_Si_etch : str, optional
Layer or cross-section name used by the device. Default is None.
layer_ox_open : str, optional
Layer or cross-section name used by the device. Default is None.
"""
def __init__(self,
name:str=None,
w_teeth_SiN:'list|float' = 0.5,
gap_teeth_SiN:'list|float' = 0.5,
w_teeth_Si:'list|float' = 0.5,
gap_teeth_Si:'list|float' = 0.5,
ori_teeth_offset:float = 5.0,
n_teeth_Si:float=30,
n_teeth_SiN:float=30,
A_gc_taper:float=25.0,
R_teeth_ori_SiN:float=40.0,
R_teeth_ori_Si:float=40.0,
L_end_Si:float=0.2,
L_end_SiN:float=5.0,
w_port : float = 0.9,
A_anti_rfl:float = 4.0,
layer_SiN_slab:str=None,
layer_Si_slab:str=None,
layer_Si_teeth:str=None,
layer_SiN_teeth:str=None,
layer_SiN_etch:str=None,
layer_Si_etch:str=None,
layer_ox_open:str=None,
):
self.name = name
self.w_teeth_SiN = w_teeth_SiN
self.gap_teeth_SiN = gap_teeth_SiN
self.w_teeth_Si = w_teeth_Si
self.gap_teeth_Si = gap_teeth_Si
self.ori_teeth_offset = ori_teeth_offset
self.n_teeth_SiN = n_teeth_SiN
self.n_teeth_Si = n_teeth_Si
self.A_gc_taper = A_gc_taper
self.w_port = w_port
self.L_end_Si = L_end_Si
self.L_end_SiN = L_end_SiN
self.A_anti_rfl = A_anti_rfl
self.R_teeth_ori_SiN = R_teeth_ori_SiN
self.R_teeth_ori_Si = R_teeth_ori_Si
self.layer_SiN_slab = layer_SiN_slab
self.layer_Si_slab = layer_Si_slab
self.layer_Si_teeth = layer_Si_teeth
self.layer_SiN_teeth = layer_SiN_teeth
self.layer_SiN_etch = layer_SiN_etch
self.layer_Si_etch = layer_Si_etch
self.layer_ox_open = layer_ox_open
self.cell = self.generate_gds()
def generate_gds(self):
""" creating instance cell or not """
if (self.name is None) : self.instantiate = False
else : self.instantiate = True
""" """
if (isinstance(self.w_teeth_SiN,list) or isinstance(self.w_teeth_SiN,np.ndarray)):
n_teeth_SiN = len(self.w_teeth_SiN)
elif (isinstance(self.w_teeth_SiN,float)):
n_teeth_SiN = self.n_teeth_SiN
w_teeth_SiN = [w_teeth_SiN]*n_teeth_SiN
""" """
if (isinstance(self.w_teeth_Si,list) or isinstance(self.w_teeth_Si,np.ndarray)):
n_teeth_Si = len(self.w_teeth_Si)
elif (isinstance(self.w_teeth_Si,float)):
n_teeth_Si = self.n_teeth_Si
w_teeth_Si = [w_teeth_Si]*n_teeth_Si
with nd.Cell(instantiate=self.instantiate, name=self.name) as C:
""" Creating SiN layer grating """
## whole area where the grating area covered
L_gc = self.R_teeth_ori_SiN + self.L_end_SiN + sum(self.w_teeth_SiN) + sum(self.gap_teeth_SiN)
w_box_gc = L_gc*np.sin(self.A_gc_taper/2*np.pi/180)*2
L_box_gc = L_gc*np.cos(self.A_gc_taper/2*np.pi/180)
x_slab = [0,L_box_gc,L_gc+w_box_gc*np.sin(self.A_anti_rfl*np.pi/180),L_gc,L_box_gc,0]
y_slab = [self.w_port/2,w_box_gc/2,w_box_gc/2,-w_box_gc/2,-w_box_gc/2,-self.w_port/2]
_my_polygon(layer_wg=self.layer_SiN_slab,vtx=np.c_[x_slab,y_slab]).put(0,0,0)
# circle(radius=self.R_teeth_ori_SiN/2,angle=self.A_gc_taper,layer=self.layer_SiN_slab,
# width=self.R_teeth_ori_SiN).cell.put(0,0,-self.A_gc_taper/2)
A_etch_ext = 4
## Placing teeth
r_in = self.R_teeth_ori_SiN
for idxT in range(0,n_teeth_SiN):
r_out = r_in + self.gap_teeth_SiN[idxT]
circle(radius=(r_out+r_in)/2,angle=self.A_gc_taper+A_etch_ext,layer=self.layer_SiN_etch,
width=self.gap_teeth_Si[idxT]).cell.put(0,0,-self.A_gc_taper/2-A_etch_ext/2)
r_in = r_out + self.w_teeth_SiN[idxT]
""" Creating Si layer grating """
w_Si_slab = sum(self.w_teeth_Si)+sum(self.gap_teeth_Si)
R_Si_slab = self.R_teeth_ori_Si+w_Si_slab/2
circle(radius=R_Si_slab,angle=self.A_gc_taper,layer=self.layer_Si_slab,
width=w_Si_slab).cell.put(0,0,-self.A_gc_taper/2)
## Placing teeth
r_in = self.R_teeth_ori_Si
for idxT in range(0,n_teeth_Si):
r_out = r_in + self.gap_teeth_Si[idxT]
if (self.layer_Si_etch is not None):
circle(radius=(r_out+r_in)/2,angle=self.A_gc_taper+A_etch_ext,layer=self.layer_Si_etch,
width=self.gap_teeth_Si[idxT]).cell.put(0,0,-self.A_gc_taper/2-A_etch_ext/2)
elif (self.layer_Si_teeth is not None):
circle(radius=r_out+(self.w_teeth_Si[idxT])/2,angle=self.A_gc_taper,layer=self.layer_Si_teeth,
width=self.w_teeth_Si[idxT]).cell.put(0,0,-self.A_gc_taper/2)
r_in = r_out + self.w_teeth_Si[idxT]
return C
class FA:
"""
FA primitive component.
This component builds the FA layout cell.
Parameters
----------
fiber_coupler : Any
Fiber coupler cell or component used by this array.
pitch : float
Spacing or gap parameter in microns.
number : int
Count or repetition parameter.
show_pins : bool, optional
Whether to draw pin markers in the generated layout. Default is False.
"""
def __init__(self,fiber_coupler: Any,pitch: float,number: int,show_pins: bool=False) -> None:
# 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