Files

1410 lines
66 KiB
Python

"""Mach-Zehnder interferometer mesh composite layouts."""
from typing import Any, Optional
import nazca as nd
import numpy as np
from ..primitives.passive import DC
from ..geometry import *
from ..routing import Route
from ..primitives.pic import *
from ..electronics import Vias,ISL
import pandas as pd
from ..primitives.passive import waveguide
from .MZI import __BS_generate__
class W_waveguide:
"""W-shaped waveguide phase shifter section.
Parameters
----------
xs_wg : str, optional
Waveguide cross-section name.
w_wg : float, optional
Optical waveguide width in microns.
R_bend : int, optional
Bend radius used by W-shaped routing.
dL : float, optional
Vertical excursion of the W routing.
L_wg : int, optional
Total phase shifter waveguide length.
xs_heater : str, optional
Heater cross-section name.
w_ht : float, optional
Heater width. Use 0 to disable heater geometry.
xs_metal : str, optional
Metal routing cross-section name.
w_metal : float, optional
Metal routing width.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Optional isolation helper.
n_bends : int, optional
Number of W bend periods.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
ISL_UPPER : bool, optional
Enable upper isolation placement when used.
ISL_LOWER : bool, optional
Enable lower isolation placement when used.
L_patch : float, optional
Straight patch length at bend interfaces.
reverse : bool, optional
Swap optical input/output pin naming.
Attributes
----------
cell : nazca.Cell
Generated W-waveguide layout cell.
"""
def __init__(self,
xs_wg: str='strip',
w_wg: float = 0.45,
R_bend: int=10,
dL: float = 20,
L_wg: int = 80,
xs_heater: str = 'heater',
w_ht: float = 2.5,
xs_metal: str = 'metal',
w_metal: float = 6,
via_h2m: Any = None,
isl: Any = None,
n_bends: int=3,
show_pins: bool=False,
ISL_UPPER: bool = True,
ISL_LOWER: bool = True,
L_patch: float = 0.25,
reverse: bool=False) -> None:
"""Initialize the W waveguide composite.
See the class docstring for parameter descriptions.
"""
self.xs_wg = xs_wg
self.w_wg = w_wg
self.R_bend = R_bend
self.xs_heater = xs_heater
self.w_ht = w_ht
self.dL = dL
self.xs_metal = xs_metal
self.w_metal = w_metal
self.via_h2m = via_h2m
self.isl = isl,
self.L_wg = L_wg
self.reverse = reverse
self.n_bends = n_bends
self.L_patch = L_patch
self.ISL_LOWER = ISL_LOWER
self.ISL_UPPER = ISL_UPPER
self.cell = self.generate_gds(show_pins)
def generate_gds(self,show_pins=False):
with nd.Cell(instantiate=False) as C:
""" Generating the basic via_h2m """
if (self.via_h2m==None):
vias = Vias(xs=None,area=self.w_metal,sz=0,spacing=0,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## only putting metal blocks
else:
if (hasattr(self.via_h2m,"cell")):
vias = self.via_h2m
else :
vias = Vias(xs=self.via_h2m.xs,area=self.w_metal,sz=self.via_h2m.sz,spacing=self.via_h2m.spacing,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## placing vias
pic_strip = Route(radius=self.R_bend,width=self.w_wg,xs=self.xs_wg)
wg_begin = nd.strt(length=0,width=self.w_wg,xs=self.xs_wg).put(0,0,0)
pin_pre = wg_begin.pin['b0']
for itn in range(0,self.n_bends):
wg_cut_U = nd.strt(length=self.L_patch/2,width=self.w_wg,xs=self.xs_wg).put(pin_pre.x+self.R_bend*2+self.L_patch ,self.dL,0)
wg_cut_M = nd.strt(length=self.L_patch/2,width=self.w_wg,xs=self.xs_wg).put(wg_cut_U.pin['b0'].x+self.R_bend*2+self.L_patch, 0,0)
pic_strip.sbend_p2p(pin1=wg_cut_U.pin['b0'],pin2=wg_cut_M.pin['a0'],Lstart=self.L_patch/2).put()
pic_strip.sbend_p2p(pin1=pin_pre,pin2=wg_cut_U.pin['a0'],Lstart=self.L_patch/2).put()
pin_pre = wg_cut_M.pin['b0']
wg_end = nd.strt(length=self.L_patch/2,width=self.w_wg,xs=self.xs_wg).put(self.L_wg-self.L_patch/2,0,0)
pic_strip.strt_p2p(pin1=wg_end.pin['b0'],pin2=pin_pre,arrow=False).put()
if (self.w_ht>0):
# vias = Vias(xs=self.xs_via_h2m,spacing=self.sp_via,sz=self.sz_via,xs_l1=self.xs_heater,xs_l2=self.xs_metal,
# area=[self.w_metal,self.w_metal])
VIA_L = vias.cell.put(0,0,180,flip=1)
ht_strip = Route(radius=self.R_bend,width=self.w_ht,xs=self.xs_heater)
ht_begin = nd.strt(length=0,width=self.w_ht,xs=self.xs_heater).put(0,0,0)
pin_pre = ht_begin.pin['b0']
for itn in range(0,self.n_bends):
ht_cut_U = nd.strt(length=self.L_patch/2,width=self.w_ht,xs=self.xs_heater).put(pin_pre.x+self.R_bend*2+self.L_patch,self.dL,0)
ht_cut_M = nd.strt(length=self.L_patch/2,width=self.w_ht,xs=self.xs_heater).put(ht_cut_U.pin['b0'].x+self.R_bend*2+self.L_patch,0,0)
ht_strip.sbend_p2p(pin1=ht_cut_U.pin['b0'],pin2=ht_cut_M.pin['a0'],Lstart=self.L_patch/2).put()
ht_strip.sbend_p2p(pin1=pin_pre,pin2=ht_cut_U.pin['a0'],Lstart=self.L_patch/2).put()
pin_pre = ht_cut_M.pin['b0']
ht_end = nd.strt(length=self.L_patch/2,width=self.w_ht,xs=self.xs_heater).put(self.L_wg-self.L_patch,0,0)
ht_strip.strt_p2p(pin1=ht_end.pin['b0'],pin2=pin_pre,arrow=False).put()
## Bug fixed, 2022.12.30, the vias are not connected to heater
VIA_R = vias.cell.put(ht_end.pin['b0'].x,0,0,flip=0)
nd.Pin(name='ep1',width=self.w_metal,xs=self.xs_metal).put(VIA_L.pin['b0'])
nd.Pin(name='en1',width=self.w_metal,xs=self.xs_metal).put(VIA_R.pin['b0'])
# if (self.xs_isl!=None):
# ## The isolation inside
# if (self.ISL_LOWER):
# ISL(xs=self.xs_isl,width=self.w_isl,length=self.L_wg).cell.put('a1',0,-self.w_metal/2-self.w_isl/2-self.sp_isl_xs,0)
# if (self.ISL_UPPER):
# ISL(xs=self.xs_isl,width=self.w_isl,length=self.L_wg).cell.put('a1',0, self.dL+self.w_isl/2+self.sp_isl_xs+self.w_metal/2 ,0)
# L_isl_side = self.dL-self.w_metal/2-self.sp_isl_xs
# if (L_isl_side > self.w_isl):
# ISL(xs=self.xs_isl,width=self.w_isl,length=L_isl_side).cell.put('a1',0,self.w_metal/2+self.sp_isl_xs,90)
# ISL(xs=self.xs_isl,width=self.w_isl,length=L_isl_side).cell.put('a1',self.L_wg,self.w_metal/2+self.sp_isl_xs,90)
if (self.reverse):
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1',pin=wg_begin.pin['a0']).put()
nd.Pin(name='opt_b1',pin=wg_begin.pin['a0'],type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',pin=wg_end.pin['b0']).put()
nd.Pin(name='opt_a1',pin=wg_end.pin['b0'],type="optical:").put()
else :
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',pin=wg_begin.pin['a0']).put()
nd.Pin(name='opt_a1',pin=wg_begin.pin['a0'],type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1',pin=wg_end.pin['b0']).put()
nd.Pin(name='opt_b1',pin=wg_end.pin['b0'],type="optical:").put()
return C
## Standard unit of a mesh with one MZI and a PS
class UMat_2x2_S:
"""Standard 2x2 MZI mesh unit with phase shifter routing.
Parameters
----------
name : str, optional
Nazca cell name.
BS : Any, optional
Beam splitter cell or object. If omitted, a default DC is generated.
xs_wg : str, optional
Optical waveguide cross-section name.
L_arm : int, optional
Straight arm length between beam splitters.
D_arm : int, optional
Vertical spacing between MZI arms.
w_wg : float, optional
Input/output waveguide width.
R_bend : int, optional
Routing bend radius.
w_arm : float, optional
Internal arm width. If omitted, ``w_wg`` is used.
Ltp : int, optional
Taper length between bus and arm widths.
xs_heater : str, optional
Heater cross-section name.
bend_heaters : bool, optional
Route heaters along bent arms.
dL_ht : float, optional
Heater routing vertical offset.
dL_AMZI : float, optional
Differential length added for AMZI behavior.
L_heater : Any, optional
Optional explicit heater length.
xs_metal : str, optional
Metal routing cross-section name.
w_ht : float, optional
Heater width.
w_metal : float, optional
Metal routing width.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Isolation helper object or cell.
ht_same_side : bool, optional
Place both heater contacts on the same side.
port_align : bool, optional
Align optical ports to a common grid.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
Attributes
----------
cell : nazca.Cell
Generated 2x2 unit mesh layout cell.
"""
def __init__(self,
name: str="unit_mesh_2x2",
BS: Any=None,
xs_wg: str='strip',
L_arm: int = 80,
D_arm: int = 50,
w_wg: float = 0.45,
R_bend: int=6,
w_arm: Optional[float] = None,
Ltp: int = 15,
xs_heater: str = 'heater',
bend_heaters: bool = False,
dL_ht: float = 30,
dL_AMZI: float = 0,
L_heater: Any = None,
xs_metal: str = 'metal',
w_ht: float = 2.5,
w_metal: float = 8,
via_h2m: Any = None,
isl: Any = None,
ht_same_side: bool= False,
port_align: bool = True,
show_pins: bool=False) -> None:
"""Initialize the UMat 2x2 S composite.
See the class docstring for parameter descriptions.
"""
self.BS = __BS_generate__(BS=BS,xs=xs_wg,func_name="mxpic::functioanl::Umat_2x2_S")
if (w_arm==None):
w_arm = w_wg
self.name = name
self.L_arm = L_arm
self.L_heater = L_heater
self.D_arm = D_arm
self.R_bend = R_bend
self.w_wg = w_wg
self.xs_wg = xs_wg
self.xs_heater = xs_heater
self.xs_metal = xs_metal
self.w_ht = w_ht
self.w_metal = w_metal
self.via_h2m = via_h2m
self.isl = isl
self.bend_heaters = bend_heaters
self.dL_ht = dL_ht
self.ht_same_side= ht_same_side
self.port_align = port_align
self.Ltp = Ltp
self.w_arm = w_arm
self.cell = self.generate_gds(show_pins=show_pins)
def __BS_generate__(self,BS,xs):
if (BS==None):
BS_cell = DC(xs=xs,w_cp=0.45,w_wg=0.45,L_cp=9.27,angle=10,R0=15,Rmax=15,Rmin=15).cell
elif (isinstance(BS,nd.Cell)):
BS_cell = BS
elif (hasattr(BS,'cell')):
BS_cell = BS.cell
else : raise Exception("ERROR:: In <mxpic::UMat_2x2_S>, BS not recognizable")
return BS_cell
def generate_gds(self,show_pins):
if (self.name==None):
instantiate = False
else :
instantiate = True
with nd.Cell(name=self.name,instantiate=instantiate) as MZI_Unit:
pic_strip = Route(radius=self.R_bend,width=self.w_wg,xs=self.xs_wg)
## revised in 2026.06.07 by Qin Yue
# legacy: self.dL_BS = np.abs(self.BS.pin['a1'].x - self.BS.pin['b1'].x)
self.dL_BS = np.abs(self.BS.pin['opt_a1'].x - self.BS.pin['opt_b1'].x)
## 2023.1.21 modified
## revised in 2026.06.07 by Qin Yue
# legacy: dY_BS = abs(self.BS.pin['b1'].y - self.BS.pin['b2'].y)
dY_BS = abs(self.BS.pin['opt_b1'].y - self.BS.pin['opt_b2'].y)
## revised in 2026.06.07 by Qin Yue
# legacy: BS_L = self.BS.put('b1',-2*self.R_bend - self.L_arm/2-1,dY_BS/2,180)
BS_L = self.BS.put('opt_b1',-2*self.R_bend - self.L_arm/2-1,dY_BS/2,180)
## revised in 2026.06.07 by Qin Yue
# legacy: BS_R = self.BS.put('a2', 2*self.R_bend + self.L_arm/2+1,dY_BS/2,0,flip=1)
BS_R = self.BS.put('opt_a2', 2*self.R_bend + self.L_arm/2+1,dY_BS/2,0,flip=1)
if (self.bend_heaters):
wg_ht = W_waveguide(L_wg=self.L_arm,w_wg=self.w_wg,xs_wg=self.xs_wg,dL=self.dL_ht,R_bend=self.R_bend,
w_ht=self.w_ht,w_metal=self.w_metal,
via_h2m=self.via_h2m,
isl = self.isl,
xs_metal=self.xs_metal,
).cell
wg_oht = W_waveguide(L_wg=self.L_arm,w_wg=self.w_wg,xs_wg=self.xs_wg,dL=self.dL_ht,R_bend=self.R_bend,reverse=True,
w_ht=0,w_metal=0,
).cell
else :
wg_ht = waveguide(L_wg=self.L_arm,
w_wg=self.w_arm,
xs_wg=self.xs_wg,
w_port=self.w_wg,
Ltp=self.Ltp,
L_heater=self.L_heater,
w_heater=self.w_ht,w_metal=self.w_metal,
xs_heater=self.xs_heater,
via_h2m=self.via_h2m,
isl = self.isl,
xs_metal=self.xs_metal,
).cell
wg_oht = waveguide(L_wg=self.L_arm,w_wg=self.w_arm,xs_wg=self.xs_wg,w_port=self.w_wg,Ltp=self.Ltp,
w_heater=0,w_metal=0,
xs_heater=self.xs_heater,xs_metal=self.xs_metal).cell
if (self.ht_same_side):
## revised in 2026.06.07 by Qin Yue
# legacy: wg_U = wg_oht.put('a1',-self.L_arm/2,self.D_arm/2,0)
wg_U = wg_oht.put('opt_a1',-self.L_arm/2,self.D_arm/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: wg_D = wg_ht.put('a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
wg_D = wg_ht.put('opt_a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
else:
## revised in 2026.06.07 by Qin Yue
# legacy: wg_U = wg_ht.put('a1',-self.L_arm/2,self.D_arm/2,0)
wg_U = wg_ht.put('opt_a1',-self.L_arm/2,self.D_arm/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: wg_D = wg_oht.put('a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
wg_D = wg_oht.put('opt_a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_L.pin['b1'],pin2=wg_U.pin['a1']).put()
pic_strip.sbend_p2p(pin1=BS_L.pin['opt_b1'],pin2=wg_U.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_L.pin['b2'],pin2=wg_D.pin['a1']).put()
pic_strip.sbend_p2p(pin1=BS_L.pin['opt_b2'],pin2=wg_D.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_R.pin['a2'],pin2=wg_U.pin['b1']).put()
pic_strip.sbend_p2p(pin1=BS_R.pin['opt_a2'],pin2=wg_U.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_R.pin['a1'],pin2=wg_D.pin['b1']).put()
pic_strip.sbend_p2p(pin1=BS_R.pin['opt_a1'],pin2=wg_D.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: ps_U = wg_oht.put('b1',BS_L.pin['a1'].x-self.R_bend*2-1,self.D_arm/2,180,flip=1)
ps_U = wg_oht.put('opt_b1',BS_L.pin['opt_a1'].x-self.R_bend*2-1,self.D_arm/2,180,flip=1)
## revised in 2026.06.07 by Qin Yue
# legacy: ps_D = wg_ht.put('b1',BS_L.pin['a1'].x-self.R_bend*2-1,-self.D_arm/2,180,flip=0)
ps_D = wg_ht.put('opt_b1',BS_L.pin['opt_a1'].x-self.R_bend*2-1,-self.D_arm/2,180,flip=0)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=ps_U.pin['b1'],pin2=BS_L.pin['a1']).put()
pic_strip.sbend_p2p(pin1=ps_U.pin['opt_b1'],pin2=BS_L.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=ps_D.pin['b1'],pin2=BS_L.pin['a2']).put()
pic_strip.sbend_p2p(pin1=ps_D.pin['opt_b1'],pin2=BS_L.pin['opt_a2']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['b2'].x+self.R_bend*2+1, self.D_arm/2,0)
patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['opt_b2'].x+self.R_bend*2+1, self.D_arm/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['b1'].x+self.R_bend*2+1,-self.D_arm/2,0)
patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['opt_b1'].x+self.R_bend*2+1,-self.D_arm/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_R.pin['b2']).put()
pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_R.pin['opt_b2']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_R.pin['b1']).put()
pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_R.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',pin=ps_U.pin['a1'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_a1',pin=ps_U.pin['opt_a1'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a2',pin=ps_D.pin['a1'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_a2',pin=ps_D.pin['opt_a1'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_b1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_b2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
nd.Pin(name='ep1',pin=ps_D.pin['ep1']).put()
nd.Pin(name='en1',pin=ps_D.pin['en1']).put()
if (self.ht_same_side):
nd.Pin(name='ep2',pin=wg_D.pin['ep1']).put()
nd.Pin(name='en2',pin=wg_D.pin['en1']).put()
else:
nd.Pin(name='ep2',pin=wg_U.pin['ep1']).put()
nd.Pin(name='en2',pin=wg_U.pin['en1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: dX_unit = abs(ps_U.pin['a1'].x - patch_U.pin['b0'].x)
dX_unit = abs(ps_U.pin['opt_a1'].x - patch_U.pin['b0'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: dY_unit = abs(ps_U.pin['a1'].y - ps_D.pin['a1'].y)
dY_unit = abs(ps_U.pin['opt_a1'].y - ps_D.pin['opt_a1'].y)
return MZI_Unit
class MZI_mesh_U:
"""Universal MZI mesh built from repeated 2x2 MZI units.
Parameters
----------
BS : Any, optional
Beam splitter cell or object used by unit cells.
xs_wg : str, optional
Optical waveguide cross-section name.
L_arm : int, optional
Straight arm length in each unit MZI.
D_arm : int, optional
Vertical spacing between MZI arms.
w_wg : float, optional
Input/output waveguide width.
n_ports : int, optional
Number of mesh ports.
R_bend : int, optional
Routing bend radius.
L_compensate : int, optional
Length used for compensation routing.
R_compensate : int, optional
Bend radius used by compensation routing.
mesh_type : str, optional
Mesh topology name, such as ``"triangle"`` or ``"parallelogram"``.
xs_heater : str, optional
Heater cross-section name.
bend_heaters : bool, optional
Route heaters along bent arms.
dL_ht : float, optional
Heater routing vertical offset.
xs_metal : str, optional
Metal routing cross-section name.
w_ht : float, optional
Heater width.
w_metal : float, optional
Metal routing width.
w_arm : float, optional
Internal arm width. If omitted, ``w_wg`` is used.
Ltp : int, optional
Taper length between bus and arm widths.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Isolation helper object or cell.
port_align : bool, optional
Align optical ports to a common grid.
L_heater : Any, optional
Optional explicit heater length.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
Attributes
----------
cell : nazca.Cell
Generated universal MZI mesh layout cell.
"""
def __init__(self,
BS: Any=None,
xs_wg: str='strip',
L_arm: int = 80,
D_arm: int = 50,
w_wg: float = 0.45,
n_ports: int = 8,
R_bend: int=6,
L_compensate: int = 10,
R_compensate: int = 10,
mesh_type: str = 'triangle',
xs_heater: str = 'heater',
bend_heaters: bool = True,
dL_ht: float = 30,
xs_metal: str = 'metal',
w_ht: float = 2.5,
w_metal: float = 8,
w_arm: Optional[float] = None,
Ltp: int = 10,
via_h2m: Any = None,
isl: Any = None,
port_align: bool = True,
L_heater: Any = None,
show_pins: bool=False) -> None:
"""Initialize the MZI mesh U composite.
See the class docstring for parameter descriptions.
"""
self.BS = __BS_generate__(BS=BS,xs=xs_wg,func_name="mxpic::functioanl::MZI_mesh_U")
self.L_arm = L_arm
self.L_heater = L_heater
self.D_arm = D_arm
self.n_ports = n_ports
self.R_bend = R_bend
self.w_wg = w_wg
self.xs_wg = xs_wg
self.xs_heater = xs_heater
self.xs_metal = xs_metal
self.w_ht = w_ht
self.w_metal = w_metal
self.bend_heaters = bend_heaters
self.dL_ht = dL_ht
self.mesh_type = mesh_type
self.port_align = port_align
MZI_unit = UMat_2x2_S(BS=BS,
xs_wg=xs_wg,
L_arm=L_arm,
L_heater=L_heater,
D_arm=D_arm,
w_wg=w_wg,
R_bend=R_bend,
w_arm=w_arm,
Ltp=Ltp,
xs_heater=xs_heater,xs_metal=xs_metal,dL_ht=dL_ht,
w_ht=w_ht,w_metal=w_metal,
via_h2m=via_h2m,
isl=isl,
show_pins=show_pins)
self.MZI_unit = MZI_unit.cell
self.dL_BS = MZI_unit.dL_BS
# self.cell_offset = MZI_unit.cell_offset
self.cell_compensate = self.__len_compensate_generate__(L_compensate, R_compensate)
self.cell = self.generate_gds(show_pins=show_pins)
def __BS_generate__(self,BS,xs):
if (BS==None):
BS_cell = DC(xs=xs,w_cp=0.45,w_wg=0.45,L_cp=9.27,angle=10,R0=15,Rmax=15,Rmin=15).cell
elif (isinstance(BS,nd.Cell)):
BS_cell = BS
elif (hasattr(BS,'cell')):
BS_cell = BS.cell
else : raise Exception("ERROR:: In <mxpic::MZI_mehs>, BS not recognizable")
return BS_cell
def __len_compensate_generate__(self, L_compensate, R_compensate) :
with nd.Cell(name='Bend_Len_Compensate', instantiate=False) as ICell :
strip = Route(radius=R_compensate,width=self.w_wg,xs='strip')
strip_input = strip.strt(length=0.2).put(0,0,0)
strip.bend_route(radius=R_compensate,angle=90).put()
strip.strt(length=L_compensate).put()
strip.bend_route(radius=R_compensate,angle=-180).put()
strip.strt(length=L_compensate).put()
strip.bend_route(radius=R_compensate,angle=180).put()
strip.strt(length=L_compensate).put()
strip.bend_route(radius=R_compensate,angle=-180).put()
strip.strt(length=L_compensate).put()
strip.bend_route(radius=R_compensate,angle=90).put()
strip_output = strip.strt(length=0.2).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',width=self.w_wg).put(strip_input.pin['a0'])
nd.Pin(name='opt_a1',width=self.w_wg,type="optical:").put(strip_input.pin['a0'])
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1',width=self.w_wg).put(strip_output.pin['b0'])
nd.Pin(name='opt_b1',width=self.w_wg,type="optical:").put(strip_output.pin['b0'])
nd.Pin(name='a0',width=self.w_wg).put(strip_input.pin['a0'])
nd.Pin(name='b0',width=self.w_wg).put(strip_output.pin['b0'])
return ICell
def generate_gds(self,show_pins):
with nd.Cell(name="mesh_"+self.mesh_type, instantiate=True) as mesh:
print("## ===== %s mesh generating ===== ##" % (self.mesh_type))
rows = self.n_ports-1
pic_strip = Route(radius=self.R_bend,width=self.w_wg,xs='strip')
## revised in 2026.06.07 by Qin Yue
# legacy: dX_unit = abs(self.MZI_unit.pin['a1'].x-self.MZI_unit.pin['b1'].x)
dX_unit = abs(self.MZI_unit.pin['opt_a1'].x-self.MZI_unit.pin['opt_b1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: dY_unit = abs(self.MZI_unit.pin['a1'].y-self.MZI_unit.pin['a2'].y)
dY_unit = abs(self.MZI_unit.pin['opt_a1'].y-self.MZI_unit.pin['opt_a2'].y)
x_spacing = dX_unit ## incase of overlapping
y_spacing = dY_unit ## incase of overlapping
x_list = []
pin_a_list = []
pin_b_list = []
epin_label = 0
for r_idx in range(1,rows+1): ## start from the largest row
if (self.mesh_type=='triangle'):
cols = r_idx ## Triangle type
else :
cols = int(np.floor((self.n_ports + np.mod(r_idx,2) )/2)) ## (N+1)/2 or (N)/2, rectangle type
## Defining starting points ##
if (self.mesh_type=='triangle'):
x_init = -(x_spacing)*(cols-1)
else:
x_init = x_spacing*(1-np.mod(r_idx,2))
_y_ = -(y_spacing)*(r_idx-1)
for c_idx in range(0,cols):
_x_ = c_idx*x_spacing*2 + x_init
INSTR = self.MZI_unit.put(_x_,_y_,0)
epin_label = epin_label+1
nd.Pin(name='ep'+str(epin_label),pin=INSTR.pin['ep1']).put()
nd.Pin(name='en'+str(epin_label),pin=INSTR.pin['en1']).put()
epin_label = epin_label+1
nd.Pin(name='ep'+str(epin_label),pin=INSTR.pin['ep2']).put()
nd.Pin(name='en'+str(epin_label),pin=INSTR.pin['en2']).put()
#### ============= Connecting the unattached waveguides ================ ####
if (c_idx>=1 and r_idx==1): ### This will not happen in Triangle cases
# pic_strip.sbend_p2p(pin1=pin_up_pre,pin2=INSTR.pin['a1']).put()
pic_strip.strt(pin=pin_up_pre,length=self.L_arm).put()
## revised in 2026.06.07 by Qin Yue
# legacy: bend_compensate = self.cell_compensate.put('a1',nd.Pin().put(),flip=True)
bend_compensate = self.cell_compensate.put('opt_a1',nd.Pin().put(),flip=True)
pic_strip.sbend_p2p(
## revised in 2026.06.07 by Qin Yue
# legacy: pin1=bend_compensate.pin['b1'],
pin1=bend_compensate.pin['opt_b1'],
## revised in 2026.06.07 by Qin Yue
# legacy: pin2=INSTR.pin['a1']
pin2=INSTR.pin['opt_a1']
).put()
# self.cell_offset.put(flip=1)
# self.cell_offset.put(flip=0)
# self.cell_offset.put(flip=1)
# self.cell_offset.put(flip=0)
# pic_strip.sbend_p2p(pin2=INSTR.pin['a1']).put()
if (c_idx>=1 and r_idx==rows):
if self.mesh_type=='parallelogram':
pic_strip.strt(pin=pin_down_pre,length=self.L_arm).put()
## revised in 2026.06.07 by Qin Yue
# legacy: bend_compensate = self.cell_compensate.put('a1',nd.Pin().put())
bend_compensate = self.cell_compensate.put('opt_a1',nd.Pin().put())
pic_strip.sbend_p2p(
## revised in 2026.06.07 by Qin Yue
# legacy: pin1=bend_compensate.pin['b1'],
pin1=bend_compensate.pin['opt_b1'],
## revised in 2026.06.07 by Qin Yue
# legacy: pin2=INSTR.pin['a2']
pin2=INSTR.pin['opt_a2']
).put()
# self.cell_offset.put(flip=1)
# self.cell_offset.put(flip=0)
# # pic_strip.strt(length=self.L_arm+self.dL_BS).put()
# self.cell_offset.put(flip=1)
# self.cell_offset.put(flip=0)
# pic_strip.sbend_p2p(pin2=pin_down_pre).put()
elif self.mesh_type=='triangle':
pic_strip.strt(pin=pin_down_pre,length=self.L_arm).put()
## revised in 2026.06.07 by Qin Yue
# legacy: bend_compensate = self.cell_compensate.put('a1',nd.Pin().put())
bend_compensate = self.cell_compensate.put('opt_a1',nd.Pin().put())
pic_strip.sbend_p2p(
## revised in 2026.06.07 by Qin Yue
# legacy: pin1=bend_compensate.pin['b1'],
pin1=bend_compensate.pin['opt_b1'],
## revised in 2026.06.07 by Qin Yue
# legacy: pin2=INSTR.pin['a2']
pin2=INSTR.pin['opt_a2']
).put()
# pic_strip.sbend_p2p(pin1=pin_down_pre,pin2=INSTR.pin['a2']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pin_down_pre = INSTR.pin['b2']
pin_down_pre = INSTR.pin['opt_b2']
## revised in 2026.06.07 by Qin Yue
# legacy: pin_up_pre = INSTR.pin['b1']
pin_up_pre = INSTR.pin['opt_b1']
""" recongizing pins """
if (self.mesh_type=='triangle'):
if (c_idx==0):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a1'])
pin_a_list.append(INSTR.pin['opt_a1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a1'].x)
x_list.append(INSTR.pin['opt_a1'].x)
if (c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b1'])
pin_b_list.append(INSTR.pin['opt_b1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b1'].x)
x_list.append(INSTR.pin['opt_b1'].x)
if (r_idx==rows):
if (c_idx==0):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a2'])
pin_a_list.append(INSTR.pin['opt_a2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a2'].x)
x_list.append(INSTR.pin['opt_a2'].x)
elif (c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b2'])
pin_b_list.append(INSTR.pin['opt_b2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b2'].x)
x_list.append(INSTR.pin['opt_b2'].x)
else :
if (np.mod(self.n_ports,2)==1):
if (np.mod(r_idx,2)==1):
if (c_idx==0):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a1'])
pin_a_list.append(INSTR.pin['opt_a1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a1'].x)
x_list.append(INSTR.pin['opt_a1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a2'])
pin_a_list.append(INSTR.pin['opt_a2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a2'].x)
x_list.append(INSTR.pin['opt_a2'].x)
if (c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b1'])
pin_b_list.append(INSTR.pin['opt_b1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b1'].x)
x_list.append(INSTR.pin['opt_b1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b2'])
pin_b_list.append(INSTR.pin['opt_b2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b2'].x)
x_list.append(INSTR.pin['opt_b2'].x)
elif (r_idx==rows):
if (c_idx==0):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a2'])
pin_a_list.append(INSTR.pin['opt_a2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a2'].x)
x_list.append(INSTR.pin['opt_a2'].x)
elif (c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b2'])
pin_b_list.append(INSTR.pin['opt_b2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b2'].x)
x_list.append(INSTR.pin['opt_b2'].x)
else :
if (np.mod(r_idx,2)==0 and c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b1'])
pin_b_list.append(INSTR.pin['opt_b1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b1'].x)
x_list.append(INSTR.pin['opt_b1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b2'])
pin_b_list.append(INSTR.pin['opt_b2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b2'].x)
x_list.append(INSTR.pin['opt_b2'].x)
if (np.mod(r_idx,2)==1 and c_idx==0):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a1'])
pin_a_list.append(INSTR.pin['opt_a1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a1'].x)
x_list.append(INSTR.pin['opt_a1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: pin_a_list.append(INSTR.pin['a2'])
pin_a_list.append(INSTR.pin['opt_a2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['a2'].x)
x_list.append(INSTR.pin['opt_a2'].x)
if (r_idx==rows and c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b2'])
pin_b_list.append(INSTR.pin['opt_b2'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b2'].x)
x_list.append(INSTR.pin['opt_b2'].x)
if (r_idx==1 and c_idx==cols-1):
## revised in 2026.06.07 by Qin Yue
# legacy: pin_b_list.append(INSTR.pin['b1'])
pin_b_list.append(INSTR.pin['opt_b1'])
## revised in 2026.06.07 by Qin Yue
# legacy: x_list.append(INSTR.pin['b1'].x)
x_list.append(INSTR.pin['opt_b1'].x)
xmax = np.max(x_list)
xmin = np.min(x_list)
if (self.port_align):
# print("A:",len(pin_a_list))
# print("B:",len(pin_b_list))
if self.mesh_type == "triangle" :
for itn in range(0,len(pin_a_list)):
nd.strt(length=0.2,width=self.w_wg,xs=self.xs_wg).put(pin_a_list[itn])
num_compensate_cell = len(pin_a_list)-2-itn
if num_compensate_cell < 0 : num_compensate_cell = 0
for _num_ in range(num_compensate_cell) :
self.cell_compensate.put()
nd.strt(
## revised in 2026.06.07 by Qin Yue
# legacy: length=abs(self.MZI_unit.pin['a1'].x-self.MZI_unit.pin['b1'].x)-abs(self.cell_compensate.pin['a1'].x-self.cell_compensate.pin['b1'].x),
length=abs(self.MZI_unit.pin['opt_a1'].x-self.MZI_unit.pin['opt_b1'].x)-abs(self.cell_compensate.pin['opt_a1'].x-self.cell_compensate.pin['opt_b1'].x),
width=self.w_wg, xs=self.xs_wg
).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a'+str(itn+1),pin=nd.Pin().put(),width=0.45).put()
nd.Pin(name='opt_a'+str(itn+1),pin=nd.Pin().put(),width=0.45,type="optical:").put()
nd.strt(length=0.2,width=self.w_wg,xs=self.xs_wg).put(pin_b_list[itn])
num_compensate_cell = len(pin_a_list)-2-itn
if num_compensate_cell < 0 : num_compensate_cell = 0
for _num_ in range(num_compensate_cell) :
self.cell_compensate.put(flip=True)
nd.strt(
## revised in 2026.06.07 by Qin Yue
# legacy: length=abs(self.MZI_unit.pin['a1'].x-self.MZI_unit.pin['b1'].x)-abs(self.cell_compensate.pin['a1'].x-self.cell_compensate.pin['b1'].x),
length=abs(self.MZI_unit.pin['opt_a1'].x-self.MZI_unit.pin['opt_b1'].x)-abs(self.cell_compensate.pin['opt_a1'].x-self.cell_compensate.pin['opt_b1'].x),
width=self.w_wg, xs=self.xs_wg
).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b'+str(itn+1),pin=nd.Pin().put(),width=0.45).put()
nd.Pin(name='opt_b'+str(itn+1),pin=nd.Pin().put(),width=0.45,type="optical:").put()
elif self.mesh_type == "parallelogram" :
for itn in range(0,len(pin_a_list)):
if np.abs(xmin != pin_a_list[itn].x)>0.001 :
nd.strt(length=self.L_arm,width=self.w_wg,xs=self.xs_wg).put(pin_a_list[itn])
temp = self.cell_compensate.put()
temp = nd.strt(length=abs(xmin-temp.pin['b0'].x),width=self.w_wg,xs=self.xs_wg).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a'+str(itn+1) ,width=0.45, pin=temp.pin['b0'].move(-0.05,0,0)).put()
nd.Pin(name='opt_a'+str(itn+1) ,width=0.45, pin=temp.pin['b0'].move(-0.05,0,0),type="optical:").put()
else :
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a'+str(itn+1) ,width=0.45).put(pin_a_list[itn])
nd.Pin(name='opt_a'+str(itn+1) ,width=0.45,type="optical:").put(pin_a_list[itn])
if np.abs(xmax-pin_b_list[itn].x)>0.001 :
nd.strt(length=self.L_arm,width=self.w_wg,xs=self.xs_wg).put(pin_b_list[itn])
temp = self.cell_compensate.put(flip=(itn==0))
temp = nd.strt(length=abs(xmax-temp.pin['b0'].x)+0.001,width=self.w_wg,xs=self.xs_wg).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b'+str(itn+1) ,width=0.45, pin=temp.pin['b0'].move(-0.05,0,0)).put()
nd.Pin(name='opt_b'+str(itn+1) ,width=0.45, pin=temp.pin['b0'].move(-0.05,0,0),type="optical:").put()
else :
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b'+str(itn+1) ,width=0.45).put(pin_b_list[itn])
nd.Pin(name='opt_b'+str(itn+1) ,width=0.45,type="optical:").put(pin_b_list[itn])
print("## ===== %s mesh DONE ===== ##" % (self.mesh_type))
if (show_pins):
nd.put_stub()
return mesh
class AMZI_W:
"""Asymmetric MZI with W-shaped phase shifter arms.
Parameters
----------
name : str, optional
Nazca cell name.
BS : Any, optional
Beam splitter cell or object. If omitted, a default DC is generated.
xs_wg : str, optional
Optical waveguide cross-section name.
D_arm : int, optional
Vertical spacing between MZI arms.
w_wg : float, optional
Input/output waveguide width.
R_bend : int, optional
Routing bend radius.
n_bend : int, optional
Number of W-shaped bend periods.
w_arm : float, optional
Internal arm width. If omitted, ``w_wg`` is used.
Ltp : int, optional
Taper length between bus and arm widths.
xs_heater : str, optional
Heater cross-section name.
dL_ht : float, optional
Heater routing vertical offset.
dL_AMZI : float, optional
Differential length added for AMZI behavior.
L_heater : Any, optional
Optional explicit heater length.
xs_metal : str, optional
Metal routing cross-section name.
w_ht : float, optional
Heater width.
w_metal : float, optional
Metal routing width.
D_port : Any, optional
Output port pitch override.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Isolation helper object or cell.
port_align : bool, optional
Align optical ports to a common grid.
L_patch : float, optional
Straight patch length at routing interfaces.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
Attributes
----------
cell : nazca.Cell
Generated asymmetric W-MZI layout cell.
"""
def __init__(self,
name: str="AMZI_W",
BS: Any=None,
xs_wg: str='strip',
D_arm: int = 50,
w_wg: float = 0.45,
R_bend: int=6,
n_bend: int=3,
w_arm: Optional[float] = None,
Ltp: int = 15,
xs_heater: str = 'heater',
dL_ht: float = 30,
dL_AMZI: float = 0,
L_heater: Any = None,
xs_metal: str = 'metal',
w_ht: float = 2.5,
w_metal: float = 8,
D_port: Any = None,
via_h2m: Any = None,
isl: Any = None,
port_align: bool = True,
L_patch: float = 0.25,
show_pins: bool=False) -> None:
"""Initialize the AMZI W composite.
See the class docstring for parameter descriptions.
"""
self.BS = __BS_generate__(BS=BS,xs=xs_wg,func_name="mxpic::functioanl::Umat_2x2_S")
if (w_arm==None):
w_arm = w_wg
self.L_arm = n_bend*R_bend*4+L_patch*4*n_bend+L_patch*2
self.name = name
self.L_heater = L_heater
self.D_arm = D_arm
self.dL_AMZI = dL_AMZI
self.R_bend = R_bend
self.n_bend = n_bend
self.w_wg = w_wg
self.xs_wg = xs_wg
self.D_port = D_port
self.xs_heater = xs_heater
self.xs_metal = xs_metal
self.w_ht = w_ht
self.w_metal = w_metal
self.via_h2m = via_h2m
self.isl = isl
self.dL_ht = dL_ht
self.port_align = port_align
self.Ltp = Ltp
self.w_arm = w_arm
self.cell = self.generate_gds(show_pins=show_pins)
def __BS_generate__(self,BS,xs):
if (BS==None):
BS_cell = DC(xs=xs,w_cp=0.45,w_wg=0.45,L_cp=9.27,angle=10,R0=15,Rmax=15,Rmin=15).cell
elif (isinstance(BS,nd.Cell)):
BS_cell = BS
elif (hasattr(BS,'cell')):
BS_cell = BS.cell
else : raise Exception("ERROR:: In <mxpic::UMat_2x2_S>, BS not recognizable")
return BS_cell
def generate_gds(self,show_pins):
if (self.name==None):
instantiate = False
else :
instantiate = True
self.instantiate = instantiate
with nd.Cell(name=self.name,instantiate=instantiate) as MZI_Unit:
pic_strip = Route(radius=self.R_bend,width=self.w_wg,xs=self.xs_wg)
## revised in 2026.06.07 by Qin Yue
# legacy: self.dL_BS = np.abs(self.BS.pin['a1'].x - self.BS.pin['b1'].x)
self.dL_BS = np.abs(self.BS.pin['opt_a1'].x - self.BS.pin['opt_b1'].x)
## revised in 2026.06.07 by Qin Yue
# legacy: dY_BS = abs(self.BS.pin['b1'].y - self.BS.pin['b2'].y)
dY_BS = abs(self.BS.pin['opt_b1'].y - self.BS.pin['opt_b2'].y)
## revised in 2026.06.07 by Qin Yue
# legacy: dYarm = self.D_arm/2 - self.BS.pin['b1'].y
dYarm = self.D_arm/2 - self.BS.pin['opt_b1'].y
dLmin = np.sqrt(abs(self.R_bend**2 - (self.R_bend-dYarm)**2))*2
if (dLmin>2*self.R_bend):
dLmin = 2*self.R_bend
## revised in 2026.06.07 by Qin Yue
# legacy: BS_L = self.BS.put('b1',-dLmin - self.L_arm/2-1,dY_BS/2,180)
BS_L = self.BS.put('opt_b1',-dLmin - self.L_arm/2-1,dY_BS/2,180)
## revised in 2026.06.07 by Qin Yue
# legacy: BS_R = self.BS.put('a2', dLmin + self.L_arm/2+1,dY_BS/2,0,flip=1)
BS_R = self.BS.put('opt_a2', dLmin + self.L_arm/2+1,dY_BS/2,0,flip=1)
wg_ht = W_waveguide(L_wg=self.L_arm,w_wg=self.w_wg,xs_wg=self.xs_wg,
dL=self.dL_ht+self.dL_AMZI/2/self.n_bend,R_bend=self.R_bend,
w_ht=self.w_ht,w_metal=self.w_metal,
n_bends=self.n_bend,
via_h2m=self.via_h2m,
isl = self.isl,
xs_metal=self.xs_metal,
).cell
wg_oht = W_waveguide(L_wg=self.L_arm,w_wg=self.w_wg,xs_wg=self.xs_wg,
dL=self.dL_ht,R_bend=self.R_bend,
w_ht=0,w_metal=self.w_metal,
n_bends=self.n_bend,
via_h2m=self.via_h2m,
isl = self.isl,
xs_metal=self.xs_metal,
).cell
## revised in 2026.06.07 by Qin Yue
# legacy: wg_U = wg_ht.put('a1',-self.L_arm/2,self.D_arm/2,0)
wg_U = wg_ht.put('opt_a1',-self.L_arm/2,self.D_arm/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: wg_D = wg_oht.put('a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
wg_D = wg_oht.put('opt_a1',-self.L_arm/2,-self.D_arm/2,0,flip=1)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_L.pin['b1'],pin2=wg_U.pin['a1']).put()
pic_strip.sbend_p2p(pin1=BS_L.pin['opt_b1'],pin2=wg_U.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_L.pin['b2'],pin2=wg_D.pin['a1']).put()
pic_strip.sbend_p2p(pin1=BS_L.pin['opt_b2'],pin2=wg_D.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_R.pin['a2'],pin2=wg_U.pin['b1']).put()
pic_strip.sbend_p2p(pin1=BS_R.pin['opt_a2'],pin2=wg_U.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=BS_R.pin['a1'],pin2=wg_D.pin['b1']).put()
pic_strip.sbend_p2p(pin1=BS_R.pin['opt_a1'],pin2=wg_D.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_L.pin['a2'].x-self.R_bend*2-1, self.D_port/2,180)
patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_L.pin['opt_a2'].x-self.R_bend*2-1, self.D_port/2,180)
## revised in 2026.06.07 by Qin Yue
# legacy: patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_L.pin['a1'].x-self.R_bend*2-1,-self.D_port/2,180)
patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_L.pin['opt_a1'].x-self.R_bend*2-1,-self.D_port/2,180)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_L.pin['a1']).put()
pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_L.pin['opt_a1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_L.pin['a2']).put()
pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_L.pin['opt_a2']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_a1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='a2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_a2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['b2'].x+self.R_bend*2+1, self.D_port/2,0)
patch_U = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['opt_b2'].x+self.R_bend*2+1, self.D_port/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['b1'].x+self.R_bend*2+1,-self.D_port/2,0)
patch_D = nd.strt(length=0.5,width=self.w_wg,xs=self.xs_wg).put('a0',BS_R.pin['opt_b1'].x+self.R_bend*2+1,-self.D_port/2,0)
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_R.pin['b2']).put()
pic_strip.sbend_p2p(pin1=patch_U.pin['a0'],pin2=BS_R.pin['opt_b2']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_R.pin['b1']).put()
pic_strip.sbend_p2p(pin1=patch_D.pin['a0'],pin2=BS_R.pin['opt_b1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_b1',pin=patch_U.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
## revised in 2026.06.07 by Qin Yue
# legacy: nd.Pin(name='b2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg).put()
nd.Pin(name='opt_b2',pin=patch_D.pin['b0'].move(-0.05,0,0),width=self.w_wg,type="optical:").put()
if (self.w_ht>0):
nd.Pin(name='ep1',pin=wg_U.pin['ep1']).put()
nd.Pin(name='en1',pin=wg_U.pin['en1']).put()
return MZI_Unit
def generate_test_gds(self,dXgc2gc,dYgc2gc,gc,w_wg=0.5,R_bend=10):
if (isinstance(gc,nd.Cell)) :
gc_cell = gc
elif (hasattr(gc,"cell")):
gc_cell = gc.cell
gds_name = None
if (self.name is not None):
gds_name = self.name + "_test"
with nd.Cell(name=gds_name,instantiate=self.instantiate) as C:
GC1Instr = gc_cell.put('g1',0,0,180)
GC2Instr = gc_cell.put('g1',dXgc2gc,0,0)
GC3Instr = gc_cell.put('g1',0,-dYgc2gc,180)
GC4Instr = gc_cell.put('g1',dXgc2gc,-dYgc2gc,0)
## revised in 2026.06.07 by Qin Yue
# legacy: dYcell = abs(self.cell.pin['a1'].y - self.cell.pin['a2'].y)
dYcell = abs(self.cell.pin['opt_a1'].y - self.cell.pin['opt_a2'].y)
dYoffset = (dYcell - dYgc2gc)/2
## revised in 2026.06.07 by Qin Yue
# legacy: dXcell = abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x)
dXcell = abs(self.cell.pin['opt_a1'].x - self.cell.pin['opt_b1'].x)
dXoffset = -(dXcell - dXgc2gc)/2
## revised in 2026.06.07 by Qin Yue
# legacy: cellInstr = self.cell.put('a1',dXoffset,dYoffset,0)
cellInstr = self.cell.put('opt_a1',dXoffset,dYoffset,0)
pic = Route(xs=self.xs_wg,width=w_wg,radius=R_bend)
## revised in 2026.06.07 by Qin Yue
# legacy: pic.sbend_p2p(pin1=cellInstr.pin['a1'],pin2=GC1Instr.pin['g1']).put()
pic.sbend_p2p(pin1=cellInstr.pin['opt_a1'],pin2=GC1Instr.pin['g1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic.sbend_p2p(pin1=cellInstr.pin['b1'],pin2=GC2Instr.pin['g1']).put()
pic.sbend_p2p(pin1=cellInstr.pin['opt_b1'],pin2=GC2Instr.pin['g1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic.sbend_p2p(pin1=cellInstr.pin['a2'],pin2=GC3Instr.pin['g1']).put()
pic.sbend_p2p(pin1=cellInstr.pin['opt_a2'],pin2=GC3Instr.pin['g1']).put()
## revised in 2026.06.07 by Qin Yue
# legacy: pic.sbend_p2p(pin1=cellInstr.pin['b2'],pin2=GC4Instr.pin['g1']).put()
pic.sbend_p2p(pin1=cellInstr.pin['opt_b2'],pin2=GC4Instr.pin['g1']).put()
return C
## Parlogon mesh
class MZI_mesh_Parl(MZI_mesh_U):
"""Parallelogram MZI mesh topology wrapper.
Parameters
----------
BS : Any, optional
Beam splitter cell or object used by unit cells.
xs_wg : str, optional
Optical waveguide cross-section name.
L_arm : int, optional
Straight arm length in each unit MZI.
D_arm : int, optional
Vertical spacing between MZI arms.
w_wg : float, optional
Input/output waveguide width.
n_ports : int, optional
Number of mesh ports.
R_bend : int, optional
Routing bend radius.
L_compensate : int, optional
Length used for compensation routing.
R_compensate : int, optional
Bend radius used by compensation routing.
xs_heater : str, optional
Heater cross-section name.
bend_heaters : bool, optional
Route heaters along bent arms.
dL_ht : float, optional
Heater routing vertical offset.
xs_metal : str, optional
Metal routing cross-section name.
w_ht : float, optional
Heater width.
w_metal : float, optional
Metal routing width.
w_ram : float, optional
Internal arm width passed to the unit mesh generator.
Ltp : int, optional
Taper length between bus and arm widths.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Isolation helper object or cell.
L_heater : Any, optional
Optional explicit heater length.
port_align : bool, optional
Align optical ports to a common grid.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
Attributes
----------
cell : nazca.Cell
Generated parallelogram MZI mesh layout cell.
"""
def __init__(self,
BS: Any=None, xs_wg: str='strip',
L_arm: int=80, D_arm: int=50,
w_wg: float=0.45, n_ports: int=8, R_bend: int=6, L_compensate: int=10, R_compensate: int=10,
xs_heater: str='heater', bend_heaters: bool=True, dL_ht: float=0, xs_metal: str='metal', w_ht: float=2.5, w_metal: float=8,
w_ram: float = 0.45,Ltp: int=15,
via_h2m: Any = None,
isl: Any = None,
L_heater: Any = None,
port_align: bool=True, show_pins: bool=False) -> None:
"""Initialize the MZI mesh Parl composite.
See the class docstring for parameter descriptions.
"""
super().__init__(BS, xs_wg, L_arm, D_arm, w_wg, n_ports, R_bend, L_compensate, R_compensate, 'parallelogram', xs_heater, bend_heaters, dL_ht, xs_metal, w_ht, w_metal,
w_ram,Ltp,
via_h2m,isl, port_align, L_heater,show_pins)
## Triangle mesh
class MZI_mesh_Tri(MZI_mesh_U):
"""Triangular MZI mesh topology wrapper.
Parameters
----------
BS : Any, optional
Beam splitter cell or object used by unit cells.
xs_wg : str, optional
Optical waveguide cross-section name.
L_arm : int, optional
Straight arm length in each unit MZI.
D_arm : int, optional
Vertical spacing between MZI arms.
w_wg : float, optional
Input/output waveguide width.
n_ports : int, optional
Number of mesh ports.
R_bend : int, optional
Routing bend radius.
L_compensate : int, optional
Length used for compensation routing.
R_compensate : int, optional
Bend radius used by compensation routing.
xs_heater : str, optional
Heater cross-section name.
bend_heaters : bool, optional
Route heaters along bent arms.
dL_ht : float, optional
Heater routing vertical offset.
xs_metal : str, optional
Metal routing cross-section name.
w_ht : float, optional
Heater width.
w_metal : float, optional
Metal routing width.
w_ram : float, optional
Internal arm width passed to the unit mesh generator.
Ltp : int, optional
Taper length between bus and arm widths.
via_h2m : Any, optional
Heater-to-metal via object or cell.
isl : Any, optional
Isolation helper object or cell.
L_heater : Any, optional
Optional explicit heater length.
port_align : bool, optional
Align optical ports to a common grid.
show_pins : bool, optional
Show Nazca pin stubs in the generated layout.
Attributes
----------
cell : nazca.Cell
Generated triangular MZI mesh layout cell.
"""
def __init__(self,
BS: Any=None, xs_wg: str='strip',
L_arm: int=80, D_arm: int=50, w_wg: float=0.45,
n_ports: int=8, R_bend: int=6, L_compensate: int=10, R_compensate: int=10,
xs_heater: str='heater', bend_heaters: bool=True, dL_ht: float=0, xs_metal: str='metal', w_ht: float=2.5, w_metal: float=8,
w_ram: float = 0.45,Ltp: int=15,
via_h2m: Any = None,
isl: Any = None,
L_heater: Any = None,
port_align: bool=True, show_pins: bool=False) -> None:
"""Initialize the MZI mesh Tri composite.
See the class docstring for parameter descriptions.
"""
super().__init__(BS, xs_wg, L_arm, D_arm, w_wg, n_ports, R_bend, L_compensate, R_compensate, 'triangle', xs_heater, bend_heaters, dL_ht, xs_metal, w_ht, w_metal,
w_ram,Ltp,
via_h2m,isl, port_align, L_heater,show_pins)