1243 lines
56 KiB
Python
1243 lines
56 KiB
Python
import nazca as nd
|
||
import numpy as np
|
||
import math
|
||
import pandas as pd
|
||
|
||
from ...routing import Route
|
||
from ...structures import _my_polygon,circle,Clothoid,hole
|
||
from ...basic import __cell_arg__
|
||
|
||
|
||
''' Class for nanoantenna '''
|
||
class Nano_ant():
|
||
"""
|
||
Configure a nano-antenna for optical phased-array grating couplers.
|
||
|
||
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
|
||
Input waveguide width in microns (default is 0.41).
|
||
xs_wg : str, optional
|
||
Nazca cross-section key for the feed waveguide (default is "strip").
|
||
define_type : str, optional
|
||
Antenna definition scheme, either "non-periodic" or "periodic" (default is "non-periodic").
|
||
vector : Sequence[float], optional
|
||
Alternating etched/filled segment lengths (µm) when ``define_type`` is "non-periodic"
|
||
(default is ``[0.5, 0.5, 0.5, 0.5, 0.5, 0.5]``).
|
||
taper_length : float, optional
|
||
Linear taper length preceding the teeth region in microns (default is 3).
|
||
width : float, optional
|
||
Maximum aperture width in microns (default is 6).
|
||
max_theta : float, optional
|
||
Fan-out opening angle in degrees (default is 110).
|
||
pitch : float or Sequence[float], optional
|
||
Tooth pitch (µm) when ``define_type`` is "periodic"; scalar applies to all periods (default is 0.6).
|
||
duty_cycle : float or Sequence[float], optional
|
||
Etched fraction per period for periodic antennas; scalar or dual-entry list for dual-etch (default is 0.3).
|
||
teeth_number : int, optional
|
||
Number of etched teeth when periodic mode is used (default is 6).
|
||
etch_depth : Sequence[str], optional
|
||
List of etch-depth identifiers ("FETCH", "METCH", "SETCH"); length determines single/dual etch (default is ["METCH"]).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers on exported pins (default is True).
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
w_wg: float = 0.41,
|
||
xs_wg: str = "strip",
|
||
|
||
define_type: str = "non-periodic",
|
||
vector: 'float|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|list' = 0.6,
|
||
duty_cycle: 'float|list' = 0.3,
|
||
teeth_number: float = 6,
|
||
|
||
etch_depth: 'str|list' = ["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() :
|
||
"""
|
||
Create a stand-alone planar taper cell for 2D antenna feeds.
|
||
|
||
Parameters
|
||
----------
|
||
width1 : float, optional
|
||
Input width in microns (default is 4).
|
||
width2 : float, optional
|
||
Output width in microns (default is 0.45).
|
||
length : float, optional
|
||
Physical taper length in microns (default is 30).
|
||
type : str, optional
|
||
Transition profile, "linear" or "parabolic" (default is "linear").
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers for debugging (default is False).
|
||
"""
|
||
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() :
|
||
"""
|
||
Define a single 2D hole-array grating (diffraction + reflector + taper).
|
||
This is a class for 2D Grating in IMEC.
|
||
|
||
Parameters
|
||
----------
|
||
w_wg : float, optional
|
||
Feed waveguide width in microns (default is 0.5).
|
||
w_gt : float, optional
|
||
Square grating aperture width in microns (default is 5).
|
||
l_taper : float, optional
|
||
Taper length from grating to feed in microns (default is 30).
|
||
type_taper : str, optional
|
||
Taper profile ("linear" or "parabolic", default is "parabolic").
|
||
gt_vector : Sequence[float], optional
|
||
Pitch list (µm) for the main etched holes (default is ``[0.5, 0.5, 0.5, 0.5, 0.5]``).
|
||
gt_diameter : float, optional
|
||
Diameter of the main holes in microns (default is 0.4).
|
||
gt_layer : str, optional
|
||
Nazca layer name used for the main holes (default is "STRIP_COR").
|
||
polysi_vector : Sequence[float], optional
|
||
Pitch list (µm) for polysilicon holes (default is ``[0.5, 0.5, 0.5, 0.5, 0.5]``).
|
||
polysi_diameter : float, optional
|
||
Diameter of polysilicon holes in microns (default is 0.4).
|
||
polysi_layer : str, optional
|
||
Layer name for polysilicon etch (default is "FCW_TRE").
|
||
reflector_vector : Sequence[float], optional
|
||
Alternating reflector spacing/width values in microns (default is ``[0.3, 0.3, 0.3, 0.3, 0.3, 0.3]``).
|
||
l_field_center : float, optional
|
||
Offset from the grating edge to the mode-field center in microns (default is 1).
|
||
"""
|
||
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
|
||
|
||
""" Renamed for simplification in 2023.04.02 """
|
||
class GC_STD_2D:
|
||
"""
|
||
General-purpose 2D grating coupler generator with rectangular or arc shapes.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None, meaning uninstantiated cell).
|
||
etch_type : str, optional
|
||
Etch depth selector: "FETCH", "METCH", or "SETCH" (default is "FETCH").
|
||
xs_wg : str, optional
|
||
Cross-section for the grating slab and output waveguide (default is "grating").
|
||
Dx_hole : float or Sequence[float], optional
|
||
Hole size along x (µm). Scalar applies to all columns (default is 0.3).
|
||
Dy_hole : float or Sequence[float], optional
|
||
Hole size along y (µm). Scalar applies to all rows (default is 0.3).
|
||
hole_shape : str, optional
|
||
Individual hole shape, "circle" or "rectangle" (default is "circle").
|
||
shape : str, optional
|
||
Overall grating footprint, "circle", "arc", or "rectangle" (default is "circle").
|
||
xs_open : str or None, optional
|
||
Optional open-area cross-section for keep-out regions (default is None).
|
||
Px : float or Sequence[float], optional
|
||
Periods along x in microns; scalar broadcasts (default is 0.57).
|
||
Py : float or Sequence[float], optional
|
||
Periods along y in microns; scalar broadcasts (default is 0.57).
|
||
num_x : int, optional
|
||
Number of periods along x when ``Px`` is scalar (default is 25).
|
||
num_y : int, optional
|
||
Number of periods along y when ``Py`` is scalar (default is 25).
|
||
Lx_taper : float, optional
|
||
Horizontal taper length to the output port in microns (default is 50).
|
||
Ly_taper : float, optional
|
||
Vertical taper length in microns (default is 0).
|
||
Lx_end : float, optional
|
||
Extra straight length appended to the positive-x end (default is 1).
|
||
Ly_end : float, optional
|
||
Extra straight length appended to the +/-y ends (default is 1).
|
||
Lx_side : float, optional
|
||
Lateral margin on the +/−x sides in microns (default is 0.5).
|
||
Ly_side : float, optional
|
||
Lateral margin on the +/−y sides in microns (default is 0.5).
|
||
Lx_port : float, optional
|
||
Straight-section length after the x-port taper in microns (default is 5).
|
||
Ly_port : float, optional
|
||
Straight-section length after the y-port taper in microns (default is 5).
|
||
w_wg : float, optional
|
||
Output waveguide width in microns (default is 0.5).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers (default is False).
|
||
P_AR : float, optional
|
||
Anti-reflection pitch in microns (default is 0.6).
|
||
L_AR : float, optional
|
||
Anti-reflection taper length in microns (default is 1).
|
||
|
||
Raises:
|
||
Exception: Period do not match D_hole
|
||
"""
|
||
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.0,
|
||
Ly_taper:float = 0.0,
|
||
Lx_end:float = 1.0,
|
||
Ly_end:float = 1.0,
|
||
Lx_side:float = 0.5,
|
||
Ly_side:float = 0.5,
|
||
Lx_port:float=5.0,
|
||
Ly_port:float=5.0,
|
||
w_wg:float=0.5,
|
||
show_pins:bool=False,
|
||
P_AR: float = 0.6,
|
||
L_AR: float = 1,
|
||
):
|
||
|
||
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:
|
||
"""
|
||
Versatile 1D grating coupler supporting sector and rectangular layouts.
|
||
|
||
Parameters
|
||
----------
|
||
name : str, optional
|
||
Nazca cell name (default is None).
|
||
xs_wg : str, optional
|
||
Cross-section key for the slab/taper region (default is "strip").
|
||
w_wg : float, optional
|
||
Input waveguide width in microns (default is 0.5).
|
||
etch_type : str, optional
|
||
Etch depth selector: "FETCH", "METCH", or "SETCH" (default is "FETCH").
|
||
xs_open : str or None, optional
|
||
Optional cross-section for keep-out/open regions (default is None).
|
||
L_taper : float, optional
|
||
Length of the entrance taper in microns (default is 10).
|
||
L_end : float, optional
|
||
Terminal slab length after the grating in microns (default is 2).
|
||
A_taper : float, optional
|
||
Fan-out angle in degrees (default is 30).
|
||
Period : float or Sequence[float], optional
|
||
Grating periods in microns; scalar broadcasts (default is 0.5).
|
||
eta_etch : float or Sequence[float], optional
|
||
Etch duty (between 0 and 1) per period (default is 0.5).
|
||
num : int, optional
|
||
Number of periods when ``Period`` and ``eta_etch`` are scalars (default is 20).
|
||
sector_gc : bool, optional
|
||
Use sector (True) or rectangular (False) geometry (default is True).
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers (default is False).
|
||
L_tail : float, optional
|
||
Extra straight length added before the taper in microns (default is 2).
|
||
P_AR : float, optional
|
||
Anti-reflection pitch in microns (default is 1).
|
||
L_AR : float, optional
|
||
Anti-reflection taper length in microns (default is 2).
|
||
"""
|
||
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.0,
|
||
L_end :float = 2.0,
|
||
A_taper :float = 30.0,
|
||
Period :float = 0.5,
|
||
eta_etch :float = 0.5,
|
||
num :int = 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.0, ### adding anti reflection pitches
|
||
L_AR: float = 2.0,
|
||
):
|
||
|
||
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:
|
||
"""
|
||
Instantiate a fiber-array fanout from repeated grating/fiber couplers.
|
||
|
||
Parameters
|
||
----------
|
||
fiber_coupler : nd.Cell or object
|
||
Reference coupler cell or instance exposing ``cell`` and pin ``g1``/``a0``.
|
||
pitch : float, optional
|
||
Center-to-center spacing between adjacent couplers in microns.
|
||
number : int
|
||
Total number of channels in the array.
|
||
show_pins : bool, optional
|
||
Draw Nazca stub markers on exported pins (default is False).
|
||
"""
|
||
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
|
||
|
||
class GC_SiN_Si_Dual_Layer:
|
||
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
|
||
|
||
|