Files

865 lines
36 KiB
Python

import nazca as nd
import numpy as np
import os
import json
import time
import sys
import h5py
import matplotlib.pyplot as plt
from typing import Any, Literal, Optional
def __getLumericalLibPATH__():
path = os.path.dirname(os.path.abspath(__file__)) + "\\Lumerical\\"
return path
def __checkLumericalDIR__():
path = os.path.dirname(os.path.abspath(__file__))
with open(path+"\\LumericalPATH.json") as FID:
FILE_CONTEXT = json.load(FID)
DEFAULT_PATH = FILE_CONTEXT["DEFAULT_PATH"]
for idx in range(len(DEFAULT_PATH)):
if (os.path.exists(DEFAULT_PATH[idx])):
return DEFAULT_PATH[idx]
raise Exception("NO FDTD link found using default paths")
def __PathGenerate__(dev_name, path=None):
""" creating folders """
if (path is None):
folder = f"{dev_name}_simu\\"
elif (isinstance(path,str)):
if (path.endswith("\\")):
folder = f"{path}{dev_name}_simu\\"
else:
folder = f"{path}\\{dev_name}_simu\\"
try:
os.makedirs(folder)
except:
pass
return folder
def __LoggAllAttrs__(device,folder,dev_name):
attrs = dir(device)
time_curr = time.strftime('%Y%m%d-%H%M%S', time.localtime())
attrDict = {}
attrDict["time"] = time_curr
""" recording ALL parameters inside the device """
for attr in attrs:
if not attr.startswith("__"):
attrCur = getattr(device,attr)
if (isinstance(attrCur,float) or isinstance(attrCur,int)):
attrDict[attr] = attrCur
elif (isinstance(attrCur,list)):
if (isinstance(attrCur[0],float) or isinstance(attrCur[0],int)):
attrDict[attr] = attrCur
elif (isinstance(attrCur,np.ndarray)):
attrDict[attr] = attrCur.tolist()
""" """
with open(folder+f"{dev_name}_mxpic.json","w") as FID:
json.dump(attrDict,FID)
device = device.cell
def PortParas(pin,width,height,radius=0):
port_dict = {}
port_dict["x"] = pin.x
port_dict["y"] = pin.y
port_dict["a"] = pin.a
port_dict["width"] = width
port_dict["height"] = height
port_dict["radius"] = radius
return port_dict
def MonitorParas(x,y,z,dx,dy,dz):
mont_dict = {}
mont_dict["x"] = x
mont_dict["y"] = y
mont_dict["z"] = z
mont_dict["dx"] = dx
mont_dict["dy"] = dy
mont_dict["dz"] = dz
return mont_dict
def DEVICE_2X2_FDTD_INIT(fdtd,run=False,instrcutPATH=None,LibPATH=None):
fdtd.eval("newproject;")
fdtd.eval("switchtolayout;")
fdtd.eval("deleteall;")
fdtd.eval("clc;")
if (LibPATH is None):
fdtd.eval("ABSOLUTE_LIB_DIR = read(\'lib_path.txt\');")
elif (isinstance(LibPATH,str)):
if (LibPATH.endswith("\\")):
fdtd.eval("ABSOLUTE_LIB_DIR = \'"+LibPATH+"\';")
else:
fdtd.eval("ABSOLUTE_LIB_DIR = \'"+LibPATH+"\\\\\';")
##### Install maxwell's library #####
fdtd.eval("PATH_LIB = ABSOLUTE_LIB_DIR + \'mx_lib_install.lsf\';")
fdtd.eval("feval(PATH_LIB);")
# fdtd.eval("feval(\'GDS_SIMU_DEVICE_2X2.lsf\');")
fdtd.eval("FUNC_DEVICE_2X2(\'"+instrcutPATH+"\');")
if (run):
fdtd.eval("run;")
fdtd.eval("DATA_RETRIEVE_DEVICE_2X2(\'"+instrcutPATH+"\');")
def tuple_to_complex(t):
return complex(t[0], t[1])
## revised in 2026.06.07 by Qin Yue
# legacy: def SimuDataFigurePlot(simuPath,devName,saveFlag=True,ports=["a1","b1"]):
def SimuDataFigurePlot(simuPath,devName,saveFlag=True,ports=["opt_a1","opt_b1"]):
if (simuPath.endswith("\\")):
pass
else:
simuPath = simuPath + "\\"
""" data analysis """
data = h5py.File(simuPath + devName + "_results.mat","r")
dataDict = {}
for portName in ports:
""" Getting result of transmission """
dataDict[portName] = {}
dataDict[portName]["trans"] = np.squeeze(data[portName]["power"]["T"][()])
dataDict[portName]["wl"] = np.squeeze(data[portName]["power"]["lambda"][()])
dataDict[portName]["modes"] = np.squeeze(data[portName]["modes"]["T_net"][()])
dataDict[portName]["E"] = np.squeeze(data[portName]["E"]["E"][()])
dataDict[portName]["H"] = np.squeeze(data[portName]["H"]["H"][()])
""" Calculating the propagation field """
E_prg = np.squeeze(data["z1"]["E"]["E"][()])
wl_prg = np.squeeze(data["z1"]["E"]["lambda"][()])
x = np.squeeze(data["z1"]["E"]["x"][()])
y = np.squeeze(data["z1"]["E"]["y"][()])
z = np.squeeze(data["z1"]["E"]["z"][()])
E_prg = np.vectorize(tuple_to_complex)(E_prg)
# plt.figure(figsize=(12,6))
fig,ax = plt.subplots(3,3,figsize=(20, 9))
plt.subplots_adjust(wspace=0.5, hspace=0.3)
manager = plt.get_current_fig_manager()
manager.window.wm_geometry("+100+100")
wl_idx_plt = [0,int(np.floor(len(wl_prg)/2+1))-1,len(wl_prg)-1]
for idx in range(len(wl_idx_plt)):
# plt.subplot(3,2,2*idx+1)
Ex = np.abs(E_prg[idx,0,:].reshape(len(y),len(x)))
Ey = np.abs(E_prg[idx,1,:].reshape(len(y),len(x)))
Ez = np.abs(E_prg[idx,2,:].reshape(len(y),len(x)))
E_mag = np.sqrt(np.square(Ex) + np.square(Ey) + np.square(Ez))
ax[0,idx].set_title(f"wavelength = {wl_prg[wl_idx_plt[idx]]*1e+9:.1f} nm")
ax[0,idx].pcolor(np.real(E_mag))
""" Plotting the port transmission """
## revised in 2026.06.07 by Qin Yue
# legacy: if ("b1" in ports and "a1" in ports):
if ("opt_b1" in ports and "opt_a1" in ports):
dataDict["Ephase_11"] = np.squeeze(data["Ephase_11"][()])
## revised in 2026.06.07 by Qin Yue
# legacy: Trans_11 = dataDict["b1"]["trans"]
Trans_11 = dataDict["opt_b1"]["trans"]
## revised in 2026.06.07 by Qin Yue
# legacy: Tmodes = dataDict["b1"]["modes"]
Tmodes = dataDict["opt_b1"]["modes"]
ax1 = ax[1,0]
ax2 = ax[2,0]
ax1.set_title("a_1 to b_1 trans [Through]")
## revised in 2026.06.07 by Qin Yue
# legacy: ax1.plot(dataDict["a1"]["wl"]*1e+6,Trans_11,linewidth=3)
ax1.plot(dataDict["opt_a1"]["wl"]*1e+6,Trans_11,linewidth=3)
""" plotting the eigen mode decomposition """
dataSZ = np.shape(Tmodes)
## revised in 2026.06.07 by Qin Yue
# legacy: plt_wl = dataDict["a1"]["wl"]*1e+6
plt_wl = dataDict["opt_a1"]["wl"]*1e+6
dataPlt = []
""" Plotting the Mode crosstalk for target modes """
if (len(dataSZ) > 1):
for pltIdx in range(0,dataSZ[0]):
_data_ = np.abs(Tmodes[pltIdx,:])
dataPlt.append(_data_)
else:
_data_ = np.abs(Tmodes)
dataPlt.append(_data_)
for pltIdx in range(0,len(dataPlt)):
_data_ = dataPlt[pltIdx]
ax1.plot(plt_wl,_data_,linewidth=3)
Trans_dB = 10*np.log10(_data_)
ax2.plot(plt_wl,Trans_dB,label=f"mode_{pltIdx}",linewidth=3)
## revised in 2026.06.07 by Qin Yue
# legacy: if ("b2" in ports and "a1" in ports):
if ("opt_b2" in ports and "opt_a1" in ports):
dataDict["Ephase_21"] = np.squeeze(data["Ephase_11"][()])
## revised in 2026.06.07 by Qin Yue
# legacy: Trans_21 = dataDict["b2"]["trans"]
Trans_21 = dataDict["opt_b2"]["trans"]
## revised in 2026.06.07 by Qin Yue
# legacy: Tmodes = dataDict["b2"]["modes"]
Tmodes = dataDict["opt_b2"]["modes"]
ax1 = ax[1,1]
ax2 = ax[2,1]
ax1.set_title("a_1 to b_1 trans [Through]")
## revised in 2026.06.07 by Qin Yue
# legacy: ax1.plot(dataDict["a1"]["wl"]*1e+6,Trans_21)
ax1.plot(dataDict["opt_a1"]["wl"]*1e+6,Trans_21)
""" plotting the eigen mode decomposition """
dataSZ = np.shape(Tmodes)
## revised in 2026.06.07 by Qin Yue
# legacy: plt_wl = dataDict["a1"]["wl"]*1e+6
plt_wl = dataDict["opt_a1"]["wl"]*1e+6
dataPlt = []
""" Plotting the Mode crosstalk for target modes """
if (len(dataSZ) > 1):
for pltIdx in range(0,dataSZ[0]):
_data_ = np.abs(Tmodes[pltIdx,:])
dataPlt.append(_data_)
else:
_data_ = np.abs(Tmodes)
dataPlt.append(_data_)
for pltIdx in range(0,len(dataPlt)):
_data_ = dataPlt[pltIdx]
ax1.plot(plt_wl,_data_,linewidth=3)
Trans_dB = 10*np.log10(_data_)
ax2.plot(plt_wl,Trans_dB,label=f"mode_{pltIdx}",linewidth=3)
## revised in 2026.06.07 by Qin Yue
# legacy: if ("a2" in ports and "a1" in ports):
if ("opt_a2" in ports and "opt_a1" in ports):
## revised in 2026.06.07 by Qin Yue
# legacy: Refl_21 = dataDict["a2"]["trans"]
Refl_21 = dataDict["opt_a2"]["trans"]
# fig,ax = plt.subplots(3,2,6)
ax[1,2].set_title("a_1 to a_2 trans [Replection]")
## revised in 2026.06.07 by Qin Yue
# legacy: ax[1,2].plot(dataDict["a1"]["wl"]*1e+6,Refl_21,linewidth=3)
ax[1,2].plot(dataDict["opt_a1"]["wl"]*1e+6,Refl_21,linewidth=3)
ax2 = ax[2,2]
Trans_dB = 10*np.log10(Refl_21)
## revised in 2026.06.07 by Qin Yue
# legacy: ax2.plot(dataDict["a1"]["wl"]*1e+6,Trans_dB,linewidth=3)
ax2.plot(dataDict["opt_a1"]["wl"]*1e+6,Trans_dB,linewidth=3)
if (saveFlag):
""" in CPU mode, there will be no folder """
try:
os.makedirs(simuPath + devName + "_simu\\")
except:
pass
plt.savefig(simuPath + devName + "_simu\\"+devName+"_results.jpg", format='jpg', dpi=300)
plt.close()
data.close()
class DEVICE_PORTS:
def __init__(self,dev_name: str,device: Any,simu_xs: str="strip",
port_width: int=3,path: Optional[str]=None,wl: list=[1.5,1.6],
mesh_order: int=5,layer_heights: list=[0.22],FDTD_height: int=2,
material: str="Si (Silicon) - Palik",
CladMaterial: str = "SiO2 (Glass) - Palik",
modeIdx: list=[1,2,3,4],
sourceMode: int = 1,
## revised in 2026.06.07 by Qin Yue
# legacy: ports_extend: list=["a1"],
ports_extend: list=["opt_a1"],
SimuBox: Any = None,
## revised in 2026.06.07 by Qin Yue
# legacy: port_radius: dict={"a1":0},
port_radius: dict={"opt_a1":0},
sample_points: int = 101,
Field_sample: int = 3,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
runFDTD: bool = False,
GPUOn: bool = True,
## revised in 2026.06.07 by Qin Yue
# legacy: port_names: list = ["a1","b1","a2","b2"],
port_names: list = ["opt_a1","opt_b1","opt_a2","opt_b2"],
) -> None:
self.dev_name = dev_name
time_curr = time.strftime('%Y%m%d-%H%M%S', time.localtime())
""" creating folders """
folder = __PathGenerate__(path=path,dev_name=dev_name)
if (isinstance(device,nd.Cell)):
device = device
elif (hasattr(device,"cell")):
__LoggAllAttrs__(device=device,folder=folder,dev_name=dev_name)
device = device.cell
""" """
else:
raise Exception("Argument type not recongized")
if (dev_name == device.cell_name):
self.dev_name = dev_name + "_GDS"
dev_name = self.dev_name
""" exporting GDS """
fname = f"{dev_name}.gds"
with nd.Cell(name=dev_name) as CELL_INSTR:
instr = device.put()
instr.raise_pins()
for name in ports_extend:
if (device.pin[name].xs is None):
_xs_extend_ = simu_xs
else:
_xs_extend_ = device.pin[name].xs
nd.strt(xs=_xs_extend_,length=port_width*2,width=device.pin[name].width).put(instr.pin[name])
nd.text(layer=1001,text=time_curr,height=20).put()
nd.export_gds(filename=folder + fname,topcells=CELL_INSTR,flat=True)
jsonFile = {}
jsonFile["ports"] = {}
jsonFile["mont"] = {}
jsonFile["input"] = {}
jsonFile["layers"] = {}
jsonFile["ports"]["names"] = port_names
for name in jsonFile["ports"]["names"]:
jsonFile["ports"][name] = PortParas(pin=CELL_INSTR.pin[name],width=port_width,height=FDTD_height)
# jsonFile["ports"]["a1"] = PortParas(pin=CELL_INSTR.pin['a1'],width=port_width,height=FDTD_height)
# jsonFile["ports"]["a2"] = PortParas(pin=CELL_INSTR.pin['a2'],width=port_width,height=FDTD_height)
# jsonFile["ports"]["b1"] = PortParas(pin=CELL_INSTR.pin['b1'],width=port_width,height=FDTD_height)
# jsonFile["ports"]["b2"] = PortParas(pin=CELL_INSTR.pin['b2'],width=port_width,height=FDTD_height)
for key in port_radius:
jsonFile["ports"][key]["radius"] = port_radius[key]
""" port Z for propagation recording """
ports = jsonFile["ports"]
## revised in 2026.06.07 by Qin Yue
# legacy: dx = abs(ports["a1"]["x"] - ports["b1"]["x"])
dx = abs(ports["opt_a1"]["x"] - ports["opt_b1"]["x"])
## revised in 2026.06.07 by Qin Yue
# legacy: cX = (ports["a1"]["x"] + ports["b1"]["x"])/2
cX = (ports["opt_a1"]["x"] + ports["opt_b1"]["x"])/2
## revised in 2026.06.07 by Qin Yue
# legacy: if ("b2" in jsonFile["ports"]["names"]):
if ("opt_b2" in jsonFile["ports"]["names"]):
## revised in 2026.06.07 by Qin Yue
# legacy: dy = abs(ports["b1"]["y"] - ports["b2"]["y"])
dy = abs(ports["opt_b1"]["y"] - ports["opt_b2"]["y"])
## revised in 2026.06.07 by Qin Yue
# legacy: cY = (ports["b1"]["y"] + ports["b2"]["y"])/2
cY = (ports["opt_b1"]["y"] + ports["opt_b2"]["y"])/2
elif (SimuBox is not None):
dy = SimuBox["dy"]
## revised in 2026.06.07 by Qin Yue
# legacy: cY = ports["b1"]["y"]
cY = ports["opt_b1"]["y"]
else:
dy = 0
## revised in 2026.06.07 by Qin Yue
# legacy: cY = ports["b1"]["y"]
cY = ports["opt_b1"]["y"]
FDTD = {}
FDTD["x"] = cX
FDTD["y"] = cY
FDTD["dx"] = dx
FDTD["dy"] = dy
FDTD["z"] = 0
SimuBoxKeys = ["x","y","dx","dy"]
if (SimuBox is not None):
for key in SimuBoxKeys:
if (key in SimuBox.keys()):
# if ("dx" in SimuBox.keys()):
FDTD[key] = SimuBox[key]
# FDTD["dy"] = SimuBox["dy"]
# if ("x" in SimuBox.keys()):
# FDTD["x"] = SimuBox["x"]
# FDTD["y"] = SimuBox["y"]
""" expansion """
FDTD["dx"] = FDTD["dx"] + port_width
FDTD["dy"] = FDTD["dy"] + port_width
FDTD["dz"] = FDTD_height
FDTD["wl"] = wl
FDTD["mesh_order"] = mesh_order
FDTD["sourceMode"] = sourceMode
if (GPUOn is True):
FDTD["GPUOn"] = 1
else:
FDTD["GPUOn"] = 0
FDTD["Trans_sample_points"] = sample_points
FDTD["Field_sample_points"] = Field_sample
jsonFile["mont"]["z1"] = MonitorParas(x=FDTD["x"],y=FDTD["y"],z=0,dx=FDTD["dx"],dy=FDTD["dy"],dz=0)
""" exporting json configure files """
jsonFile["FDTD"] = FDTD
jsonFile["wafer"] = {}
jsonFile["wafer"]["material"] = material
jsonFile["clad"] = {}
jsonFile["clad"]["material"] = CladMaterial
jsonFile["time"] = time_curr
jsonFile["geometry"] = {}
jsonFile["modes"] = modeIdx
layerNumList = []
layerDataTypeList = []
growxList = []
for layers,growx,growy,acc in nd.layeriter(xs=simu_xs):
(a1,b1), (a2,b2),c1,c2 = growx
layerInfo = nd.get_layer_tuple(layers)
layerNumList.append(layerInfo.layer)
layerDataTypeList.append(layerInfo.datatype)
growxList.append(b1)
if (len(layer_heights) != len(layerNumList)):
raise Exception("Input wafer heigh list is not same length as layer numbers")
jsonFile["layers"]["numbers"] = layerNumList
jsonFile["layers"]["datatype"] = layerDataTypeList
jsonFile["layers"]["growth"] = growxList
jsonFile["layers"]["heights"] = layer_heights
""" """
jsonPath = folder+f"{dev_name}.json"
with open(jsonPath,"w") as FID:
json.dump(jsonFile,FID)
jsonPath = os.path.abspath(jsonPath)
jsonPath = jsonPath.replace('\\',"\\\\")
self.jsonPath = jsonPath
GDSPath = folder + fname
GDSPath = os.path.abspath(GDSPath)
GDSPath = GDSPath.replace('\\',"\\\\")
self.GDSPath = GDSPath
FolderPath = folder
FolderPath = os.path.abspath(FolderPath)
FolderPath = FolderPath.replace('\\',"\\\\")
self.FolderPath = FolderPath
""" writing instruction to Lumerical for operation """
instPath = f"{self.FolderPath}\\Instruction.txt"
self.instPath = instPath
with open(file=instPath,mode="w") as FInst:
FInst.write(f'devName = \"{self.dev_name}\";\n\r')
FInst.write(f'gdspath = \"{self.GDSPath}\";\n\r')
FInst.write(f'folder = \"{self.FolderPath}\";\n\r')
FInst.write(f'jsonPath = \"{self.jsonPath}\";\n\r')
""" Internal Building FDTD """
if (FDTDBuild):
if (LumericalPATH is None):
LuPATH = __checkLumericalDIR__()
else:
if (not os.path.exists(LumericalPATH)):
raise Exception("No Lumerical installation found in the given paths")
LuPATH = LumericalPATH
sys.path.append(LuPATH)
# sys.path.append(os.path.dirname(__file__)) #Current directory
# print(sys.path)
import lumapi
fdtd = lumapi.FDTD()
""" constructing simulation files """
print("Building FDTD project ... ")
DEVICE_2X2_FDTD_INIT(fdtd=fdtd,run=runFDTD,LibPATH=__getLumericalLibPATH__(),instrcutPATH=self.instPath)
print("... FDTD Project to ",self.FolderPath)
fdtd.close()
if (runFDTD):
SimuDataFigurePlot(simuPath=self.FolderPath,devName=self.dev_name,ports=port_names)
self.resultPath = self.FolderPath + "\\" + self.dev_name + "_results.mat"
class DEVICE_RING_BUS(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any, r_ring: float,port_distance: int=6,Aport: Any=None,
simu_xs: str="strip", port_width: int=3,
path: Optional[str]=None, wl: list=[1.5, 1.6], mesh_order: int=5, layer_heights: list=[0.22],
FDTD_height: int=2,
material: str="Si (Silicon) - Palik",
CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
sample_points: int=101,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
GPUOn: bool = True,
runFDTD: bool = False,) -> None:
if (isinstance(device,nd.Cell)):
cell_dev = device
elif (hasattr(device,"cell")):
cell_dev = device.cell
else:
raise Exception("ERROR :: <device> not recongized")
## revised in 2026.06.07 by Qin Yue
# legacy: dx = abs(cell_dev.pin["a1"].x - cell_dev.pin["b1"].x)+port_width
dx = abs(cell_dev.pin["opt_a1"].x - cell_dev.pin["opt_b1"].x)+port_width
if (Aport is None):
## revised in 2026.06.07 by Qin Yue
# legacy: if (cell_dev.pin['b1'].x > r_ring):
if (cell_dev.pin['opt_b1'].x > r_ring):
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['b2'] = nd.Pin(name="b2").put(r_ring,0, 90)
cell_dev.pin['opt_b2'] = nd.Pin(name="opt_b2",type="optical:").put(r_ring,0, 90)
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['a2'] = nd.Pin(name="a2").put(-r_ring,0, 90)
cell_dev.pin['opt_a2'] = nd.Pin(name="opt_a2",type="optical:").put(-r_ring,0, 90)
else:
## revised in 2026.06.07 by Qin Yue
# legacy: x = cell_dev.pin['b1'].x
x = cell_dev.pin['opt_b1'].x
y = -np.sqrt(r_ring**2 - x**2)
a = np.arcsin(x/r_ring)/np.pi*180
## revised in 2026.06.07 by Qin Yue
# legacy: dy_ports = abs(y-cell_dev.pin['b1'].y)
dy_ports = abs(y-cell_dev.pin['opt_b1'].y)
if (dy_ports > port_distance):
## revised in 2026.06.07 by Qin Yue
# legacy: y = cell_dev.pin['b1'].y + port_distance
y = cell_dev.pin['opt_b1'].y + port_distance
x = np.sqrt(r_ring**2 - (abs(y))**2)
a = np.arcsin(x/r_ring)/np.pi*180
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['b2'] = nd.Pin(name="b2").put( x,y, a)
cell_dev.pin['opt_b2'] = nd.Pin(name="opt_b2",type="optical:").put( x,y, a)
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['a2'] = nd.Pin(name="a2").put(-x,y,180-a)
cell_dev.pin['opt_a2'] = nd.Pin(name="opt_a2",type="optical:").put(-x,y,180-a)
else :
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['b2'] = nd.Pin(name="b2").put( x,y, a)
cell_dev.pin['opt_b2'] = nd.Pin(name="opt_b2",type="optical:").put( x,y, a)
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['a2'] = nd.Pin(name="a2").put(-x,y,180-a)
cell_dev.pin['opt_a2'] = nd.Pin(name="opt_a2",type="optical:").put(-x,y,180-a)
else :
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['b2'] = nd.Pin(name="b2").put( r_ring*np.sin(Aport/180*np.pi),-r_ring*np.cos(Aport/180*np.pi), Aport)
cell_dev.pin['opt_b2'] = nd.Pin(name="opt_b2",type="optical:").put( r_ring*np.sin(Aport/180*np.pi),-r_ring*np.cos(Aport/180*np.pi), Aport)
## revised in 2026.06.07 by Qin Yue
# legacy: cell_dev.pin['a2'] = nd.Pin(name="a2").put(-r_ring*np.sin(Aport/180*np.pi),-r_ring*np.cos(Aport/180*np.pi), Aport)
cell_dev.pin['opt_a2'] = nd.Pin(name="opt_a2",type="optical:").put(-r_ring*np.sin(Aport/180*np.pi),-r_ring*np.cos(Aport/180*np.pi), Aport)
## revised in 2026.06.07 by Qin Yue
# legacy: yMax = cell_dev.pin['b2'].y
yMax = cell_dev.pin['opt_b2'].y
yMin = -r_ring - port_width
dy = abs(yMax - yMin)
cy = (yMax + yMin)/2
if (isinstance(device,nd.Cell)):
device = cell_dev
elif (hasattr(device,"cell")):
device.cell = cell_dev
super().__init__(dev_name=dev_name, device=device, simu_xs=simu_xs, port_width=port_width, path=path, wl=wl,
mesh_order=mesh_order, layer_heights=layer_heights,
FDTD_height=FDTD_height, material=material, CladMaterial=CladMaterial,
## revised in 2026.06.07 by Qin Yue
# legacy: modeIdx=modeIdx, ports_extend=["a1","b1"], SimuBox = {"dx":dx,"dy":dy,"y":cy}, port_radius={"a2":r_ring,"b2":r_ring},
modeIdx=modeIdx, ports_extend=["opt_a1","opt_b1"], SimuBox = {"dx":dx,"dy":dy,"y":cy}, port_radius={"opt_a2":r_ring,"opt_b2":r_ring},
sample_points=sample_points,FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,
GPUOn=GPUOn,
## revised in 2026.06.07 by Qin Yue
# legacy: port_names = ["a1","b1","a2","b2"],)
port_names = ["opt_a1","opt_b1","opt_a2","opt_b2"],)
class DEVICE_COUPLER(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any, simu_xs: str="strip", port_width: int=3, path: Optional[str]=None,
wl: list=[1.5, 1.6], mesh_order: int=5, layer_heights: list=[0.22], FDTD_height: int=2,
material: str="Si (Silicon) - Palik", CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
sample_points: int=101,
sourceMode: int = 1,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
runFDTD: bool = False,
GPUOn: bool = True,
) -> None:
super().__init__(dev_name, device, simu_xs, port_width, path, wl, mesh_order,
## revised in 2026.06.07 by Qin Yue
# legacy: layer_heights, FDTD_height, material, CladMaterial, modeIdx, ports_extend=["a1","a2","b1","b2"],SimuBox=None,port_radius={},
layer_heights, FDTD_height, material, CladMaterial, modeIdx, ports_extend=["opt_a1","opt_a2","opt_b1","opt_b2"],SimuBox=None,port_radius={},
sample_points=sample_points,FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,GPUOn=GPUOn,
## revised in 2026.06.07 by Qin Yue
# legacy: port_names = ["a1","b1","a2","b2"],sourceMode=sourceMode)
port_names = ["opt_a1","opt_b1","opt_a2","opt_b2"],sourceMode=sourceMode)
class EULER_CROW_INTER_CP(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any, simu_xs: str="strip",
port_width: int=3, path: Optional[str]=None, wl: list=[1.5, 1.6],
mesh_order: int=5, layer_heights: list=[0.22],
FDTD_height: int=2, material: str="Si (Silicon) - Palik",
CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
SimuBox: Any=None,
sample_points: int=101,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
GPUOn: bool=True,
runFDTD: bool = False) -> None:
""" Device MUST Be CROW device """
""" The pins reconized in here is ra1,ra2,ra3,ra4 and rb1,rb2,rb3,rb4 """
newDev = device
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['a1'] = device.cell.pin['ra2']
newDev.cell.pin['opt_a1'] = device.cell.pin['ra2']
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['a2'] = device.cell.pin['rb2']
newDev.cell.pin['opt_a2'] = device.cell.pin['rb2']
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['b1'] = device.cell.pin['ra4']
newDev.cell.pin['opt_b1'] = device.cell.pin['ra4']
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['b2'] = device.cell.pin['rb4']
newDev.cell.pin['opt_b2'] = device.cell.pin['rb4']
## revised in 2026.06.07 by Qin Yue
# legacy: port_radius = {"a1":device.R1, "a2": device.R1, "b1":-device.R1, "b2":-device.R1}
port_radius = {"opt_a1":device.R1, "opt_a2": device.R1, "opt_b1":-device.R1, "opt_b2":-device.R1}
super().__init__(dev_name, newDev, simu_xs, port_width, path, wl, mesh_order, layer_heights, FDTD_height, material, CladMaterial, modeIdx,
ports_extend=[],
SimuBox=SimuBox, port_radius=port_radius, sample_points=sample_points,
## revised in 2026.06.07 by Qin Yue
# legacy: FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["a1","b1","a2","b2"],
FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["opt_a1","opt_b1","opt_a2","opt_b2"],
GPUOn=GPUOn)
class EULER_CROW_BUS(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any, simu_xs: str="strip",
port_width: int=3, path: Optional[str]=None, wl: list=[1.5, 1.6],
mesh_order: int=5, layer_heights: list=[0.22],
FDTD_height: int=2, material: str="Si (Silicon) - Palik",
CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
SimuBox: Any=None,
sample_points: int=101,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
GPUOn: bool = True,
runFDTD: bool = False) -> None:
""" Device MUST Be CROW device """
""" The pins reconized in here is ra1,ra2,ra3,ra4 and rb1,rb2,rb3,rb4 """
newDev = device
# newDev.cell.pin['a1'] = device.cell.pin['ra2']
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['a2'] = device.cell.pin['ra2']
newDev.cell.pin['opt_a2'] = device.cell.pin['ra2']
# newDev.cell.pin['b1'] = device.cell.pin['ra4']
## revised in 2026.06.07 by Qin Yue
# legacy: newDev.cell.pin['b2'] = device.cell.pin['ra4']
newDev.cell.pin['opt_b2'] = device.cell.pin['ra4']
## revised in 2026.06.07 by Qin Yue
# legacy: port_radius = {"a2": device.R1, "b2":-device.R1}
port_radius = {"opt_a2": device.R1, "opt_b2":-device.R1}
if (SimuBox is None):
## revised in 2026.06.07 by Qin Yue
# legacy: yMax = newDev.cell.pin['b2'].y
yMax = newDev.cell.pin['opt_b2'].y
## revised in 2026.06.07 by Qin Yue
# legacy: yMin = newDev.cell.pin['b1'].y - newDev.ring_cell[0].sz[1]/2 - port_width/2
yMin = newDev.cell.pin['opt_b1'].y - newDev.ring_cell[0].sz[1]/2 - port_width/2
SimuBox = {}
SimuBox["dy"] = yMax - yMin
SimuBox["y"] = (yMax+yMin)/2
super().__init__(dev_name, newDev, simu_xs, port_width, path, wl, mesh_order, layer_heights, FDTD_height, material, CladMaterial, modeIdx,
## revised in 2026.06.07 by Qin Yue
# legacy: ports_extend=["a1","b1"],
ports_extend=["opt_a1","opt_b1"],
SimuBox=SimuBox, port_radius=port_radius, sample_points=sample_points,
## revised in 2026.06.07 by Qin Yue
# legacy: FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["a1","b1","a2","b2"],
FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["opt_a1","opt_b1","opt_a2","opt_b2"],
GPUOn=GPUOn)
class RESONATOR(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any,
simu_xs: str="strip", port_width: int=3,
path: Optional[str]=None, wl: list=[1.5, 1.6],
mesh_order: int=5, layer_heights: list=[0.22],
FDTD_height: int=2, material: str="Si (Silicon) - Palik", CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
## revised in 2026.06.07 by Qin Yue
# legacy: ports_extend: list=["a1"],
ports_extend: list=["opt_a1"],
sample_points: int=10001,
SimuBox: Any=None,
FDTDBuild: bool = False,
LumericalPATH: Any = None,
runFDTD: bool = False) -> None:
super().__init__(dev_name, device, simu_xs, port_width, path, wl, mesh_order, layer_heights, FDTD_height, material, CladMaterial,
## revised in 2026.06.07 by Qin Yue
# legacy: modeIdx, ports_extend=["a1","a2","b1","b2"], SimuBox=SimuBox, port_radius=[], sample_points=sample_points,
modeIdx, ports_extend=["opt_a1","opt_a2","opt_b1","opt_b2"], SimuBox=SimuBox, port_radius=[], sample_points=sample_points,
## revised in 2026.06.07 by Qin Yue
# legacy: FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["a1","b1","a2","b2"],)
FDTDBuild=FDTDBuild,LumericalPATH=LumericalPATH,runFDTD=runFDTD,port_names = ["opt_a1","opt_b1","opt_a2","opt_b2"],)
class RING_PHASE(DEVICE_PORTS):
def __init__(self, dev_name: str, device: Any, simu_xs: str="strip",
port_width: int=3, path: Optional[str]=None, wl: list=[1.5, 1.6],
mesh_order: int=5, layer_heights: list=[0.22],
FDTD_height: int=2,
material: str="Si (Silicon) - Palik",
CladMaterial: str="SiO2 (Glass) - Palik",
modeIdx: list=[1, 2, 3, 4],
SimuBox: Any=None,
## revised in 2026.06.07 by Qin Yue
# legacy: port_radius: dict={ "a1": 0 },
port_radius: dict={ "opt_a1": 0 },
sample_points: int=101,
FDTDBuild: bool=False,
LumericalPATH: Any=None,
runFDTD: bool=False,
GPUOn: bool = True,
) -> None:
if (hasattr(device,"cell")):
dev_cell = device.cell
elif (isinstance(device,nd.Cell)):
dev_cell = device
## revised in 2026.06.07 by Qin Yue
# legacy: dev_cell.pin['a1'] = dev_cell.pin['r1']
dev_cell.pin['opt_a1'] = dev_cell.pin['r1']
## revised in 2026.06.07 by Qin Yue
# legacy: dev_cell.pin['b1'] = dev_cell.pin['r3']
dev_cell.pin['opt_b1'] = dev_cell.pin['r3']
## revised in 2026.06.07 by Qin Yue
# legacy: dy = abs(dev_cell.pin['a1'].y - dev_cell.pin['b1'].y )
dy = abs(dev_cell.pin['opt_a1'].y - dev_cell.pin['opt_b1'].y )
## revised in 2026.06.07 by Qin Yue
# legacy: cy = (dev_cell.pin['a1'].y + dev_cell.pin['b1'].y)/2
cy = (dev_cell.pin['opt_a1'].y + dev_cell.pin['opt_b1'].y)/2
if (SimuBox is None):
SimuBox = {}
SimuBox["dy"] = dy
SimuBox["y"] = cy
if (hasattr(device,"sz")):
SimuBox["dx"] = device.sz[0]/2
SimuBox["x"] = -device.sz[0]/4
# SimuBox["dx"] = dx
super().__init__(dev_name, dev_cell, simu_xs, port_width, path, wl, mesh_order, layer_heights,
FDTD_height, material, CladMaterial, modeIdx, ports_extend=[],
SimuBox=SimuBox, port_radius=port_radius, sample_points=sample_points,
## revised in 2026.06.07 by Qin Yue
# legacy: FDTDBuild=FDTDBuild, LumericalPATH=LumericalPATH, runFDTD=runFDTD, port_names=["a1","b1"],
FDTDBuild=FDTDBuild, LumericalPATH=LumericalPATH, runFDTD=runFDTD, port_names=["opt_a1","opt_b1"],
GPUOn = GPUOn)