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(pya.QMessageBox_StandardButton(response) == pya.QMessageBox.Yes): dialog = pya.QFileDialog() path = str(dialog.getOpenFileName()) path = path.replace('/', '\\') subprocess.Popen(args=[path, '-run', filename2], shell=True)
def github_get_filenames(user, repo, filesearch, extension='', auth=None, verbose=None): import sys import pya try: import requests except ImportError: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText( "Missing Python module: 'requests'. Please install, restart KLayout, and try again." ) pya.QMessageBox_StandardButton(warning.exec_()) return [] import json import os filenames = [] folders = [] filesearch = filesearch.replace('%20', ' ') r = requests.get( "https://api.github.com/search/code?q='%s'+in:path+repo:%s/%s" % (filesearch, user, repo), auth=auth) if 'items' not in json.loads(r.text): if 'message' in json.loads(r.text): message = json.loads(r.text)['message'] else: message = json.loads(r.text) pya.MessageBox.warning("GitHub error", "GitHub error: %s" % (message), pya.MessageBox.Ok) return '' for r in json.loads(r.text)['items']: dl = ('https://github.com/' + user + '/' + repo + '/raw/master/' + str(r['url']).split('/contents/')[1]).split('?')[0] filename = dl.split('/')[-1] path = dl.split('/raw/master/')[-1] if extension in filename[-len(extension):]: filenames.append([filename, path]) if verbose: print(' %s: %s' % (filename, path)) return filenames
def github_check_SiEPICTools_version(): import sys import pya try: import requests except ImportError: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText( "Missing Python module: 'requests'. Please install, restart KLayout, and try again." ) pya.QMessageBox_StandardButton(warning.exec_()) return [] import json import os try: r = requests.get( "https://api.github.com/repos/lukasc-ubc/SiEPIC-Tools/releases/latest" ) except: return '' if 'name' not in json.loads(r.text): if 'message' in json.loads(r.text): message = json.loads(r.text)['message'] else: message = json.loads(r.text) pya.MessageBox.warning("GitHub error", "GitHub error: %s" % (message), pya.MessageBox.Ok) return '' version = json.loads(r.text)['name'] print(version) from SiEPIC.__init__ import __version__ if __version__ not in version: pya.MessageBox.warning( "SiEPIC-Tools: new version available", "SiEPIC-Tools: new version available: %s.\nUpgrade using Tools > Manage Packages > Update Packages" % (version), pya.MessageBox.Ok)
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 2019b.app/Contents/API/Python" if os.path.exists(path_fdtd): path = path_fdtd path_intc = "/Applications/Lumerical 2019b.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 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 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 load_lumapi(verbose=False): if verbose: print("SiEPIC.lumerical.load_lumapi") try: import numpy except: print('Missing numpy. Cannot load Lumerical Python integration') warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Cancel) warning.setText( "Missing Python module numpy. \nCannot load Lumerical Python integration. " ) warning.setInformativeText( "Some SiEPIC-Tools Lumerical functionality will not be available.\nPlease install numpy. For Windows users, install the Package Windows_Python_packages_for_KLayout." ) pya.QMessageBox_StandardButton(warning.exec_()) return 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() == '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, commands siepic_tools_lumerical_folder = os.path.dirname( os.path.abspath(inspect.getfile(inspect.currentframe()))) if 0: filename = (siepic_tools_lumerical_folder + '/SiEPIC_Tools_Lumerical_KLayout_environment.plist') if not os.path.exists(filename): raise Exception('Missing file: %s' % filename) # Check if Paths are correctly set, and KLayout Python sees them a, b = commands.getstatusoutput( 'echo $SiEPIC_Tools_Lumerical_KLayout_environment') if b == '': # Not yet installed... copy files, install cmd1 = ('launchctl unload %s' % filename) a, b = commands.getstatusoutput(cmd1) if a != 0: raise Exception('Error calling: %s, %s' % (cmd1, b)) cmd1 = ('launchctl load %s' % filename) a, b = commands.getstatusoutput(cmd1) if a != 0 or b != '': raise Exception('Error calling: %s, %s' % (cmd1, b)) cmd1 = ('killall Dock') a, b = commands.getstatusoutput(cmd1) if a != 0 or b != '': raise Exception('Error calling: %s, %s' % (cmd1, b)) # Check if Paths are correctly set, and KLayout Python sees them a, b = commands.getstatusoutput( 'echo $SiEPIC_Tools_Lumerical_KLayout_environment') if b == '': # Not loaded print( "The System paths have been updated. Please restart KLayout to use Lumerical tools." ) # raise Exception ('The System paths have been updated. Please restart KLayout to use Lumerical tools.') warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText( "The System paths have been updated. \nPlease restart KLayout to use Lumerical tools, and try this again." ) # warning.setInformativeText("Do you want to Proceed?") pya.QMessageBox_StandardButton(warning.exec_()) return if 1: 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 1: # Fix for Lumerical Python OSX API: if not path in sys.path: sys.path.append(path) # os.chdir(path) 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_()) # print (commands.getstatusoutput('chmod a+x %s' % lumapi_osx_fix )) if not os.path.exists(lumapi_osx_fix_lib): 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) # 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 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 = [ n for n in pya.Library.library_names() if (pya.Library.library_by_name(n).technology == TECHNOLOGY['technology_name']) ] for n in [pya.Library.library_by_name(l) for l in libraries]: print(n.layout().meta_info_value("path")) except: pass 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 technology CML if missing in INTC # 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)) # 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 path_to_wireguide(cell=None, lv_commit=True, verbose=False, select_wireguides=False): from . import _globals TECHNOLOGY, lv, ly, top_cell = get_layout_variables_m() if not cell: cell = top_cell if verbose: print("SiEPIC.scripts path_to_wireguide()") if lv_commit: lv.transaction("Path to Wireguide") layers_to_select = list() for key in TECHNOLOGY.keys(): if 'Wireguide' in key: layers_to_select.append((TECHNOLOGY[key], key)) print(key) selected_paths = select_paths_m( layers_to_select, cell, verbose=verbose) # find all paths on the selected layers if verbose: print("SiEPIC.scripts path_to_wireguide(): selected_paths = %s" % selected_paths) selection = [] warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Yes | pya.QMessageBox.Cancel) warning.setDefaultButton(pya.QMessageBox.Yes) params = { 'radius': 0, 'width': 2, 'adiabatic': False, 'bezier': '0', 'offset': 0 } # set parameters if verbose: print("SiEPIC.scripts path_to_wireguide(): params = %s" % params) for obj in selected_paths: path = obj.shape.path dbu = obj.layout().dbu # get the database units params[ 'width'] = path.width * dbu # adjust the width and save for wireguide creation if obj.shape.property( 'LayerName' ): # check if obj has LayerName (for no mouse selections) input_layer_name = obj.shape.property( 'LayerName') # save layer name for wireguide creation else: # find the name of the layer if not specified (for mouse selections) lv = pya.Application.instance().main_window().current_view() ly = lv.active_cellview().layout() dbu = ly.dbu lp_found = None iter = lv.begin_layers() # loop through all layers while not iter.at_end(): lp = iter.current() if lp.cellview() == obj.cv_index and lp.layer_index( ) == obj.layer: # find specified layer within the layer loop lp_found = lp # save layer properties iter.next() input_layer_name = lp_found.name # save layer name for wireguide creation path.unique_points() if not path.is_manhattan_endsegments(): warning.setText( "Warning: Wireguide segments (first, last) are not Manhattan (vertical, horizontal)." ) warning.setInformativeText("Do you want to Proceed?") if (pya.QMessageBox_StandardButton( warning.exec_()) == pya.QMessageBox.Cancel): return if not path.is_manhattan(): warning.setText( "Error: Wireguide segments are not Manhattan (vertical, horizontal). This is not supported in SiEPIC-Tools." ) warning.setInformativeText("Do you want to Proceed?") if (pya.QMessageBox_StandardButton( warning.exec_()) == pya.QMessageBox.Cancel): return if not path.radius_check(params['radius'] / TECHNOLOGY['dbu']): warning.setText( "Warning: One of the wireguide segments has insufficient length to accommodate the desired bend radius." ) warning.setInformativeText("Do you want to Proceed?") if (pya.QMessageBox_StandardButton( warning.exec_()) == pya.QMessageBox.Cancel): return path.snap_m(cell.find_pins()) Dpath = path.to_dtype(TECHNOLOGY['dbu']) width_devrec = params[ 'width'] + _globals.WG_DEVREC_SPACE * 2 # get DevRec width based on the wireguide width found earlier try: pcell = ly.create_cell( "Wireguide", TECHNOLOGY['technology_name'], { "path": Dpath, # input parameters "radius": params['radius'], "width": params['width'], "adiab": params['adiabatic'], "bezier": params['bezier'], "layers": [input_layer_name] + [ 'DevRec' ], # set the layer as the same as the path that the wireguide came from (along with DevRec) "widths": [params['width']] + [width_devrec], "offsets": [params['offset']] + [0] }) print( "SiEPIC.metal_menu_helper.path_to_wireguide(): Wireguide from %s, %s" % (TECHNOLOGY['technology_name'], pcell)) except: pass if not pcell: try: pcell = ly.create_cell( "Wireguide", "SiEPIC General", { "path": Dpath, # input parameters "radius": params['radius'], "width": params['width'], "adiab": params['adiabatic'], "bezier": params['bezier'], "layers": [input_layer_name] + [ 'DevRec' ], # set the layer as the same as the path that the wireguide came from (along with DevRec) "widths": [params['width']] + [width_devrec], "offsets": [params['offset']] + [0] }) print( "SiEPIC.metal_menu_helper.path_to_wireguide(): Wireguide from SiEPIC General, %s" % pcell) except: pass if not pcell: raise Exception( "'Wireguide' in 'SiEPIC General' library is not available. Check that the library was loaded successfully." ) selection.append(pya.ObjectInstPath()) selection[-1].top = obj.top selection[-1].append_path( pya.InstElement.new( cell.insert( pya.CellInstArray(pcell.cell_index(), pya.Trans(pya.Trans.R0, 0, 0))))) obj.shape.delete() lv.clear_object_selection() if select_wireguides: lv.object_selection = selection if lv_commit: lv.commit()
def spice_netlist_export(self, verbose = False, opt_in_selection_text=[]): import SiEPIC from . import _globals from time import strftime from .utils import eng_str # get the netlist from the entire layout nets, components = self.identify_nets () if not components: return '', '', 0 # Get information about the laser and detectors: # this updates the Optical IO Net laser_net, detector_nets, wavelength_start, wavelength_stop, wavelength_points, orthogonal_identifier, ignoreOpticalIOs = \ get_LumericalINTERCONNECT_analyzers(self, components, verbose=verbose) # if Laser and Detectors are not defined if not laser_net or not detector_nets: # Use opt_in labels laser_net, detector_nets, wavelength_start, wavelength_stop, wavelength_points, orthogonal_identifier, ignoreOpticalIOs = \ get_LumericalINTERCONNECT_analyzers_from_opt_in(self, components, verbose=verbose, opt_in_selection_text=opt_in_selection_text) if not laser_net or not detector_nets: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText("To run a simulation, you need to define a laser and detector(s), or have an opt_in label.") pya.QMessageBox_StandardButton(warning.exec_()) return '', '', 0 # trim the netlist, based on where the laser is connected laser_component = [c for c in components if any([p for p in c.pins if p.type == _globals.PIN_TYPES.OPTICALIO and 'laser' in p.pin_name]) ] from .scripts import trim_netlist nets, components = trim_netlist (nets, components, laser_component[0]) if not components: return '', '', 0 if verbose: print ("* Display list of components:" ) [c.display() for c in components] print ("* Display list of nets:" ) [n.display() for n in nets] text_main = '* Spice output from KLayout SiEPIC-Tools v%s, %s.\n\n' % (SiEPIC.__version__, strftime("%Y-%m-%d %H:%M:%S") ) text_subckt = text_main # convert KLayout GDS rotation/flip to Lumerical INTERCONNECT # KLayout defines mirror as an x-axis flip, whereas INTERCONNECT does y-axis flip # KLayout defines rotation as counter-clockwise, whereas INTERCONNECT does clockwise # input is KLayout Rotation,Flip; output is INTERCONNECT: KLayoutInterconnectRotFlip = \ {(0, False):[0, False], \ (90, False):[270, False], \ (180, False):[180, False], \ (270, False):[90, False], \ (0, True):[180,True], \ (90, True):[90, True], \ (180, True):[0,True], \ (270, True):[270, False]} # Determine the Layout-to-Schematic (x,y) coordinate scaling # Find the distances between all the components, in order to determine scaling sch_positions = [o.Dcenter for o in components] sch_distances = [] for j in range(len(sch_positions)): for k in range(j+1,len(sch_positions)): dist = (sch_positions[j] - sch_positions[k]).abs() sch_distances.append ( dist ) sch_distances.sort() if verbose: print("Distances between components: %s" % sch_distances) # remove any 0 distances: while 0.0 in sch_distances: sch_distances.remove(0.0) # scaling based on nearest neighbour: Lumerical_schematic_scaling = 0.6 / min(sch_distances) print ("Scaling for Lumerical INTERCONNECT schematic: %s" % Lumerical_schematic_scaling) # but if the layout is too big, limit the size MAX_size = 0.05*1e3 if max(sch_distances)*Lumerical_schematic_scaling > MAX_size: Lumerical_schematic_scaling = MAX_size / max(sch_distances) print ("Scaling for Lumerical INTERCONNECT schematic: %s" % Lumerical_schematic_scaling) # find electrical IO pins electricalIO_pins = "" DCsources = "" # string to create DC sources for each pin Vn = 1 SINGLE_DC_SOURCE = 2 # (1) attach all electrical pins to the same DC source # (2) or to individual DC sources # (3) or choose based on number of DC sources, if > 5, use single DC source # create individual sources: for c in components: 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 # create 1 source if (SINGLE_DC_SOURCE == 1) or ( (SINGLE_DC_SOURCE == 3) and (Vn > 5)): electricalIO_pins_subckt = "" for c in components: for p in c.pins: if p.type == _globals.PIN_TYPES.ELECTRICAL: NetName = " N$" electricalIO_pins_subckt += NetName DCsources = "N1" + NetName + " dcsource amplitude=0 sch_x=-2 sch_y=0\n" # find optical IO pins opticalIO_pins='' for c in components: for p in c.pins: if p.type == _globals.PIN_TYPES.OPTICALIO: NetName = ' ' + p.pin_name print(p.pin_name) opticalIO_pins += NetName # create the top subckt: text_subckt += '.subckt %s%s%s\n' % (self.name, electricalIO_pins, opticalIO_pins) text_subckt += '.param MC_uniformity_width=0 \n' # assign MC settings before importing netlist components text_subckt += '.param MC_uniformity_thickness=0 \n' text_subckt += '.param MC_resolution_x=100 \n' text_subckt += '.param MC_resolution_y=100 \n' text_subckt += '.param MC_grid=10e-6 \n' text_subckt += '.param MC_non_uniform=99 \n' for c in components: # optical nets: must be ordered electrical, optical IO, then optical nets_str = '' for p in c.pins: if p.type == _globals.PIN_TYPES.ELECTRICAL: nets_str += " " + c.component +'_' + str(c.idx) + '_' + p.pin_name for p in c.pins: if p.type == _globals.PIN_TYPES.OPTICALIO: nets_str += " " + str(p.net.idx) for p in c.pins: if p.type == _globals.PIN_TYPES.OPTICAL: nets_str += " N$" + str(p.net.idx) trans = KLayoutInterconnectRotFlip[(c.trans.angle, c.trans.is_mirror())] flip = ' sch_f=true' if trans[1] else '' if trans[0] > 0: rotate = ' sch_r=%s' % str(trans[0]) else: rotate = '' # Check to see if this component is an Optical IO type. pinIOtype = any([p for p in c.pins if p.type == _globals.PIN_TYPES.OPTICALIO]) if ignoreOpticalIOs and pinIOtype: # Replace the Grating Coupler or Edge Coupler with a 0-length waveguide. component1 = "ebeam_wg_strip_1550" params1 = "wg_length=0u wg_width=0.500u" else: component1 = c.component params1 = c.params text_subckt += ' %s %s %s ' % ( c.component.replace(' ', '_') +"_"+str(c.idx), nets_str, c.component.replace(' ', '_') ) if c.library != None: text_subckt += 'library="%s" ' % c.library x, y = c.Dcenter.x, c.Dcenter.y text_subckt += '%s lay_x=%s lay_y=%s sch_x=%s sch_y=%s %s%s\n' % \ ( c.params, eng_str(x * 1e-6), eng_str(y * 1e-6), \ eng_str(x * Lumerical_schematic_scaling), eng_str(y * Lumerical_schematic_scaling), \ rotate, flip) text_subckt += '.ends %s\n\n' % (self.name) if laser_net: 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(detector_nets)): text_main += ' + input(%s)=%s,%s\n' % (i+1, self.name, detector_nets[i].idx) text_main += ' + output=%s,%s\n' % (self.name, laser_net.idx) # main circuit text_main += '%s %s %s %s sch_x=-1 sch_y=-1 ' % (self.name, electricalIO_pins_subckt, opticalIO_pins, self.name) if len(DCsources) > 0: text_main += 'sch_r=270\n\n' else: text_main += '\n\n' text_main += DCsources return text_subckt, text_main, len(detector_nets)
def get_LumericalINTERCONNECT_analyzers_from_opt_in(self, components, verbose=None, opt_in_selection_text=[]): """ From the opt_in label, find the trimmed circuit, and assign a laser and detectors returns: parameters, nets in order usage: laser_net, detector_nets, wavelength_start, wavelength_stop, wavelength_points, ignoreOpticalIOs = get_LumericalINTERCONNECT_analyzers_from_opt_in(topcell, components) """ from . import _globals from .core import Net from SiEPIC.utils import load_DFT DFT=load_DFT() if not DFT: if verbose: print(' no DFT rules available.') return False, False, False, False, False, False, False from .scripts import user_select_opt_in opt_in_selection_text, opt_in_dict = user_select_opt_in(verbose=verbose, option_all=False, opt_in_selection_text=opt_in_selection_text) if not opt_in_dict: if verbose: print(' no opt_in selected.') return False, False, False, False, False, False, False # find closest GC to opt_in (pick the 1st one... ignore the others) t = opt_in_dict[0]['Text'] components_sorted = sorted([c for c in components if [p for p in c.pins if p.type == _globals.PIN_TYPES.OPTICALIO]], key=lambda x: x.trans.disp.to_p().distance(pya.Point(t.x, t.y).to_dtype(1))) dist_optin_c = components_sorted[0].trans.disp.to_p().distance(pya.Point(t.x, t.y).to_dtype(1)) if verbose: print( " - Found opt_in: %s, nearest GC: %s. Locations: %s, %s. distance: %s" % (opt_in_dict[0]['Text'], components_sorted[0].instance, components_sorted[0].center, pya.Point(t.x, t.y), dist_optin_c) ) if dist_optin_c > float(DFT['design-for-test']['opt_in']['max-distance-to-grating-coupler'])*1000: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText("To run a simulation, you need to have an opt_in label with %s microns from the nearest grating coupler" % int(DFT['design-for-test']['opt_in']['max-distance-to-grating-coupler']) ) pya.QMessageBox_StandardButton(warning.exec_()) return False, False, False, False, False, False, False # starting with the opt_in label, identify the sub-circuit, then GCs detector_GCs = [ c for c in components if [p for p in c.pins if p.type == _globals.PIN_TYPES.OPTICALIO] if (c.trans.disp - components_sorted[0].trans.disp).to_p() != pya.DPoint(0,0)] if verbose: print(" N=%s, detector GCs: %s" % (len(detector_GCs), [c.display() for c in detector_GCs]) ) vect_optin_GCs = [(c.trans.disp - components_sorted[0].trans.disp).to_p() for c in detector_GCs] # Laser at the opt_in GC: p = [p for p in components_sorted[0].pins if p.type == _globals.PIN_TYPES.OPTICALIO] p[0].pin_name += '_laser' laser_net = p[0].net=Net(idx=p[0].pin_name, pins=p) if verbose: print(" - pin_name: %s" % (p[0].pin_name) ) if DFT['design-for-test']['tunable-laser']['wavelength'] == opt_in_dict[0]['wavelength']: wavelength_start, wavelength_stop, wavelength_points = float(DFT['design-for-test']['tunable-laser']['wavelength-start']), float(DFT['design-for-test']['tunable-laser']['wavelength-stop']), int(DFT['design-for-test']['tunable-laser']['wavelength-points']) else: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText("No laser at %s nm is available. Tunable laser definition is in the technology's DFT.xml file." % opt_in_dict[0]['wavelength']) pya.QMessageBox_StandardButton(warning.exec_()) return False, False, False, False, False, False, False if opt_in_dict[0]['pol'] == 'TE': orthogonal_identifier = 1 elif opt_in_dict[0]['pol'] == 'TM': orthogonal_identifier = 2 else: warning = pya.QMessageBox() warning.setStandardButtons(pya.QMessageBox.Ok) warning.setText("Unknown polarization: %s." % opt_in_dict[0]['pol']) pya.QMessageBox_StandardButton(warning.exec_()) return False, False, False, False, False, False, False ignoreOpticalIOs = False # find the GCs in the circuit and connect detectors based on DFT rules detectors_info = [] detector_number = 0 for d in list(range(int(DFT['design-for-test']['grating-couplers']['detectors-above-laser'])+1,0,-1)) + list(range(-1, -int(DFT['design-for-test']['grating-couplers']['detectors-below-laser'])-1,-1)): if pya.DPoint(0,d*float(DFT['design-for-test']['grating-couplers']['gc-pitch'])*1000) in vect_optin_GCs: detector_number += 1 index = vect_optin_GCs.index(pya.DPoint(0,d*float(DFT['design-for-test']['grating-couplers']['gc-pitch'])*1000)) detector_GCs[index] # component p = [p for p in detector_GCs[index].pins if p.type == _globals.PIN_TYPES.OPTICALIO] p[0].pin_name += '_detector' + str(detector_number) p[0].net=Net(idx=p[0].pin_name, pins=p) detectors_info.append(Detector_info(p[0].net, detector_number) ) if verbose: print(" - pin_name: %s" % (p[0].pin_name) ) # Sort the detectors: detectors_info2 = sorted(detectors_info, key=lambda d: d.detector_number) # output: detector_nets = [] for d in detectors_info2: detector_nets.append (d.detector_net) return laser_net, detector_nets, wavelength_start, wavelength_stop, wavelength_points, orthogonal_identifier, ignoreOpticalIOs
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; closeall; \ 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+1500e-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() def send_polygons_to_FDTD(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(" number of polygons: %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 lumapi.evalScript(_globals.FDTD, "switchtolayout; select('polygons'); delete; addgroup; set('name','polygons'); set('x',0); set('y',0);") for i in range(0,len(polygons_vertices)): print(" polygons' vertices (%s): %s" % (len(polygons_vertices[i]), polygons_vertices[i]) ) 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); set('x',0); set('y',0); \ addtogroup('polygons'); \ ?'Polygons added'; " % (FDTD_settings['material_Si'], FDTD_settings['thickness_Si']) ) send_polygons_to_FDTD(polygons) # 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) ) print("Sparam_pin_max_modes = %s" % Sparam_pin_max_modes) # 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']: for w in [-FDTD_settings['width_Si_corners'],FDTD_settings['width_Si_corners']]: polygons_w = [p for p in pya.Region(polygons).sized(w/2*1e9).each_merged()] send_polygons_to_FDTD(polygons_w) for h in [-FDTD_settings['thickness_Si_corners'],FDTD_settings['thickness_Si_corners']]: lumapi.evalScript(_globals.FDTD, " \ switchtolayout; selectpartial('polygons::'); set('z span',%s);\ " % (FDTD_settings['thickness_Si']+h) ) Sparams, Sparams_modes = FDTD_run_Sparam_simple(pins, in_pin=in_pin, modes = mode_selection_index, plots = True) # 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)) # filenames for the s-parameter files files_sparam = [] # run s-parameter sweep, collect results, visualize results # export S-parameter data to file named xxx.dat to be loaded in INTERCONNECT pin_h0, pin_w0 = str(round(FDTD_settings['thickness_Si'],9)), str(round(pins[0].path.width*dbum,9)) file_sparam = os.path.join(_globals.TEMP_FOLDER, '%s_t=%s_w=%s.dat' % (component.instance,pin_h0,pin_w0)) files_sparam.append(file_sparam) 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); \n\ exportsweep('s-parameter sweep','%s'); \ " % (file_sparam) ) if verbose: print(" S-Parameter file: %s" % file_sparam) # 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' % (pin_h0, pin_w0, os.path.basename(file_sparam)) fh = open(xml_filename, "w") fh.writelines(xml_out) # Perform final corner analysis, for Monte Carlo simulations if FDTD_settings['Perform-final-corner-analysis']: lumapi.evalScript(_globals.FDTD, "leg=cell(4*%s); li=0; \n" % (len(mode_selection_index))) # legend for corner plots for w in [-FDTD_settings['width_Si_corners'],FDTD_settings['width_Si_corners']]: polygons_w = [p for p in pya.Region(polygons).sized(w/2*1e9).each_merged()] send_polygons_to_FDTD(polygons_w) for h in [-FDTD_settings['thickness_Si_corners'],FDTD_settings['thickness_Si_corners']]: lumapi.evalScript(_globals.FDTD, " \ switchtolayout; selectpartial('polygons::'); set('z span',%s);\ " % (FDTD_settings['thickness_Si']+h) ) # run s-parameter sweep, collect results, visualize results # export S-parameter data to file named xxx.dat to be loaded in INTERCONNECT pin_h, pin_w = str(round(FDTD_settings['thickness_Si']+h,9)), str(round(pins[0].path.width*dbum+w,9)) file_sparam = os.path.join(_globals.TEMP_FOLDER, '%s_t=%s_w=%s.dat' % (component.instance,pin_h,pin_w)) files_sparam.append(file_sparam) 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'); \ exportsweep('s-parameter sweep','%s'); \ # visualize(S_parameters); \n\ " % (file_sparam) ) if verbose: print(" S-Parameter file: %s" % file_sparam) #if verbose: # print(' Plot S_%s_%s Sparam' % (p.pin_name,in_pin.pin_name) ) # plot results of the corner analysis: for m in mode_selection_index: lumapi.evalScript(_globals.FDTD, " \ plot(wavelengths, 20*log10(abs(S_parameters.S%s1)), 'Wavelength (um)', 'Transmission (dB)'); holdon; \ li = li + 1; \ leg{li} = 'S_%s_%s:%s - %s, %s'; \ " % ( Sparam_pin_max_modes[mode_selection_index.index(m)]+1, pins[Sparam_pin_max_modes[mode_selection_index.index(m)]].pin_name, in_pin.pin_name, mode_selection_index.index(m)+1, pin_h,pin_w) ) # Write XML file for INTC scripted compact model # height and width are set to the first pin width/height xml_out = '\ <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' % (pin_h, pin_w, os.path.basename(file_sparam)) fh.writelines(xml_out) # Add legend to the Corner plots lumapi.evalScript(_globals.FDTD, "legend(leg);\n") xml_out = '\ </lumerical_lookup_table>' fh.writelines(xml_out) files_sparam.append(xml_filename) fh.close() if verbose: print(" XML file: %s" % xml_filename) if addto_CML: # INTC custom library name INTC_Lib = 'SiEPIC_user' # Run using Python integration: from . import interconnect interconnect.run_INTC() from .. import _globals lumapi = _globals.LUMAPI # Create a component port_dict2 = {0.0: 'Right', 90.0: 'Top', 180.0: 'Left', -90.0: 'Bottom'} t = 'switchtodesign; new; 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' % (files_sparam[0]) t+= 'set("load from file", false);\n' t+= 'set("passivity", "enforce");\n' t+= 'set("prefix", component);\n' t+= 'set("description", component);\n' # Add variables for Monte Carlo simulations: t+= 'addproperty(component, "MC_uniformity_thickness", "wafer", "Matrix");\n' t+= 'addproperty(component, "MC_uniformity_width", "wafer", "Matrix");\n' t+= 'addproperty(component, "MC_grid", "wafer", "Number");\n' t+= 'addproperty(component, "MC_resolution_x", "wafer", "Number");\n' t+= 'addproperty(component, "MC_resolution_y", "wafer", "Number");\n' t+= 'addproperty(component, "MC_non_uniform", "wafer", "Number");\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) lumapi.evalScript(_globals.INTC, t) # for Monte Carlo simulations, copy model files, create the component script if FDTD_settings['Perform-final-corner-analysis']: # Copy files to the INTC Custom library folder lumapi.evalScript(_globals.INTC, "out=customlibrary;") INTC_custom=lumapi.getVar(_globals.INTC, "out") INTC_files = os.path.join(INTC_custom, INTC_Lib, "source_data", component.instance) if not(os.path.exists(INTC_files)): try: os.makedirs(INTC_files) except: pass from shutil import copy2 for f in files_sparam: copy2(f, INTC_files) # Variables for the Monte Carlo component, linked to the top schematic t+='setexpression(component,"MC_uniformity_thickness","%MC_uniformity_thickness%");\n' t+='setexpression(component,"MC_uniformity_width","%MC_uniformity_width%");\n' t+='setexpression(component,"MC_grid","%MC_grid%");\n' t+='setexpression(component,"MC_resolution_x","%MC_resolution_x%");\n' t+='setexpression(component,"MC_resolution_y","%MC_resolution_y%");\n' t+='setexpression(component,"MC_non_uniform","%MC_non_uniform%");\n' lumapi.evalScript(_globals.INTC, t) script = ' \ ############################################### \n\ # SiEPIC compact model library (CML) \n\ # custom generated component created by SiEPIC-Tools; script by Zeqin Lu, Xu Wang, Lukas Chrostowski \n\ ############################################### \n\ \n\ # nominal geometry: \n\ waveguide_width = %s; \n\ waveguide_thickness = %s; \n\ # S-parameter data file table \n\ component = "%s"; table = "index_table"; \n\ filename = %%local path%%+"/source_data/"+component+"/"+component+".xml"; \n\ if (fileexists(filename)) { \n\ \n\ if (MC_non_uniform==1) { \n\ # location of component on the wafer map \n\ x=%%x coordinate%%; y=%%y coordinate%%; \n\ x1_wafer = floor(x/MC_grid); y1_wafer = floor(y/MC_grid); \n\ \n\ # geometry variation: \n\ devi_width = MC_uniformity_width(MC_resolution_x/2 + x1_wafer, MC_resolution_y/2 + y1_wafer)*1e-9; \n\ devi_thickness = MC_uniformity_thickness(MC_resolution_x/2 + x1_wafer, MC_resolution_y/2 + y1_wafer)*1e-9; \n\ \n\ # geometry for this MC run \n\ waveguide_width = waveguide_width + devi_width; # [m] \n\ waveguide_thickness = waveguide_thickness + devi_thickness; # [m] \n\ \n\ } \n\ \n\ # design (input parameters) \n\ design = cell(2); \n\ design{1} = struct; design{1}.name = "width"; design{1}.value = waveguide_width; \n\ design{2} = struct; design{2}.name = "height"; design{2}.value = waveguide_thickness; \n\ \n\ # Load (interpolate for MC simulation) the S-Parameters \n\ M = lookupreadnportsparameter(filename, table, design, "sparam"); \n\ setvalue("SPAR_1","s parameters",M); \n\ } \n\ ' % (pin_w0, pin_h0, component.instance) # Script for the Monte Carlo component, to load S-Param data: lumapi.putString(_globals.INTC, "script", script) t+='select(component); set("setup script", script);' lumapi.evalScript(_globals.INTC, t) # Add to library t = 'select(component); addtolibrary("%s",true);\n' % INTC_Lib t+= '?"created and added " + component + " to library %s";\n' % INTC_Lib lumapi.evalScript(_globals.INTC, t) return component.instance, file_sparam, [], xml_filename, svg_filename