def TOP(): # Get technology and layout details from ..utils import get_layout_variables TECHNOLOGY, lv, ly, cell = get_layout_variables() dbum = TECHNOLOGY['dbu'] * 1e-6 # dbu to m conversion # get selected instances; only one from ..utils import select_instances from .. import _globals # print error message if no or more than one component selected selected_instances = select_instances() error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok) if len(selected_instances) != 1: error.setText("Error: Need to have one component selected.") response = error.exec_() return # text = 'Information on selected components:<br><br>' text = '' # parse PCell parameters into text string and params array for obj in selected_instances: #print(" selected component: %s" % obj.inst().cell ) c = cell.find_components(cell_selected=[obj.inst().cell], verbose=True) if c: text += c[0].display().replace(';', '<br> ') if c[0].cell.is_pcell_variant(): params = c[0].cell.pcell_parameters_by_name() for key in params.keys(): text += ("Parameter: %s, Value: %s") % (key, params[key]) #params.append([key,params[key]]) text += '<br><br>' print(params) # check if selected PCell is a contra DC if "component: ebeam_contra_dc" not in text: error.setText("Error: selected component must be a contra-DC PCell.") response = error.exec_() return # parse into individual variables in meters N = params["number_of_periods"] period = params["grating_period"] * 1e-6 dW_1 = params["corrugation_width1"] * 1e-6 dW_2 = params["corrugation_width2"] * 1e-6 W_1 = params["wg1_width"] * 1e-6 W_2 = params["wg2_width"] * 1e-6 gap = params["gap"] * 1e-6 a = params["index"] if params["sinusoidal"] == False: sinusoidal = 0 else: sinusoidal = 1 from SiEPIC.lumerical.fdtd import generate_CDC_bandstructure from SiEPIC.lumerical.mode import find_neff_supermode [n_eff1_fit, n_eff2_fit, n_g1_fit, n_g2_fit] = find_neff_supermode(W_1, W_2, gap) [bandwidth, lambda_0] = generate_CDC_bandstructure(W_1, W_2, dW_1, dW_2, period, gap, sinusoidal) #[bandwidth, lambda_0] = [7e-9, 1550e-9] os.chdir('C:\\Users\\Mustafa\\Desktop') # create data files dataFile = open("contraDC_params", "w") dataFile.write( str(gap) + ',' + str(W_1) + ',' + str(W_2) + ',' + str(dW_1) + ',' + str(dW_2) + ',' + str(period) + ',' + str(a) + ',' + str(N)) dataFile = open("contraDC_mode", "w") dataFile.write( str(n_eff1_fit) + ',' + str(n_eff2_fit) + ',' + str(n_g1_fit) + ',' + str(n_g2_fit)) dataFile = open("contraDC_fdtd", "w") dataFile.write(str(bandwidth) + ',' + str(lambda_0)) #os. #os.system('python matlabenginestart.py') import subprocess p = subprocess.Popen(["start", "cmd", "/k", "python matlabenginestart.py"], shell=True)
def load_lumapi(verbose=False): import pya if verbose: print("SiEPIC.lumerical.load_lumapi") import sys try: import numpy except: try: import pip import pya install = pya.MessageBox.warning( "Install package?", "Install package 'numpy' using pip? [required for Lumerical tools]", pya.MessageBox.Yes + pya.MessageBox.No) if install == pya.MessageBox.Yes: # try installing using pip from SiEPIC.install import get_pip_main main = get_pip_main() main(['install', 'numpy']) except ImportError: pass import os, platform, sys, inspect # Load the Lumerical software location from KLayout configuration path = pya.Application.instance().get_config( 'siepic_tools_Lumerical_Python_folder') # if it isn't defined, start with Lumerical's defaults if not path: if platform.system() == 'Darwin': path_fdtd = "/Applications/Lumerical/FDTD Solutions/FDTD Solutions.app/Contents/API/Python" if os.path.exists(path_fdtd): path = path_fdtd path_intc = "/Applications/Lumerical/INTERCONNECT/INTERCONNECT.app/Contents/API/Python" if os.path.exists(path_intc): path = path_intc elif platform.system() == 'Linux': path_fdtd = "/opt/lumerical/fdtd/api/python" if os.path.exists(path_fdtd): path = path_fdtd path_intc = "/opt/lumerical/interconnect/api/python" if os.path.exists(path_intc): path = path_intc elif platform.system() == 'Windows': path_fdtd = "C:\\Program Files\\Lumerical\\FDTD Solutions\\api\\python" if os.path.exists(path_fdtd): path = path_fdtd path_intc = "C:\\Program Files\\Lumerical\\INTERCONNECT\\api\\python" if os.path.exists(path_intc): path = path_intc else: print('Not a supported OS') return # if it is still not found, ask the user if not os.path.exists(path): print('SiEPIC.lumerical.load_api: Lumerical software not found') question = pya.QMessageBox() question.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.No) question.setDefaultButton(pya.QMessageBox.Yes) question.setText( "Lumerical software not found. \nDo you wish to locate the software?" ) if (pya.QMessageBox_StandardButton( question.exec_()) == pya.QMessageBox.Yes): p = pya.QFileDialog() p.setFileMode(pya.QFileDialog.DirectoryOnly) p.exec_() path = p.directory().path if verbose: print(path) else: return # check if we have the correct path, containing lumapi.py if not os.path.exists(os.path.join(path, 'lumapi.py')): # check sub-folders for lumapi.py import fnmatch dir_path = path search_str = 'lumapi.py' matches = [] for root, dirnames, filenames in os.walk(dir_path, followlinks=True): for filename in fnmatch.filter(filenames, search_str): matches.append(root) if matches: if verbose: print(matches) path = matches[0] if not os.path.exists(os.path.join(path, 'lumapi.py')): print('SiEPIC.lumerical.load_api: Lumerical lumapi.py not found') warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Cancel) warning.setText("Lumerical's lumapi.py not found.") warning.setInformativeText( "Some SiEPIC-Tools Lumerical functionality will not be available." ) pya.QMessageBox_StandardButton(warning.exec_()) return # Save the Lumerical software location to the KLayout configuration pya.Application.instance().set_config( 'siepic_tools_Lumerical_Python_folder', path) CWD = os.path.dirname(os.path.abspath(__file__)) if platform.system() == 'Darwin': # Check if any Lumerical tools are installed ################################################################## # Configure OSX Path to include Lumerical tools: # Copy the launch control file into user's Library folder # execute launctl to register the new paths import os, fnmatch siepic_tools_lumerical_folder = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) os.environ[ 'PATH'] += ':/Applications/Lumerical/FDTD Solutions/FDTD Solutions.app/Contents/MacOS' os.environ[ 'PATH'] += ':/Applications/Lumerical/INTERCONNECT/INTERCONNECT.app/Contents/MacOS' os.environ[ 'PATH'] += ':/Applications/Lumerical/INTERCONNECT/INTERCONNECT.app/Contents/API/Python' os.environ[ 'PATH'] += ':/Applications/Lumerical/INTERCONNECT/INTERCONNECT.app/Contents/API/Matlab' # Also add path for use in the Terminal home = os.path.expanduser("~") if not os.path.exists(home + "/.bash_profile"): text_bash = '\n' text_bash += '# Setting PATH for Lumerical API\n' text_bash += 'export PATH=/Applications/Lumerical/FDTD\ Solutions/FDTD\ Solutions.app/Contents/MacOS:$PATH\n' text_bash += 'export PATH=/Applications/Lumerical/MODE\ Solutions/MODE\ Solutions.app/Contents/MacOS:$PATH\n' text_bash += 'export PATH=/Applications/Lumerical/DEVICE/DEVICE.app/Contents/MacOS:$PATH\n' text_bash += 'export PATH=/Applications/Lumerical/INTERCONNECT/INTERCONNECT.app/Contents/MacOS:$PATH\n' text_bash += '\n' file = open(home + "/.bash_profile", 'w') file.write(text_bash) file.close() if not path in sys.path: sys.path.append(path) # Fix for Lumerical Python OSX API, for < March 5 2018 versions: if not os.path.exists(os.path.join(path, 'libinterop-api.1.dylib')): lumapi_osx_fix = siepic_tools_lumerical_folder + '/lumapi_osx_fix.bash' lumapi_osx_fix_lib = path + '/libinterop-api.so.1' if not os.path.exists(lumapi_osx_fix_lib): warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText( "We need to do a fix in the Lumerical software folder for Python integration. \nPlease note that for this to work, we assume that Lumerical INTERCONNECT is installed in the default path: /Applications/Lumerical/INTERCONNECT/\nPlease enter the following in a Terminal.App window, and enter your root password when prompted. Ok to continue when done." ) warning.setInformativeText("source %s" % lumapi_osx_fix) pya.QMessageBox_StandardButton(warning.exec_()) if not os.path.exists(lumapi_osx_fix_lib): import sys if int(sys.version[0]) > 2: import subprocess subprocess.Popen([ '/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal', '-run', lumapi_osx_fix ]) else: import commands print( commands.getstatusoutput( '/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal %s' % lumapi_osx_fix)) # Windows elif platform.system() == 'Windows': if os.path.exists(path): if not path in sys.path: sys.path.append(path) # windows os.chdir(path) # Linux elif platform.system() == 'Linux': if os.path.exists(path): if not path in sys.path: sys.path.append(path) # windows os.chdir(path) # for all operating systems: from .. import _globals if not _globals.LUMAPI: try: import lumapi _globals.LUMAPI = lumapi except: print('import lumapi failed') return print('import lumapi success, %s' % _globals.LUMAPI) # _globals.INTC = lumapi.open('interconnect') # _globals.FDTD = lumapi.open('fdtd') os.chdir(CWD)
def component_simulation(verbose=False, simulate=True): import sys, os, string from .. import _globals # get selected instances from ..utils import select_instances selected_instances = select_instances() from ..utils import get_layout_variables TECHNOLOGY, lv, ly, cell = get_layout_variables() # check that it is one or more: error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok ) if len(selected_instances) == 0: error.setText("Error: Need to have a component selected.") return warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.Cancel) warning.setDefaultButton(pya.QMessageBox.Yes) if len(selected_instances) > 1 : warning.setText("Warning: More than one component selected.") warning.setInformativeText("Do you want to Proceed?") if(pya.QMessageBox_StandardButton(warning.exec_()) == pya.QMessageBox.Cancel): return # Check if the component has a compact model loaded in INTERCONNECT # Loop if more than one component selected for obj in selected_instances: # *** not working. .returns Flattened. # c = obj.inst().cell.find_components()[0] if verbose: print(" selected component: %s" % obj.inst().cell ) c = cell.find_components(cell_selected=[obj.inst().cell]) if c: c=c[0] else: continue if not c.has_model(): if len(selected_instances) == 0: error.setText("Error: Component '%s' does not have a compact model. Cannot perform simulation." % c) continue # GUI to ask which pin to inject light into pin_names = [p.pin_name for p in c.pins if p.type == _globals.PIN_TYPES.OPTICAL or p.type == _globals.PIN_TYPES.OPTICALIO] if not pin_names: continue pin_injection = pya.InputDialog.ask_item("Pin selection", "Choose one of the pins in component '%s' to inject light into." % c.component, pin_names, 0) if not pin_injection: return if verbose: print("Pin selected from InputDialog = %s, for component '%s'." % (pin_injection, c.component) ) # Write spice netlist and simulation script from ..utils import get_technology TECHNOLOGY = get_technology() # get current technology import SiEPIC from time import strftime text_main = '* Spice output from KLayout SiEPIC-Tools v%s, %s technology (SiEPIC.lumerical.interconnect.component_simulation), %s.\n\n' % (SiEPIC.__version__, TECHNOLOGY['technology_name'], strftime("%Y-%m-%d %H:%M:%S") ) # find electrical IO pins electricalIO_pins = "" DCsources = "" # string to create DC sources for each pin Vn = 1 # (2) or to individual DC sources # create individual sources: for p in c.pins: if p.type == _globals.PIN_TYPES.ELECTRICAL: NetName = " " + c.component +'_' + str(c.idx) + '_' + p.pin_name electricalIO_pins += NetName DCsources += "N" + str(Vn) + NetName + " dcsource amplitude=0 sch_x=%s sch_y=%s\n" % (-2-Vn/3., -2+Vn/8.) Vn += 1 electricalIO_pins_subckt = electricalIO_pins # component nets: must be ordered electrical, optical IO, then optical nets_str = '' DCsources = "" # string to create DC sources for each pin Vn = 1 for p in c.pins: if p.type == _globals.PIN_TYPES.ELECTRICAL: if not p.pin_name: continue NetName = " " + c.component +'_' + str(c.idx) + '_' + p.pin_name nets_str += NetName DCsources += "N" + str(Vn) + NetName + " dcsource amplitude=0 sch_x=%s sch_y=%s\n" % (-2-Vn/3., -2+Vn/8.) Vn += 1 if p.type == _globals.PIN_TYPES.OPTICAL or p.type == _globals.PIN_TYPES.OPTICALIO: nets_str += " " + str(p.pin_name) # *** todo: some other way of getting this information; not hard coded. # GUI? Defaults from PCell? orthogonal_identifier=1 wavelength_start=1500 wavelength_stop=1600 wavelength_points=2000 text_main += '* Optical Network Analyzer:\n' text_main += '.ona input_unit=wavelength input_parameter=start_and_stop\n + minimum_loss=80\n + analysis_type=scattering_data\n + multithreading=user_defined number_of_threads=1\n' text_main += ' + orthogonal_identifier=%s\n' % orthogonal_identifier text_main += ' + start=%4.3fe-9\n' % wavelength_start text_main += ' + stop=%4.3fe-9\n' % wavelength_stop text_main += ' + number_of_points=%s\n' % wavelength_points for i in range(0,len(pin_names)): text_main += ' + input(%s)=SUBCIRCUIT,%s\n' % (i+1, pin_names[i]) text_main += ' + output=SUBCIRCUIT,%s\n\n' % (pin_injection) text_main += DCsources text_main += 'SUBCIRCUIT %s SUBCIRCUIT sch_x=-1 sch_y=-1 \n\n' % (nets_str) text_main += '.subckt SUBCIRCUIT %s\n' % (nets_str) text_main += ' %s %s %s ' % ( c.component.replace(' ', '_') +"_1", nets_str, c.component.replace(' ', '_') ) if c.library != None: text_main += 'library="%s" %s ' % (c.library, c.params) text_main += '\n.ends SUBCIRCUIT\n' from .. import _globals tmp_folder = _globals.TEMP_FOLDER import os filename = os.path.join(tmp_folder, '%s_main.spi' % c.component) filename2 = os.path.join(tmp_folder, '%s.lsf' % c.component) filename_icp = os.path.join(tmp_folder, '%s.icp' % c.component) # Write the Spice netlist to file file = open(filename, 'w') file.write (text_main) file.close() if verbose: print(text_main) ''' # Ask user whether to start a new visualizer, or use an existing one. opt_in_labels = [o['opt_in'] for o in opt_in] opt_in_labels.insert(0,'All opt-in labels') opt_in_selection_text = pya.InputDialog.ask_item("opt_in selection", "Choose one of the opt_in labels, to fetch experimental data.", opt_in_labels, 0) if not opt_in_selection_text: # user pressed cancel pass ''' # Write the Lumerical INTERCONNECT start-up script. text_lsf = 'switchtolayout;\n' text_lsf += "cd('%s');\n" % tmp_folder text_lsf += 'deleteall;\n' text_lsf += "importnetlist('%s');\n" % filename text_lsf += "save('%s');\n" % filename_icp text_lsf += 'run;\n' if 0: for i in range(0, len(pin_names)): text_lsf += 'h%s = haveresult("ONA_1", "input %s/mode 1/gain");\n' % (i+1, i+1) text_lsf += 'if (h%s>0) { visualize(getresult("ONA_1", "input %s/mode 1/gain")); } \n' % (i+1, i+1) if 1: text_lsf += 't = "";\n' for i in range(0, len(pin_names)): text_lsf += 'h%s = haveresult("ONA_1", "input %s/mode 1/gain");\n' % (i+1, i+1) text_lsf += 'if (h%s>0) { t%s = getresult("ONA_1", "input %s/mode 1/gain"); t=t+"t%s,"; } \n' % (i+1, i+1, i+1, i+1) text_lsf += 't = substring(t, 1, length(t) - 1);\n' text_lsf += 'eval("visualize(" + t + ");");\n' file = open(filename2, 'w') file.write (text_lsf) file.close() if verbose: print(text_lsf) if simulate: # Run using Python integration: try: from .. import _globals run_INTC() # Run using Python integration: lumapi = _globals.LUMAPI lumapi.evalScript(_globals.INTC, "cd ('" + tmp_folder + "');") lumapi.evalScript(_globals.INTC, "feval('"+ c.component + "');\n") except: from .. import scripts scripts.open_folder(tmp_folder) INTC_commandline(filename) else: from .. import scripts scripts.open_folder(tmp_folder)
def Setup_Lumerical_KLayoutPython_integration(verbose=False): import sys, os, string, pya from ..utils import get_technology, get_technology_by_name # get current technology TECHNOLOGY = get_technology(query_activecellview_technology=True) # load more technology details (CML file location) TECHNOLOGY = get_technology_by_name(TECHNOLOGY['technology_name']) # location for the where the CMLs will locally be installed: dir_path = os.path.join(pya.Application.instance().application_data_path(), 'Lumerical_CMLs') try: libraries = [] library_ids = [n for n in pya.Library.library_ids() if (pya.Library.library_by_id(n).technology == TECHNOLOGY['technology_name'])] for n in [pya.Library.library_by_id(l) for l in library_ids]: print(n.layout().meta_info_value("path")) libraries.append ( n.name() ) except: pass if 'INTC_CMLs_path' in TECHNOLOGY: question = pya.QMessageBox() question.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.No) question.setDefaultButton(pya.QMessageBox.Yes) question.setText("SiEPIC-Tools will install the Compact Model Library (CML) in Lumerical INTERCONNECT for the currently active technology. \nThis includes the libraries %s. \nProceed?" % libraries) informative_text = "\nTechnology: %s\n" % TECHNOLOGY['technology_name'] for i in range(0,len(TECHNOLOGY['INTC_CMLs_path'])): informative_text += "Source CML file {}: {}\n".format(i+1, TECHNOLOGY['INTC_CMLs_path'][i]) informative_text += "Install location: %s" % dir_path question.setInformativeText(informative_text) if(pya.QMessageBox_StandardButton(question.exec_()) == pya.QMessageBox.No): return ################################################################## # Load Lumerical API: from .. import _globals run_INTC() lumapi = _globals.LUMAPI if not lumapi: print('SiEPIC.lumerical.interconnect.Setup_Lumerical_KLayoutPython_integration: lumapi not loaded') return import os # Read INTC element library lumapi.evalScript(_globals.INTC, "out=library;") _globals.INTC_ELEMENTS=lumapi.getVar(_globals.INTC, "out") # Install other CMLs for each library that has a path folder for i in range(0,len(library_ids)): print(' - checking for library: %s' % (pya.Library.library_by_id(library_ids[i]).name()) ) if 'path' in dir(pya.Library.library_by_id(library_ids[i])): path = pya.Library.library_by_id(library_ids[i]).path # look for CML files print(' - checking for CMLs: %s' % (path) ) for f in os.listdir(path): if f.lower().endswith(".cml"): ff = os.path.join(path,f) print(' - CML file: %s' % (ff) ) # install CML print("Lumerical INTC, installdesignkit ('%s', '%s', true);" % (ff, dir_path ) ) lumapi.evalScript(_globals.INTC, "installdesignkit ('%s', '%s', true);" % (ff, dir_path ) ) # Install technology CML if missing in INTC # check if the latest version of the CML is in KLayout's tech if 'INTC_CMLs_path' in TECHNOLOGY and TECHNOLOGY['INTC_CML_path']: if not ("design kits::"+TECHNOLOGY['technology_name'].lower()+"::"+TECHNOLOGY['INTC_CML_version'].lower().replace('.cml','').lower()) in _globals.INTC_ELEMENTS: # install CML print("Lumerical INTC, installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CML_path'], dir_path ) ) lumapi.evalScript(_globals.INTC, "installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CML_path'], dir_path ) ) # Install other CMLs within technology for i in range(0,len(TECHNOLOGY['INTC_CMLs_name'])): if not ("design kits::"+TECHNOLOGY['INTC_CMLs_name'][i].lower()+"::"+TECHNOLOGY['INTC_CMLs_version'][i].lower().replace('.cml','').lower()) in _globals.INTC_ELEMENTS: # install CML print("Lumerical INTC, installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CMLs_path'][i], dir_path ) ) lumapi.evalScript(_globals.INTC, "installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CMLs_path'][i], dir_path ) ) # Re-Read INTC element library lumapi.evalScript(_globals.INTC, "out=library;") _globals.INTC_ELEMENTS=lumapi.getVar(_globals.INTC, "out") # Close INTERCONNECT so that the library information is saved, then re-open lumapi.close(_globals.INTC) run_INTC() # Save INTC element library to KLayout application data path if not os.path.exists(dir_path): os.makedirs(dir_path) fh = open(os.path.join(dir_path,"Lumerical_INTC_CMLs.txt"), "w") fh.writelines(_globals.INTC_ELEMENTS) fh.close() integration_success_message = "message('KLayout-Lumerical INTERCONNECT integration successful, CML library/libraries:\n" for cml_name in TECHNOLOGY['INTC_CMLs_name']: integration_success_message += "design kits::"+cml_name.lower()+"\n" integration_success_message += "');switchtodesign;\n" lumapi.evalScript(_globals.INTC, integration_success_message) # instantiate all library elements onto the canvas question = pya.QMessageBox() question.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.No) question.setDefaultButton(pya.QMessageBox.Yes) question.setText("Do you wish to see all the components in %s library?" % TECHNOLOGY['technology_name']) # question.setInformativeText("Do you wish to see all the components in the library?") if(pya.QMessageBox_StandardButton(question.exec_()) == pya.QMessageBox.No): # lumapi.evalScript(_globals.INTC, "b=0:0.01:10; plot(b,sin(b),'Congratulations, Lumerical is now available from KLayout','','Congratulations, Lumerical is now available from KLayout');") return intc_elements = _globals.INTC_ELEMENTS.split('\n') # tech_elements = [ e.split('::')[-1] for e in intc_elements if "design kits::"+TECHNOLOGY['technology_name'].lower()+"::" in e ] tech_elements = [ e for e in intc_elements if "design kits::"+TECHNOLOGY['technology_name'].lower()+"::" in e ] i, x, y, num = 0, 0, 0, len(tech_elements) for i in range(0, num): lumapi.evalScript(_globals.INTC, "a=addelement('%s'); setposition(a,%s,%s); " % (tech_elements[i],x,y) ) y += 250 if (i+1) % int(num**0.5) == 0: x += 250 y = 0
def Setup_Lumerical_KLayoutPython_integration(verbose=False): import sys, os, string ################################################################## # Load Lumerical API: from .. import _globals run_INTC() lumapi = _globals.LUMAPI if not lumapi: print( 'SiEPIC.lumerical.interconnect.Setup_Lumerical_KLayoutPython_integration: lumapi not loaded' ) return import os # Read INTC element library lumapi.evalScript(_globals.INTC, "out=library;") _globals.INTC_ELEMENTS = lumapi.getVar(_globals.INTC, "out") # Install technology CML if missing in INTC dir_path = os.path.join(pya.Application.instance().application_data_path(), 'Lumerical_CMLs') from ..utils import get_technology, get_technology_by_name # get current technology TECHNOLOGY = get_technology(query_activecellview_technology=True) # load more technology details (CML file location) TECHNOLOGY = get_technology_by_name(TECHNOLOGY['technology_name']) # check if the latest version of the CML is in KLayout's tech if not ("design kits::" + TECHNOLOGY['technology_name'].lower() + "::" + TECHNOLOGY['INTC_CML_version'].lower().replace( '.cml', '').lower()) in _globals.INTC_ELEMENTS: # install CML print("Lumerical INTC, installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CML_path'], dir_path)) lumapi.evalScript( _globals.INTC, "installdesignkit ('%s', '%s', true);" % (TECHNOLOGY['INTC_CML_path'], dir_path)) # Re-Read INTC element library lumapi.evalScript(_globals.INTC, "out=library;") _globals.INTC_ELEMENTS = lumapi.getVar(_globals.INTC, "out") # Close INTERCONNECT so that the library information is saved, then re-open lumapi.close(_globals.INTC) run_INTC() # Save INTC element library to KLayout application data path if not os.path.exists(dir_path): os.makedirs(dir_path) fh = open(os.path.join(dir_path, "Lumerical_INTC_CMLs.txt"), "w") fh.writelines(_globals.INTC_ELEMENTS) fh.close() lumapi.evalScript( _globals.INTC, "message('KLayout-Lumerical INTERCONNECT integration successful, CML library (%s) is available.');switchtodesign;\n" % ("design kits::" + TECHNOLOGY['technology_name'].lower())) # instantiate all library elements onto the canvas question = pya.QMessageBox() question.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.No) question.setDefaultButton(pya.QMessageBox.Yes) question.setText("Do you wish to see all the components in the library?") # question.setInformativeText("Do you wish to see all the components in the library?") if (pya.QMessageBox_StandardButton( question.exec_()) == pya.QMessageBox.No): # lumapi.evalScript(_globals.INTC, "b=0:0.01:10; plot(b,sin(b),'Congratulations, Lumerical is now available from KLayout','','Congratulations, Lumerical is now available from KLayout');") return intc_elements = _globals.INTC_ELEMENTS.split('\n') # tech_elements = [ e.split('::')[-1] for e in intc_elements if "design kits::"+TECHNOLOGY['technology_name'].lower()+"::" in e ] tech_elements = [ e for e in intc_elements if "design kits::" + TECHNOLOGY['technology_name'].lower() + "::" in e ] i, x, y, num = 0, 0, 0, len(tech_elements) for i in range(0, num): lumapi.evalScript( _globals.INTC, "a=addelement('%s'); setposition(a,%s,%s); " % (tech_elements[i], x, y)) y += 250 if (i + 1) % int(num**0.5) == 0: x += 250 y = 0
def generate_component_sparam(do_simulation=True, addto_CML=True, verbose=False, FDTD_settings=None): if verbose: print('SiEPIC.lumerical.fdtd: generate_component_sparam()') # Get technology and layout details from ..utils import get_layout_variables TECHNOLOGY, lv, ly, cell = get_layout_variables() dbum = TECHNOLOGY['dbu'] * 1e-6 # dbu to m conversion # get selected instances; only one from ..utils import select_instances selected_instances = select_instances() error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok) if len(selected_instances) != 1: error.setText("Error: Need to have one component selected.") response = error.exec_() return # get selected component if verbose: print(" selected component: %s" % selected_instances[0].inst().cell) component = cell.find_components( cell_selected=[selected_instances[0].inst().cell])[0] # create an SVG icon for the component, for INTC compact model icon from .. import _globals import os from ..utils import svg_from_component svg_filename = os.path.join(_globals.TEMP_FOLDER, '%s.svg' % component.instance) if verbose: print(" SVG filename: %s" % svg_filename) svg_from_component(component, svg_filename) # simulation filenames fsp_filename = os.path.join(_globals.TEMP_FOLDER, '%s.fsp' % component.instance) xml_filename = os.path.join(_globals.TEMP_FOLDER, '%s.xml' % component.instance) file_sparam = os.path.join(_globals.TEMP_FOLDER, '%s.dat' % component.instance) # get Component pins pins = component.find_pins() pins = sorted(pins, key=lambda p: p.pin_name) for p in pins: p.pin_name = p.pin_name.replace(' ', '') # remove spaces in pin names if do_simulation: import numpy as np # run Lumerical FDTD Solutions from .. import _globals run_FDTD() lumapi = _globals.LUMAPI if not lumapi: print( 'SiEPIC.lumerical.fdtd.generate_component_sparam: lumapi not loaded' ) return if verbose: print(lumapi) # Python Lumerical INTERCONNECT integration handle # get FDTD settings from XML file if not FDTD_settings: from SiEPIC.utils import load_FDTD_settings FDTD_settings = load_FDTD_settings() if FDTD_settings: if verbose: print(FDTD_settings) # Configure wavelength and polarization # polarization = {'quasi-TE', 'quasi-TM', 'quasi-TE and -TM'} mode_selection = FDTD_settings['mode_selection'] mode_selection_index = [] if 'fundamental TE mode' in mode_selection or '1' in mode_selection: mode_selection_index.append(1) if 'fundamental TM mode' in mode_selection or '2' in mode_selection: mode_selection_index.append(2) if not mode_selection_index: error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok) error.setText("Error: Invalid modes requested.") response = error.exec_() return # wavelength wavelength_start = FDTD_settings['wavelength_start'] wavelength_stop = FDTD_settings['wavelength_stop'] # get DevRec layer devrec_box = component.DevRec_polygon.bbox() print("%s, %s, %s, %s" % (devrec_box.left * dbum, devrec_box.right * dbum, devrec_box.bottom * dbum, devrec_box.top * dbum)) # create FDTD simulation region (extra large) FDTDzspan = FDTD_settings['Initial_FDTD_Z_span'] if mode_selection_index == 1: Z_symmetry = 'Symmetric' elif mode_selection_index == 2: Z_symmetry = 'Anti-Symmetric' else: Z_symmetry = FDTD_settings['Initial_Z-Boundary-Conditions'] FDTDxmin, FDTDxmax, FDTDymin, FDTDymax = ( devrec_box.left) * dbum - 200e-9, ( devrec_box.right) * dbum + 200e-9, ( devrec_box.bottom) * dbum - 200e-9, ( devrec_box.top) * dbum + 200e-9 sim_time = max(devrec_box.width(), devrec_box.height()) * dbum * 4.5 lumapi.evalScript(_globals.FDTD, " \ newproject; \ addfdtd; set('x min',%s); set('x max',%s); set('y min',%s); set('y max',%s); set('z span',%s);\ set('force symmetric z mesh', 1); set('mesh accuracy',1); \ set('x min bc','Metal'); set('x max bc','Metal'); \ set('y min bc','Metal'); set('y max bc','Metal'); \ set('z min bc','%s'); set('z max bc','%s'); \ setglobalsource('wavelength start',%s); setglobalsource('wavelength stop', %s); \ setglobalmonitor('frequency points',%s); set('simulation time', %s/c+400e-15); \ addmesh; set('override x mesh',0); set('override y mesh',0); set('override z mesh',1); set('z span', 0); set('dz', %s); set('z', %s); \ ?'FDTD solver with mesh override added'; " % ( FDTDxmin,FDTDxmax,FDTDymin,FDTDymax,FDTDzspan, \ Z_symmetry, FDTD_settings['Initial_Z-Boundary-Conditions'], \ wavelength_start,wavelength_stop, \ FDTD_settings['frequency_points_monitor'], sim_time, \ FDTD_settings['thickness_Si']/4, FDTD_settings['thickness_Si']/2) ) # add substrate and cladding: lumapi.evalScript(_globals.FDTD, " \ addrect; set('x min',%s); set('x max',%s); set('y min',%s); set('y max',%s); set('z min', %s); set('z max',%s);\ set('material', '%s'); set('alpha',0.1); \ ?'oxide added'; " % (FDTDxmin-1e-6, FDTDxmax+1e-6, FDTDymin-1e-6, FDTDymax+1e-6, \ -FDTD_settings['thickness_BOX']-FDTD_settings['thickness_Si']/2, -FDTD_settings['thickness_Si']/2, \ FDTD_settings['material_Clad'] ) ) lumapi.evalScript(_globals.FDTD, " \ addrect; set('x min',%s); set('x max',%s); set('y min',%s); set('y max',%s); set('z min', %s); set('z max',%s);\ set('material', '%s'); set('alpha',0.1); \ ?'oxide added'; " % (FDTDxmin-1e-6, FDTDxmax+1e-6, FDTDymin-1e-6, FDTDymax+1e-6, \ -FDTD_settings['thickness_Si']/2, FDTD_settings['thickness_Clad']-FDTD_settings['thickness_Si']/2, \ FDTD_settings['material_Clad'] ) ) # get polygons from component polygons = component.get_polygons() if verbose: print(" polygons: %s" % [p for p in polygons]) polygons_vertices = [[ [vertex.x * dbum, vertex.y * dbum] for vertex in p.each_point() ] for p in [p.to_simple_polygon() for p in polygons]] if verbose: print(" polygons' vertices: %s" % len(polygons_vertices)) if len(polygons_vertices) < 1: error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok) error.setText("Error: Component needs to have polygons.") response = error.exec_() return # send polygons to FDTD for i in range(0, len(polygons_vertices)): lumapi.putMatrix(_globals.FDTD, "polygon_vertices", np.array(polygons_vertices[i])) lumapi.evalScript( _globals.FDTD, " \ addpoly; set('vertices',polygon_vertices); \ set('material', '%s'); set('z span', %s); \ ?'Polygons added'; " % (FDTD_settings['material_Si'], FDTD_settings['thickness_Si'])) # create FDTD ports # configure boundary conditions to be PML where we have ports # FDTD_bc = {'y max bc': 'Metal', 'y min bc': 'Metal', 'x max bc': 'Metal', 'x min bc': 'Metal'} port_dict = { 0.0: 'x max bc', 90.0: 'y max bc', 180.0: 'x min bc', -90.0: 'y min bc' } for p in pins: if p.rotation in [180.0, 0.0]: lumapi.evalScript( _globals.FDTD, " \ addport; set('injection axis', 'x-axis'); set('x',%s); set('y',%s); set('y span',%s); set('z span',%s); \ " % (p.center.x * dbum, p.center.y * dbum, 2e-6, FDTDzspan)) if p.rotation in [270.0, 90.0, -90.0]: lumapi.evalScript( _globals.FDTD, " \ addport; set('injection axis', 'y-axis'); set('x',%s); set('y',%s); set('x span',%s); set('z span',%s); \ " % (p.center.x * dbum, p.center.y * dbum, 2e-6, FDTDzspan)) if p.rotation in [0.0, 90.0]: p.direction = 'Backward' else: p.direction = 'Forward' lumapi.evalScript(_globals.FDTD, " \ set('name','%s'); set('direction', '%s'); set('frequency points', %s); updateportmodes(%s); \ select('FDTD'); set('%s','PML'); \ ?'Added pin: %s, set %s to PML'; " % (p.pin_name, p.direction, 1, mode_selection_index, \ port_dict[p.rotation], p.pin_name, port_dict[p.rotation] ) ) # Calculate mode sources # Get field profiles, to find |E| = 1e-6 points to find spans import sys if not 'win' in sys.platform: # Windows getVar ("E") doesn't work. min_z, max_z = 0, 0 for p in [pins[0]]: # if all pins are the same, only do it once for m in mode_selection_index: lumapi.evalScript( _globals.FDTD, " \ select('FDTD::ports::%s'); mode_profiles=getresult('FDTD::ports::%s','mode profiles'); E=mode_profiles.E%s; x=mode_profiles.x; y=mode_profiles.y; z=mode_profiles.z; \ ?'Selected pin: %s'; " % (p.pin_name, p.pin_name, m, p.pin_name)) E = lumapi.getVar(_globals.FDTD, "E") x = lumapi.getVar(_globals.FDTD, "x") y = lumapi.getVar(_globals.FDTD, "y") z = lumapi.getVar(_globals.FDTD, "z") # remove the wavelength from the array, # leaving two dimensions, and 3 field components if p.rotation in [180.0, 0.0]: Efield_xyz = np.array(E[0, :, :, 0, :]) else: Efield_xyz = np.array(E[:, 0, :, 0, :]) # find the field intensity (|Ex|^2 + |Ey|^2 + |Ez|^2) Efield_intensity = np.empty( [Efield_xyz.shape[0], Efield_xyz.shape[1]]) print(Efield_xyz.shape) for a in range(0, Efield_xyz.shape[0]): for b in range(0, Efield_xyz.shape[1]): Efield_intensity[a, b] = abs( Efield_xyz[a, b, 0])**2 + abs( Efield_xyz[a, b, 1])**2 + abs( Efield_xyz[a, b, 2])**2 # find the max field for each z slice (b is the z axis) Efield_intensity_b = np.empty([Efield_xyz.shape[1]]) for b in range(0, Efield_xyz.shape[1]): Efield_intensity_b[b] = max(Efield_intensity[:, b]) # find the z thickness where the field has sufficiently decayed indexes = np.argwhere( Efield_intensity_b > FDTD_settings['Efield_intensity_cutoff_eigenmode']) min_index, max_index = int(min(indexes)), int(max(indexes)) if min_z > z[min_index]: min_z = z[min_index] if max_z < z[max_index]: max_z = z[max_index] if verbose: print( ' Port %s, mode %s field decays at: %s, %s microns' % (p.pin_name, m, z[max_index], z[min_index])) if FDTDzspan > max_z - min_z: FDTDzspan = float(max_z - min_z) if verbose: print(' Updating FDTD Z-span to: %s microns' % (FDTDzspan)) # Configure FDTD region, mesh accuracy 1 # run single simulation lumapi.evalScript( _globals.FDTD, " \ select('FDTD'); set('z span',%s);\ save('%s');\ ?'FDTD Z-span updated to %s'; " % (FDTDzspan, fsp_filename, FDTDzspan)) # Calculate, plot, and get the S-Parameters, S21, S31, S41 ... # optionally simulate a subset of the S-Parameters # assume input on port 1 # return Sparams: last mode simulated [wavelength, pin out index], and # Sparams_modes: all modes [mode, wavelength, pin out index] def FDTD_run_Sparam_simple(pins, in_pin=None, out_pins=None, modes=[1], plots=False): if verbose: print(' Run simulation S-Param FDTD') Sparams_modes = [] if not in_pin: in_pin = pins[0] for m in modes: lumapi.evalScript( _globals.FDTD, "\ switchtolayout; select('FDTD::ports');\ set('source port','%s');\ set('source mode','mode %s');\ run; " % (in_pin.pin_name, m)) port_pins = [in_pin] + out_pins if out_pins else pins for p in port_pins: if verbose: print(' port %s expansion' % p.pin_name) lumapi.evalScript( _globals.FDTD, " \ P=Port_%s=getresult('FDTD::ports::%s','expansion for port monitor'); \ " % (p.pin_name, p.pin_name)) lumapi.evalScript(_globals.FDTD, "wavelengths=c/P.f*1e6;") wavelengths = lumapi.getVar(_globals.FDTD, "wavelengths") Sparams = [] for p in port_pins[1::]: if verbose: print(' S_%s_%s Sparam' % (p.pin_name, in_pin.pin_name)) lumapi.evalScript(_globals.FDTD, " \ Sparam=S_%s_%s= Port_%s.%s/Port_%s.%s; \ " % (p.pin_name, in_pin.pin_name, \ p.pin_name, 'b' if p.direction=='Forward' else 'a', \ in_pin.pin_name, 'a' if in_pin.direction=='Forward' else 'b') ) Sparams.append(lumapi.getVar(_globals.FDTD, "Sparam")) if plots: if verbose: print(' Plot S_%s_%s Sparam' % (p.pin_name, in_pin.pin_name)) lumapi.evalScript( _globals.FDTD, " \ plot (wavelengths, 10*log10(abs(Sparam(:,%s))^2), 'Wavelength (um)', 'Transmission (dB)', 'S_%s_%s, mode %s'); \ " % (modes.index(m) + 1, p.pin_name, in_pin.pin_name, modes.index(m) + 1)) Sparams_modes.append(Sparams) return Sparams, Sparams_modes # Run the first FDTD simulation in_pin = pins[0] Sparams, Sparams_modes = FDTD_run_Sparam_simple( pins, in_pin=in_pin, modes=mode_selection_index, plots=True) # find the pin that has the highest Sparam (max over 1-wavelength and 2-modes) # use this Sparam for convergence testing # use the highest order mode for the convergence testing and reporting IL values. Sparam_pin_max_modes = [] Mean_IL_best_port = [] # one for each mode for m in mode_selection_index: Sparam_pin_max_modes.append( np.amax(np.absolute( np.array(Sparams)[:, :, mode_selection_index.index(m)]), axis=1).argmax() + 1) Mean_IL_best_port.append(-10 * np.log10( np.mean( np.absolute(Sparams)[Sparam_pin_max_modes[-1] - 1, :, mode_selection_index.index(m)])**2)) # user verify ok? warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.Cancel) warning.setDefaultButton(pya.QMessageBox.Yes) info_text = "First FDTD simulation complete (coarse mesh, lowest accuracy). Highest transmission S-Param: \n" for m in mode_selection_index: info_text += "mode %s, S_%s_%s has %s dB average insertion loss\n" % ( m, pins[Sparam_pin_max_modes[mode_selection_index.index( m)]].pin_name, in_pin.pin_name, Mean_IL_best_port[mode_selection_index.index(m)]) warning.setInformativeText(info_text) warning.setText("Do you want to Proceed?") if verbose: print(info_text) else: if (pya.QMessageBox_StandardButton( warning.exec_()) == pya.QMessageBox.Cancel): return # Convergence testing on S-Parameters: # convergence test on simulation z-span (assume symmetric increases) # loop in Python so we can check if it is good enough # use the highest order mode mode_convergence = [mode_selection_index[-1]] Sparam_pin_max = Sparam_pin_max_modes[-1] if FDTD_settings['convergence_tests']: test_converged = False convergence = [] Sparams_abs_prev = np.array( [np.absolute(Sparams)[Sparam_pin_max - 1, :, :]]) while not test_converged: FDTDzspan += FDTD_settings['convergence_test_span_incremement'] lumapi.evalScript( _globals.FDTD, " \ switchtolayout; select('FDTD'); set('z span',%s);\ " % (FDTDzspan)) Sparams, Sparams_modes = FDTD_run_Sparam_simple( pins, in_pin=in_pin, out_pins=[pins[Sparam_pin_max]], modes=mode_convergence, plots=True) Sparams_abs = np.array(np.absolute(Sparams)) rms_error = np.sqrt( np.mean((Sparams_abs_prev - Sparams_abs)**2)) convergence.append([FDTDzspan, rms_error]) Sparams_abs_prev = Sparams_abs if verbose: print(' convergence: span and rms error %s' % convergence[-1]) lumapi.evalScript( _globals.FDTD, " \ ?'FDTD Z-span: %s, rms error from previous: %s (convergence testing until < %s)'; " % (FDTDzspan, rms_error, FDTD_settings['convergence_test_rms_error_limit'])) if rms_error < FDTD_settings[ 'convergence_test_rms_error_limit']: test_converged = True FDTDzspan += -1 * FDTD_settings[ 'convergence_test_span_incremement'] # check if the last 3 points have reducing rms if len(convergence) > 2: test_rms = np.polyfit( np.array(convergence)[-3:, 0], np.array(convergence)[-3:, 1], 1) if verbose: print(' convergence rms trend: %s; fit data: %s' % (test_rms, np.array(convergence)[:, -3:])) if test_rms[0] > 0: if verbose: print( ' convergence problem, not improving rms. terminating convergence test.' ) lumapi.evalScript( _globals.FDTD, "?'convergence problem, not improving rms. terminating convergence test.'; " ) test_converged = True FDTDzspan += -2 * FDTD_settings[ 'convergence_test_span_incremement'] lumapi.putMatrix(_globals.FDTD, 'convergence', convergence) lumapi.evalScript( _globals.FDTD, "plot(convergence(:,1), convergence(:,2), 'Simulation span','RMS error between simulation','Convergence testing');" ) # Perform quick corner analysis if FDTD_settings['Perform-quick-corner-analysis']: pass # Configure FDTD region, higher mesh accuracy, update FDTD ports mode source frequency points lumapi.evalScript( _globals.FDTD, " \ switchtolayout; select('FDTD'); set('mesh accuracy',%s);\ set('z min bc','%s'); set('z max bc','%s'); \ ?'FDTD mesh accuracy updated %s, Z boundary conditions: %s'; " % (FDTD_settings['mesh_accuracy'], FDTD_settings['Z-Boundary-Conditions'], FDTD_settings['Z-Boundary-Conditions'], FDTD_settings['mesh_accuracy'], FDTD_settings['Z-Boundary-Conditions'])) for p in pins: lumapi.evalScript( _globals.FDTD, " \ select('FDTD::ports::%s'); set('frequency points', %s); \ ?'updated pin: %s'; " % (p.pin_name, FDTD_settings['frequency_points_expansion'], p.pin_name)) # Run full S-parameters # add s-parameter sweep task lumapi.evalScript( _globals.FDTD, " \ deletesweep('s-parameter sweep'); \ addsweep(3); \ NPorts=%s; \ " % (len(pins))) for p in pins: for m in mode_selection_index: # add index entries to s-matrix mapping table lumapi.evalScript( _globals.FDTD, " \ index1 = struct; \ index1.Port = '%s'; \ index1.Mode = 'mode %s'; \ addsweepparameter('s-parameter sweep',index1); \ " % (p.pin_name, m)) # run s-parameter sweep, collect results, visualize results # export S-parameter data to file named xxx.dat to be loaded in INTERCONNECT lumapi.evalScript( _globals.FDTD, " \ runsweep('s-parameter sweep'); \ S_matrix = getsweepresult('s-parameter sweep','S matrix'); \ S_parameters = getsweepresult('s-parameter sweep','S parameters'); \ S_diagnostic = getsweepresult('s-parameter sweep','S diagnostic'); \ visualize(S_parameters); \ exportsweep('s-parameter sweep','%s'); \ " % (file_sparam)) if verbose: print(" S-Parameter file: %s" % file_sparam) # Perform final corner analysis, for Monte Carlo simulations if FDTD_settings['Perform-final-corner-analysis']: pass # Write XML file for INTC scripted compact model # height and width are set to the first pin width/height xml_out = '\ <?xml version="1.0" encoding="UTF-8"?> \n\ <lumerical_lookup_table version="1.0" name = "index_table"> \n\ <association> \n\ <design> \n\ <value name="height" type="double">%s</value> \n\ <value name="width" type="double">%s</value> \n\ </design> \n\ <extracted> \n\ <value name="sparam" type="string">%s</value> \n\ </extracted> \n\ </association>\n' % (FDTD_settings['thickness_Si'], pins[0].path.width * dbum, component.instance) fh = open(xml_filename, "w") fh.writelines(xml_out) fh.close() if verbose: print(" XML file: %s" % xml_filename) if addto_CML: # Run using Python integration: from . import interconnect interconnect.run_INTC() from .. import _globals lumapi = _globals.LUMAPI # Copy files to the INTC Custom library folder lumapi.evalScript(_globals.INTC, "out=customlibrary;") INTC_custom = lumapi.getVar(_globals.INTC, "out") # Create a component port_dict2 = { 0.0: 'Right', 90.0: 'Top', 180.0: 'Left', -90.0: 'Bottom' } t = 'switchtodesign; deleteall; \n' t += 'addelement("Optical N Port S-Parameter"); createcompound; select("COMPOUND_1");\n' t += 'component = "%s"; set("name",component); \n' % component.instance import os if os.path.exists(svg_filename): t += 'seticon(component,"%s");\n' % (svg_filename) else: print(" SiEPIC.lumerical.fdtd.component... missing SVG icon: %s" % svg_filename) t += 'select(component+"::SPAR_1"); set("load from file", true);\n' t += 'set("s parameters filename", "%s");\n' % (file_sparam) t += 'set("load from file", false);\n' t += 'set("passivity", "enforce");\n' t += 'set("prefix", component);\n' t += 'setposition(component+"::SPAR_1",100,-100);\n' count = 0 for p in pins: count += 1 if p.rotation in [0.0, 180.0]: location = 1 - (p.center.y - component.DevRec_polygon.bbox().bottom + 0.) / component.DevRec_polygon.bbox().height() # print(" p.y %s, c.bottom %s, location %s: " % (p.center.y,component.polygon.bbox().bottom, location) ) else: location = (p.center.x - component.DevRec_polygon.bbox().left + 0.) / component.DevRec_polygon.bbox().width() print(location) t += 'addport(component, "%s", "Bidirectional", "Optical Signal", "%s",%s);\n' % ( p.pin_name, port_dict2[p.rotation], location) t += 'connect(component+"::RELAY_%s", "port", component+"::SPAR_1", "port %s");\n' % ( count, count) t += 'select(component); addtolibrary("SiEPIC_user",true);\n' t += '?"created and added " + component + " to library SiEPIC_user";\n' lumapi.evalScript(_globals.INTC, t) # Script for the component, to load S-Param data: t = '###############################################\n' t += '# SiEPIC ebeam compact model library (CML)\n' t += '# custom generated component created by SiEPIC-Tools; script by Zeqin Lu, Xu Wang, Lukas Chrostowski\n' t += '?filename = %local path%+"/source_data/' + '%s/%s.xml";\n' % ( component.instance, component.instance) # Monte Carlo part: ''' if (MC_non_uniform==1) { x=%x coordinate%; y=%y coordinate%; x1_wafer = floor(x/MC_grid); # location of component on the wafer map y1_wafer = floor(y/MC_grid); devi_width = MC_uniformity_width(MC_resolution_x/2 + x1_wafer, MC_resolution_y/2 + y1_wafer)*1e-9; devi_thickness = MC_uniformity_thickness(MC_resolution_x/2 + x1_wafer, MC_resolution_y/2 + y1_wafer)*1e-9; initial_width = 500e-9; initial_thickness = 220e-9; waveguide_width = initial_width + devi_width; # [m] waveguide_thickness = initial_thickness + devi_thickness; # [m] # effective index and group index interpolations # The following built-in script interpolates effective index (neff), group index (ng), and dispersion, # and applies the interpolated results to the waveguide. filename = %local path%+"/source_data/y_branch_source/y_lookup_table.xml"; table = "index_table"; design = cell(2); extracted = cell(1); #design (input parameters) design{1} = struct; design{1}.name = "width"; design{1}.value = waveguide_width; design{2} = struct; design{2}.name = "height"; design{2}.value = waveguide_thickness; M = lookupreadnportsparameter(filename, table, design, "y_sparam"); setvalue('SPAR_1','s parameters',M); } else { filename = %local path%+"/source_data/y_branch_source/y_lookup_table.xml"; table = "index_table"; design = cell(2); extracted = cell(1); #design (input parameters) design{1} = struct; design{1}.name = "width"; design{1}.value = 500e-9; design{2} = struct; design{2}.name = "height"; design{2}.value = 220e-9; M = lookupreadnportsparameter(filename, table, design, "y_sparam"); setvalue('SPAR_1','s parameters',M); } ''' return component.instance, file_sparam, [], xml_filename, svg_filename
def generate_GC_sparam(do_simulation=True, addto_CML=True, verbose=False, FDTD_settings=None, GC_settings=None): if verbose: print('SiEPIC.lumerical.fdtd: generate_GC_sparam()') # Get technology and layout details from ..utils import get_layout_variables TECHNOLOGY, lv, ly, cell = get_layout_variables() dbum = TECHNOLOGY['dbu'] * 1e-6 # dbu to m conversion if do_simulation: import numpy as np # run Lumerical FDTD Solutions from .. import _globals run_FDTD() lumapi = _globals.LUMAPI if not lumapi: print( 'SiEPIC.lumerical.fdtd.generate_component_sparam: lumapi not loaded' ) return if verbose: print(lumapi) # Python Lumerical INTERCONNECT integration handle # get FDTD settings from XML file if not FDTD_settings: from SiEPIC.utils import load_FDTD_settings FDTD_settings = load_FDTD_settings() if FDTD_settings: if verbose: print(FDTD_settings) # get GC settings from XML file if not GC_settings: from SiEPIC.utils import load_GC_settings GC_settings = load_GC_settings() if GC_settings: if verbose: print(GC_settings) # Configure wavelength and polarization # polarization = {'quasi-TE', 'quasi-TM', 'quasi-TE and -TM'} mode_selection = FDTD_settings['mode_selection'] mode_selection_index = [] if 'fundamental TE mode' in mode_selection or '1' in mode_selection: mode_selection_index.append(1) if 'fundamental TM mode' in mode_selection or '2' in mode_selection: mode_selection_index.append(2) if not mode_selection_index: error = pya.QMessageBox() error.setStandardButtons(pya.QMessageBox.Ok) error.setText("Error: Invalid modes requested.") response = error.exec_() # wavelength wavelength_start = FDTD_settings['wavelength_start'] wavelength_stop = FDTD_settings['wavelength_stop'] # create FDTD simulation region (extra large) FDTDzspan = FDTD_settings['Initial_FDTD_Z_span'] if mode_selection_index == 1: Z_symmetry = 'Symmetric' elif mode_selection_index == 2: Z_symmetry = 'Anti-Symmetric' else: Z_symmetry = FDTD_settings['Initial_Z-Boundary-Conditions'] # search for 2D GC tempelate project file in technology from ..utils import get_technology TECHNOLOGY = get_technology() tech_name = TECHNOLOGY['technology_name'] import os, fnmatch dir_path = pya.Application.instance().application_data_path() search_str = 'grating_coupler_2D.fsp' matches = [] for root, dirnames, filenames in os.walk(dir_path, followlinks=True): for filename in fnmatch.filter(filenames, search_str): if tech_name in root: matches.append(os.path.join(root, filename)) filename = matches[0] ''' # set 2D GC geometry parameters lumapi.evalScript(_globals.FDTD, "load('%s'); select('grating_coupler_2D');\ set('duty cycle',%s); set('target length',%s);\ set('etch depth',%s); set('pitch',%s);\ set('target length',%s); set('input length',%s);'" % (filename, GC_settings['duty_cycle'], GC_settings['target_length'], GC_settings['etch_depth'], GC_settings['pitch'],GC_settings['target_length'],GC_settings['L_extra'])) ''' # set 2D GC geometry parameters lumapi.evalScript( _globals.FDTD, "load('%s'); select('grating_coupler_2D');\ set('duty cycle',%s); set('target length',%s);\ set('etch depth',%s); set('pitch',%s);\ set('input length',%s);" % (filename, GC_settings['duty_cycle'], GC_settings['target_length'], GC_settings['etch_depth'], GC_settings['pitch'], GC_settings['length_extra'])) # set fiber angle lumapi.evalScript( _globals.FDTD, "select('fiber'); set('theta0',%s);" % (GC_settings['angle'])) # set polarization if GC_settings['polarization'] == 'TE': polarization = '2' elif GC_settings['polarization'] == 'TM': polarization = '3' lumapi.evalScript( _globals.FDTD, "select('FDTD::ports::port 1');\ set('mode selection',%s);" % polarization) lumapi.evalScript( _globals.FDTD, "select('FDTD::ports::port 2');\ set('mode selection',%s);" % polarization) # run s-parameters sweep lumapi.evalScript(_globals.FDTD, "runsweep('s-parameter sweep');") if GC_settings['particle_swarm_optimization'] == 'yes': # run optimization (PSO) to find optimal duty cycle and length lumapi.evalScript(_globals.FDTD, "runsweep('optimization');") # return optimized results from PSO sweep lumapi.evalScript( _globals.FDTD, "bestParams = getsweepdata('optimization','bestParams');\ pitch = bestParams(1); duty_cycle = bestParams(2);") GC_settings['duty_cycle'] = lumapi.getVar(_globals.FDTD, "duty_cycle") GC_settings['pitch'] = lumapi.getVar(_globals.FDTD, "pitch") # run 3D FDTD simulation dir_path = pya.Application.instance().application_data_path() search_str = 'grating_coupler_3D.fsp' matches = [] for root, dirnames, filenames in os.walk(dir_path, followlinks=True): for filename in fnmatch.filter(filenames, search_str): if tech_name in root: matches.append(os.path.join(root, filename)) filename = matches[0] # set 2D GC geometry parameters lumapi.evalScript( _globals.FDTD, "load('%s'); select('grating_coupler_3D'); set('duty cycle',%s);\ set('etch depth',%s); set('pitch',%s);\ set('target length',%s); set('L extra',%s);\ set('radius',%s); set('y span',%s);\ set('waveguide width',%s); set('waveguide length,%s);'" % (filename, GC_settings['duty_cycle'], GC_settings['etch_depth'], GC_settings['pitch'], GC_settings['target_length'], GC_settings['length_extra'], GC_settings['radius'], GC_settings['y_span'], GC_settings['waveguide_width'], GC_settings['waveguide_length'])) # set polarization, update port monitors lumapi.evalScript( _globals.FDTD, "select('FDTD::ports::port 1'); set('mode selection',%s);\ updateportmodes; select('FDTD::ports::port 2');\ set('mode selection',%s); updateportmodes;" % (polarization, polarization)) file_sparam = os.path.join(_globals.TEMP_FOLDER, '%s.dat' % "GC_sparams") # run s-parameter sweep, collect results, visualize results # export S-parameter data to file named xxx.dat to be loaded in INTERCONNECT lumapi.evalScript( _globals.FDTD, " \ runsweep('s-parameter sweep'); \ S_matrix = getsweepresult('s-parameter sweep','S matrix'); \ S_parameters = getsweepresult('s-parameter sweep','S parameters'); \ S_diagnostic = getsweepresult('s-parameter sweep','S diagnostic'); \ visualize(S_parameters); \ exportsweep('s-parameter sweep','%s'); \ " % (file_sparam)) if verbose: print(" S-Parameter file: %s" % file_sparam) # Run INTC using Python integration: from . import interconnect interconnect.run_INTC() from .. import _globals lumapi = _globals.LUMAPI # Copy files to the INTC Custom library folder lumapi.evalScript(_globals.INTC, "out=customlibrary;") INTC_custom = lumapi.getVar(_globals.INTC, "out") # Create a component port_dict2 = { 0.0: 'Right', 90.0: 'Top', 180.0: 'Left', -90.0: 'Bottom' } t = 'switchtodesign; deleteall; \n' t += 'addelement("Optical N Port S-Parameter"); createcompound; select("COMPOUND_1");\n' t += 'component = "%s"; set("name",component); \n' % "GC_sparams" t += 'select(component+"::SPAR_1"); set("load from file", true);\n' t += 'set("s parameters filename", "%s");\n' % (file_sparam) t += 'set("load from file", false);\n' t += 'set("passivity", "enforce");\n' t += 'set("prefix", component);\n' t += 'setposition(component+"::SPAR_1",100,-100);\n' lumapi.evalScript(_globals.INTC, t)
def INTC_commandline(filename2): print("Running Lumerical INTERCONNECT using the command interface.") import sys, os, string if sys.platform.startswith('linux'): import subprocess # Linux-specific code here... print("Running INTERCONNECT") # Location of INTERCONNECT program (this found from RPM installation) file_path = '/opt/lumerical/interconnect/bin/interconnect' subprocess.Popen([file_path, '-run', filename2]) elif sys.platform.startswith('darwin'): # OSX specific import sys if int(sys.version[0]) > 2: import subprocess subprocess.Popen([ '/usr/bin/open -n /Applications/Lumerical/INTERCONNECT/INTERCONNECT.app', '-run', '--args -run %s' % filename2 ]) else: import commands print("Running INTERCONNECT") runcmd = ( 'source ~/.bash_profile; /usr/bin/open -n /Applications/Lumerical/INTERCONNECT/INTERCONNECT.app --args -run %s' % filename2) print("Running in shell: %s" % runcmd) a = commands.getstatusoutput(runcmd) print(a) elif sys.platform.startswith('win'): # Windows specific code here import subprocess print("Running INTERCONNECT") #check Interconnect installation directory file_path_a = 'C:\\Program Files\\Lumerical\\INTERCONNECT\\bin\\interconnect.exe' file_path_b = 'C:\\Program Files (x86)\\Lumerical\\INTERCONNECT\\bin\\interconnect.exe' file_path_c = 'C:\\Program Files\\Lumerical\\v202\\bin\\interconnect.exe' if (os.path.isfile(file_path_a) == True): subprocess.Popen(args=[file_path_a, '-run', filename2], shell=True) elif (os.path.isfile(file_path_b) == True): subprocess.Popen(args=[file_path_b, '-run', filename2], shell=True) elif (os.path.isfile(file_path_c) == True): subprocess.Popen(args=[file_path_c, '-run', filename2], shell=True) else: warning_window = pya.QMessageBox() warning_window.setText( "Warning: The program could not find INTERCONNECT.") warning_window.setInformativeText( "Do you want to specify it manually?") warning_window.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.Cancel) warning_window.setDefaultButton(pya.QMessageBox.Yes) response = warning_window.exec_() if (response == pya.QMessageBox.Yes): dialog = pya.QFileDialog() path = str(dialog.getOpenFileName()) path = path.replace('/', '\\') subprocess.Popen(args=[path, '-run', filename2], shell=True)
def covert_buttion_clicked(self, checked): image_path = self.input_text.text print("image path: ", image_path) main_window = pya.Application.instance().main_window() #print(dir(main_window)) #current_view = main_window.current_view() #print(type(current_view)) #if not current_view: main_window.create_view() current_view = main_window.current_view() #raise Exception("no view availbe!") #cell_view = current_view.active_cellview() cell_view_id = current_view.create_layout(True) current_view.set_active_cellview_index(cell_view_id) cell_view = current_view.cellview(cell_view_id) if not cell_view.is_valid(): raise Exception("cell view is not available") layout_layers = [ pya.LayerInfo.new(0, 0), #pya.LayerInfo.new( 1, 0), #pya.LayerInfo.new( 2, 0), #pya.LayerInfo.new( 3, 0), ] layers = [] for item in layout_layers: print(item) layer = cell_view.layout().insert_layer(item) lp = pya.LayerPropertiesNode() lp.source_layer = item.layer lp.source_datatype = item.datatype current_view.init_layer_properties(lp) current_view.insert_layer(current_view.end_layers(), lp) layers.append(layer) current_view.update_content() dbu = 1 try: dbu = float(self.dbu_text.text) except ValueError: error_message_box = pya.QMessageBox(self) error_message_box.setText("Please set dbu to a number!") return image = pya.Image(image_path) print("image: {}, width = {}, height = {}".format( image_path, image.width(), image.height())) current_view.transaction("Image channels to RGB") trans = pya.ICplxTrans.from_dtrans( pya.DCplxTrans.new(1 / dbu) * image.trans * pya.DCplxTrans.new(dbu)) # The dimension of one pixel pixel_width = image.pixel_width / dbu pixel_height = image.pixel_height / dbu print("pixel width: {} pixel height: {}".format( pixel_width, pixel_height)) cell_view.layout().create_cell("TOP") cell_view.cell_name = "TOP" #print("cell::", type(cell_view.cell), cell_view.cell) try: threshold = int(self.threshold_text.text) except ValueError: error_message_box = pya.QMessageBox(self) error_message_box.setText( "Please set the threshold to a number (0 ~ 255)!") return print("threshold", threshold) for layer in layers: shapes = cell_view.cell.shapes(layer) image_width = self.raw_image.width() image_height = self.raw_image.height() for x in range(image_width): for y in range(image_height): d = self.image_array[x][image_height - 1 - y] if (not self.inverse_checkbox.isChecked() and d > threshold ) or (self.inverse_checkbox.isChecked() and d < threshold): p1 = pya.DPoint(x * pixel_width, y * pixel_height) p2 = pya.DPoint((x + 1) * pixel_width, (y + 1) * pixel_height) #print("draw: box: {}, {}".format(x, y)) dbox = pya.DBox.new(p1, p2) box = pya.Box.from_dbox(dbox) poly = pya.Polygon.new(box) shapes.insert(poly.transformed_cplx(trans)) print("drawing done") current_view.commit()