full compile system build. 2. beam_spliter, MMI and spiral classes added
Build and Release mxPIC Wheels / Build on ubuntu-latest (release) Failing after 7m8s
Build and Release mxPIC Wheels / Build on windows-latest (release) Has been cancelled
Build and Release mxPIC Wheels / Publish to GitHub Releases (release) Has been cancelled

This commit is contained in:
2026-05-07 17:10:00 +08:00
parent dda69d5b84
commit d7c19ed782
115 changed files with 5448 additions and 509 deletions
+6 -2
View File
@@ -1,2 +1,6 @@
from .directional_couplers.directional_couplers import *
from .edge_couplers.EC_dual_layer_px3 import *
from .directional_couplers import *
from .EC_dual_layer_px3 import *
from .directional_couplers import *
from .beam_splitters import *
from .multimode_interferometers import *
from .spiral import *
@@ -0,0 +1,250 @@
import nazca as nd
import numpy as np
from scipy.interpolate import CubicSpline
from ...routing import Route
from ...structures import *
from ...foundries import *
import pandas as pd
from ...structures import _my_polygon
from ...basic import __cell_arg__
class YBranch:
"""
Broadband spline-shaped Y-branch with two bent output ports.
Parameters
----------
name : str or None, optional
Nazca cell name. ``None`` keeps the cell uninstantiated (default is None).
xs : str, optional
Cross-section key used for both the taper body and attachments (default is "strip").
w : Sequence[float], optional
Width control points (µm) used by the cubic spline along the taper axis.
Length must be >= 2. Default is ``[1.2, 1.0, 1.8, 1.2, 1.0, 1.2, 1.2]``.
L : float, optional
Total spline length in microns (default is 6).
R_att : float, optional
Bend radius of each attachment waveguide in microns (default is 10).
A_att : float, optional
Bend angle (degrees) per attachment arc (default is 10).
w_port : float, optional
Output port width in microns (default is 0.45).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
sharp_patch : bool, optional
Add chamfer helpers inside polygons when True (default is True).
res : float, optional
Longitudinal sampling pitch (µm) for polygon discretization (default is 0.1).
"""
def __init__(self,
name : str = None,
xs : str = 'strip',
w : 'list|np.ndarray' = [1.2,1.0,1.8,1.2,1.0,1.2,1.2],
L : float = 6,
R_att : float = 10,
A_att : float = 10,
w_port : float = 0.45,
show_pins : bool = False,
sharp_patch : bool = True,
res : float = 0.1,
) -> None:
self.name = name
if (name!=None):
self.instantiate = True
else :
self.instantiate = False
self.w = w
self.L = L
self.res = res
self.R_att = R_att
self.A_att = A_att
self.w_port = w_port
self.xs = xs
self.cell = self.generate_gds(show_pins=show_pins,sharp_patch=sharp_patch)
def generate_gds(self,show_pins=False,sharp_patch=True):
with nd.Cell(name=self.name,instantiate=self.instantiate) as C:
w = np.r_[self.w]
n_sects = len(self.w)-1
res = self.L/n_sects
n_points = int(self.L/self.res)+1
L = np.linspace(0,self.L,n_sects+1)
L_act = np.linspace(0,self.L,n_points)
f = CubicSpline(L,w) ## cubic spline interpolant
w_act = f(L_act)
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
(a1,b1), (a2,b2),c1,c2 = growx
w_cur = w_act*(a1-a2) + (b1-b2)
if (b1!=0 and b2!=0):
w_cur = max(w_cur)*np.ones(np.shape(w_cur))
vtx_x = np.r_[L_act,np.flip(L_act,0)]
vtx_y = np.r_[w_cur/2,-np.flip(w_cur/2,0)]
vtx = np.c_[vtx_x,vtx_y]
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,self.w[-1]/2 - self.w_port/2,0)
temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=1)
nd.Pin(name='b1',width=self.w_port).put(temp.pin['b1'])
temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,-self.w[-1]/2 + self.w_port/2,0,flip=1)
temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=0)
nd.Pin(name='b2',width=self.w_port).put(temp.pin['b1'])
nd.Pin(name='a1',width=self.w[0]).put(0,0,180)
if (show_pins):
nd.put_stub()
return C
class Ybranch_3wg:
"""
Initialization of a symmetric tapered coupler for 3dB coupling
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
1. taper part
w0 : float, optional
Width (µm) of the center arm at the coupling region entrance (default is 0.4).
w1 : float, optional
Width (µm) of the outer arms at the coupling region exit (default is 0.2).
gap : float, optional
Vertical spacing (µm) between adjacent arms inside the coupler (default is 0.18).
Lcp : float, optional
Length (µm) of each taper section forming the coupler (default is 20).
xs : str, optional
Cross-section key for all segments (default is "strip").
2. attachment part
w_wg : float, optional
External IO waveguide width in microns (default is 0.45).
R0 : float, optional
Bend radius (µm) used for both output waveguides (default is 10).
angle : float, optional
Bend deflection angle in degrees (default is 20).
L_attach : float, optional
Length (µm) of straight sections appended after the output tapers (default is 3).
L_in_tp : float, optional
Taper length (µm) that links the IO waveguide to width ``w0`` (default is 3).
sharp_patch : bool, optional
Insert chamfer helpers when True (default is True).
"""
def __init__(self,
name = None,
w0:float=0.4,
w1:float=0.2,
gap:float=0.18,
Lcp:float=20,
xs:str='strip',
w_wg:float=0.45,
R0:float=10,
angle:float=20,
L_attach:float=3,
L_in_tp:float=3,
sharp_patch:bool=True):
self.name = name
if (self.name==None):
self.instantiate = False
else :
self.instantiate = True
self.w0 = w0
self.w1 = w1
self.gap = gap
self.Lcp = Lcp
self.xs = xs
self.w_wg = w_wg
self.R0 = R0
self.angle = angle
self.L_attach = L_attach
self.L_in_tp = L_in_tp
self.cell = self.generate_gds(sharp_patch=sharp_patch)
self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x)
def generate_gds(self,sharp_patch,err_asy=0):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
w0 = self.w0
w1 = self.w1
Lcp = self.Lcp
gap = self.gap
xs = self.xs
w_wg = self.w_wg
L_attach = self.L_attach
L_in_tp = self.L_in_tp
angle = self.angle
R0 = self.R0
t_mid = nd.taper(width1=w0,width2=w1,length=Lcp,xs=xs).put(0,0,0)
t_u = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,w1/2+w0/2+gap,0)
t_d = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,-(w1/2+w0/2+gap),0)
t_in = nd.taper(width1=w_wg,width2=w0,length=L_in_tp,xs=xs).put(-L_in_tp,0,0)
t_in = nd.strt(width=w_wg,length=L_attach,xs=xs).put(t_in.pin['a0'],flip=0)
nd.Pin(name='a1',pin=t_in.pin['b0']).put()
au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_u.pin['b0'],flip=0)
au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(au.pin['b0'],flip=1)
au = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(au.pin['b0'],flip=0)
au = nd.strt(width=w_wg,length=L_attach,xs=xs).put(au.pin['b0'],flip=0)
nd.Pin(name='b1',pin=au.pin['b0']).put()
ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_d.pin['b0'],flip=1)
ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(ad.pin['b0'],flip=0)
ad = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(ad.pin['b0'],flip=0)
ad = nd.strt(width=w_wg,length=L_attach,xs=xs).put(ad.pin['b0'],flip=0)
nd.Pin(name='b2',pin=ad.pin['b0']).put()
if (sharp_patch==True):
dY = np.abs(ad.pin['b0'].y-au.pin['b0'].y)+w_wg
for layers,growx,growy,acc in nd.layeriter(xs=xs):
(a1,b1), (a2,b2),c1,c2 = growx
if (b1!=0 and b2!=0):
L_patch = dY*(a1-a2)+(b1-b2)
W_patch = dY*(a1-a2)+(b1-b2)
nd.strt(length=W_patch,width=L_patch,layer=layers).put(ad.pin['b0'].x,0,0)
return C
def generate_test_gds(self,gc,dX_gc2gc=400,dY_gc2gc=80,sharp_patch = True,Rbend=15):
with nd.Cell(instantiate=False) as C:
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::Ybranch_3wg::generate_test_gds")
inst = self.cell.put('a1',-self.L/2,0,0)
gc_In = gc_cell.put('g1',-dX_gc2gc/2,0,180)
gc_O1 = gc_cell.put('g1',dX_gc2gc/2, dY_gc2gc/2,0)
gc_O2 = gc_cell.put('g1',dX_gc2gc/2,-dY_gc2gc/2,0)
pic_strip = Route(radius=Rbend,width=self.w_wg,xs=self.xs)
pic_strip.taper(pin=gc_O1.pin['g1'],width1=gc_O1.pin['g1'].width,width2=self.w_wg,length=5,arrow=False)
pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b1'],arrow=False).put()
pic_strip.taper(pin=gc_O2.pin['g1'],width1=gc_O2.pin['g1'].width,width2=self.w_wg,length=5,arrow=False)
pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b2'],arrow=False).put()
pic_strip.taper_p2p(pin1=gc_In.pin['g1'],pin2=inst.pin['a1'],arrow=False).put()
return C
@@ -3,9 +3,9 @@ import nazca as nd
import numpy as np
from numpy import pi
from ....routing import Route
from ....structures import _my_polygon,circle,Clothoid
from ....basic import __cell_arg__
from ...routing import Route
from ...structures import _my_polygon,circle,Clothoid
from ...basic import __cell_arg__
class ring_bus_wg:
## two types:
@@ -1 +0,0 @@
from .directional_couplers import *
@@ -3,9 +3,9 @@ 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__
from ...routing import Route
from ...structures import _my_polygon,circle,Clothoid,hole
from ...basic import __cell_arg__
''' Class for nanoantenna '''
@@ -0,0 +1,260 @@
from turtle import shape
import nazca as nd
import numpy as np
import math
from ...routing import Route
from ...structures import *
from ...structures import _my_polygon,Conchoid
""" Mono layer MMI """
class MMI_ML:
"""
Multi-layer (mono-layer) multimode interference (MMI) device generator.
Parameters
----------
name : str or None, optional
Nazca cell name. ``None`` keeps the cell uninstantiated (default is None).
L_arm : Sequence[float], optional
Segment lengths (µm) of each arm taper section (default is ``[10]``).
w_arm : Sequence[float], optional
Corresponding arm widths (µm). Length must be ``len(L_arm) + 1`` (default is ``[0.45, 1.35]``).
xs : str, optional
Nazca cross-section key used for both arm and MMI regions (default is "strip").
arm_sine_width : bool, optional
If True, arm width follows a cosine taper instead of linear interpolation (default is False).
L_mmi : Sequence[float], optional
Segment lengths (µm) within the central MMI body (default is ``[10]``).
w_mmi : Sequence[float], optional
MMI widths (µm). Length must be ``len(L_mmi) + 1`` (default is ``[5, 5]``).
mmi_sine_width : bool, optional
If True, MMI width transition uses cosine instead of linear interpolation (default is False).
sharp_patch : bool, optional
Insert chamfer polygons at acute corners when ``True`` (default is True).
show_pins : bool, optional
Draw Nazca stub markers for debugging when ``True`` (default is False).
res : float, optional
Longitudinal sampling resolution (µm) for polygon generation (default is 0.01).
N_out : int, optional
Number of output ports (default is 3).
N_in : int, optional
Number of input ports (default is 1).
Dp_out : float, optional
Vertical pitch (µm) between adjacent output ports (default is 1.5).
Dp_in : float, optional
Vertical pitch (µm) between adjacent input ports (default is 1.5).
"""
def __init__(self,
name=None,
L_arm=[10],
w_arm=[0.45,1.35],
xs = 'strip',
arm_sine_width=False,
L_mmi = [10],
w_mmi = [5,5],
mmi_sine_width=False,
sharp_patch=True,
show_pins = False,
res = 0.01,
N_out = 3,
N_in = 1,
Dp_out = 1.5,
Dp_in = 1.5,
) -> None:
self.name = name
if (self.name==None):
self.instantiate = False
else :
self.instantiate = True
self.L_arm = L_arm
self.xs = xs
self.w_arm = w_arm
self.arm_sine_width = arm_sine_width
self.L_mmi = L_mmi
self.w_mmi = w_mmi
self.res = res
self.N_out = N_out
self.N_in = N_in
self.Dp_out = Dp_out
self.Dp_in = Dp_in
self.mmi_sine_width = mmi_sine_width
self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins)
self.L = np.sum(self.L_arm)*2+np.sum(self.L_mmi)
def generate_gds(self,sharp_patch,show_pins):
with nd.Cell(instantiate=self.instantiate,name=self.name) as C:
L = 0
Lsg = []
Wsg = []
for idx in range(0,len(self.L_arm)):
n_points = round(self.L_arm[idx]/self.res)+1
L_sect = np.linspace(L,L+self.L_arm[idx],n_points)
Lsg = np.r_[Lsg,L_sect]
if (self.arm_sine_width):
dw = self.w_arm[idx+1]-self.w_arm[idx]
w_sect = -np.cos(L_sect/self.L_arm[idx]*pi)*dw + (self.w_arm[idx+1]-self.w_arm[idx])/2
else:
w_sect = np.linspace(self.w_arm[idx],self.w_arm[idx+1],n_points)
Wsg = np.r_[Wsg,w_sect]
L = L + self.L_arm[idx]
with nd.Cell(instantiate=False) as Arm:
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
(a1,b1), (a2,b2),c1,c2 = growx
vtx_y = np.r_[Wsg*a1+b1, np.flip(Wsg,0)*a2+b2]
vtx_x = np.r_[Lsg, np.flip(Lsg,0)]
vtx = np.c_[vtx_x,vtx_y]
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
nd.Pin(name='a1',width=Wsg[0]).put(0,0,180)
nd.Pin(name='b1',width=Wsg[-1]).put(L,0,0)
""" For central MMI """
L_mmi = 0
Lsg_mmi = []
Wsg_mmi = []
for idx in range(0,len(self.L_mmi)):
n_points = round(self.L_mmi[idx]/self.res)+1
L_sect = np.linspace(L_mmi,L_mmi+self.L_mmi[idx],n_points)
Lsg_mmi = np.r_[Lsg_mmi,L_sect]
if (self.arm_sine_width):
dw = self.w_mmi[idx+1]-self.w_mmi[idx]
w_sect = -np.cos(L_sect/self.L_mmi[idx]*pi)*dw + (self.w_mmi[idx+1]-self.w_mmi[idx])/2
else:
w_sect = np.linspace(self.w_mmi[idx],self.w_mmi[idx+1],n_points)
Wsg_mmi = np.r_[Wsg_mmi,w_sect]
L_mmi = L_mmi + self.L_mmi[idx]
with nd.Cell(instantiate=False) as MMI:
for layers,growx,growy,acc in nd.layeriter(xs=self.xs):
(a1,b1), (a2,b2),c1,c2 = growx
vtx_y = np.r_[Wsg_mmi*a1+b1, np.flip(Wsg_mmi,0)*a2+b2]
vtx_x = np.r_[Lsg_mmi, np.flip(Lsg_mmi,0)]
vtx = np.c_[vtx_x,vtx_y]
if (b1==0 and b2==0):
_my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0)
else :
w = max(Wsg_mmi)+b1*2
L = max(Lsg_mmi)+b1*2
nd.strt(length=L,layer=layers,width=w).put(-b1,0,0)
nd.Pin(name='a1',width=Wsg_mmi[0]).put(0,0,180)
nd.Pin(name='b1',width=Wsg_mmi[-1]).put(L_mmi,0,0)
for idx_in in range(0,self.N_in):
Arm_inst = Arm.put('b1',0,self.Dp_in*(-idx_in+(self.N_in-1)/2),180)
nd.Pin(name='a'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put()
for idx_in in range(0,self.N_out):
Arm_inst = Arm.put('b1',L_mmi,self.Dp_out*(-idx_in+(self.N_out-1)/2),0)
nd.Pin(name='b'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put()
MMI.put('a1',0,0,0)
if (show_pins):
nd.put_stub()
return C
def generate_test_gds(self,gc,dX_gc2gc,dY_gc2gc,R_bend=10,Xout_offset=50):
if (isinstance(gc,nd.Cell)):
gc_cell =gc
elif (hasattr(gc,'cell')):
gc_cell = gc.cell
else :
raise Exception("ERROR: In <mxpic::passive::ADC_STD_2x2::generate_test_gds>, <gc> is not recongized as a cell")
with nd.Cell(instantiate=False) as C:
INST = self.cell.put(-self.L/2,0,0)
pic_strip = Route(width=self.w_arm[0],radius=R_bend,xs=self.xs)
for idx_in in range(0,self.N_in):
GC = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc*(-idx_in + (self.N_in-1)/2),180)
pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['a'+str(idx_in+1)],Lstart=dX_gc2gc/10).put()
for idx_in in range(0,self.N_out):
toggle = np.mod(idx_in,2)-0.5
GC = gc_cell.put('g1', dX_gc2gc/2+Xout_offset*toggle,dY_gc2gc*(-idx_in + (self.N_out-1)/2),0)
pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['b'+str(idx_in+1)],Lstart=dX_gc2gc/10).put()
return C
class MMI_STD(MMI_ML):
"""
Convenience wrapper for standard MMIs with equal-length arms and uniform MMI body.
Parameters
----------
name : str or None, optional
Nazca cell name (default is None).
N_out : int, optional
Number of output ports (default is 3).
N_in : int, optional
Number of input ports (default is 1).
L_arm : float, optional
Single arm length in microns (default is 10).
w_wg : float, optional
Input/output waveguide width in microns (default is 0.45).
w_port : float, optional
Width at the transition between the taper and MMI (default is 1.2).
xs : str, optional
Cross-section key for all regions (default is "strip").
L_mmi : float, optional
Central MMI length in microns (default is 10).
w_mmi : float, optional
Central MMI width in microns (default is 5).
sharp_patch : bool, optional
Add chamfer helpers when True (default is True).
show_pins : bool, optional
Draw Nazca stub markers when True (default is False).
Dp_out : float, optional
Output port pitch in microns (default is 1.5).
Dp_in : float, optional
Input port pitch in microns (default is 1.5).
"""
def __init__(self,
name=None,
N_out=3,
N_in=1,
L_arm=10,
w_wg=0.45,
w_port = 1.2,
xs='strip',
L_mmi=10,
w_mmi=5,
sharp_patch=True,
show_pins=False,
Dp_out=1.5,
Dp_in=1.5) -> None:
super().__init__(name=name,
L_arm=[L_arm],
w_arm=[w_wg,w_port],
xs=xs,
arm_sine_width=False,
L_mmi=[L_mmi],
w_mmi=[w_mmi,w_mmi],
mmi_sine_width=False,
sharp_patch=sharp_patch,
show_pins=show_pins,
res=min([L_mmi,L_arm]), ## taper resolution
N_out=N_out,
N_in=N_in,
Dp_out=Dp_out,
Dp_in=Dp_in)
File diff suppressed because it is too large Load Diff