예제 #1
0
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)
예제 #2
0
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
예제 #3
0
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)
예제 #4
0
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)
예제 #5
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
예제 #6
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)
예제 #7
0
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)
예제 #8
0
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
예제 #9
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()
예제 #10
0
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)
예제 #11
0
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
예제 #12
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; 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