├── .gitignore ├── Materials └── refbase.mat ├── pyfimm ├── media │ ├── Cavity.calc_-_field_01.png │ ├── Cavity.calc_-_field_02.png │ └── WG_Wavelength_Sweep_Animation_v3.gif ├── __version.py ├── colormap_HotCold.py ├── PhotonDesignLib │ ├── __init__.py │ ├── pdAppclient.py │ └── pdPythonLib.py ├── proprietary │ ├── __init__.py │ └── ExampleModule.py ├── __globals.py ├── __init__.py ├── __Tapers.py ├── __pyfimm.py ├── __Cavity.py └── __CavityMode.py ├── Example 2 - Two Modes with Prop Const.png ├── example5 - open Device from File with Variables v1.py ├── example4 - open Device from File v1.py ├── example4 - WG Device 1.prj ├── example1 - Rect WG.py ├── example5 - Device with Variables v1.prj ├── example2 - Rect Device with material db.py ├── example3 - Cyl DFB Cavity v4.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | 4 | -------------------------------------------------------------------------------- /Materials/refbase.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisjohn/pyFIMM/HEAD/Materials/refbase.mat -------------------------------------------------------------------------------- /pyfimm/media/Cavity.calc_-_field_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisjohn/pyFIMM/HEAD/pyfimm/media/Cavity.calc_-_field_01.png -------------------------------------------------------------------------------- /pyfimm/media/Cavity.calc_-_field_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisjohn/pyFIMM/HEAD/pyfimm/media/Cavity.calc_-_field_02.png -------------------------------------------------------------------------------- /Example 2 - Two Modes with Prop Const.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisjohn/pyFIMM/HEAD/Example 2 - Two Modes with Prop Const.png -------------------------------------------------------------------------------- /pyfimm/media/WG_Wavelength_Sweep_Animation_v3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/demisjohn/pyFIMM/HEAD/pyfimm/media/WG_Wavelength_Sweep_Animation_v3.gif -------------------------------------------------------------------------------- /pyfimm/__version.py: -------------------------------------------------------------------------------- 1 | versionnum = "1.3.3" # the version number 2 | versiondate = "2017-04-20" # the date of this version 3 | 4 | version = "v"+versionnum+", "+versiondate 5 | 6 | # if this file called by itself, print the version number: 7 | if __name__ == "__main__": 8 | print version 9 | 10 | 11 | -------------------------------------------------------------------------------- /pyfimm/colormap_HotCold.py: -------------------------------------------------------------------------------- 1 | # ColorMap 2 | # Red-Black-Blue, like Matlab's 'FireIce' or 'HotCold' 3 | # http://stackoverflow.com/questions/24997926/making-a-custom-colormap-using-matplotlib-in-python 4 | 5 | from matplotlib.colors import LinearSegmentedColormap 6 | ltblue = [x/255. for x in (170,170,255)] # set the RBG vals here 7 | ltred = [x/255. for x in (255,100,100)] 8 | cm_hotcold = LinearSegmentedColormap.from_list('coldhot', [ltblue, 'black', ltred] , N=256) 9 | 10 | ''' 11 | # Use as so, 12 | # to keep black at 0, set vmin/vmax to extent of data: 13 | maxfield = np.max( np.abs( np.array(field).real ) ) 14 | cont = ax.contourf( np.array(x), np.array(y), np.array(field) , vmin=-maxfield, vmax=maxfield, cmap=cm_coldhot) 15 | 16 | (also for pcolor() etc.) 17 | ''' -------------------------------------------------------------------------------- /pyfimm/PhotonDesignLib/__init__.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python2.7 2 | # 3 | # __init__.py 4 | # Module to load all PhotonDesign Python libraries/modules 5 | # This file will cause the folder it's in to be a Python module 6 | # to be importable as a module, where the module automatically includes 7 | # all the files within the folder. 8 | # 9 | # Taken from here: 10 | # http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python 11 | # 12 | # Demis John, Oct 2014 13 | # 14 | ############################################################ 15 | 16 | import os # file path manipulations 17 | import glob # file-name matching 18 | 19 | # the following directs __init__ to import add to __all__ all the files within it's directory that match *.py 20 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob(os.path.dirname(__file__)+"/*.py")] 21 | -------------------------------------------------------------------------------- /pyfimm/proprietary/__init__.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/python2.7 2 | # 3 | # __init__.py 4 | # Module to load all Python libraries/modules in this folder 5 | # This file will cause the folder it's in to be a Python module 6 | # and importable as a module, where the module automatically includes 7 | # all the files within the folder. 8 | # 9 | # Taken from here: 10 | # http://stackoverflow.com/questions/1057431/loading-all-modules-in-a-folder-in-python 11 | # 12 | # Demis John, Oct 2011 13 | # 14 | ############################################################ 15 | 16 | 17 | import os # file path manipulations 18 | import glob # file-name matching 19 | 20 | # the following directs __init__ to import (add to __all__) all the files within it's directory that match *.py 21 | __all__ = [ os.path.basename(f)[:-3] for f in glob.glob( os.path.dirname(__file__) + "/*.py" ) ] 22 | 23 | -------------------------------------------------------------------------------- /pyfimm/PhotonDesignLib/pdAppclient.py: -------------------------------------------------------------------------------- 1 | #pdAppClient (PYTHON version) 2 | 3 | from pdPythonLib import * 4 | import sys 5 | from string import * 6 | 7 | if len(sys.argv)<3: 8 | print "pdAppClient (PYTHON Version) Syntax:" 9 | print "pdAppClient " 10 | print " = the port number on which the application is serving" 11 | print " = the name (or IP address) where application is serving" 12 | else: 13 | _portNo = atoi(sys.argv[1]) 14 | f = pdApp() 15 | retmsg = f.ConnectToApp(sys.argv[2],_portNo) 16 | if retmsg!="": 17 | print retmsg 18 | else: 19 | print "Connected to Application" 20 | print "Enter your commands or enter exit to finish" 21 | isDone = 0 22 | while isDone==0: 23 | comm = raw_input("COMMAND: ") 24 | if comm[0:4]=="exit": 25 | isDone = 1 26 | else: 27 | rec = f.Exec(comm) 28 | print rec 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example5 - open Device from File with Variables v1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ########################################################################## 3 | 4 | Example 5: 5 | Import a Project + Device from File & access the internal variables 6 | 7 | ########################################################################## 8 | ''' 9 | 10 | import pyfimm as pf # Every script must begin with this line 11 | 12 | pf.connect() 13 | import sys, os 14 | ScriptPath, ScriptFile = os.path.split( os.path.realpath(__file__) ) # Get directory of this script 15 | 16 | pf.set_working_directory(ScriptPath) # Set FimmWave directory to the location of your script (needed to capture output files) 17 | ''' Since we're loading an existing Project, we might not need any of these global parameters. Haven't tested that yet. ''' 18 | pf.set_eval_type('n_eff') # FIMMWAVE will label modes by the effective index (options: n_eff or beta) 19 | pf.set_mode_finder_type('stable') # options: stable or fast 20 | pf.set_mode_solver('vectorial FMM real') # Three words, any permuation of: 'vectorial/semivecTE/semivecTM FDM/FMM real/complex' for RWG. 21 | pf.set_wavelength(1.55) # The unit of space is always 1 micrometer 22 | pf.set_N_1d(100) # # of 1D modes found in each slice (FMM solver only) 23 | pf.set_NX(100) # # of horiz. grid points for plotting & FDM 24 | pf.set_NY(100) # # of vertical grid points for plotting & FDM 25 | pf.set_N(3) # # of modes to solve for 26 | 27 | pf.set_material_database('Materials/refbase.mat') 28 | 29 | 30 | 31 | ##################################################### 32 | # Import a Device from a saved FimmWave project file 33 | # 34 | # First open the Project file 35 | # Then make a new pyFIMM Device that points to the loaded Device 36 | ##################################################### 37 | 38 | #pf.set_DEBUG() # Turn on Debugging verbose output. 39 | 40 | ex5prj = pf.import_Project('example5 - Device with Variables v1.prj', overwrite=True) 41 | # If the project is already loaded, try `overwrite='reuse'` to prevent reloading it. 42 | 43 | # Tell pyFIMM the name of the Variable Node in this Project: 44 | ex5prj.set_variables_node('Variables 1') 45 | 46 | # The variables can be interrogated, get and set, via the Project's new attribute: `ex5prj.variablesnode` 47 | # For example: 48 | #print ex5prj.variablesnode.get_var('wCore') 49 | #allvars = ex5prj.variablesnode.get_all() # save all vars as dictionary 50 | print ex5prj.variablesnode # show all variables and formulae 51 | # See `help(ex5prj.variablesnode)` for the full list of methods. 52 | 53 | # Load the Device '1x2 Coupler' into a pyFIMM Device object: 54 | dev = pf.import_device(project=ex5prj, fimmpath='1x2 Coupler') 55 | ''' 56 | We just opened a Device from a file, and made a pyFIMM Device object 57 | that points to it. Since the Device was made in FimmProp, not pyFIMM, 58 | pyFIMM does not try to understand it's inner workings in detail. 59 | Many Device properties are still created though, so that you can 60 | plot fields, reference elements etc. 61 | ''' 62 | 63 | # Do something with the new Device: 64 | print dev.name + ": Total Device Length = %f um" %( dev.get_length() ) 65 | 66 | 67 | -------------------------------------------------------------------------------- /example4 - open Device from File v1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ########################################################################## 3 | 4 | Example 4: 5 | Import a Project from File, and insert a Device from File into a new Project 6 | 7 | ########################################################################## 8 | ''' 9 | 10 | 11 | import pyfimm as pf # Every script must begin with this line 12 | 13 | pf.connect() 14 | 15 | import sys, os 16 | ScriptPath, ScriptFile = os.path.split( os.path.realpath(__file__) ) # Get directory of this script 17 | 18 | ''' Since we're loading an existing Project, we might not need any of these global parameters. Haven't tested that yet. ''' 19 | pf.set_working_directory(ScriptPath) # Set FimmWave directory to the location of your script (needed to capture output files) 20 | pf.set_eval_type('n_eff') # FIMMWAVE will label modes by the effective index (options: n_eff or beta) 21 | pf.set_mode_finder_type('stable') # options: stable or fast 22 | pf.set_mode_solver('vectorial FMM real') # Three words, any permuation of: 'vectorial/semivecTE/semivecTM FDM/FMM real/complex' for RWG. 23 | pf.set_wavelength(1.55) # The unit of space is always 1 micrometer 24 | pf.set_N_1d(100) # # of 1D modes found in each slice (FMM solver only) 25 | pf.set_NX(100) # # of horiz. grid points for plotting & FDM 26 | pf.set_NY(100) # # of vertical grid points for plotting & FDM 27 | pf.set_N(3) # # of modes to solve for 28 | 29 | pf.set_material_database('Materials/refbase.mat') 30 | 31 | 32 | 33 | # 1st Make our own project, as usual: 34 | myprj = pf.Project() 35 | myprj.buildNode('Example 4 - Import Device', overwrite=True) 36 | 37 | 38 | 39 | ##################################################### 40 | # Import a Device from a saved FimmWave project file 41 | # 42 | # First open the Project file 43 | # Then copy the Device into our own project 44 | ##################################################### 45 | 46 | #pf.set_DEBUG() # Turn on Debugging verbose output. 47 | 48 | # Open a saved Project file: 49 | openedprj = pf.import_Project('T:\Python Work\pyFIMM Simulations\example4 - WG Device 1.prj') 50 | # If the project is already loaded, try `overwrite='reuse'` to prevent reloading it. `overwrite=True` will delete the opened project before loading the file. 51 | ''' 52 | `openedprj` now refers to the opened Project file, which contains the Device we want to add to our own Project 53 | You can optionally provide a name to use in FimMWave, along with the usual `overwrite` and `warn` options. 54 | ''' 55 | 56 | 57 | # Copy the Device 'SlabDev' into our own project, myprj: 58 | dev2 = myprj.import_device(project=openedprj, fimmpath='SlabDev') 59 | ''' 60 | We just imported a Device into our own Project, myprj. We told it to import it from the opened Project, `openedprj`, and grab the FimMWave node named `SlabDev`. 61 | `dev2` now refers to this new Device, in our own Project. In FimmWave, you will see that the Device has been copied into our own Project, 'Example 4 - Import Device'. 62 | Since the Device was made in FimmWave, not pyFIMM, the object `dev2` does not have knowledge about the device's internal workings (for example, paths and complex layouts). Most Device methods (such as calculating, plotting, getting Smat's) should still work though. 63 | ''' 64 | 65 | # Do something with the new Device: 66 | print dev2.name + ": Total Device Length = %f um" %( dev2.get_length() ) 67 | 68 | 69 | -------------------------------------------------------------------------------- /pyfimm/__globals.py: -------------------------------------------------------------------------------- 1 | '''pyFIMM's Global Variables 2 | Contains/defines global variables - most importantly the fimmwave connection object `fimm`. 3 | 4 | This separate file is required to prevent circular module imports, and enable nested-modules (eg. in /proprietary/) to use the FimmWave connection. 5 | ''' 6 | 7 | import numpy as np 8 | import matplotlib.pyplot as plt 9 | 10 | ''' 11 | ## The following were various tests for resolving cyclic imports - can probably be deleted 12 | 13 | # import some pyFIMM objects/functions for global access from within the module: 14 | ## for Mode.py: 15 | import pylab as pl 16 | #import matplotlib.pyplot as plt 17 | from pylab import cm # color maps 18 | #import numpy as np 19 | import math 20 | import os # for filepath manipulations (os.path.join/os.mkdir/os.path.isdir) 21 | from __pyfimm import get_N, get_wavelength 22 | 23 | ## For Device.py 24 | from __pyfimm import Node, Project, Material, Layer, Slice 25 | from __Waveguide import Waveguide # rectangular waveguide class 26 | from __Circ import Circ # cylindrical (fiber) waveguide class 27 | from __Tapers import Taper, Lens # import Taper/WGLens classes 28 | from __Mode import Mode # import Mode class 29 | 30 | 31 | ## for pyfimm.py: 32 | from __Device import Device # Device class 33 | #import numpy as np 34 | import datetime as dt # for date/time strings 35 | import os.path # for path manipulation 36 | import random # random number generators 37 | 38 | ## for Waveguide.py & Circ.py: 39 | from numpy import inf # infinity, for hcurv/bend_radius 40 | #from __pyfimm import * # all global modesolver params. 41 | 42 | ## for Tapers.py: 43 | #from __pyfimm import * # import the main module (should already be imported), includes many 'rect' classes/funcs 44 | #from __Mode import * # import Mode class 45 | #from __Waveguide import * # import Waveguide class 46 | #from __Circ import * # import Circ class 47 | #from __pyfimm import DEBUG() # Value is set in __pyfimm.py 48 | #from numpy import inf # infinity, for hcurv/bend_radius 49 | #import numpy as np # math 50 | ''' 51 | 52 | #print "**** __globals.py: Finished importing pyFIMM modules" 53 | 54 | global pf_DEBUG 55 | pf_DEBUG = False # set to true for verbose outputs onto Python console - applies to all submodules/files 56 | # can be changed at run-time via `set/unset_DEBUG()` 57 | 58 | global pf_WARN 59 | pf_WARN = True # globally set warning mode 60 | 61 | # custom colormaps: 62 | from colormap_HotCold import cm_hotcold 63 | 64 | 65 | # Create FimmWave connection object. 66 | import PhotonDesignLib.pdPythonLib as pd 67 | global fimm 68 | fimm = pd.pdApp() # used in all scripts to send commands, via `fimm.Exec('CommandsToSend')` 69 | pdApp = fimm # alias to the above. 70 | 71 | 72 | 73 | 74 | # These override the value set above in `pf_DEBUG` 75 | def set_DEBUG(): 76 | '''Enable verbose output for debugging.''' 77 | global pf_DEBUG 78 | pf_DEBUG = True 79 | 80 | def unset_DEBUG(): 81 | '''Disable verbose debugging output.''' 82 | global pf_DEBUG 83 | pf_DEBUG = False 84 | 85 | def DEBUG(): 86 | '''Returns whether DEBUG is true or false''' 87 | return pf_DEBUG 88 | 89 | # the global WARN is not currently implemented in the main functions yet. 90 | def set_WARN(): 91 | '''Enable verbose output for debugging.''' 92 | global pf_WARN 93 | pf_WARN = True 94 | 95 | def unset_WARN(): 96 | '''Disable verbose debugging output.''' 97 | global pf_WARN 98 | pf_WARN = False 99 | 100 | def WARN(): 101 | '''Returns whether WARN is true or false''' 102 | return pf_WARN 103 | 104 | def AMF_FolderStr(): 105 | '''Folder name to store temporary files in.''' 106 | return 'pyFIMM_temp' 107 | -------------------------------------------------------------------------------- /pyfimm/proprietary/ExampleModule.py: -------------------------------------------------------------------------------- 1 | ''' 2 | pyFIMM/proprietary/ExampleModule.py 3 | 4 | This is an example of how to add your own proprietary functionality to pyFIMM. 5 | You could also keep this file outside the main pyFIMM directory and import it in your script, 6 | but importing it as part of pyFIMM gives it access to all the pyFIMM methods etc. 7 | 8 | 9 | This example module adds the following functions: 10 | Creates a new function `get_total_width()` as part of this module. 11 | and 12 | Adds a `set_temperature()` method to the `Waveguide` object 13 | Adds a `get_temperature()` method to the `Waveguide` object 14 | 15 | The functions can then be called as so: 16 | >>> pf.ExampleModule.get_total_width( WaveguideObj1, WaveguideObj2, WaveguideObj3 ) 17 | and 18 | >>> WaveguideObj1.set_temperature( 451.0 ) # set the waveguide's temperature 19 | ''' 20 | 21 | from ..__globals import * # import global vars & FimmWave connection object & DEBUG() variable 22 | import numpy as np 23 | 24 | 25 | ''' 26 | ######################################################## 27 | New Functions from this ExampleModule 28 | ######################################################## 29 | ''' 30 | def get_total_width( *args ): 31 | '''Return the total width of the waveguides passed. 32 | 33 | Parameters 34 | ---------- 35 | *args : any number of Waveguide or Circ objects, each as an individual arguments 36 | 37 | Examples 38 | -------- 39 | >>> pf.ExampleModule.get_total_width( WaveguideObj1, WaveguideObj2, WaveguideObj2 ) 40 | : 44.2 # returns the total width in microns 41 | ''' 42 | width = 0 43 | for wg in args: 44 | width += wg.get_width() 45 | return width 46 | 47 | 48 | 49 | 50 | ''' 51 | ######################################################## 52 | New Functions for the Waveguide object 53 | ######################################################## 54 | ''' 55 | 56 | from ..__Waveguide import * # import the Waveguide class, to add functions to it. 57 | 58 | # `self` here will be the Waveguide object, once this func is called as a method of that object 59 | # Use a temporary place-holder name. The real name comes later when we add it to the Waveguide Class 60 | # Double-underscores (___ is a convention that means this function should be hidden from the user. We don't want anyone calling this function directly (ie. not as a Waveguide method). 61 | def __WG_set_temperature(self,temp): 62 | '''Set temperature of this Waveguide. FimmWave default is -1000.0. 63 | Waveguide Object should have already been built. 64 | 65 | Parameters 66 | ---------- 67 | temp : float 68 | Temperature in degrees Celcius. 69 | 70 | Examples 71 | -------- 72 | >>> WaveguideObj.set_temperature( 25.0 ) 73 | ''' 74 | 75 | if not self.built: raise UserWarning( "Waveguide.set_temperature(): This waveguide has not been built yet! Please call WaveguideObj.buildNode() first!" ) 76 | 77 | # Construct the command-string to send to FimmWave: 78 | wgString = self.nodestring + ".temp = " + str(temp) 79 | # nodestring is the fimmwave string to reference this Waveguide node. 80 | # So this command expands to something like: 81 | # app.subnodes[1].subnodes[5].temp = 451.0 82 | 83 | # Execute the above command: 84 | fimm.Exec(wgString) 85 | #end __WG_set_temperature() 86 | 87 | # add the above function to the Waveguide class: 88 | Waveguide.set_temperature = __WG_set_temperature 89 | # This determines the real name of the function as a Waveguide method, and points to this function. 90 | 91 | 92 | def __WG_get_temperature(self): 93 | '''Return temperature setting of this Waveguide. 94 | 95 | Returns 96 | ------- 97 | temp : float 98 | Temperature in degrees Celcius. Defaults to `-1000.0` if unset. 99 | ''' 100 | return fimm.Exec( self.nodestring + ".temp" ) 101 | #end __WG_get_temperature() 102 | 103 | # add the above function to the Waveguide class: 104 | Waveguide.get_temperature = __WG_get_temperature 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /example4 - WG Device 1.prj: -------------------------------------------------------------------------------- 1 | begin 2 | FIMMWAVE 6.2.2 // applicationname applicationversion 3 | 1968 // licensenumber 4 | 6.1 (64 bit) 2995.55 // windowsversion RAM_in_MB 5 | 11/12/2015 7:38:12 // savedate(dd/mm/yyyy) savetime(hh:mm:ss) 6 | end 7 | begin "Example 4 - Project with saved Device" 8 | begin "Variables 1" 9 | tCore = 0.06 10 | wCore = 1.25 11 | tUClad = 1 12 | etchCore = tCore+tAir 13 | wSim = 30 14 | tAir = 1 15 | wavelength = 1.05 16 | tEtch = tCore+tAir 17 | end 18 | begin "SlabDev" 19 | 1.05 "" // lambda [um] temperature [oC] 20 | "" // materialDatabase 21 | METALWALL 0 22 | METALWALL 0 23 | MAGWALL 0 24 | MAGWALL 0 25 | 1 0.01 0.01 3 0 1 // propMethod propTolerance propMinStepsizeFrac jointMethod jointNormalise enmodesharing 26 | 5 1.1 // psWidth[um] psEtchDepth[um] 27 | begin 28 | //list of subelements follow: 29 | begin 30 | 1 "3" // zFlip refSectionID 31 | end 32 | begin 33 | 0 0 0 0 0 0 0 0 0 0 "" 0 // xoff yoff xalign yalign h_tilt1 v_tilt1 rotation1 h_tilt2 v_tilt2 rotation2 method powerNormalise 34 | end 35 | begin 36 | "SiN Slab" "" 50 1.1 "" "" "" "" "" 0 //"swgname" width length depth joint_method int_method tolerance minSTPfrac enableEVscan isStraight 37 | UNDEFINED "" "" 38 | UNDEFINED "" "" 39 | UNDEFINED "" "" 40 | UNDEFINED "" "" 41 | "Z" //"zCoordName" 42 | BEGINPARS 43 | ENDPARS 44 | begin 45 | 0 1 // isMirrored etchGrowType 46 | 0 1 "" "0" // widthType widthL widthR "widthFn" 47 | 1 0 0 "0" // offsetType offsetL offsetR "offsetFn" 48 | "1.5" // "depth" 49 | "0.5" // "growThickness" 50 | 1.997 0 // one of a) {rix alpha} b) {anirix anialpha} c) matname(mx,my) 51 | end 52 | begin 53 | 1 0 "" "" // autoRun minTEfrac maxTEfrac 54 | N(1e+50) N(-1e+50) // evstart evend 55 | "" "" "" "" // maxNmodes molabOpt nx ny 56 | 1.05 0 NULL 0 0 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 57 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 58 | end 59 | end 60 | begin 61 | 0 0 0 0 0 0 0 0 0 0 "" 0 // xoff yoff xalign yalign h_tilt1 v_tilt1 rotation1 h_tilt2 v_tilt2 rotation2 method powerNormalise 62 | end 63 | begin 64 | 1 "3" // zFlip refSectionID 65 | end 66 | begin 67 | 1 // numPorts 68 | // list of ports follow 69 | 2.5 0 1 0 // portWidth portPosn portNModes portUseCurv 70 | end 71 | end 72 | //lhs input field 73 | begin 74 | 1 //input type 75 | 1 //single mode input 76 | end 77 | //rhs input field 78 | begin 79 | 1 //input type 80 | 0 //single mode input 81 | end 82 | begin 83 | 1 0 0 100 // autoRun minTEfrac maxTEfrac 84 | N(1e+50) N(-1e+50) // evstart evend 85 | 2 0 90 90 // maxNmodes molabOpt nx ny 86 | 1.05 0 RFDMVEC 0 0 V1 90 90 0 100 0.000100 16 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 87 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 88 | end 89 | begin "SiN Slab" 90 | "" -1000 91 | begin 92 | 0.5 1 0 0 93 | 0.5 1.445 0 0 94 | 2 1.445 0 0 95 | end 96 | end 97 | end 98 | begin "strip2" 99 | begin 100 | "T:\Python Work\pyFIMM Simulations\Materials\refbase.mat" -1000 101 | METALWALL 0 102 | METALWALL 0 103 | METALWALL 0 104 | METALWALL 0 105 | 6 0 1 106 | 9.3 0 2 107 | 6 0 3 108 | ENDSLICELIST 109 | begin 110 | 12.1 1.4456 0 0 111 | end 112 | begin 113 | 6 1.4456 0 0 114 | 0.1 AlGaAs(0.98) 1 115 | 6 1.4456 0 0 116 | end 117 | begin 118 | 12.1 1.4456 0 0 119 | end 120 | end 121 | begin 122 | 1 0 0 100 // autoRun minTEfrac maxTEfrac 123 | N(1e+50) N(-1e+50) // evstart evend 124 | 3 1 100 100 // maxNmodes molabOpt nx ny 125 | 1.55 0 RVEC 0 0 V2 100 0 1 300 300 15 25 0 5 5 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 126 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /example1 - Rect WG.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ########################################################################## 3 | 4 | Simple rectangular waveguide example using the FMM solver 5 | Demonstrates basic WG construction & plotting capabilities. 6 | 7 | In Spyder, make sure you run the script with the Run > Configure... settings 8 | "Execute in current Python console" 9 | or 10 | "Execute in a new dedicated Python console" & "Interact with the Python console after execution" 11 | to allow for dynamic commands and interacting with the objects you created. 12 | If Spyder doesn't return an interactive console after running the script, then 13 | check this setting in the menu Run > Configure... 14 | 15 | Note that other python sessions using FimmWave connections should be terminated 16 | before a new connection can be created, or the python terminal won't be able to 17 | connect to FimmWave. 18 | 19 | ########################################################################## 20 | ''' 21 | 22 | import pyfimm as pf # Every script must begin with this line 23 | ''' Get help on commands and objects by typing things like: 24 | >>> help( pf ) 25 | >>> dir( pf ) # lists all functions and variables provided by the module 26 | >>> help( pf.set_mode_solver ) # help on one function 27 | >>> help( pf.Waveguide ) # help on the Waveguide object 28 | >>> dir ( pf.Waveguide ) # list all functions/variables in the Waveguide object 29 | >>> help( pf.Waveguide.mode(0).plot ) # help on funciton 'plot' of the Waveguide object 30 | >>> help( pf.Circ.buildNode ) # help on the `buildNode` function of the Circ object 31 | 32 | or even easier, while building the script interactively, or after execution, try: 33 | >>> clad = pf.Material(1.4456) 34 | >>> core = pf.Material(1.9835) 35 | >>> help(clad) # Will show help on the Material object 36 | >>> strip = pf.Waveguide( side(w_side) + center(w_core) + side(w_side) ) 37 | >>> dir(strip) # will show functions available in the Waveguide object 38 | >>> help(strip.buildNode) # show help on the Waveguide.buildNode() method 39 | 40 | after strip.calc(), try 41 | >>> dir( strip.mode(0) ) # list the functions of a Mode object 42 | >>> help( strip.mode(0).plot ) # detailed help on the mode plotting function 43 | ''' 44 | 45 | pf.connect() # connect to the FimmWave application, which must already running. 46 | 47 | 48 | # Set global Parameters (Your copy of FIMMWAVE has default values for these. You can change more than shown here. See `dir(pyfimm)`, `help(pyfimm)`, or open the file `pyFIMM/__pyfimm.py` 49 | import sys, os 50 | ScriptPath, ScriptFile = os.path.split( os.path.realpath(__file__) ) # Get directory of this script 51 | 52 | pf.set_working_directory(ScriptPath) # Set this directory to the location of your script, which is usually given by sys.path[0] 53 | pf.set_eval_type('n_eff') # FIMMWAVE will label modes by the effective index (options: n_eff or beta) 54 | pf.set_mode_finder_type('stable') # options: stable or fast 55 | pf.set_mode_solver('vectorial FMM real') # Three words, any permuation of: 'vectorial/semivecTE/semivecTM FDM/FMM real/complex' 56 | pf.set_wavelength(1.55) # The unit of space is always 1 um 57 | pf.set_N_1d(100) # No. of 1D modes found in each slice (FMM solver only) 58 | pf.set_NX(100) # No. of horizontal grid points 59 | pf.set_NY(100) # No. of vertical grid points 60 | pf.set_N(3) # No. of modes to solve for 61 | 62 | # Project Node: You must build a project node at the beginning of every script 63 | wg_prj = pf.Project('Example 1 - WG Proj') # Make a Project object, pass a project name to the constructor 64 | wg_prj.buildNode() # the buildNode() method is what makes FIMMWAVE build your python objects. If you don't call it, your script won't do anything! 65 | 66 | 67 | # Construct the Waveguide Node 68 | # WG Geometry: 69 | t_clad = 6.0 # cladding thickness 70 | t_core = 0.1 71 | 72 | w_core = 2.8 73 | w_side = 6.0 # cladding width 74 | 75 | 76 | 77 | clad = pf.Material(1.4456) # Construct a Material python object, pass a refractive index to the constructor 78 | core = pf.Material(1.9835) 79 | 80 | center = pf.Slice( clad(t_clad) + core(t_core, cfseg=True) + clad(t_clad) ) 81 | side = pf.Slice( clad(2*t_clad + t_core) ) 82 | # Passing a thickness to a Material object as the argument creates a Layer object. 83 | # Layer objects can be stacked (bottom to top) using the + operator - "clad" & "core" have been stacked here. 84 | # You then pass a stack of Layer objects to the Slice object constructor 85 | # You can also set the "cfseg" (Confinement Factor) flag for a layer if desired, as done here for the waveguide core. 86 | 87 | 88 | strip = pf.Waveguide( side(w_side) + center(w_core) + side(w_side) ) 89 | # Construct a Waveguide object by adding Slice objects (left to right). 90 | # You can pass the Slice width to the Slice object with ()'s 91 | 92 | print "Printing `strip`:" 93 | print strip # you can print your python objects to the shell to check them 94 | 95 | strip.set_parent(wg_prj) # You have to tell python which project node to build the waveguide node under 96 | strip.name = 'strip' # Name the node 97 | strip.buildNode() # You must always build the node! 98 | 99 | # The above three lines can also be done in one line: 100 | #strip.buildNode(parent=wg_prj, name='strip') 101 | 102 | 103 | print "Calculating Modes..." 104 | strip.calc() # Tell FIMMWAVE to solve for the modes! 105 | 106 | #strip.mode(0).plot() # Plot the fundamental mode with python! 107 | #strip.mode(0).plot('Ey') # plot Ey instead 108 | strip.mode('all').plot(title='Strip WG: All Modes') # plot all the calc'd modes (3 in this case) on one figure 109 | #strip.mode( [0,2] ).plot() # plot only modes #0 and 2 110 | 111 | 112 | #strip.delete() # delete FIMMWAVE nodes if you want to! 113 | #wg_prj.delete() 114 | 115 | 116 | #pf.disconnect() # close TCP connection to application. Other pyFIMM scripts won't be able to use FimmWave until you either disconnect or kill the script's shell entirely. -------------------------------------------------------------------------------- /pyfimm/__init__.py: -------------------------------------------------------------------------------- 1 | '''pyFIMM Documentation: 2 | pyFIMM provides a python interface to Photon Design's FIMMWAVE/FIMMPROP simulation tools. 3 | 4 | The interface is set up like Peter Beinstman's CAMFR (CAvity Modelling FRamework) system, in which 1-D Slices are concatenated to produce arbitrary 2-D index profiles (waveguides), which can be further concatenated to produce full 3-D photonic integrated circuits. 5 | Photon Design's pdPythonLib is included in the module. 6 | 7 | Originally created by Jared Bauters at the University of California Santa Barbara in 2011. 8 | Updated by Demis D. John, 2015. 9 | 10 | 11 | Examples 12 | -------- 13 | Example of rectangular waveguide construction syntax: We will create a rectangular waveguide of SiO2 cladding and SiN core, calculate the fundamental mode & plot it. `pyfimm` should be replaced with whatever name you imported the pyFIMM module as - for example, if you imported it like so: 14 | >>> import pyfimm as pf 15 | then replace `pyfimm` with `pf` in the following examples. 16 | 17 | First, create some Materials with some refractive index: 18 | >>> SiO = pyfimm.Material(1.45) # refractive index of SiO2 19 | >>> SiN = pyfimm.Material(2.01) # refractive index of Si3N4 20 | 21 | Then, create some 1-D slabs, by calling those Materials with a thickness value, and adding them together from top to bottom in a Slice: 22 | clad = pyfimm.Slice( SiO(15.75) ) # Thicknesses in microns 23 | core = pyfimm.Slice( SiO(10.0) + SiN(2.5) + SiO(5.0) ) 24 | This created an imaginary structure from bottom-to-top, for example `core` looks like: 25 | 26 | top 27 | -------------------- 28 | SiO 29 | 5.0 um thick 30 | -------------------- 31 | SiN 32 | 2.50 um thick 33 | -------------------- 34 | SiO 35 | 10.0 um thick 36 | -------------------- 37 | bottom 38 | 39 | Then make a 2-D structure by calling these Slices with a width value, and adding them together from left to right in a Waveguide: 40 | >>> WG = pyfimm.Waveguide( clad(3.0) + core(1.0) + clad(4.0) ) # Widths in microns 41 | Which creates this imaginary 2-D Waveguide structure from left-to-right: 42 | top 43 | --------------------------------------------------------- 44 | |<----- 3.0um------>|<-----1.0um------>|<---- 4.0um---->| 45 | | | SiO | | 46 | | | 5.0 um thick | | 47 | | |------------------| | 48 | | SiO | SiN | SiO | 49 | | 15.75um | 0.750 um thick | 15.75um | 50 | | thick |------------------| thick | 51 | | | SiO | | 52 | | | 10.0 um thick | | 53 | --------------------------------------------------------- 54 | bottom 55 | 56 | Then tell FimmWave to actually build these structures: 57 | >>> WG.buildNode(name='Waveguide', parent=wg_prj) # Build the Fimmwave Node 58 | Now the RWG waveguide node is available in the Fimmwave GUI. (Note you should have already made a Project node in fimmwave, which is referenced as the `parent` here. See Examples for full code.) 59 | 60 | You can then calculate the modes as so: 61 | >>> WG.calc() 62 | 63 | And inspect the modes like so: 64 | >>> WG.mode(0).plot() # plots the fundamental mode. 65 | Or extract field values like so: 66 | >>> Mode1_Ex = WG.mode(1).get_field('Ex') # Saves x-direction E-field for 2nd mode 67 | 68 | See the Examples directory for full examples, as some details are missing in these. 69 | 70 | 71 | Requires 72 | -------- 73 | numpy, 74 | matplotlib 75 | FimmWave, setup with TCP port number access (see FimmWave manual section on Python usage). 76 | 77 | 78 | 79 | Get help on commands and objects by typing things like: 80 | (after you've created some objects, or run your script with 'interact with shell afterwards' enabled and then try these.) 81 | >>> import pyFIMM as pf # import the module 82 | >>> help( pf ) 83 | >>> dir( pf ) # lists all functions and variables provided by the module 84 | >>> help( pf.set_mode_solver ) # help on one function 85 | >>> help( pf.Waveguide ) # help on the Waveguide object 86 | >>> dir ( pf.Waveguide ) # list all functions/variables in the Waveguide object 87 | >>> help( pf.Waveguide.mode(0).plot ) # help on funciton 'plot' of the Waveguide object 88 | >>> help( pf.Circ.buildNode ) # help on the `buildNode` function of the Circ object 89 | 90 | or even easier, while building the script try: 91 | >>> clad = pf.Material(1.4456) 92 | >>> core = pf.Material(1.9835) 93 | >>> help(clad) # Will show help on the Material object 94 | >>> strip = pf.Waveguide( side(w_side) + center(w_core) + side(w_side) ) 95 | >>> dir(strip) # will show functions of the Waveguide object 96 | >>> help(strip.buildNode) # show help on the Waveguide.buildNode() method 97 | 98 | after strip.calc(), try 99 | >>> dir( strip.mode(0) ) # list the functions of a Mode object 100 | >>> help( strip.mode(0).plot ) # detailed help on the mode plotting function 101 | 102 | 103 | ''' 104 | 105 | import __version as v # file with the version number. 106 | version = v.versionnum 107 | versiondate = v.versiondate 108 | 109 | # Splash screen. 110 | print 111 | print "pyFIMM", v.version, "" 112 | print "Python Interface to Photon Design's FIMMWave software package." 113 | print "Based on Peter Beinstman's CAMFR (CAvity Modelling FRamework) interface." 114 | print 115 | print "Created by Jared Bauters University of California, Santa Barbara & updated by Demis D. John." 116 | print 117 | 118 | 119 | from __globals import * # import global vars & FimmWave connection object 120 | from __pyfimm import * # import the main module, many global functions, base objects like Project, Material, Slice, Section and some rectangular waveguide functions. 121 | from __Waveguide import * # contains the Waveguide class, including most of the Fimmwave commands for WG creation. 122 | from __Circ import * # contains Circ class & all other functions for cylindrical geometries. 123 | from __Device import * # contains the Device class, for constructing 3-D devices 124 | from __Mode import * # contains the Mode class, for WGobj.mode(0).xyz operations 125 | from __Tapers import * # contains all Taper classes, including WG_Lens 126 | from __Cavity import * # Cavity object & calculations 127 | from __CavityMode import * # contains the CavityMode class, for CavityOb.mode(0).xyz operations 128 | 129 | 130 | #################################################################################### 131 | # Import Proprietary Modules 132 | #################################################################################### 133 | 134 | from proprietary import * # the 'proprietary' folder contains modules/functions from other institutions. 135 | -------------------------------------------------------------------------------- /example5 - Device with Variables v1.prj: -------------------------------------------------------------------------------- 1 | begin 2 | FIMMWAVE 6.2.2 // applicationname applicationversion 3 | 1968 // licensenumber 4 | 6.1 (64 bit) 2995.55 // windowsversion RAM_in_MB 5 | 29/04/2016 1:44:34 // savedate(dd/mm/yyyy) savetime(hh:mm:ss) 6 | end 7 | begin "Example 5 - Device with Variables" 8 | begin "Variables 1" 9 | tCore = 0.35 10 | wCore = 2 11 | tUClad = 1 12 | etchCore = tCore+tAir 13 | wSim = 30 14 | tAir = 1 15 | wavelength = 1.55 16 | tEtch = tCore+tAir 17 | wCore2 = 15 18 | lCore2 = 125 19 | outwg_offset = 4 20 | end 21 | begin "1x2 Coupler" 22 | "wavelength" "" // lambda [um] temperature [oC] 23 | "" // materialDatabase 24 | METALWALL 0 25 | METALWALL 0 26 | MAGWALL 0 27 | MAGWALL 0 28 | 1 0.01 0.01 3 0 1 // propMethod propTolerance propMinStepsizeFrac jointMethod jointNormalise enmodesharing 29 | 5 1.1 // psWidth[um] psEtchDepth[um] 30 | begin 31 | //list of subelements follow: 32 | begin 33 | "SiN Slab" "4+wCore" 50 1.1 "" "" "" "" "" 0 //"swgname" width length depth joint_method int_method tolerance minSTPfrac enableEVscan isStraight 34 | UNDEFINED "" "" 35 | UNDEFINED "" "" 36 | UNDEFINED "" "" 37 | UNDEFINED "" "" 38 | "Z" //"zCoordName" 39 | BEGINPARS 40 | ENDPARS 41 | begin 42 | 0 1 // isMirrored etchGrowType 43 | 0 "wCore" "" "0" // widthType widthL widthR "widthFn" 44 | 1 0 0 "0" // offsetType offsetL offsetR "offsetFn" 45 | "1.5" // "depth" 46 | "tCore" // "growThickness" 47 | 1.997 0 // one of a) {rix alpha} b) {anirix anialpha} c) matname(mx,my) 48 | end 49 | begin 50 | 1 0 "" "" // autoRun minTEfrac maxTEfrac 51 | N(1e+050) N(-1e+050) // evstart evend 52 | "" "" "" "" // maxNmodes molabOpt nx ny 53 | 1.55 0 NULL 0 0 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 54 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 55 | end 56 | end 57 | begin 58 | 0 0 0 0 0 0 0 0 0 0 0 0 // xoff yoff xalign yalign h_tilt1 v_tilt1 rotation1 h_tilt2 v_tilt2 rotation2 method powerNormalise 59 | end 60 | begin 61 | "SiN Slab" "5+wCore2" "lCore2" 1.1 "" "" "" "" "" 0 //"swgname" width length depth joint_method int_method tolerance minSTPfrac enableEVscan isStraight 62 | UNDEFINED "" "" 63 | UNDEFINED "" "" 64 | UNDEFINED "" "" 65 | UNDEFINED "" "" 66 | "Z" //"zCoordName" 67 | BEGINPARS 68 | ENDPARS 69 | begin 70 | 0 1 // isMirrored etchGrowType 71 | 0 "wCore2" "" "0" // widthType widthL widthR "widthFn" 72 | 1 0 0 "0" // offsetType offsetL offsetR "offsetFn" 73 | "1.5" // "depth" 74 | "tCore" // "growThickness" 75 | 1.997 0 // one of a) {rix alpha} b) {anirix anialpha} c) matname(mx,my) 76 | end 77 | begin 78 | 1 0 "" "" // autoRun minTEfrac maxTEfrac 79 | N(1e+050) N(-1e+050) // evstart evend 80 | 10 "" "" "" // maxNmodes molabOpt nx ny 81 | 1.55 0 NULL 0 0 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 82 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 83 | end 84 | end 85 | begin 86 | 0 0 0 0 0 0 0 0 0 0 0 0 // xoff yoff xalign yalign h_tilt1 v_tilt1 rotation1 h_tilt2 v_tilt2 rotation2 method powerNormalise 87 | end 88 | begin 89 | "SiN Slab" "12+wCore" 50 1.1 "" "" "" "" "" 0 //"swgname" width length depth joint_method int_method tolerance minSTPfrac enableEVscan isStraight 90 | UNDEFINED "" "" 91 | UNDEFINED "" "" 92 | UNDEFINED "" "" 93 | UNDEFINED "" "" 94 | "Z" //"zCoordName" 95 | BEGINPARS 96 | ENDPARS 97 | begin 98 | 1 1 // isMirrored etchGrowType 99 | 0 "wCore" "" "0" // widthType widthL widthR "widthFn" 100 | 1 "outwg_offset" "outwg_offset" "0" // offsetType offsetL offsetR "offsetFn" 101 | "1.5" // "depth" 102 | "tCore" // "growThickness" 103 | 1.997 0 // one of a) {rix alpha} b) {anirix anialpha} c) matname(mx,my) 104 | end 105 | begin 106 | 1 0 "" "" // autoRun minTEfrac maxTEfrac 107 | N(1e+050) N(-1e+050) // evstart evend 108 | 5 "" "" "" // maxNmodes molabOpt nx ny 109 | 1.55 0 NULL 0 0 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 110 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 111 | end 112 | end 113 | begin 114 | 2 // numPorts 115 | // list of ports follow 116 | "wCore+2" "outwg_offset" 1 0 // portWidth portPosn portNModes portUseCurv 117 | "wCore+2" "-outwg_offset" 1 0 // portWidth portPosn portNModes portUseCurv 118 | end 119 | end 120 | //lhs input field 121 | begin 122 | 2 //input type 123 | 0 //normalise vector 124 | //vector input (real,imag,...,real,imag): 125 | STARTVEC 126 | 1 0 0 0 127 | ENDVEC 128 | end 129 | //rhs input field 130 | begin 131 | 1 //input type 132 | 0 //single mode input 133 | end 134 | begin 135 | 1 0 0 100 // autoRun minTEfrac maxTEfrac 136 | N(1e+050) N(-1e+050) // evstart evend 137 | 2 0 90 90 // maxNmodes molabOpt nx ny 138 | "wavelength" 0 RFDMVEC 0 0 V1 90 90 0 100 0.000100 16 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 139 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 140 | end 141 | begin "SiN Slab" 142 | "" -1000 143 | begin 144 | 0.5 1 0 0 145 | 0.5 1.445 0 0 146 | 2 1.445 0 0 147 | end 148 | end 149 | end 150 | begin "strip2" 151 | begin 152 | "T:\Python Work\pyFIMM Simulations\Materials\refbase.mat" -1000 153 | METALWALL 0 154 | METALWALL 0 155 | METALWALL 0 156 | METALWALL 0 157 | 6 0 1 158 | 9.3 0 2 159 | 6 0 3 160 | ENDSLICELIST 161 | begin 162 | 12.1 1.4456 0 0 163 | end 164 | begin 165 | 6 1.4456 0 0 166 | 0.1 AlGaAs(0.98) 1 167 | 6 1.4456 0 0 168 | end 169 | begin 170 | 12.1 1.4456 0 0 171 | end 172 | end 173 | begin 174 | 1 0 0 100 // autoRun minTEfrac maxTEfrac 175 | N(1e+050) N(-1e+050) // evstart evend 176 | 3 1 100 100 // maxNmodes molabOpt nx ny 177 | 1.55 0 RVEC 0 0 V2 100 0 1 300 300 15 25 0 5 5 // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 178 | // lambda hCurv solverID Hsymmetries Vsymmetries solverParms 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /example2 - Rect Device with material db.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ########################################################################## 3 | 4 | Simple FimmProp Device example. 5 | 6 | Creates a rectangular WG (RWG) with AlGaAs core using the default material database, `refbase.mat` 7 | Solves for modes & plots the fundamental mode. 8 | Then makes an identical waveguide that is wider, and creates a Device with the two different waveguide stuck together. 9 | 10 | ########################################################################## 11 | ''' 12 | 13 | ''' Get help on commands and objects by typing things into the console, like: 14 | >>> help(pyfimm) or after the import below, >>> help(pf) 15 | >>> help(pyfimm.set_mode_solver) 16 | >>> help(pyfimm.Waveguide) 17 | >>> help( pyfimm.Mode ) # the Mode class, for selecting a mode to work with 18 | >>> help(pyfimm.Waveguide.buildNode) 19 | 20 | or even easier, while building your script try: 21 | >>> help(core) # will show help on the Material object 22 | >>> help(strip) # will show help on the Waveguide object 23 | >>> help(strip.buildNode) # shows options for Circ.buildNode() 24 | >>> dir( strip.mode(0) ) # shows all the available functions that can be performed on modes, which are actually Mode objects. 25 | >>> help( strip.mode(0).plot ) # help on the mode plotting function 26 | 27 | For more verbose output, while programming the libraries for example, set the pyfimm DEBUG flag as so: 28 | >>> pyFIMM.set_DEBUG() 29 | This will enable various levels of extra output, that aids in finding out where a calculation or bug is occurring. 30 | ''' 31 | 32 | 33 | import pyfimm as pf # Every script must begin with this line 34 | #pf.set_DEBUG() # Enable Debugging output 35 | 36 | pf.connect() # this connects to the FimmWave application. The FimmWave program should already be open (pdPythonLib.StartApplication() is not supported yet) 37 | 38 | # Set Parameters (Your copy of FIMMWAVE has default values for these. You can change more than shown here. See __jaredwave.py 39 | import sys, os 40 | ScriptPath, ScriptFile = os.path.split( os.path.realpath(__file__) ) # Get directory of this script 41 | 42 | pf.set_working_directory(ScriptPath) # Set this directory to the location of your script 43 | pf.set_working_directory(ScriptPath) # Set FimmWave directory to the location of your script (needed to capture output files) 44 | pf.set_eval_type('n_eff') # FIMMWAVE will label modes by the effective index (options: n_eff or beta) 45 | pf.set_mode_finder_type('stable') # options: stable or fast 46 | pf.set_mode_solver('vectorial FMM real') # Three words, any permuation of: 'vectorial/semivecTE/semivecTM FDM/FMM real/complex' for RWG. 47 | pf.set_wavelength(1.55) # The unit of space is always 1 micrometer 48 | pf.set_N_1d(100) # # of 1D modes found in each slice (FMM solver only) 49 | pf.set_NX(100) # # of horiz. grid points for plotting & FDM 50 | pf.set_NY(100) # # of vertical grid points for plotting & FDM 51 | pf.set_N(3) # # of modes to solve for 52 | 53 | pf.set_material_database('Materials/refbase.mat') # Use the material database provided by PhotonDesign. Only one matDB can be used at a time - to use multiple, set up your matDB to `include` other files. 54 | 55 | # Project Node - You must build a project node at the beginning of every script 56 | wg_prj = pf.Project() # Construct a Project object, pass a project name to the constructor (optional). 57 | wg_prj.buildNode('Example 2 - Waveguide Device', overwrite=True) 58 | # the buildNode() method makes FIMMWAVE build the objects. 59 | # Here we've also set it to overwrite any existing project of the same name. 60 | 61 | 62 | # Start constructing the Waveguide Node 63 | t_clad = 6.0 # cladding thickness 64 | t_core = 0.1 # core thickness 65 | 66 | clad = pf.Material(1.4456) # Construct a Material python object, pass a refractive index as the argument 67 | 68 | core=pf.Material('AlGaAs', 0.98) # AlGaAs with 98% Aluminum: defined in material database 69 | # See `help(core)` or `help(pf.Material)` to see more info on Material objects & options to make them! 70 | 71 | center = pf.Slice( clad(t_clad) + core(t_core, cfseg=True) + clad(t_clad) ) 72 | # The Core material here is also set as the Confinement Factor Segment. 73 | 74 | side = pf.Slice( clad(2*t_clad+t_core) ) 75 | 76 | w_side = 6.0 # cladding width 77 | w_core = 2.8 # width 78 | 79 | strip = pf.Waveguide( side(w_side) + center(w_core) + side(w_side) ) 80 | # You can pass the Slice width to the Slice object with ()s 81 | 82 | #strip.set_material_database('Materials/refbase.mat') # can set waveguide-specific material database - not recommended, as Device does not support this. 83 | 84 | print "Printing `strip`:" 85 | print strip # you can print your python objects to check them 86 | 87 | #strip.set_parent(wg_prj) # You have to tell python which project node to build the waveguide node under 88 | #strip.name = 'strip' # Name the node 89 | #strip.buildNode() 90 | strip.buildNode(name='strip', parent=wg_prj) # You can also set the parent & name while building. 91 | #You must always build the node! This sends the actual Fimmwave commands to generate this waveguide in Fimmwave. 92 | 93 | print "Calculating 'strip'..." 94 | strip.calc() # Tell FIMMWAVE to solve for the modes! 95 | 96 | 97 | 98 | # More sophisticated mode plotting: plot the Ex's of two selected modes & return the handles so that we can manipulate the plots with matplotlib: 99 | fig, axes, images = strip.mode( [0,2] ).plot('Ex', return_handles=True) 100 | 101 | # add the propagation constant of each mode to the plots: 102 | # position text in axis-scale, not data-scale (`transform=...`) 103 | PlotString = r"kz = %0.3f um^-1" % ( strip.mode(0).get_kz().real ) # insert the propagation const. into the %0.3f 104 | axes[0].text( 0.05, 0.05, \ 105 | PlotString, \ 106 | transform=axes[0].transAxes, horizontalalignment='left', color='green', fontsize=14, fontweight='bold') 107 | 108 | # Do some TeX formatting (sub/superscripts) with a 'raw' (r"...") string. 109 | PlotString = r"$k_z = %0.3f \mu{}m^{-1}$" % ( strip.mode(2).get_kz().real ) 110 | axes[1].text( 0.05, 0.05, \ 111 | PlotString, \ 112 | transform=axes[1].transAxes, horizontalalignment='left', color='green', fontsize=14, fontweight='bold') 113 | 114 | # Save the modified figure as so: 115 | fig.savefig('Example 2 - Two Modes with Prop Const.png') 116 | 117 | 118 | 119 | # Create a second waveguide that is identical but with 6.5um wider core: 120 | 121 | strip2 = pf.Waveguide( side(w_side) + center(w_core+6.5) + side(w_side) ) 122 | 123 | #strip2.name='strip 2' 124 | #strip2.set_parent(wg_prj) 125 | strip2.buildNode(name='strip2', parent=wg_prj) # Two waveguides under the one project. 126 | 127 | 128 | 129 | # Create a FimmProp Device with these two Waveguides concatenated (to propagate through multiple waveguides). Pass the lengths of each WG as arguments. 130 | dev = pf.Device( strip(10.0) + strip2(15.0) ) 131 | 132 | #dev.set_parent(wg_prj) 133 | #dev.name = 'WG Device' 134 | #dev.buildNode() 135 | dev.buildNode(name='WG Device', parent=wg_prj) # same as the above three lines 136 | 137 | # You should now see the Device called "WG Device" in FimmProp! 138 | # See `help(dev)` or `dir(dev)` to see what further funcionality is available via pyfimm. 139 | 140 | 141 | 142 | # View fields in the device 143 | dev.set_input( [1,0,0] ) # Set to launch Mode #0 only 144 | dev.plot('I') # plot the intensity versus Z. 145 | dev.plot('Ex', direction='-z', title='Reflected (-z) field') # Plot reflected wave only 146 | 147 | #wg_prj.savetofile('rectdev with mat db') # save the project to a file. '.prj' will be appended. 148 | #wg_prj.delete() # Delete the whole project! 149 | 150 | #pyfimm.disconnect() # close TCP connection to application. -------------------------------------------------------------------------------- /example3 - Cyl DFB Cavity v4.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ########################################################################## 3 | 4 | Cylindrical waveguide & Device example - a distributed feed-back device 5 | similar to a VCSEL cavity. 6 | 7 | Based on the Photon Design web-example: 8 | "Modelling a passive optical cavity (VCSEL, DFB)" 9 | http://www.photond.com/products/fimmprop/fimmprop_applications_17.htm 10 | 11 | Calculates the Cavity modes of a cylindrical GaAs/AlGaAs DFB using the 12 | "Cavity Mode Calculator" code written by Vincent Brulis @ Photon Design, 2014 13 | 14 | Requires pyFIMM v1.2.8 or greater 15 | 16 | ########################################################################## 17 | ''' 18 | 19 | import numpy as np # Array math.functions. Used here for `argmax()`. 20 | 21 | import pyfimm as pf # Import the pyFIMM module 22 | ''' Get help on commands and objects by typing things into the console, like: 23 | >>> help(pyfimm) or after the above import, >>> help(pf) 24 | >>> help(pyfimm.set_mode_solver) 25 | >>> help(pyfimm.Waveguide) 26 | >>> help( pyfimm.Mode ) # the Mode class, for selecting a mode to work with 27 | >>> help(pyfimm.Circ.buildNode) 28 | 29 | or even easier, while building your script try: 30 | >>> AlOx = pyfimm.Material(1.60) # setting up some Materials 31 | >>> AlGaAs = pyfimm.Material(3.25) 32 | >>> help(AlOx) # will show help on the Material object 33 | >>> CurrentAperture = pyfimm.Circ( AlGaAs(3.5) + AlOx(4.5) ) 34 | >>> help(CurrentAperture) # will show help on the Circ object 35 | >>> help(CurrentAperture.buildNode) # shows options for Circ.buildNode() 36 | >>> help( CurrentAperture.mode(0) ) # shows functions that can be performed on modes, which are actually Mode objects. 37 | >>> help( CurrentAperture.mode(0).plot ) # help on the mode plotting function 38 | 39 | For more verbose output, while programming the libraries for example, set the pyfimm DEBUG parameter like so: 40 | pyfimm.set_DEBUG() 41 | at the point you want debugging output turned on. This will enable various levels of extra output, that aids in finding out where a calculation or bug is occurring. `unset_DEBUG()` can be used to turn off this extra verbosity. 42 | ''' 43 | 44 | 45 | pf.connect() # this connects to the FimmWave application. The FimmWave program should already be open (pdPythonLib.StartApplication() is not supported yet) 46 | 47 | 48 | wl = 1.100 # center wavelength in microns - sets the Bragg wavelength 49 | 50 | # Set Parameters (Your copy of FIMMWAVE has default values for these. You can change more than shown here. 51 | import sys 52 | ScriptPath = sys.path[0] # Get directory of this script 53 | pf.set_working_directory(ScriptPath) # Set FimmWave directory to the location of your script (needed to capture output files) 54 | pf.set_eval_type('n_eff') # FIMMWAVE will label modes by the effective index (options: n_eff or beta) 55 | pf.set_mode_finder_type('stable') # options: stable or fast 56 | pf.set_mode_solver('Vectorial GFS Real') # See `help(pyfimm.set_mode_solver)` for all options. 57 | pf.set_wavelength( wl ) # The unit of space is always 1 micrometer 58 | pf.set_N_1d(100) # Num. of 1D modes found in each slice (FMM solver only) 59 | 60 | pf.set_N(3) # Num. of modes to solve for 61 | pf.set_Nm(1) # theta mode order. Can accept start/stop values as list, eg. [1,5]. See `help(pf.set_Nm)`. 62 | pf.set_Np(2) # polarization mode order, also can accept start/stop values as list. See `help(pf.set_Np)`. 63 | 64 | dfbproj = pf.Project('Example 3 - DFB Cavity', buildNode=True, overwrite=True) # Create Proj & build the node in one line. `overwrite` will overwrite an existing project with the same name. 65 | 66 | 67 | 68 | 69 | # Define materials. 70 | ## Refractive indices: 71 | n_GaAs = 3.53 72 | n_AlGaAs = 3.08 73 | CoreHi = pf.Material(n_GaAs) # GaAs 74 | CoreLo = pf.Material(n_AlGaAs) # AlGaAs 75 | Clad = pf.Material(1.56) 76 | 77 | rCore = 20/2. 78 | TotalDiam = 30 79 | rClad = TotalDiam/2-rCore 80 | 81 | pf.set_circ_pml(0) # thickness of perfectly matched layers for cylindrical (circ) objects 82 | 83 | # Fiber waveguides: 84 | Hi = pf.Circ( CoreHi(rCore) + Clad(rClad) ) 85 | Lo = pf.Circ( CoreLo(rCore) + Clad(rClad) ) 86 | 87 | #Hi.set_joint_type("special complete") # default is "complete". Set this before building the FimmProp Device node. 88 | #Lo.set_joint_type("special complete") 89 | 90 | # Build these waveguides in FimmWave. The Device will reference the pre-built waveguide nodes. 91 | Hi.buildNode(name='Hi', parent=dfbproj) 92 | Lo.buildNode(name='Lo', parent=dfbproj) 93 | 94 | 95 | # Lengths 96 | dHi = wl/4/n_GaAs #77.90368e-3 97 | dLo = wl/4/n_AlGaAs #89.28571e-3 98 | 99 | # Construct the device, split into two parts with same waveguide type at central split. This is important so that the modal basis set of each half of the cavity is the same. 100 | Nperiods = 50 101 | # Devices are built from left to right: 102 | dfb_left = pf.Device( Lo(1.0) + Nperiods*( Hi(dHi) + Lo(dLo) ) + Hi(dHi/2) ) 103 | # DFB_Right has Hi waveguide cut in half at center & quarter-wave shift (Lo section with double length): 104 | dfb_right = pf.Device( Hi(dHi/2) + Lo(dLo*2) + Hi(dHi) + Nperiods*( Lo(dLo) + Hi(dHi)) + Lo(1.0) ) 105 | 106 | dfb_left.set_joint_type('special complete') 107 | dfb_right.set_joint_type('special complete') 108 | 109 | # Build these Devices in FimmProp: 110 | dfb_left.buildNode(name='DFBleft', parent=dfbproj) 111 | dfb_right.buildNode(name='DFBright', parent=dfbproj) 112 | 113 | # Show the devices in the FImmWave GUI: 114 | pf.Exec(dfb_right.nodestring + '.findorcreateview()') 115 | pf.Exec(dfb_left.nodestring + '.findorcreateview()') 116 | 117 | 118 | 119 | """ calculate modes of half the cavity only - just for demonstrating Device functions. 120 | This is not pertinent to the Cavity resonance calculation 121 | """ 122 | dfb_right.calc() # calc scattering matrix of this Device (only half the cavity) 123 | dfb_right.plot_refractive_index() # Fig1: show the refractive index versus Z for this device. 124 | dfb_left.set_input([1,0,0], side='right', normalize=True) # launch only 1st Mode from right side 125 | dfb_left.plot('Ex', direction='left') # Fig2: Plot Ex field for this launch, for left-propagating field (since injected on right side) 126 | #dfb_left.plot('Ey', refractive_index=True) # can also plot refractive index on same figure 127 | 128 | 129 | 130 | 131 | """ --- Now Calculate the Cavity modes! --- """ 132 | #WLs = [1.080, 1.100, 1.120] # for fast example 133 | #WLs = np.arange( 1.100-0.060, 1.100+0.060, 0.005 ) # coarse eigenmode calculation 134 | #WLs = np.concatenate([ np.arange(1.100-0.060, 1.100-0.007, 0.005) , np.arange(1.100-0.007, 1.100+0.007, 0.0005) , np.arange(1.100+0.007, 1.100+0.060, 0.005) ]) # coarse away from resonance, fine at resonance 135 | WLs = np.arange( wl-0.010, wl+0.010, 0.005 ) # refined calc @ resonance only 136 | 137 | 138 | # Set up Cavity with Left & Right devices: 139 | DFB = pf.Cavity(dfb_left, dfb_right) 140 | 141 | DFB.plot_refractive_index() # Fig3: show the refractive index profile along Z, at (x,y)=(0,0) 142 | 143 | DFB.calc(WLs) # Calculate the Cavity resonances etc. 144 | # try `dir(DFB)` After calling calc() - you'll see that new variables are available, such as the eigenvectors & resonance wavelengths etc. 145 | 146 | #DFB.mode(0).plot() # plot eigenvalues of 1st mode (plot defaults to 'EigV') 147 | DFB.mode('all').plot('EigVals') # Fig4: plot eigenvalues of all modes 148 | 149 | 150 | # plot resonance fields for 2 of the modes: 151 | DFB.mode( [0,1] ).plot('Ex', 'resonance', refractive_index=True, title="DFB + 1/2-wave") # Fig5: plot Ex field for the resonance wavelengths of specified modes. 152 | 153 | 154 | """ 155 | To view the transverse cavity mode profile: 156 | In FimmProp, on Either device, select 157 | View > Input Field 158 | And then select the appropriate tab (Left-Hand or Right-Hand input), and 159 | click 'Update' in the Preview area, to see what the superposition of modes 160 | according to the EigenVector looks like. 161 | """ 162 | 163 | #pyfimm.disconnect() # close TCP connection to application. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyFIMM 2 | A Python Interface to [PhotonDesign's FimmWave/FimmProp software](http://www.photond.com/products/fimmwave.htm). 3 | 4 | >PhotonDesign has now incorporated this functionality into their tools, with auto-generated full OO python interface! Don't use this python module, instead use PhotonDesign's built-in python libraries. 5 | 6 | Interface based on [Peter Beinstman's CAMFR simulation software](http://camfr.sourceforge.net). 7 | Originally created by Jared F. Bauters at the [University of California Santa Barbara](ucsb.edu) in 2011; 8 | Updated by Demis D. John, 2014-2017. 9 | 10 | 11 | ## Description 12 | 13 | pyFIMM provides a Python scripting interface to [Photon Design's FIMMWAVE/FIMMPROP electromagnetic/photonic simulation tools](http://www.photond.com/products/fimmwave.htm), to construct, simulate, sweep, plot and analyze photonic devices using FimmWave. This enables one to do additional analysis and math on your simulations from python itself, and define complex Python functions or loops that use FimmWave to do the heavy-lifting of solving for modes and Scattering Matrices, but analyze and plot the data in Python. You can also interact with nodes you've constructed using the PhotonDesign GUIs. FimmWave can return solved field values & mode profiles or plot modes/fields directly in MatPlotLib. 14 | 15 | The FimmWave/FimmProp GUI contains much functionality that is not implemented by *pyFIMM*, so pyFIMM includes the ability to import saved Projects, Devices and Waveguides and run analysis on them. This way you can design your device in the FimmProp GUI, and run sweeps, data analysis and plotting via pyFIMM. 16 | 17 | Some examples of pyFIMM's utility include large multivariable sweeps and subsequent plotting (eg. waveguide width, thickness and wavelength) & devices that require analysis between sweep iterations (eg. the resonant cavity, in which eigenvalues are calculated at each wavelength to determine the resonant wavelength and resulting cavity mode profile). 18 | Some analysis functions are included, based on examples provided by Photon Design. Some useful features are the ability to solve/plot the fields in an optical Cavity & Mode/Field-plotting. 19 | 20 | 21 | ### Wavelength Sweep of a Waveguide object: 22 | ![Waveguide wavelength sweep](pyfimm/media/WG_Wavelength_Sweep_Animation_v3.gif) 23 | 24 | 25 | ### Cavity Mode Solver: locating the cavity resonance wavelength 26 | ![Cavity Mode Solve](pyfimm/media/Cavity.calc_-_field_01.png) 27 | ![Cavity Mode Solve](pyfimm/media/Cavity.calc_-_field_02.png) 28 | 29 | The interface is set up like [Peter Beinstman's CAMFR (CAvity Modelling FRamework)](http://camfr.sourceforge.net) system, in which 1-D Slices are concatenated to produce arbitrary 2-D index profiles, which can be further concatenated to produce full 3-D photonic integrated circuits. This is also similar to how you construct waveguides via Slices in the FimmWave GUI. 30 | 31 | Photon Design's Python library, `pdPythonLib`, is included in the module. 32 | 33 | 34 | ## Brief Example 35 | Example of rectangular waveguide construction syntax: We will create a rectangular waveguide of SiO2 cladding and SiN core, calculate the fundamental mode & plot it. 36 | 37 | The following assumes you imported the module via `import pyfimm`, with your script residing in the same directory as the *pyfimm* folder. `pyfimm` could be replaced with whatever name you imported the pyFIMM module under - for example, if you imported it like so: 38 | 39 | >>> import pyfimm as pf 40 | 41 | then replace `pyfimm` with `pf` in the following examples. 42 | 43 | First, create some Materials with some refractive index: 44 | 45 | >>> SiO = pyfimm.Material( 1.45 ) # refractive index of SiO2 46 | >>> SiN = pyfimm.Material( 2.01 ) # refractive index of Si3N4 47 | 48 | Then, create some 1-D slabs, by calling those Materials with a thickness value, and adding them together from top to bottom in a Slice: 49 | 50 | clad = pyfimm.Slice( SiO(15.75) ) # Thicknesses in microns 51 | core = pyfimm.Slice( SiO(10.0) + SiN(2.5) + SiO(5.0) ) 52 | 53 | This created an imaginary structure from bottom-to-top. For example `core` looks like: 54 | 55 | top 56 | -------------------- 57 | SiO 58 | 5.0 um thick 59 | -------------------- 60 | SiN 61 | 2.50 um thick 62 | -------------------- 63 | SiO 64 | 10.0 um thick 65 | -------------------- 66 | bottom 67 | 68 | Then make a 2-D structure by calling these Slices with a width value, and adding them together from left to right in a Waveguide: 69 | 70 | >>> WG = pyfimm.Waveguide( clad(3.0) + core(1.0) + clad(4.0) ) # Widths in microns 71 | 72 | Which creates this imaginary 2-D Waveguide structure from left-to-right: 73 | 74 | top 75 | --------------------------------------------------------- 76 | |<----- 3.0um------>|<-----1.0um------>|<---- 4.0um---->| 77 | | | SiO | | 78 | | | 5.0 um thick | | 79 | | |------------------| | 80 | | SiO | SiN | SiO | 81 | | 15.75um | 2.50 um thick | 15.75um | 82 | | thick |------------------| thick | 83 | | | SiO | | 84 | | | 10.0 um thick | | 85 | --------------------------------------------------------- 86 | bottom 87 | 88 | Then tell FimmWave to actually build these structures: 89 | 90 | >>> WG.buildNode(name='Waveguide', parent=wg_prj) # Build the Fimmwave Node 91 | 92 | Now the RWG (Rectangular) waveguide node is available in the Fimmwave GUI. (Note you should have already made a Project node in fimmwave, which is referenced as the `parent` here. See *Example1* for full code.) 93 | 94 | You can then have FimmWave calculate the modes as so: 95 | 96 | >>> WG.calc() 97 | 98 | And plot the modes like so: 99 | 100 | >>> WG.mode(0).plot() # plots the fundamental mode. 101 | >>> WG.mode( 'all' ).plot() # plots all modes on one figure 102 | 103 | Or extract field values like so: 104 | 105 | >>> Mode1_Ex = WG.mode(1).get_field('Ex') # Saves x-direction E-field for 2nd mode 106 | >>> Mode0_Ex, Mode1_Ex = WG.mode( [0,1] ).get_field('Ex') # Saves x-direction E-field for 1st & 2nd modes 107 | 108 | See the Examples directory for full examples, as some details are missing here. 109 | 110 | 111 | 112 | ## Installation 113 | pyFIMM currently only supports Python 2.7. 114 | 115 | To use pyFIMM, simply download one of the released versions (see the "releases" or "tags" section of this page), or the bleeding-edge code, and extract the archive into a directory. Your Python script should reside in the same directory as the *pyfimm* folder, or else you should add the parent directory of the *pyfimm* folder to your Python path at the beginning of your script. 116 | 117 | The preferred method to run your scripts is through a Python IDE like Spyder (a matlab-like IDE). The simplest installation of Spyder (along with all typical scientific python modules) can be accomplished via [Python(x,y)](https://code.google.com/p/pythonxy/) (Win) or [Anaconda](http://continuum.io/downloads) (Mac,Win,Linux). 118 | 119 | These pyfimm scripts can also be run like any typical Python script on the command line via `python myScript.py` or `python -i myScript.py` to make it interactive afterwards. 120 | 121 | ### Setting up FimmWave for Scripting 122 | Make sure your FimmWave executable starts up with the ability to interact with external scripts like Python/Matlab (see FimmWave manual section 11.9). 123 | To set up the scripting connection, start Fimmwave with the `-pt 5101` command-line option, to listen on port 5101. 124 | 125 | You can do this by making a shortcut to `fimmwave.exe`, and in the *Properties* of that shortcut, add the `-pt 5101` argument as so: 126 | 127 | Shortcut Properties/**Target**= `"C:\Program Files\PhotonD\Fimmwave\bin64\fimmwave.exe" -pt 5101` 128 | 129 | Note that the argument comes outside the quotation marks. 130 | 131 | Alternatively, you can start FimMWave with the port argument from Python, by adding the following line to the start of your Python script: 132 | 133 | import os 134 | os.system('"C:\\Program Files\\PhotonD\\Fimmwave\\bin64\\fimmwave.exe" -pt 5101' ) 135 | 136 | Note the single & double quotes! 137 | 138 | 139 | ### Requires 140 | Since FimmWave & FimmProp require Windows, you must run this on a Windows system with FimmWave installed (or via a virtual-machine of some sort). 141 | * [FimmWave by Photon Design](http://www.photond.com/products/fimmwave.htm), setup with TCP port access (see FimmWave manual section on Python usage, sec. 11.9). 142 | * Python 2.7 (may work on other 2.x versions, untested) 143 | * [numpy python package](http://www.numpy.org) 144 | * [matplotlib python package](http://matplotlib.org) 145 | * **RECOMMENDED**: all of the above Python modules are included as part of the scientific python environments 146 | * [Python(x,y)](https://code.google.com/p/pythonxy/) and 147 | * [Anaconda](http://continuum.io/downloads)). 148 | * These packages include everything you need for scientific python work, including a Matlab-like IDE interface. 149 | 150 | ### Contact 151 | Feel free to add issues/feature requests, or even better, Fork the `git` repository and create your own updates, and merge back into this repo when your updates work (see this [how-to](http://kbroman.org/github_tutorial/pages/fork.html))! Help with [updating for python 3.x](https://github.com/demisjohn/pyFIMM/issues/67) would be great. 152 | 153 | Jan. 2016, Demis D. John 154 | -------------------------------------------------------------------------------- /pyfimm/PhotonDesignLib/pdPythonLib.py: -------------------------------------------------------------------------------- 1 | # pdPythonLib version 1.6 2 | # Command Line Interface with Python for Photon Design products 3 | from string import * 4 | from socket import * 5 | from struct import * 6 | from math import ceil 7 | from time import sleep 8 | import os 9 | import re 10 | import __main__ 11 | import types 12 | import time # for pausing execution 13 | 14 | INTBUFFSIZE = 20 #tcp/ip buffer length defined in the application 15 | portsTaken = []#list of ports that are already taken 16 | nextPortAvailable = 5101 17 | CONNECTIONATTEMPTS = 10 18 | MaxBuffSize = 4096 #maximum data size that can be retrieved at once (recommended values: 4096 (more stable) or 8192 (faster)) 19 | delay = 0.01 #delay (in s) between two batches of data (recommended values: 0.01 (more stable) or 0.001 (faster)) 20 | 21 | def IsPortAvailable(portNo): 22 | global portsTaken 23 | a = 1 24 | if (len(portsTaken)==1): 25 | if (portNo == portsTaken[0]): 26 | return 0 27 | for i in range(0,len(portsTaken)): 28 | if (portNo==portsTaken[i]): 29 | a=0 30 | return a 31 | 32 | def getNextAvailablePort(): 33 | global nextPortAvailable 34 | a = 0 35 | while (1): 36 | a = IsPortAvailable(nextPortAvailable) 37 | if (a==1): 38 | break 39 | nextPortAvailable = nextPortAvailable + 1 40 | return nextPortAvailable 41 | 42 | def getNumOrStr(msgstr): 43 | if (msgstr[0]=='('): 44 | reidx = find(msgstr,',') 45 | imidx = find(msgstr,')',0) 46 | try: 47 | rebit = float(msgstr[1:reidx]) 48 | except: 49 | return msgstr 50 | try: 51 | imbit = float(msgstr[reidx+1:imidx]) 52 | except: 53 | return msgstr 54 | return rebit + imbit*1j 55 | retval = None 56 | nlidx = find(msgstr,'\n') 57 | if (nlidx!=-1): 58 | recmsg2 = msgstr[0:nlidx] 59 | else: 60 | recmsg2 = msgstr 61 | try: 62 | retval = float(recmsg2) 63 | except: 64 | retval = msgstr 65 | return retval 66 | 67 | def InterpretString1(commStr,varList): 68 | currIdx = 0 69 | nextIdx = 0 70 | noExpr = 0 71 | while (1): 72 | currIdx = find(commStr,'{',currIdx) 73 | nextIdx = find(commStr,'}',nextIdx) 74 | if ((currIdx==-1) or (nextIdx==-1)): 75 | break 76 | expression = commStr[currIdx+1:nextIdx] 77 | #Now find '%' and replace with object values 78 | idxtemp = 0 79 | while (1): 80 | idxtemp = find(expression,'%',idxtemp) 81 | if idxtemp==-1: 82 | break 83 | expression = expression[0:idxtemp] + repr(varList[noExpr]) + expression[idxtemp+1:] 84 | noExpr = noExpr + 1 85 | subobj = eval(expression,__main__.__dict__) 86 | if (type(subobj)==types.StringType): 87 | commStr = commStr[0:currIdx] + subobj + commStr[nextIdx+1:] 88 | else: 89 | commStr = commStr[0:currIdx] + repr(subobj) + commStr[nextIdx+1:] 90 | return commStr 91 | 92 | def InterpretString(commStr,varList): 93 | commStr1 = "" 94 | commStr2 = "" 95 | currIdx = 0 96 | nextIdx = 0 97 | isStringDone = 0 98 | while (isStringDone!=1): 99 | nextIdx = find(commStr,'"',currIdx) 100 | if (nextIdx==-1): 101 | isStringDone=1 102 | commStr1 = commStr[currIdx:len(commStr)] 103 | commStr2 = commStr2 + InterpretString1(commStr1,varList) 104 | else: 105 | commStr1 = commStr[currIdx:nextIdx] 106 | commStr2 = commStr2 + InterpretString1(commStr1,varList) 107 | currIdx = find(commStr,'"',nextIdx+1) #Must have open quotes and end quotes!!! 108 | if (currIdx==-1): 109 | print "Error interpreting command\n" 110 | return commStr 111 | commStr2 = commStr2 + commStr[nextIdx:currIdx+1] 112 | currIdx = currIdx + 1 113 | return commStr2 114 | 115 | #NB: msgstr must contain (".....RETVAL:.......") or it will fail!!! 116 | def InterpretString3(msgstr): 117 | retvalidx = find(msgstr,"RETVAL:") 118 | if (retvalidx==-1): 119 | return msgstr 120 | msgstr = msgstr[retvalidx+7:] 121 | currIdx = find(msgstr,'[') 122 | if (currIdx!=-1): #might be a list, a 1d array or a 2d array 123 | arrStr = re.split("\s*",msgstr) 124 | del arrStr[0] #if it is a list or an array, first element is '' 125 | arrStrlen = len(arrStr) 126 | del arrStr[arrStrlen-1] #last element is the \000 character 127 | arrList = [] 128 | #check format to see if it is a list, a 1d array or a 2d array 129 | #list or 1d array format of arrStr[0] MUST BE: 130 | #[integer] 131 | #for a 2d array it is: 132 | #[integer][integer] 133 | currIdx = find(arrStr[0],'[') 134 | nextIdx = find(arrStr[0],']',currIdx) 135 | testStr = arrStr[0] 136 | idx1Start = 0 137 | try: 138 | idx1Start = int(testStr[currIdx+1:nextIdx]) 139 | except: 140 | return msgstr 141 | #Now we know it's an array 142 | #We can fill array up to the first index 143 | for i in range(0,idx1Start): 144 | arrList.append(None) 145 | if (nextIdx==(len(testStr)-1)): # only one '[...]' 146 | #This is either a 1D array or list 147 | # we now need to work out whether this is an array of a list 148 | #list format of arrStr[1] MUST BE: 149 | #[integer] 150 | #for a 1d array it is: 151 | #value (no '[') 152 | try: 153 | arrayOrList = find(arrStr[1],'[') 154 | except IndexError: # this is a list with only one element 155 | return msgstr 156 | if arrayOrList==-1: 157 | # this is a 1D array 158 | for i in range(1,arrStrlen-1,2): # was range(1,arrStrlen-1,2); not sure why! 159 | arrList.append(getNumOrStr(arrStr[i])) 160 | return arrList 161 | else: 162 | # this is a list 163 | for i in range(0,arrStrlen-1,1): # was range(1,arrStrlen-1,2); not sure why! 164 | arrList.append(getNumOrStr(arrStr[i])) 165 | return arrList 166 | nextIdx = nextIdx +1 167 | if (testStr[nextIdx]!='['): 168 | return msgstr 169 | currIdx = find(testStr[nextIdx:],']') + nextIdx 170 | if (currIdx!=-1): 171 | try: 172 | idx2Start = int(testStr[nextIdx+1:currIdx]) 173 | except: 174 | return msgstr 175 | #Now we know it's a 2d array 176 | idx1 = -1 177 | for i in range(0,arrStrlen-2,2): 178 | testStr = arrStr[i] 179 | currIdx = find(testStr,'[') 180 | nextIdx = find(testStr[currIdx:],']') + currIdx 181 | x = int(testStr[currIdx+1:nextIdx]) 182 | currIdx2 = find(testStr[nextIdx:],'[') + nextIdx 183 | nextIdx2 = find(testStr[currIdx2:],']') + currIdx2 184 | y = int(testStr[currIdx2+1:nextIdx2]) 185 | #Assumed to ALWAYS be an int and currIdx+1!=nextIdx 186 | if (x!=idx1): #next row of matrix 187 | idx1 = x 188 | arrList.append([]) 189 | for k in range(0,idx2Start): 190 | arrList[idx1].append(None) 191 | #fill inner list(array) up to first index 192 | arrList[idx1].append(getNumOrStr(arrStr[i+1])) 193 | return arrList 194 | else: 195 | return getNumOrStr(msgstr) 196 | 197 | class pdApp: 198 | def __init__(self): 199 | self.appSock = None 200 | self.currPort = None 201 | self.cmdList = '' 202 | 203 | def __del__(self): 204 | if (self.appSock!=None): 205 | self.appSock.close() #close() = os function? 206 | self.CleanUpPort() 207 | 208 | def CleanUpPort(self): 209 | global portsTaken 210 | global nextPortAvailable 211 | if (len(portsTaken)==1): 212 | portsTaken = [] 213 | for i in range(0,len(portsTaken)-1): 214 | if (portsTaken[i]==self.currPort): 215 | nextPortAvailable = portsTaken[i] 216 | del portsTaken[i] 217 | self.currPort = None 218 | 219 | def StartApp(self,path,portNo = 5101): 220 | retstr = '' 221 | if (self.appSock!=None): 222 | return "This object is already in use." 223 | a = IsPortAvailable(portNo) 224 | if (a==0): 225 | retstr = retstr + "Port No: " + repr(portNo) + " is not available\n" 226 | portNo = getNextAvailablePort() 227 | retstr = retstr + "Using Port No: " + repr(portNo) +" instead.\n" 228 | 229 | #here try to change dir to the exe path dir. 230 | a = rfind(path,"\\") 231 | if (a!=-1): 232 | if (path[0:a]==''): 233 | os.chdir("\\") 234 | else: 235 | os.chdir(path[0:a]) 236 | 237 | try: 238 | os.spawnv(os.P_DETACH,path,[path,"-pt",repr(portNo)]) 239 | except: 240 | retstr = retstr + "Could not start the application\n" 241 | return retstr 242 | retstr1 = self.ConnectToApp1('localhost',portNo,0) 243 | retstr = retstr + retstr1 244 | 245 | def ConnectToApp(self,hostname = 'localhost',portNo = 5101): 246 | return self.ConnectToApp1(hostname,portNo,1) 247 | 248 | def ConnectToApp1(self,hostname,portNo,selectPort = 1): 249 | retstr = '' 250 | if (self.appSock!=None): 251 | return "This object is already in use.\n" 252 | global portsTaken 253 | global CONNECTIONATTEMPTS 254 | if (selectPort==1): 255 | a = IsPortAvailable(portNo) 256 | if (a==0): 257 | retstr = retstr + "Port No: " + repr(portNo) + " is not available\n" 258 | portNo = getNextAvailablePort() 259 | retstr = retstr + "Using Port No: " + repr(portNo) +" instead.\n" 260 | 261 | self.appSock = socket(AF_INET,SOCK_STREAM) 262 | a = 0 263 | print "Attempting to connect to application on TCP/IP Port No. " + repr(portNo) 264 | while (aMaxBuffSize): # if there is more data than can be transmitted in one go 313 | batches=int(ceil(float(recmsglen)/float(MaxBuffSize))) 314 | for i in range(1,batches+1,1): 315 | while True: 316 | try: 317 | recmsg = recmsg + self.appSock.recv(MaxBuffSize) 318 | sleep(delay) 319 | break 320 | except: 321 | pass 322 | else: 323 | recmsg = self.appSock.recv(recmsglen) 324 | #now test to see what has been returned 325 | if (len(recmsg) 'complete' " 122 | self.__jointtype = 0 123 | 124 | if len(args) == 0: asnumeric = False # output as string by default 125 | if len(args) == 1: asnumeric = args[0] 126 | if len(args) > 1: raise ValueError("get_joint_type(): Too many arguments provided.") 127 | 128 | if asnumeric: 129 | out= self.__jointtype 130 | else: 131 | if self.__jointtype == 0: 132 | out= 'complete' 133 | elif self.__jointtype == 1: 134 | out= 'normal fresnel' 135 | elif self.__jointtype == 2: 136 | out= 'oblique fresnel' 137 | elif self.__jointtype == 3: 138 | out= 'special complete' 139 | #if DEBUG(): print "get_joint_type(): ", out 140 | return out 141 | #end get_joint_type() 142 | 143 | 144 | def buildNode(self): 145 | '''This may not make sense - only able to be built in a FimmProp Device.''' 146 | print "Warning: Tapers & WGLenses are only built within a FimmProp Device, not as stand-alone components. Nothing done for Taper.buildNode()." 147 | 148 | def get_buildNode_str(self, nodestring): 149 | '''Return the string needed to build this Taper.''' 150 | pass 151 | 152 | #end class Taper 153 | 154 | 155 | 156 | class Lens(Node): 157 | '''Waveguide Lens, an element of a FimmProp Device. See FimmProp Manual sec. 4.3.10. 158 | 159 | >>> NewLensObj = Lens(wgbase, radius [,optional kwargs] ) 160 | >>> NewLensObj.set_diameter( 20.0 ) 161 | >>> NewLensObj.set_type( 'polish' ) 162 | >>> DeviceObj = .Device( WG1(100) + WG2(50.0) + NewLensObj(5.0) ) 163 | 164 | 165 | Parameters 166 | ---------- 167 | wgbase : Waveguide or Circ object 168 | The lens will reference this WG object/node & deform it in the manner specified. 169 | 170 | radius : float, required 171 | Radius of curvature of this lens. 172 | 173 | 174 | Optional Keyworded Arguments 175 | ---------------------------- 176 | 177 | side : { 'left' | 'right' }, optional 178 | Which side of the element should have the curvature/lens applied. Defaults to curvature on the Right side. 179 | 180 | type : {'distortion' | 'polish convex' | 'polish concave'}, optional 181 | Which method to create taper with. Defaults to 'distortion', which distorts the passed WG into a lens. Polish instead removes parts of the structure to create the curved surface, but all interfaces in the WG remain straight. 182 | 183 | diameter : float, optional 184 | Diameter to distort, if not the entire WG diameter. This is the chord length of widest part of lens. If omitted, will use d1 & d2 185 | 186 | d1 : float, optional 187 | distance from bottom of WG to leave undistorted, if `diameter` not specified. Defaults to 0. 188 | 189 | d2 : float, optional 190 | distance from top of WG to leave undistorted, if `diameter` not specified. Defaults to 0. 191 | 192 | etchDepth : float, optional 193 | For Rect. WG: specify an etch depth for regions outside the lens region. 194 | 195 | fill_index : float, optional 196 | For Rect. WG: specify refractive-index to fill etched regions with. 197 | 198 | minStepSizeFrac : float, optional 199 | Minimum integration step size. Defaults to 0.01. 200 | 201 | tolerance : float, optional 202 | Integration tolerance. Defaults to 0.01. 203 | 204 | joint_method {'complete', 'special complete', 'normal fresnel', oblique fresnel'}, optional, case insensitive 205 | What type of joint/overlap calculation method to use in between the discretized (chopped-up) taper sections. 206 | 207 | integration_order : { 0 | 1 } 208 | Zero- or first-order integration. 209 | 210 | enableEVscan : { True | False} 211 | Enable mode scanner. True by default. 212 | 213 | 214 | Methods 215 | ------- 216 | 217 | This is a partial list - see `dir(Lens)` to see all methods. 218 | Please see help on a specific function via `help(Lens)` for detailed up-to-date info on accepted arguments etc. 219 | 220 | ''' 221 | 222 | ''' 223 | TO DO 224 | 225 | Make sure the 'length' attribute is passed on to the Section - for all inputs to Section. 226 | ''' 227 | def __init__(self,wgbase, radius, **kwargs): 228 | #if len(args) >=0: 229 | #self.name=None # unused? 230 | #self.length=0.0 # unused? 231 | #self.__materialdb = None # unused? 232 | self.bend_radius = inf # inf means straight 233 | self.built=False 234 | self.autorun = True 235 | self.origin = 'pyfimm' 236 | 237 | #if len(args) == 1: 238 | self.type = 'wglens' # unused! 239 | self.radius = radius # radius of curvature of the taper 240 | self.wgbase = wgbase # waveguide object 241 | if isinstance( self.wgbase, Circ): 242 | if len(self.wgbase.layers) < 2: 243 | ErrStr = "Circ objects must have 2 or more layers to be converted into lenses." 244 | raise UserWarning(ErrStr) 245 | #elif len(args) == 2: 246 | # self.type = 'wglens' 247 | # self.wgbase = wgbase 248 | # self.length = args[1] 249 | #else: 250 | # raise ValueError('Invalid number of inputs to WGLens(). See `help(pyfimm.WGLens)`.') 251 | 252 | # find keyworded args, with defaults provided: 253 | self.lens_type = str( kwargs.pop( 'type', 'distortion') ).lower() 254 | self.side = str( kwargs.pop( 'side', 'right' ) ).lower() 255 | 256 | #self.R = kwargs.pop( 'radius', None ) 257 | #if self.R: self.R = float(self.R) 258 | 259 | self.D = kwargs.pop( 'diameter', None ) 260 | if self.D != None: self.D = float(self.D) 261 | 262 | self.d1 = kwargs.pop( 'd1', None ) 263 | if self.d1 != None: self.d1 = float(self.d1) 264 | 265 | self.d2 = kwargs.pop( 'd2', None ) 266 | if self.d2 != None: self.d2 = float(self.d2) 267 | 268 | self.minSSfrac = float( kwargs.pop( 'minStepSizeFrac', 0.01 ) ) 269 | self.tolerance = float( kwargs.pop( 'tolerance', 0.01 ) ) 270 | 271 | self.etchdepth = kwargs.pop( 'etchDepth', None ) 272 | if self.etchdepth != None: self.etchdepth = float(self.etchdepth) 273 | 274 | self.fillRIX = kwargs.pop( 'fill_index', None ) 275 | if self.fillRIX != None: self.fillRIX = float(self.fillRIX) 276 | 277 | self.joint_method = kwargs.pop( 'joint_method', None ) 278 | self.int_method = kwargs.pop( 'integration_order', None ) 279 | self.enableevscan = kwargs.pop( 'enableEVscan', True ) 280 | 281 | #self.name = str( kwargs.pop( 'name', 'WGlens' ) 282 | #overwrite = bool( kwargs.pop( 'overwrite', False ) 283 | #self._checkNodeName( 284 | #self.parent = kwargs.pop( 'parent', None ) 285 | 286 | if kwargs: 287 | '''If there are unused key-word arguments''' 288 | ErrStr = "WARNING: Lens(): Unrecognized keywords provided: {" 289 | for k in kwargs.iterkeys(): 290 | ErrStr += "'" + k + "', " 291 | ErrStr += "}. Continuing..." 292 | print ErrStr 293 | #end __init__ 294 | 295 | 296 | def __call__(self,): 297 | '''Calling a Taper object with one argument creates a Section of passed length, and returns a list containing this new Section. 298 | Usually passed directly to Device in the list of WG's as so: 299 | >>> Device( WG1(10.5) + Lens1() + WG3(10.5) ) 300 | or 301 | >>> Device( WG1(50.0) + Taper1(200.0) + WG3(75.0) ) 302 | ''' 303 | 304 | # Always call Section with 1 args 305 | out = [ Section( self, self.get_length() ) ] 306 | return out 307 | 308 | 309 | def __add__(self,other): 310 | '''If addition used, return list with this dev prepended''' 311 | return [self,other] 312 | 313 | 314 | def get_length(self): 315 | '''Return the length in Z of this lens''' 316 | # TO DO: match up this result with fimmwave's length result 317 | if isinstance( self.wgbase, Waveguide): 318 | w = self.wgbase.get_width() 319 | elif isinstance( self.wgbase, Circ): 320 | w = 2 * self.wgbase.get_radius() 321 | r = self.radius 322 | return r - r*np.sin( np.arccos(w/2/r) ) 323 | 324 | def set_diameter(self, diam): 325 | '''Set diameter, D''' 326 | self.D = diam 327 | 328 | def get_diameter(self): 329 | '''Get diameter, D''' 330 | return self.D 331 | 332 | def set_type(self, type): 333 | '''Type of Lens. 334 | 335 | Parameters 336 | ---------- 337 | type : { 'distortion', 'polish convex', 'polish concave' } 338 | Which method to create taper with. Defaults to 'distortion', which distorts the passed WG into a lens. Polish instead removes parts of the structure to create the curved surface, but all interfaces in the WG remain straight. 339 | ''' 340 | self.lens_type = type 341 | 342 | def get_type(self): 343 | '''Return the Lens type, one of: { 'distortion', 'polish convex', 'polish concave' }''' 344 | return self.lens_type 345 | 346 | def set_joint_type(self, jtype, jointoptions=None): 347 | '''Set the joint type after this waveguide, if used in a Device. 348 | type : { 'complete' | 'special complete' | 'normal fresnel' | 'oblique fresnel' }, case-insensitive 349 | synonyms for 'complete' are { 0 }, and is also the default if unset. 350 | synonyms for 'special complete' are { 3 | 'special' } 351 | synonyms for 'normal fresnel' are { 1 | 'fresnel' } 352 | synonyms for 'oblique fresnel' are { 2 } 353 | 354 | jointoptions : Dictionary{} of options. Allows for the Device.buildnode() to set various joint options, such as angle etc. Please see help(Device) for what the possible options are. 355 | ''' 356 | if isinstance(jtype, str): jtype=jtype.lower() # make lower case 357 | if jtype == 0 or jtype == 'complete': 358 | self.__jointtype = 0 359 | if jtype == 1 or jtype == 'normal fresnel' or jtype == 'fresnel': 360 | self.__jointtype = 1 361 | if jtype == 2 or jtype == 'oblique fresnel': 362 | self.__jointtype = 2 363 | if jtype == 3 or jtype == 'special complete' or jtype == 'special': 364 | self.__jointtype = 3 365 | 366 | if isinstance(jointoptions, dict): 367 | self.__jointoptions=jointoptions 368 | elif jointoptions!=None: 369 | ErrStr = "set_joint_type(): `jointoptions` should be a dictionary. See help(Device) for the available options." 370 | raise ValueError(ErrStr) 371 | #end set_joint_type() 372 | 373 | def get_joint_type(self, *args): 374 | '''get_joint_type( [asnumeric] ) 375 | Get the joint type that will be placed between this waveguide and the next, when inserted into a Device. 376 | asnumeric : { True | False }, optional 377 | A True value will cause the output to be numeric, rather than string. See help(set_joint_type) for the numerical/string correlations. False by default. 378 | (FYI, `asnumeric=True` is used in Device.buildNode() ) 379 | 380 | Examples 381 | -------- 382 | >>> Waveguide1.get_joint_type() 383 | > 'complete' 384 | 385 | >>> Waveguide1.get_joint_type( True ) 386 | > 0 387 | ''' 388 | try: 389 | self.__jointtype # see if variable exists 390 | except AttributeError: 391 | # if the variable doesn't exist yet: 392 | if DEBUG(): print "unset " + self.name + ".__jointtype --> 'complete' " 393 | self.__jointtype = 0 394 | 395 | if len(args) == 0: asnumeric = False # output as string by default 396 | if len(args) == 1: asnumeric = args[0] 397 | if len(args) > 1: raise ValueError("get_joint_type(): Too many arguments provided.") 398 | 399 | if asnumeric: 400 | out= self.__jointtype 401 | else: 402 | if self.__jointtype == 0: 403 | out= 'complete' 404 | elif self.__jointtype == 1: 405 | out= 'normal fresnel' 406 | elif self.__jointtype == 2: 407 | out= 'oblique fresnel' 408 | elif self.__jointtype == 3: 409 | out= 'special complete' 410 | #if DEBUG(): print "get_joint_type(): ", out 411 | return out 412 | #end get_joint_type() 413 | 414 | 415 | ''' 416 | ********************* 417 | **** TO DO ***** 418 | ********************* 419 | Still need to implement get/set: 420 | set_side, set_... d1, d2, etchDepth, fill_index, joint_method etc.''' 421 | 422 | 423 | ############################# 424 | #### Node Builders #### 425 | ############################# 426 | 427 | def buildNode(self): 428 | '''This does not make sense - only able to be built/inserted in a FimmProp Device.''' 429 | print "Warning: Tapers & WGLenses are only built as part of a FimmProp Device, not as stand-alone components. Nothing done for WGLens.buildNode()." 430 | 431 | 432 | def get_buildNode_str(self, nodestring): 433 | '''Return the string needed to build this node. 434 | `nodestring` should be the full FimmProp nodestring to reference the element in the Device, eg. 435 | "app.subnodes[1].subnodes[3].cdev.eltlist[5]" 436 | ''' 437 | 438 | if isinstance( self.wgbase, Waveguide ): 439 | type='rect' # these 'types' are currently unused 440 | elif isinstance( self.wgbase, Circ ): 441 | type='cyl' 442 | else: 443 | ErrStr = "Unsupported object passed for basis waveguide of Lens, with type `%s`. "%(type(self.wgbase) + "Please pass a Waveguide or Circ object.") 444 | raise ValueError(ErrStr) 445 | 446 | fpstring = "" 447 | 448 | fpstring += nodestring + ".svp.lambda=" + str( get_wavelength() ) + " \n" 449 | 450 | if self.bend_radius == 0: 451 | self.bend_radius = inf 452 | print "Warning: bend_radius changed from 0.0 --> inf (straight waveguide)" 453 | hcurv = 0 454 | elif self.bend_radius == inf: 455 | hcurv = 0 456 | else: 457 | hcurv = 1.0/self.bend_radius 458 | #hcurv = 1/self.bend_radius 459 | fpstring += nodestring + ".svp.hcurv=" + str(hcurv) + " \n" 460 | 461 | fpstring += self.wgbase.get_solver_str(nodestring, target='wglens') 462 | 463 | # which side of element should be lensed: 464 | if self.side == 'left': 465 | i = 0 466 | elif self.side == 'right': 467 | i = 1 468 | else: 469 | ErrStr = 'Invalid side for lens; please use "left" or "right" (default).' 470 | raise ValueError(ErrStr) 471 | fpstring += nodestring + ".which_end = " +str(i) + " \n" 472 | 473 | 474 | # which type of lens 475 | if self.lens_type.lower() == 'distortion': 476 | i = 0 477 | elif self.lens_type.lower() == 'polish convex': 478 | i = 1 479 | elif self.lens_type.lower() == 'polish concave': 480 | i = 2 481 | else: 482 | ErrStr = 'Invalid option for lens type; please use "distortion" (default) or "polish convex" or "polish concave".' 483 | raise ValueError(ErrStr) 484 | fpstring += nodestring + ".lens_type = " +str(i) + " \n" 485 | 486 | 487 | if self.D: 488 | fpstring += nodestring + ".D = " +str(self.D) + " \n" 489 | 490 | if self.d1: 491 | fpstring += nodestring + ".d1 = " +str(self.d1) + " \n" 492 | 493 | if self.d2: 494 | fpstring += nodestring + ".d2 = " +str(self.d2) + " \n" 495 | 496 | if self.etchdepth: 497 | fpstring += nodestring + ".etchdepth = " +str(self.etchdepth) + " \n" 498 | 499 | if self.fillRIX: 500 | fpstring += nodestring + ".fillrix = " +str(self.fillRIX) + " \n" 501 | 502 | # discretization options: 503 | fpstring += nodestring + ".minSTPfrac = " +str(self.minSSfrac) + " \n" 504 | fpstring += nodestring + ".tolerance = " +str(self.tolerance) + " \n" 505 | 506 | if self.joint_method: 507 | if self.joint_method.lower() == 'complete': 508 | fpstring += nodestring + ".joint_method = " +str(0) + " \n" 509 | elif self.joint_method.lower() == 'special complete': 510 | fpstring += nodestring + ".joint_method = " +str(3) + " \n" 511 | elif self.joint_method.lower() == 'normal fresnel': 512 | fpstring += nodestring + ".joint_method = " +str(1) + " \n" 513 | elif self.joint_method.lower() == 'oblique fresnel': 514 | fpstring += nodestring + ".joint_method = " +str(2) + " \n" 515 | else: 516 | ErrStr = "Invalid option for Taper Joint Method `%s`" %self.joint_method 517 | raise ValueError(ErrStr) 518 | 519 | if self.int_method: 520 | fpstring += nodestring + ".int_method = " +str(self.int_method) + " \n" 521 | 522 | if self.enableevscan == False: 523 | i=0 524 | else: 525 | i=1 526 | fpstring += nodestring + ".enableevscan = " +str(i) + " \n" 527 | 528 | fpstring += nodestring + ".R = " +str(self.radius) + " \n" 529 | 530 | return fpstring 531 | #end class WGLens 532 | 533 | 534 | -------------------------------------------------------------------------------- /pyfimm/__pyfimm.py: -------------------------------------------------------------------------------- 1 | ''' 2 | pyFIMM - main module 3 | 4 | See help on the main module, `help(pyFIMM)`, for usage info. 5 | 6 | In this file are the pyFIMM global parameters - set_wavelength, set_N etc. 7 | See __Classes.py for the higher-level classes, such as Project, Node, Material, Layer, Slice and Section. 8 | Waveguide, Circ and Device classes/functions are in their respective separate files. 9 | 10 | ''' 11 | 12 | '''See file __Waveguide.py for the Waveguide class & rectangular WG funcs. 13 | -- Demis 2014-12-31''' 14 | 15 | 16 | '''See file __Mode.py for the Mode class. 17 | -- Demis 2014-12-31 ''' 18 | 19 | 20 | '''See file __Device.py for the Device class. 21 | -- Demis 2014-12-31 ''' 22 | 23 | 24 | '''See file __Circ.py for Circ class & other cylindrical waveguide functions, such as Cylindrical global params (Np, Nm etc.). 25 | -- Demis 2015-01-03''' 26 | 27 | ''' See file __Tapers.py for Taper class & WG Lens class & related functions. 28 | -- Demis 2015-01-26''' 29 | 30 | 31 | #import PhotonDesignLib.pdPythonLib as pd # moved into __globals.py to eliminate circular import 32 | #fimm = pd.pdApp() 33 | 34 | #fimm.ConnectToApp() # moved into connect() 35 | 36 | 37 | 38 | from __globals import * # import global vars & FimmWave connection object `fimm` 39 | from __Classes import * # import higher-level classes 40 | 41 | #import numpy as np 42 | #import datetime as dt # for date/time strings 43 | import os.path # for path manipulation 44 | 45 | 46 | 47 | 48 | 49 | #################################################################################### 50 | # Fimmwave General Functions 51 | #################################################################################### 52 | def connect(hostname='localhost', port=5101): 53 | '''Open connection to the Fimmwave application. 54 | 55 | Parameters 56 | ---------- 57 | hostname : string, optional; address/hostname to computer (default= 'localhost') 58 | port : int, optional; port on host computer (default= 5101) 59 | 60 | calls pdPythonLib.ConnectToApp(hostname = 'localhost',portNo = 5101) 61 | ''' 62 | #in pdPythonLib: ConnectToApp(self,hostname = 'localhost',portNo = 5101) 63 | 64 | fimm.ConnectToApp(hostname=hostname, portNo=port) 65 | '''Check the connection: ''' 66 | try: 67 | NumSubnodes = int( fimm.Exec("app.numsubnodes()") ) 68 | print "Connected! (%i Project nodes found)"%NumSubnodes 69 | except: 70 | ErrStr = "Unable to connect to Fimmwave app - make sure it is running & license is active." 71 | raise IOError(ErrStr) 72 | 73 | 74 | def disconnect(): 75 | '''Terminate the connection to the FimmWave Application & delete the object.''' 76 | global pd # use this module-level variable. Dunno why the `global` declaration is only needed in THIS module function (not others!), in order to delete it... 77 | del pd # pdPythonLib does some cleanup upon del()'ing 78 | 79 | def exitfimmwave(): 80 | '''Closes the Fimmwave app''' 81 | fimm.Exec("app.exit") 82 | 83 | def Exec(string, vars=[]): 84 | '''Send a raw command to the fimmwave application. 85 | `vars` is an optional list of arguments for the command. 86 | See `help(.PhotonDesignLib.pdPythonLib.Exec)` for more info.''' 87 | out = fimm.Exec(string, vars) 88 | if isinstance(out, list): out = strip_array(out) 89 | if isinstance(out, str): out = strip_text(out) 90 | '''if fimm.Exec returned a string, FimmWave usually appends `\n\x00' to the end''' 91 | #if out[-2:] == '\n\x00': out = out[:-2] # strip off FimmWave EOL/EOF chars. 92 | return out 93 | 94 | 95 | def close_all(warn=True): 96 | '''Close all open Projects, discarding unsaved changes. 97 | 98 | Parameters 99 | ---------- 100 | warn : { True | False }, optional 101 | True by default, which will prompt user for confirmation. 102 | ''' 103 | nodestring="app" # top-level, deleting whole Projects 104 | N_nodes = int( fimm.Exec(nodestring+".numsubnodes()") ) 105 | 106 | wstr = "Will close" if warn else "Closing" 107 | 108 | WarnStr = "WARNING: %s all the following open Projects,\n\tdiscarding unsaved changes:\n"%(wstr) 109 | SNnames = [] #subnode names 110 | for i in range(N_nodes): 111 | SNnames.append( strip_txt( fimm.Exec(nodestring+r".subnodes["+str(i+1)+"].nodename()") ) ) 112 | WarnStr = WarnStr + "\t%s\n"%(SNnames[-1]) 113 | 114 | print WarnStr 115 | 116 | if warn: 117 | # get user confirmation: 118 | cont = raw_input("Are you sure? [y/N]: ").strip().lower() 119 | else: 120 | cont = 'y' 121 | 122 | if cont == 'y': 123 | fString = '' 124 | for i in range(N_nodes): 125 | fString += nodestring + ".subnodes[1].close()\n" 126 | fimm.Exec( fString ) 127 | else: 128 | print "close_all(): Cancelled." 129 | #end close_all() 130 | 131 | 132 | 133 | 134 | #################################### 135 | # Fimmwave Global Parameters #### 136 | #################################### 137 | 138 | def set_working_directory(wdir): 139 | '''Set FimmWave working directory. Usually set to same dir as your Python script in order to find FimmWave output files.''' 140 | #if DEBUG(): print "set_working_directory(): sending setwdir() command:" 141 | fimm.Exec("app.setwdir("+str(wdir)+")") 142 | #if DEBUG(): print "set_working_directory(): finished setwdir()." 143 | 144 | def get_working_directory(): 145 | '''Get fimmwave working directory, as string.''' 146 | print "Warning: wdir string may not be in standard format." 147 | return fimm.Exec("app.wdir")[:-2] # strip off the last two EOF characters 148 | 149 | def set_wavelength(lam0): 150 | '''Set the simulated optical wavelength (microns).''' 151 | fimm.Exec("app.defaultlambda = {"+str(lam0)+"}") 152 | 153 | def get_wavelength(): 154 | '''Return the simulation's optical wavelength (microns).''' 155 | return fimm.Exec("app.defaultlambda") 156 | 157 | def wavelength(): 158 | '''Backwards compatibility only. 159 | Return the simulation's optical wavelength (microns).''' 160 | print "DeprecationWarning: Use get_wavelength() instead." 161 | return get_wavelength() 162 | 163 | def set_material_database(path): 164 | '''Set the path to the material database (*.mat) file. Only needed if you are defining materials using this database ('mat'/material type waveguides instead of 'rix'/refractive index). This sets a global materials file that will be used in every waveguide and device that is built. 165 | Although waveguide nodes can specify their own (different) materials files, it is recommended that a global file be used instead since FimmProp Devices do not accept multiple materials files (to avoid confusion and identically-named materials from different files). The single global file can be set to `include` any other materials files. 166 | 167 | Parameters 168 | ---------- 169 | path : string 170 | Absolute or relative path to the material database file. `path` will be automatically converted to an absolute path, as a workaround to a FimmProp Device Node bug that causes it to only accept absolute paths. 171 | ''' 172 | global global_matDB 173 | import os 174 | path = os.path.abspath(path) # convert to absolute path 175 | if os.path.isfile(path): 176 | global_matDB = str(path) 177 | else: 178 | ErrStr = "Material database file does not exist at the specified path `%s`" %(path) 179 | raise IOError(ErrStr) 180 | if DEBUG(): print "matDB = ", global_matDB 181 | 182 | def get_material_database(): 183 | '''Get path to global material database file. 184 | 185 | Returns 186 | ------- 187 | path : string 188 | Absolute path to the material database file that will be used when building nodes. 189 | ''' 190 | global global_matDB 191 | try: 192 | global_matDB 193 | except: 194 | if DEBUG(): print "unset global_matDB --> None" 195 | global_matDB = None 196 | return global_matDB 197 | 198 | 199 | 200 | 201 | ############################################ 202 | #### Mode Solver Parameters #### 203 | ############################################ 204 | 205 | def set_eval_type(eval_type): 206 | '''FIMMWAVE will label modes by the effective index (n_eff) or propagation constant (beta). 207 | 208 | Parameters 209 | ---------- 210 | eval_type : { 'n_eff' | 'beta' }, case insensitive 211 | Equivalent strings for 'n_eff': 'neff', 'effective index' 212 | Equivalent strings for 'beta': 'propagation constant' 213 | 214 | Examples 215 | -------- 216 | >>> set_eval_type("n_eff") 217 | ''' 218 | if eval_type.lower() == 'n_eff' or eval_type.lower() == 'neff' or eval_type.lower() == 'effective index': 219 | fimm.Exec("app.evaltype = 1") 220 | elif eval_type.lower() == 'beta' or eval_type.lower() == 'propagation constant': 221 | fimm.Exec("app.evaltype = 0") 222 | else: 223 | raise ValueError('invalid input for eval_type') 224 | 225 | def get_eval_type(): 226 | '''Return the string "n_eff" or "beta" corresponding to the FimmWave mode labelling scheme. See also set_eval_type()''' 227 | eval_type = fimm.Exec("app.evaltype") 228 | if eval_type == 1: 229 | return 'n_eff' 230 | elif eval_type == 0: 231 | return 'beta' 232 | else: 233 | return '' 234 | 235 | def eval_type(): 236 | '''Backwards compatibility only. 237 | Use get_eval_type() instead.''' 238 | print "eval_type(): DeprecationWarning: Use get_eval_type() instead." 239 | return get_eval_type() 240 | 241 | 242 | def set_mode_finder_type(mode_finder_type): 243 | '''options: "stable" or "fast", passed as string.''' 244 | if mode_finder_type.lower() == 'stable': 245 | fimm.Exec("app.homer_opt = 1") 246 | elif mode_finder_type.lower() == 'fast': 247 | fimm.Exec("app.homer_opt = 0") 248 | else: 249 | print 'invalid input for mode_finder_type' 250 | 251 | def get_mode_finder_type(): 252 | '''returns: "stable" or "fast" as string. 253 | Corresponds to the fimmwave parameter: app.homer_opt 254 | ''' 255 | mode_finder_type = fimm.Exec("app.homer_opt") 256 | if mode_finder_type == 1: 257 | return 'stable' 258 | elif mode_finder_type == 0: 259 | return 'fast' 260 | else: 261 | return '' 262 | 263 | def mode_finder_type(): 264 | '''Backwards compatibility only. Should Instead get_***().''' 265 | print "Deprecation Warning: mode_finder_type(): Use get_mode_finder_type() instead." 266 | return get_mode_finder_type() 267 | 268 | 269 | def set_solver_speed(string): 270 | '''options: 'best' (default) or 'fast' 271 | used to set the fimmwave param: 272 | >>> NodeStr.evlist.mpl.speed = ''' 273 | global global_solver_speed 274 | if string.lower() == 'best': 275 | global_solver_speed = 0 276 | elif string.lower() == 'fast': 277 | global_solver_speed = 1 278 | else: 279 | print 'invalid input for mode_finder_type' 280 | 281 | def get_solver_speed(): 282 | '''Returns 'best' or 'fast' as string. 283 | Defaults to 'best', if unset. ''' 284 | global global_solver_speed 285 | try: 286 | global_solver_speed 287 | except NameError: 288 | global_solver_speed = 0 # default value if unset 289 | 290 | if global_solver_speed==0: 291 | return 'best' 292 | elif global_solver_speed==1: 293 | return 'fast' 294 | return global_solver_speed 295 | 296 | 297 | 298 | 299 | def set_mode_solver(solver): 300 | '''Set the mode solver. Takes few words as string. 301 | 302 | Parameters 303 | ---------- 304 | solver : string, case insensitive 305 | 306 | For rectangular waveguides, use a combination of following to create the three-keyword string: 307 | "vectorial/semivecTE/semivecTM FDM/FMM real/complex" 308 | FDM = Finite Difference Method 309 | FMM = Field Mode Matching method 310 | Both of these solvers take all permutations of vectoriality & real/complex. 311 | eg. "semivecTE FMM complex" or "vectorial FDM real" 312 | 313 | For Cylindrical Waveguides, use any of these options: 314 | "vectorial/semivecTE/semivecTM FDM/GFS/Gaussian/SMF real/complex" 315 | where the FDM solver is always "vectorial", and real/complex is only applicable to the FDM solver. GFS takes 'vectorial' or 'scalar' but not 'semivec'. Inapplicable keywords will raise an error in FimmWave. 316 | FDM = Finite-Difference Method 317 | GFS = General Fiber Solver 318 | Gaussian = Gaussian Mode Fiber solver (unsupported) 319 | SMF = Single-Mode Fiber 320 | 321 | For Cylindrical Waveguides, here are all the possible options: 322 | Finite-Difference Method solver: "vectorial FDM real" , "vectorial FDM complex", 323 | General Fiber Solver: "vectorial GFS real" , "scalar GFS real", 324 | Single-Mode Fiber solver: "Vectorial SMF" , "SemivecTE SMF" , "SemivecTM SMF", 325 | Gaussian Fiber Solver (unsupported): "Vectorial Gaussian" , "SemivecTE Gaussian" , "SemivecTM Gaussian". 326 | ''' 327 | global global_mode_solver 328 | parts = solver.split() 329 | if len(parts) > 3 or len(parts)==0: raise ValueError( "Expected string separated by spaces, with max 3 words.\n`slvr`="+str( solver ) ) 330 | 331 | #### should do a RegEx to parse the mode solver params, so order or terms is arbitrary 332 | # Find the mode solver type first? 333 | # Only set the parts needed - eg. if only called set_modesolver('SemivecTE') should still use default modesolver, but only change to TE. 334 | 335 | global_mode_solver = solver 336 | 337 | def get_mode_solver(): 338 | '''Return mode solver as string. 339 | 340 | Returns 341 | ------- 342 | mode_solver : string 343 | String representation of the mode solver to use. Returns `None` if unset, and default modesolver for each waveguide type will be used. 344 | See set_mode_solver() for available parameters. 345 | Returns if unset. 346 | ''' 347 | global global_mode_solver 348 | try: 349 | global_mode_solver 350 | except NameError: 351 | global_mode_solver = None 352 | return global_mode_solver 353 | 354 | def mode_solver(): 355 | '''Backwards compatibility only. Should Instead get_***().''' 356 | print "Deprecation Warning: mode_solver(): Use get_mode_solver() instead." 357 | return get_mode_solver() 358 | 359 | 360 | def set_NX(mnx): 361 | '''Set # of horizontal grid points. 362 | 363 | Parameters 364 | ---------- 365 | mnx : int 366 | Number of horizontal grid points in mode representation/solver (depending on solver). Defaults to 60. 367 | ''' 368 | global global_NX 369 | global_NX = mnx 370 | 371 | def get_NX(): 372 | '''Return # of horizontal grid points. Defaults to 60.''' 373 | global global_NX 374 | try: 375 | global_NX 376 | except NameError: 377 | global_NX = 60 378 | return global_NX 379 | 380 | def NX(): 381 | '''Backwards compatibility only. Should Instead use get_NX().''' 382 | print "Deprecation Warning: NX(): Use get_NX() instead." 383 | return get_NX() 384 | 385 | 386 | def set_NY(mny): 387 | '''Set # of vertical grid points 388 | 389 | Parameters 390 | ---------- 391 | mny : int 392 | Number of horizontal grid points in mode representation/solver (depending on solver). Defaults to 60.''' 393 | global global_NY 394 | global_NY = mny 395 | 396 | def get_NY(): 397 | '''Return # of vertical grid points. Defaults to 60.''' 398 | global global_NY 399 | try: 400 | global_NY 401 | except NameError: 402 | global_NY = 60 403 | return global_NY 404 | 405 | def NY(): 406 | '''Backwards compatibility only. Should Instead use get_NY().''' 407 | print "Deprecation Warning: NY(): Use get_NY() instead." 408 | return get_NY() 409 | 410 | 411 | def set_N(mn): 412 | '''Set # of modes to solve for. 413 | For cylindrical waveguides, this sets the number of Axial Quantum Number modes to solve for. set_Np() chooses the polarization modes. 414 | 415 | Parameters 416 | ---------- 417 | mn : int >=1 418 | Number of modes to solve for. Defaults to 10.''' 419 | global global_N 420 | global_N = mn 421 | 422 | def get_N(): 423 | '''Return # of modes to solve for. 424 | Defaults to 10 if unset.''' 425 | global global_N 426 | try: 427 | global_N 428 | except NameError: 429 | global_N = 10 430 | return global_N 431 | 432 | def N(): 433 | '''Backwards compatibility only. Should Instead use get_***().''' 434 | print "Deprecation Warning: N(): Use get_N() instead." 435 | return get_N() 436 | 437 | 438 | def set_vertical_symmetry(symmtry): 439 | global global_vertical_symmetry 440 | global_vertical_symmetry = symmtry 441 | 442 | def get_vertical_symmetry(): 443 | global global_vertical_symmetry 444 | try: 445 | global_vertical_symmetry 446 | except NameError: 447 | global_vertical_symmetry = None 448 | return global_vertical_symmetry 449 | 450 | def vertical_symmetry(): 451 | '''Backwards compatibility only. Should Instead use get_***().''' 452 | print "Deprecation Warning: vertical_symmetry(): Use get_vertical_symmetry() instead." 453 | return get_vertical_symmetry() 454 | 455 | 456 | def set_horizontal_symmetry(symmtry): 457 | global global_horizontal_symmetry 458 | global_horizontal_symmetry = symmtry 459 | 460 | def get_horizontal_symmetry(): 461 | global global_horizontal_symmetry 462 | try: 463 | global_horizontal_symmetry 464 | except NameError: 465 | global_horizontal_symmetry = None 466 | return global_horizontal_symmetry 467 | 468 | def horizontal_symmetry(): 469 | '''Backwards compatibility only. Should Instead use get_***().''' 470 | print "Deprecation Warning: horizontal_symmetry(): Use get_horizontal_symmetry() instead." 471 | return get_horizontal_symmetry() 472 | 473 | 474 | def set_min_TE_frac(mintefrac): 475 | '''Set minimum TE fraction to constrain mode solver to a particular polarization.''' 476 | global global_min_TE_frac 477 | global_min_TE_frac = mintefrac 478 | 479 | def get_min_TE_frac(): 480 | '''Return minimum TE fraction. Defaults to 0.''' 481 | global global_min_TE_frac 482 | try: 483 | global_min_TE_frac 484 | except NameError: 485 | global_min_TE_frac = 0 486 | return global_min_TE_frac 487 | 488 | def min_TE_frac(): 489 | '''Backwards compatibility only. Should Instead use get_***().''' 490 | print "Deprecation Warning: min_TE_frac(): Use get_min_TE_frac() instead." 491 | return get_min_TE_frac() 492 | 493 | 494 | def set_max_TE_frac(maxtefrac): 495 | '''Set maximum TE fraction to constrain mode solver to a particular polarization.''' 496 | global global_max_TE_frac 497 | global_max_TE_frac = maxtefrac 498 | 499 | def get_max_TE_frac(): 500 | '''Return maximum TE fraction.''' 501 | global global_max_TE_frac 502 | try: 503 | global_max_TE_frac 504 | except NameError: 505 | global_max_TE_frac = 100 506 | return global_max_TE_frac 507 | 508 | def max_TE_frac(): 509 | '''Backwards compatibility only. Should Instead use get_***().''' 510 | print "Deprecation Warning: max_TE_frac(): Use get_max_TE_frac() instead." 511 | return get_max_TE_frac() 512 | 513 | 514 | def set_min_EV(min_ev): 515 | global global_min_ev 516 | global_min_ev = min_ev 517 | 518 | def get_min_EV(): 519 | global global_min_ev 520 | try: 521 | global_min_ev 522 | except NameError: 523 | global_min_ev = None 524 | return global_min_ev 525 | 526 | def min_EV(): 527 | '''Backwards compatibility only. Should Instead use get_***().''' 528 | print "Deprecation Warning: min_EV(): Use get_min_EV() instead." 529 | return get_min_EV() 530 | 531 | 532 | def set_max_EV(max_ev): 533 | global global_max_ev 534 | global_max_ev = max_ev 535 | 536 | def get_max_EV(): 537 | global global_max_ev 538 | try: 539 | global_max_ev 540 | except NameError: 541 | global_max_ev = None 542 | return global_max_ev 543 | 544 | def max_EV(): 545 | '''Backwards compatibility only. Should Instead use get_***().''' 546 | print "Deprecation Warning: max_EV(): Use get_max_EV() instead." 547 | return get_max_EV() 548 | 549 | 550 | def set_RIX_tol(rixTol): 551 | global global_rix_tol 552 | global_rix_tol = rixTol 553 | 554 | def get_RIX_tol(): 555 | global global_rix_tol 556 | try: 557 | global_rix_tol 558 | except NameError: 559 | global_rix_tol = None 560 | return global_rix_tol 561 | 562 | def RIX_tol(): 563 | '''Backwards compatibility only. Should Instead use get_***().''' 564 | print "Deprecation Warning: RIX_tol(): Use get_RIX_tol() instead." 565 | return get_RIX_tol() 566 | 567 | 568 | def set_N_1d(n1d): 569 | '''# of 1D modes found in each slice (FMM solver only)''' 570 | global global_n1d 571 | global_n1d = n1d 572 | 573 | def get_N_1d(): 574 | '''Return # of 1D modes found in each slice (FMM solver only)''' 575 | global global_n1d 576 | try: 577 | global_n1d 578 | except NameError: 579 | global_n1d = None 580 | return global_n1d 581 | 582 | def N_1d(): 583 | '''Backwards compatibility only. Should Instead use get_***().''' 584 | print "Deprecation Warning: N_1d(): Use get_N_1d() instead." 585 | return get_N_1d() 586 | 587 | 588 | def set_mmatch(match): 589 | ''' 590 | Parameters 591 | ---------- 592 | match : float 593 | 594 | See Fimmwave Manual section 5.4.12. 595 | If mmatch is set to zero then it will be chosen automatically. 596 | If mmatch is set to e.g. 3.5 then the interface will be set in the center of the third slice from the left. 597 | ''' 598 | global global_mmatch 599 | global_mmatch = match 600 | 601 | def get_mmatch(): 602 | '''Return mmatch - see set_mmatch() for more info.''' 603 | global global_mmatch 604 | try: 605 | global_mmatch 606 | except NameError: 607 | global_mmatch = None 608 | return global_mmatch 609 | 610 | def mmatch(): 611 | '''Backwards compatibility only. Should Instead use get_***().''' 612 | print "Deprecation Warning: mmatch(): Use get_mmatch() instead." 613 | return get_mmatch() 614 | 615 | 616 | 617 | def set_temperature(temp): 618 | ''' 619 | Parameters 620 | ---------- 621 | temp : float 622 | 623 | Set global temperature in degrees Celsius. Eventually, will be able to set temperature per-Waveguide to override this. If unset, the temperature is left to the FimmWave default. 624 | ''' 625 | print "WARNING: set_temperature(): Not implemented yet! Does not currently set the temperature in FimmWave nodes." 626 | global global_temperature 627 | global_temperature = temp 628 | 629 | def get_temperature(): 630 | '''Return global temperature in degrees Celsius. Returns if unset.''' 631 | global global_temperature 632 | try: 633 | global_temperature 634 | except NameError: 635 | global_temperature = None 636 | return global_temperature 637 | #end get_temperature 638 | 639 | 640 | def get_amf_data(modestring, filename="temp", precision=r"%10.6f", maxbytes=500): 641 | '''Return the various mode profile data from writing an AMF file. 642 | This returns data for all field components of a mode profile, the start/end x/y values in microns, number of data points along each axis and some other useful info. 643 | The AMF file and accompanying temporary files will be saved into the directory designated by the variable `AMF_Folder_Str()`, which is typically something like "pyFIMM_temp/". 644 | Temporary files are created in order to extract the commented lines. 645 | This function currently does NOT return the field vlaues, as they are much more efficiently acquired by the FimMWave functions get_field() 646 | 647 | Parameters 648 | ---------- 649 | modestring : str 650 | The entire FimmWave string required to produce the amf file, omitting the ".writeamf(...)" function itself, typically a reference to the individual mode to be output. An example would be: 651 | app.subnodes[7].subnodes[1].evlist.list[1].profile.data 652 | 653 | filename : str, optional 654 | Desired filename for the AMF-file & output. 655 | 656 | precision : str, optional 657 | String passed to the FimmWave function `writeamf()` to determine output precision of field values, as a standard C-style format string. Defaults to "%10.6f", specifying a floating point number with minimum 10 digits and 6 decimal points. 658 | 659 | maxbytes : int, optional 660 | How many bytes to read from the AMF file. This prevents reading all the field data, and speeds up execution/memory usage. Defaults to 500 bytes, which typically captures the whole AMF file header info. 661 | 662 | 663 | Returns 664 | ------- 665 | A dictionary is returned containing each value found in the AMF file header. 666 | {'beta': (5.980669+0j), # Beta (propagation constant), as complex value 667 | 'hasEX': True, # does the AMF file contain field values for these components? 668 | 'hasEY': True, 669 | 'hasEZ': True, 670 | 'hasHX': True, 671 | 'hasHY': True, 672 | 'hasHZ': True, 673 | 'isWGmode': True, # is this a waveguide mode? 674 | 'iscomplex': False, # are the field values (and Beta) complex? 675 | 'lambda': 1.55, # wavelength 676 | 'nx': 100, # Number of datapoints in the x/y directions 677 | 'ny': 100, 678 | 'xmax': 14.8, # x/y profile extents, in microns 679 | 'xmin': 0.0, 680 | 'ymax': 12.1, 681 | 'ymin': 0.0} 682 | 683 | Examples 684 | -------- 685 | >>> ns = "app.subnodes[7].subnodes[1].evlist.list[1].profile.data" 686 | >>> fs = "pyFIMM_temp\mode1_pyFIMM.amf" 687 | >>> data = pf.get_amf_data(ns, fs) 688 | 689 | ''' 690 | 691 | 692 | 693 | ''' 694 | 100 100 //nxseg nyseg 695 | 0.000000 14.800000 0.000000 12.100000 //xmin xmax ymin ymax 696 | 1 1 1 1 1 1 //hasEX hasEY hasEZ hasHX hasHY hasHZ 697 | 6.761841 0.000000 //beta 698 | 1.550000 //lambda 699 | 0 //iscomplex 700 | 1 //isWGmode 701 | ''' 702 | import re # RegEx module 703 | 704 | # write an AMF file with all the field components. 705 | if not filename.endswith(".amf"): filename += ".amf" # name of the files 706 | 707 | # SubFolder to hold temp files: 708 | if not os.path.isdir(str( AMF_FolderStr() )): 709 | os.mkdir(str( AMF_FolderStr() )) # Create the new folder if needed 710 | mode_FileStr = os.path.join( AMF_FolderStr(), filename ) 711 | 712 | if DEBUG(): print "Mode.plot(): " + modestring + ".writeamf("+mode_FileStr+",%s)"%precision 713 | fimm.Exec(modestring + ".writeamf("+mode_FileStr+",%s)"%precision) 714 | 715 | ## AMF File Clean-up 716 | #import os.path, sys # moved to the top 717 | fin = open(mode_FileStr, "r") 718 | if not fin: raise IOError("Could not open '"+ mode_FileStr + "' in " + sys.path[0] + ", Type: " + str(fin)) 719 | #data_list = fin.readlines() # put each line into a list element 720 | data_str = fin.read( maxbytes ) # read file as string, up to maxbytes. 721 | fin.close() 722 | 723 | out = {} # the data to return, as dictionary 724 | 725 | ''' Grab the data from the header lines ''' 726 | # how much of the data to search (headers only): 727 | s = [0, 2000] # just in case the entire file gets read in later, to grab field data 728 | # should disable this once we know we don't need the AMF field data 729 | 730 | # Set regex pattern to match: 731 | ''' 100 100 //nxseg nyseg''' 732 | pat = re.compile( r'\s*(\d+)\s*(\d+)\s*//nxseg nyseg' ) 733 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 734 | # m will contain any 'groups' () defined in the RegEx pattern. 735 | if m: 736 | print 'segment counts found:', m.groups() #groups() prints all captured groups 737 | nx = int( m.group(1) ) # grab 1st group from RegEx & convert to int 738 | ny = int( m.group(2) ) 739 | print '(nx, ny) --> ', nx, ny 740 | out['nx'],out['ny'] = nx, ny 741 | 742 | # Set regex pattern to match: 743 | ''' 0.000000 14.800000 0.000000 12.100000 //xmin xmax ymin ymax''' 744 | pat = re.compile( r'\s*(\d+\.?\d*)\s*(\d+\.?\d*)\s*(\d+\.?\d*)\s*(\d+\.?\d*)\s*//xmin xmax ymin ymax' ) 745 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 746 | # m will contain any 'groups' () defined in the RegEx pattern. 747 | if m: 748 | print 'window extents found:',m.groups() #groups() prints all captured groups 749 | xmin = float( m.group(1) ) # grab 1st group from RegEx & convert to int 750 | xmax = float( m.group(2) ) 751 | ymin = float( m.group(3) ) 752 | ymax = float( m.group(4) ) 753 | print '(xmin, xmax, ymin, ymax) --> ', xmin, xmax, ymin, ymax 754 | out['xmin'],out['xmax'],out['ymin'],out['ymax'] = xmin, xmax, ymin, ymax 755 | 756 | # Set regex pattern to match: 757 | ''' 1 1 1 1 1 1 //hasEX hasEY hasEZ hasHX hasHY hasHZ''' 758 | pat = re.compile( r'\s*(\d)\s*(\d)\s*(\d)\s*(\d)\s*(\d)\s*(\d)\s*//hasEX hasEY hasEZ hasHX hasHY hasHZ' ) 759 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 760 | # m will contain any 'groups' () defined in the RegEx pattern. 761 | if m: 762 | print 'components found:',m.groups() #groups() prints all captured groups 763 | hasEX = bool( int(m.group(1)) ) # grab 1st group from RegEx & convert to int 764 | hasEY = bool( int(m.group(2)) ) 765 | hasEZ = bool( int(m.group(3)) ) 766 | hasHX = bool( int(m.group(4)) ) 767 | hasHY = bool( int(m.group(5)) ) 768 | hasHZ = bool( int(m.group(6)) ) 769 | print '(hasEX, hasEY, hasEZ, hasHX, hasHY, hasHZ) --> ', hasEX, hasEY, hasEZ, hasHX, hasHY, hasHZ 770 | out['hasEX'],out['hasEY'],out['hasEZ'],out['hasHX'],out['hasHY'],out['hasHZ'] \ 771 | = hasEX, hasEY, hasEZ, hasHX, hasHY, hasHZ 772 | 773 | # Set regex pattern to match: 774 | ''' 6.761841 0.000000 //beta''' 775 | pat = re.compile( r'\s*(\d+\.?\d*)\s*(\d+\.?\d*)\s*//beta' ) 776 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 777 | # m will contain any 'groups' () defined in the RegEx pattern. 778 | if m: 779 | print 'beta found:',m.groups() #groups() prints all captured groups 780 | beta_r = float( m.group(1) ) # grab 1st group from RegEx & convert to int 781 | beta_i = float( m.group(2) ) 782 | beta = beta_r + beta_i*1j 783 | print 'beta --> ', beta 784 | out['beta'] = beta 785 | 786 | # Set regex pattern to match: 787 | ''' 1.550000 //lambda''' 788 | pat = re.compile( r'\s*(\d+\.?\d*)\s*//lambda' ) 789 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 790 | # m will contain any 'groups' () defined in the RegEx pattern. 791 | if m: 792 | print 'lambda found:',m.groups() #groups() prints all captured groups 793 | lam = float( m.group(1) ) # grab 1st group from RegEx & convert to int 794 | print 'lambda --> ', lam 795 | out['lambda'] = lam 796 | 797 | 798 | # Set regex pattern to match: 799 | ''' 0 //iscomplex''' 800 | pat = re.compile( r'\s*(\d)\s*//iscomplex' ) 801 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 802 | # m will contain any 'groups' () defined in the RegEx pattern. 803 | if m: 804 | print 'iscomplex found:',m.groups() #groups() prints all captured groups 805 | iscomplex = bool( int(m.group(1)) ) # grab 1st group from RegEx & convert to int 806 | print 'iscomplex --> ', iscomplex 807 | out['iscomplex'] = iscomplex 808 | 809 | # Set regex pattern to match: 810 | ''' 1 //isWGmode''' 811 | pat = re.compile( r'\s*(\d)\s*//isWGmode' ) 812 | m = pat.search( data_str[s[0]:s[1]] ) # perform the search 813 | # m will contain any 'groups' () defined in the RegEx pattern. 814 | if m: 815 | print 'isWGmode found:',m.groups() #groups() prints all captured groups 816 | isWGmode = bool( int(m.group(1)) ) # grab 1st group from RegEx & convert to int 817 | print 'isWGmode --> ', isWGmode 818 | out['isWGmode'] = isWGmode 819 | 820 | 821 | 822 | 823 | 824 | return out 825 | 826 | """ 827 | # Delete File Header 828 | nxy_data = data_list[1] 829 | xy_data = data_list[2] 830 | slvr_data = data_list[6] 831 | del data_list[0:9] 832 | 833 | # strip the comment lines from the nxy file: 834 | nxyFile = os.path.join( AMF_FolderStr(), "mode" + str(num) + "_pyFIMM_nxy.txt") 835 | fout = open(nxyFile, "w") 836 | fout.writelines(nxy_data) 837 | fout.close() 838 | nxy = pl.loadtxt(nxyFile, comments='//') 839 | nx = int(nxy[0]) 840 | ny = int(nxy[1]) 841 | 842 | xyFile = os.path.join( AMF_FolderStr(), "mode" + str(num) + "_pyFIMM_xy.txt") 843 | fout = open(xyFile, "w") 844 | fout.writelines(xy_data) 845 | fout.close() 846 | xy = pl.loadtxt(xyFile, comments='//') 847 | 848 | slvrFile = os.path.join( AMF_FolderStr(), "mode" + str(num) + "_pyFIMM_slvr.txt") 849 | fout = open(slvrFile, "w") 850 | fout.writelines(slvr_data) 851 | fout.close() 852 | iscomplex = pl.loadtxt(slvrFile, comments='//') 853 | 854 | # Find Field Component 855 | if field_cpt_in == None: 856 | '''If unspecified, use the component with higher field frac.''' 857 | tepercent = fimm.Exec(self.modeString + "list[{" + str(num) + "}].modedata.tefrac") 858 | if tepercent > 50: 859 | field_cpt = 'Ex'.lower() 860 | else: 861 | field_cpt = 'Ey'.lower() 862 | #end if(field_cpt_in) 863 | 864 | if field_cpt == 'Ex'.lower(): 865 | data = data_list[1:nx+2] 866 | elif field_cpt == 'Ey'.lower(): 867 | data = data_list[(nx+2)+1:2*(nx+2)] 868 | elif field_cpt == 'Ez'.lower(): 869 | data = data_list[2*(nx+2)+1:3*(nx+2)] 870 | elif field_cpt == 'Hx'.lower(): 871 | data = data_list[3*(nx+2)+1:4*(nx+2)] 872 | elif field_cpt == 'Hy'.lower(): 873 | data = data_list[4*(nx+2)+1:5*(nx+2)] 874 | elif field_cpt == 'Hz'.lower(): 875 | data = data_list[5*(nx+2)+1:6*(nx+2)] 876 | else: 877 | ErrStr = 'Invalid Field component requested: ' + str(field_cpt) 878 | raise ValueError(ErrStr) 879 | 880 | del data_list 881 | 882 | # Resave Files 883 | fout = open(mode_FileStr+"_"+field_cpt.strip().lower(), "w") 884 | fout.writelines(data) 885 | fout.close() 886 | 887 | # Get Data 888 | if iscomplex == 1: 889 | field_real = pl.loadtxt(mode_FileStr, usecols=tuple([i for i in range(0,2*ny+1) if i%2==0])) 890 | field_imag = pl.loadtxt(mode_FileStr, usecols=tuple([i for i in range(0,2*ny+2) if i%2!=0])) 891 | else: 892 | field_real = pl.loadtxt(mode_FileStr) 893 | """ 894 | 895 | 896 | #end get_amf_data() -------------------------------------------------------------------------------- /pyfimm/__Cavity.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | Cavity Calculation functions 4 | Demis D. John, 2015, Praevium Research Inc. 5 | Based on Peter Beinstman's CAMFR package's `Cavity` class, 6 | and Vincent Brulis' CavityModeCalc.py example script. 7 | 8 | 9 | ''' 10 | 11 | from __globals import * # import global vars & FimmWave connection object 12 | # DEBUG() variable is also set in __globals 13 | 14 | import numpy as np 15 | import math 16 | 17 | from __pyfimm import get_N, set_wavelength # get number of calculated modes 18 | from __CavityMode import * # import the CavityMode class, for `Cavity.mode(0)...` 19 | 20 | class Cavity(object): 21 | '''Cavity class, for calculating cavity modes & fields. 22 | 23 | Construct as so: 24 | cav = .Cavity( LHS_Dev, RHS_Dev ) 25 | 26 | Parameters 27 | ---------- 28 | LHS_Dev : Device object 29 | Device representing the left-hand side of the cavity. 30 | 31 | RHS_Dev : Device object 32 | Device representing the right-hand side of the cavity. 33 | 34 | IMPORTANT NOTE: Wherever you choose to split the cavity (arbitrary), the waveguide cross-section on either side of the split must be the same. For example, for whichever waveguide is near the desired splitting point, cut that waveguide in half, with half in the LHS_Dev & half in the RHS_Dev, so that the waveguide cross section on either side of the split is the same. 35 | This is so that the modal basis set of each half of the cavity will be the same - ie. the eigenvectors calculated will be with respect to the modes of these central waveguides, and if each side's central waveguide had different modes (because they were different waveguide geometries), the eigenvector would not represent the same superposition of modes into each RHS & LHS device. 36 | 37 | 38 | Attributes 39 | ---------- 40 | LHS_Dev : Device Object 41 | Left Side Device. Should already have been built via LHS_Dev.buildNode() in FimmWave. 42 | 43 | RHS_Dev : Device Object 44 | Right Side Device. Should already have been built via RHS_Dev.buildNode() in FimmWave. 45 | 46 | 47 | Methods 48 | ------- 49 | 50 | This is a partial list - see `dir(CavityObj)` to see all methods. 51 | Please see help on a specific function via `help(Cavity.theFunc)` for detailed up-to-date info on accepted arguments etc. 52 | 53 | calc( WLs , Display=False) 54 | Calculate the eigenmodes of the cavity at each wavelength. Based on Vincent Brulis' script from the PhotonDesign example "Modelling a passive optical cavity (VCSEL, DFB)". 55 | WLs : array-like 56 | List of wavelengths at which to calculate the cavity modes. This determines the wavelength-accuracy of the resonance wavelengths found - you will have to choose the wavelengths at which to calculate the modes. 57 | See help(Cavity.calc) for more info. 58 | 59 | plot() - DEPRECATED 60 | Plot the Eigenvalues versus Wavelength for all modes. 61 | This function has been deprecated - use `Cavity.mode('all').plot()` to do the same thing. 62 | 63 | 64 | 65 | After CavityObj.calc() has been performed, more attributes are available: 66 | 67 | Attributes 68 | ---------- 69 | 70 | wavelengths : numpy array 71 | The wavelengths at which cavity eigenvalues were calculated. 72 | 73 | eigenvalues, eigenvectors : numpy arrays 74 | The eigenvalues & eigenvectors at each wavelength. There will be N eigenvalues at each wavelength, corresponding to each lateral optical mode of the central Waveguide making up the Devices (the WG at the split). 75 | The eigenvalues are the (complex) magnitude & phase that would be applied to a field after a roundtrip in the cavity. Thus a negative magnitude means the field decays each roundtrip (radiation loss or something), and a Zero-phase means the field is in-phase with itself (resonant) and can constructively interfere with itself after a round-trip. 76 | The eigenvectors are the magnitudes/coefficients of each mode in the basis set (the modes of the central-section WG) to get the above eigenvalues. You would launch the central-section modes at these magnitudes/phases to produce the optical fields corresponding to the eigenvalue (to get that round-trip amplitude & phase). 77 | For eigenvalues & eigenvectors, indexing is like so: 78 | >>> eigenvalues[Iwavelength][Imodenum] 79 | Where `wavelengths[Iwavelength]` tells you which wavelength you're inspecting, and `Imodenum` tells you which mode number you're inspecting. 80 | 81 | resWLs , resEigVals, resEigVects : list of complex floats 82 | The Resonance wavelengths and corresponding EigenValues & EigenVectors (complex numbers). 83 | Each list index corresponds to a cavity mode with unique lateral mode-profile, and there may be multiple resonances found for each mode. If no resonances were located, `None` is entered into the list for that mode. 84 | Indexing is similar to `eigenvalues` & `eigenvectors` 85 | 86 | pseudo-attributes: 87 | mode(N) : select one or more cavity modes to extract data for, or pass the string 'all' to work with all modes. This actually (invisibly to the user) returns a `CavityMode` object, which can perform other actions on the selected mode. 88 | See `help(CavityObj.mode('all')` or`help(CavityMode)` for more info on the usage & attributes/methods available. 89 | 90 | 91 | Examples 92 | -------- 93 | 94 | Make the left & right hand side devices, with 20 periods of repeating waveguides. Note that the last waveguide in LHS is the same as the first waveguide in RHS. Location of the split and thickness on either side is arbitrary. 95 | >>> LHS = .Device( 20*( WG2(0.275) + WG3(0.125) ) + WG1(0.05) ) 96 | >>> RHS = .Device( WG1(0.05) + 20*( WG2(0.275) + WG3(0.125) ) ) 97 | 98 | >>> Cav = .Cavity( LHS, RHS ) # Define the cavity 99 | 100 | >>> WLs = numpy.array( [1.490, 1.495, 1.500, 1.505, 1.510] ) 101 | >>> Cav.calc( WLs ) # Sweep the wavelength and calculate the eigenmodes 102 | 103 | >>> Cav.mode(0).plot() # plot the eigenvalues for the first lateral mode 104 | >>> Cav.mode([0,1,2]).plot() # plot the eigenvalues for the first three lateral modes 105 | >>> Cav.mode('all').plot() # plot the eigenvalues for all modes 106 | >>> Cav.mode(0).plot('Ex') # plot the Ex electric field vs. Z for resonance of lateral Mode #0. 107 | >>> print Cav.get_resonance_wavelengths() # print the resonance wavelengths 108 | ''' 109 | 110 | def __init__(self, *args, **kwargs): 111 | '''Please see help(Cavity) for usage info.''' 112 | 113 | #if DEBUG(): print "Cavity() connection test: " + str(fimm.Exec("app.numsubnodes()")) 114 | 115 | if len(args) >= 2: 116 | self.LHS_Dev = args[0] 117 | self.RHS_Dev = args[1] 118 | self.name = "Cavity(%s/%s)"%(self.LHS_Dev.name, self.RHS_Dev.name) 119 | else: 120 | raise ValueError("Invalid Number of arguments to Cavity constructor - expected exactly 2 Device objects.") 121 | 122 | ## Should check that LHS & RHS sections have same central cross-section 123 | 124 | if kwargs: 125 | '''If there are unused key-word arguments''' 126 | ErrStr = "WARNING: Cavity(): Unrecognized keywords provided: {" 127 | for k in kwargs.iterkeys(): 128 | ErrStr += "'" + k + "', " 129 | ErrStr += "}. Continuing..." 130 | print ErrStr 131 | #end __init__ 132 | 133 | 134 | def __str__(self): 135 | ''' How to `print` this object.''' 136 | string= 10*"-" + " Left-Hand Device " + 10*"-" + "\n" 137 | string += str(LHS_Dev) 138 | string= 10*"-" + " Right-Hand Device " + 10*"-" + "\n" 139 | string += str(RHS_Dev) 140 | return string 141 | #end __str__ 142 | 143 | 144 | def buildNode(self, parent=None, overwrite=False, warn=True, build=True): 145 | '''If either of the two constituent Devices passed haven't been built, they will now have their nodes built. 146 | 147 | Parameters 148 | ---------- 149 | parent : Node object, optional 150 | Provide the parent (Project/Device) Node object for this waveguide. 151 | 152 | build : { True | False }, optional 153 | If either of the constituent Devices aren't built, attempt to call their `buildNode` method. 154 | 155 | overwrite : { True | False }, optional 156 | Overwrite existing Device node of same name? Defaults to False, which will rename the node if it has the same name as an existing node. 157 | 158 | warn : {True | False}, optional 159 | Print notification if overwriting a node/building this Cavity? True by default. 160 | ''' 161 | if warn: print "WARNING: Cavity.buildNode(): Cavity is not a FimmWave node, just a pyFimm virtual-object, so there is nothing to build in FimmWave for this Cavity. The constituent FimmWave Devices will now attempt to be built." 162 | if parent: self.parent = parent 163 | if not self.LHS_Dev.built: self.LHS_Dev.buildNode(name='LHS', parent=self.parent, overwrite=overwrite, warn=warn) 164 | if not self.RHS_Dev.built: self.RHS_Dev.buildNode(name='RHS', parent=self.parent, overwrite=overwrite, warn=warn) 165 | #end buildNode() 166 | 167 | 168 | def calc(self, WLs, Display=False): 169 | '''Calculate the scattering matrices and eigenvalues of the cavity. 170 | Based on PhotonDesign's Example "Modelling a passive optical cavity (VCSEL, DFB)" & the accompanying Python script by Vincent Brulis at Photon Design, 2014. 171 | 172 | Parameters 173 | ---------- 174 | WLs : list/array of floats 175 | List of wavelengths at which to calculate the cavity eigenvalues. This determines the wavelength-accuracy of the resonance wavelengths found - you will have to choose the wavelengths at which to calculate the modes. 176 | 177 | Display : { True | False }, optional 178 | Display the calculated eigenvalues during wavelength sweep? This allows the user to copy/paste the results, rather than using the internally generated attributes, below. Defaults to False. 179 | 180 | Returns 181 | ------- 182 | Nothing is directly returned by this operation, but new attributes of the Cavity object will be available after calc() is called. These new attributes are: 183 | 184 | wavelengths : 2-D list of floats 185 | The wavelengths at which eigenvalues were calculated. This is a direct copy of the `WLs` array passed to the calc() function. 186 | 187 | eigenvalues, eigenvectors : 2-D list of floats 188 | The complex eigenvalues & eigenvectors at each of the calculated wavelengths. First dimension of the array is to choose lateral cavity mode (up to get_N() ). eg. [ [EigV_mode0_WL0, EigV_mode0_WL1, ... EigV_mode0_WLN], [EigV_mode1_WL0, EigV_mode1_WL1, ... EigV_mode1_WLN], ... , [EigV_modeN_WL0, EigV_modeN_WL1, ... EigV_modeN_WLN] ] 189 | The imaginary part of the eigenvalue corresponds to the round-trip optical phase, and the real part corresponds to the cavity loss. The eigenvectors are vectors containing the amplitudes of each mode required to attain the corresponding eigenvalue, and they can be input directly into a Device via `Device.set_input( )`. 190 | 191 | 192 | resonance_wavelengths, resonance_eigenvalues, resonance_eigenvectors : 2-D list of floats 193 | The wavelengths & corresponding eigenvalues/vectors for cavity resonances, if any. First dimension is to choose lateral mode (up to get_N() ), identical to eigvals. `None` will be entered into the list for any modes that do not show a resonance. 194 | Resonance is located by determining at which wavelength imag(eigenvalue) is closest to zero & real(eigenvalue) is positive. The strongest resonance will show the maximum real(eigenvalue). 195 | 196 | The Cavity device will have the attributes `CavityObj.S_RHS_ll` & `CavityObj.S_LHS_rr` added, which are the left-to-left & right-to-right scattering matrices for the Right & Left devices, respectively (with reflections pointing at the central split). 197 | Also the attribute `CavityObj.S_RT` will contain the round-trip scattering matrix, as viewd from the cavity split. This is simplt the dot-product of S_RHS_ll & S_LHS_rr. 198 | 199 | 200 | Examples 201 | ------- 202 | Calculate cavity modes in the range of wavelengths from 990nm to 1200nm: 203 | >>> CavityObject.calc( numpy.arange( 0.990, 1.200, 0.01 ) ) 204 | or just at a few wavelengths: 205 | >>> CavityObject.calc( [1.000, 1.050, 1.110] ) 206 | 207 | Calculated eigenvalues can be accessed in the resulting numpy.array: 208 | >>> CavityObj.eigenvalues 209 | This is an array with eigenvalues for each mode, with the form 210 | [ [EigsMode0], [EigsMode1], [EigsMode2], ..... [EigsModeN] ] 211 | so len( CavityObj.eigenvalues ) == NumModes = pyFIMM.get_N() 212 | 213 | use pyFIMM.set_N(num_modes) to set the number of lateral waveguide modes to include in the calculation. 214 | ''' 215 | self.wavelengths = np.array(WLs) 216 | self.eigenvalues, self.eigenvectors = self.__CavityModeCalc( self.LHS_Dev, self.RHS_Dev, WLs , Display=Display) # The main calculation function/loop 217 | self.resWLs, self.resEigVals, self.resEigVects, self.resLosses = self.__FindResonance( get_N() ) 218 | 219 | #return self.eigenvalues 220 | #end calc() 221 | 222 | 223 | 224 | def mode(self, num): 225 | '''Select a lateral mode to work on. Defaults to 'all' modes. 226 | 227 | Parameters 228 | ---------- 229 | num : int, list of int's, or 'all' 230 | If an integer is passed, that lateral mode is selected. If the string "all" is passed, the functions will attempt to return data for all modes calculated, when applicable. 231 | 232 | Technically, this method returns a CavityMode object, so to find out what methods/attributes you can perform on `CavityObj.mode(0)`, type `help(pyfimm.CavityMode)` or simply `help( CavityObj.mode(0) )` 233 | ''' 234 | return CavityMode(self, num) # return CavityMode object 235 | 236 | 237 | def get_length(self): 238 | '''Get the total length of this Cavity.''' 239 | return self.LHS_Dev.get_length() + self.RHS_Dev.get_length() 240 | 241 | 242 | def __ploteigs(self, ): 243 | '''DECPRECATED: Cavity.ploteigs() is replaced by `Cavity.mode('all').plot()`, so the code is now in the __CavityMode.py module 244 | 245 | Plot the Eigenvalues for all modes at each wavelength. 246 | 247 | Real parts plotted with '-x' & imaginary parts plotted with '-o'. 248 | 249 | Returns 250 | ------- 251 | handles : tuple of (fig1, ax1, ax2, l1, leg1, l2, leg2 ) 252 | Returns handles to the plot's objects, as so: 253 | fig1 : main figure object 254 | ax1 : primary (right) axis, for the Real part of the Eigenvalues. 255 | ax2 : secondary (left) axis, for the Imaginary part of the Eigenvalues. 256 | l1 : list of line objects, for the Real part of the Eigenvalues. 257 | leg1 : legend strings for lines in l1, for the Real part of the Eigenvalues. 258 | l2 : list of line objects, for the Imaginary part of the Eigenvalues. 259 | leg2 : legend strings for lines in l2, for the Imaginary part of the Eigenvalues. 260 | 261 | ''' 262 | 263 | print "WARNING: Cavity.ploteigs() is being deprecated - please use `Cavity.mode('all').plot()` instead" 264 | import matplotlib.pyplot as plt 265 | 266 | if len(self.eigenvalues) == 0: raise UserWarning("No Cavity modes found! Cavity modes not calculated yet? Please run Cavity.calc() to do so.") 267 | 268 | EigsArray = self.eigenvalues 269 | WLs = self.wavelengths 270 | 271 | fig1, ax1 = plt.subplots(1, 1) 272 | box = ax1.get_position() 273 | ax1.set_position([ box.x0, box.y0, box.width * 0.8, box.height]) # reduce axis width to 80%, to make space for legend 274 | ax2 = ax1.twinx() 275 | 276 | # print EigenVector for each mode: 277 | l1 = []; l2 = []; leg1 = []; leg2=[] 278 | for i in range( EigsArray.shape[1] ): 279 | print "%i: "%i, 280 | l1.append( ax1.plot(WLs, EigsArray[:,i].real, '-x', label="Mode "+str(i)+": real") ) 281 | leg1txt.append("Mode "+str(i)+": real") 282 | l2.append( ax2.plot(WLs, EigsArray[:,i].imag, '-o', label="Mode "+str(i)+": imag") ) 283 | leg2txt.append("Mode "+str(i)+": imag") 284 | 285 | #ax1.plot(WLs, EigsArray[:,0].real, label="Mode "+str(i)+": real") 286 | #ax2.plot(WLs, EigsArray[:,0].imag, label="Mode "+str(i)+": imag") 287 | 288 | ax1.set_xlabel(r"Wavelength, ($\mu{}m$)") 289 | ax1.set_ylabel("Real") 290 | ax2.set_ylabel("Imaginary") 291 | ax1.set_title("Cavity Eigenvalues") 292 | #plt.legend() 293 | 294 | leg = ax1.legend( (l1, l2), (leg1txt, leg2txt), loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 295 | 296 | fig1.canvas.draw(); fig1.show() 297 | return fig1, ax1, ax2, l1, l2, leg 298 | #end ploteigs 299 | 300 | 301 | 302 | def get_refractive_index(self, zpoints=3000, zmin=0.0, zmax=None, xcut=0.0, ycut=0.0, calc=True): 303 | '''Calls `Dev.get_field('index')` of each sub-Device to return the refractive index of the device, and then concatenates them appropriately. The `component` & `direction` options have been removed as compared with `get_field()`. 304 | 305 | See `help(Device.field)` for info on the other options. 306 | ''' 307 | zptsL=int(zpoints/2.); zptsR=np.round(zpoints/2.) 308 | 309 | Lfield = self.LHS_Dev.get_field('rix', zpoints=zptsL, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction='total', calc=calc) 310 | Rfield = self.RHS_Dev.get_field('rix', zpoints=zptsR, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction='total', calc=calc) 311 | Lfield.extend(Rfield) # concatenate the L+R fields 312 | return Lfield 313 | #end get_refractive_index() 314 | 315 | 316 | def plot_refractive_index(self, zpoints=3000, zmin=0.0, zmax=None, xcut=0.0, ycut=0.0, calc=True, return_handles=False, title=None): 317 | '''Plot the refractive index versus Z. 318 | 319 | return_handles = { True | False }, optional 320 | If True, will return handles to the figure, axes, legends and lines. False by default. 321 | 322 | title = str, optional 323 | Pre-pend some text to the plot title. 324 | 325 | Other options are passed to `Dev.get_field()` of the two constituent Devices that make up this Cavity, so see `help(Device.field)` for info on the other options. 326 | ''' 327 | import matplotlib.pyplot as plt # to create new figure 328 | 329 | rix = self.get_refractive_index(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, calc=calc) 330 | z = np.linspace( 0, self.get_length(), num=len(rix) ) # Z-coord 331 | 332 | 333 | fig1, ax1 = plt.subplots(1, 1) # 1 axis 334 | l1 = [ ax1.plot(z, np.array(rix).real, 'g-', label="Refractive Index" ) ] # plot 335 | 336 | ax1.set_ylabel( "Refractive Index" ) 337 | titlestr = self.name + ": Refractive Index vs. Z" 338 | if title: titlestr = title + ": " + titlestr 339 | ax1.set_title( titlestr ) 340 | ax1.grid(axis='both') 341 | #plt.legend() 342 | ax1.set_xlabel(r"Z, ($\mu{}m$)") 343 | 344 | #leg = plt.legend() 345 | #leg = ax1.legend( loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 346 | #leg2 = ax2.legend( loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 347 | 348 | fig1.canvas.draw(); fig1.show() 349 | 350 | # return some figure handles 351 | if return_handles: 352 | return fig1, ax1, l1 353 | #end plot_refractive_index() 354 | 355 | 356 | 357 | 358 | def __CavityModeCalc(self, LHS, RHS, scan_wavelengths, OverlapThreshold=0.95, Display=False): 359 | '''Cavity Mode Calculator 360 | Based on PhotonDesign's Example "Modelling a passive optical cavity (VCSEL, DFB)" 361 | Python script by Vincent Brulis at Photon Design, 2014; heavily modified by Demis D. John to incorporate into pyFIMM. 362 | 363 | Parameters 364 | ---------- 365 | LHS : Left-hand Side Device object 366 | 367 | RHS : Right-hand Side Device object 368 | 369 | WL_range : array-like 370 | Wavelengths to solve for as list, array or similar (any iterable). 371 | 372 | OverlapThreshold : float, optional 373 | If the overlap between the eigenvector and the mode is above this threshold, we will consider this eigenvector to represent this mode number. Default= 0.95. This is important when sorting the eigenvectors, as numpy sorts the eigenproblem's solutions by the eigenvalue, while we would prefer to sort them based on which waveguide mode they represent. 374 | 375 | Display : { True | False }, optional 376 | Print the calculated eigenvalues during wavelength sweep? Defaults to False. Useful for copy/pasting the data into a text file. 377 | 378 | 379 | Returns 380 | ------- 381 | (eigenvals, eigenvects) 382 | 383 | eigenvals : numpy array 384 | Calculated eigenvalues at each wavelength as a numpy.array with eigenvalues for each waveguide mode, with the form 385 | [ [EigsMode0, EigsMode1, EigsMode2, ..... EigsModeN] <-- 1st wavelength in scan_wavelengths 386 | [EigsMode0, EigsMode1, EigsMode2, ..... EigsModeN] <-- 2nd wavelength in scan_wavelengths 387 | ... 388 | [EigsMode0, EigsMode1, EigsMode2, ..... EigsModeN] ] <-- last wavelength in scan_wavelengths 389 | so len( CavityObj.eigenvalues ) == NumModes = pyFIMM.get_N() 390 | 391 | eigenvects : numpy array 392 | The calculated eigenvectors - amplitude/phase coefficients for each calc'd mode in the central section to achieve the above eigenvalues. Similar format as eigenvects. These can be launched via `DeviceObj.set_input()`. 393 | 394 | Adds the following attributes to the Cavity object: 395 | S_RHS_ll, S_LHS,rr: lists 396 | Scattering matrices as viewed from teh cavity split, for the RHS reflection (ll) and LHS reflection (rr). 397 | 398 | S_RT : list 399 | Scattering matrix for the round-trip, which is simply the dot-product of S_RHS_ll & S_LHS_rr. 400 | ''' 401 | import sys # for progress bar 402 | nWLs = len(scan_wavelengths) # Number of WLs. 403 | 404 | #Nguided=0 #dimension of the truncated eigenmatrix, should be set to number of guided modes, please set to 0 to solve the eigenproblem for all the modes 405 | #OverlapThreshold = 0.95 # if the overlap between the eigenvector and the mode is above this threshold, we will consider them identical 406 | 407 | self.__FPList = [] 408 | self.__pathFPList = [] 409 | self.__projFPList = [] 410 | self.__ProjList = [] 411 | self.__pathProjList = [] 412 | self.__PDnames = [] 413 | self.__PDpath = [] 414 | self.__PDproj = [] 415 | self.__eigen_imag = [] 416 | self.__eigen_real = [] 417 | self.S_RHS_ll = [] 418 | self.S_LHS_rr = [] 419 | self.S_RT = [] 420 | 421 | 422 | fimm.Exec("Ref& parent = app") 423 | 424 | n = len(self.__FPList) 425 | 426 | fimm.Exec("Ref& fpLHS = " + LHS.nodestring) 427 | 428 | fimm.Exec("Ref& fpRHS = " + RHS.nodestring) 429 | 430 | 431 | # Retrieve the number of modes in the central section 432 | N=fimm.Exec("fpRHS.cdev.eltlist[1].mlp.maxnmodes") # could replace with `self.RHS.element...` 433 | while 1: 434 | try: 435 | N = int(N) # check if numerical value returned 436 | break 437 | except ValueError: 438 | print self.name + ".calc:CavityModeCalc(): WARNING: Could not identify how many modes are calculated in the cavity, using get_N()" 439 | N = get_N() 440 | 441 | #if DEBUG(): print "CMC(): N={}".format(N) 442 | 443 | 444 | # for printing our the eigenvectors/values: 445 | Ndisplay = N # we want to display all the modes 446 | 447 | labels = "lambda " 448 | for i in range(0,Ndisplay,1): 449 | labels = labels + "real_mode" + str(i+1) + " imag_mode" + str(i+1) + " " 450 | #print labels 451 | 452 | 453 | # mode 1: scan wavelength <-- This is the only mode this script currently runs in 454 | # we will display all the modes, ranked by waveguide mode 455 | 456 | EigVect = [] ## To save the EigenVectors vs. wavelength 457 | EigVal = [] 458 | 459 | # Progress Bar setup: 460 | ProgMax = 20 # number of dots in progress bar 461 | if nWLslen(scan_wavelengths). 468 | `wavelength` is the actual WL value. ''' 469 | 470 | '''scan_wavelengths is already array-like, no need to construct wavelength at each step ''' 471 | #wavelength = wavelength_min + step*(wavelength_max - wavelength_min)/wavelength_steps 472 | 473 | 474 | fimm.Exec("fpRHS.lambda="+str(wavelength)) # set Device-specific wavelength 475 | fimm.Exec("fpLHS.lambda="+str(wavelength)) 476 | # this reset is an attempt to prevent memory issues 477 | fimm.Exec("fpRHS.reset1()") 478 | fimm.Exec("fpLHS.reset1()") 479 | fimm.Exec("fpRHS.update()") # calc the Scattering Matrix 480 | RRHS = np.zeros( [N,N], dtype=complex ) 481 | SMAT = [] 482 | for i in range(1,N+1,1): 483 | ''' Get Left-to-Left (reflecting) scattering matrix for Right-hand-side of cavity, for each WG mode.''' 484 | SMAT.append( fimm.Exec("fpRHS.cdev.smat.ll["+str(i)+"]") ) 485 | for i in range(1,N+1,1): 486 | for k in range(1,N+1,1): 487 | RRHS[i-1][k-1] = SMAT[i-1][k] # the index "k" is due to the fact that the first element of each line is "None" 488 | #if DEBUG(): print "RRHS:" # temp 489 | #if DEBUG(): print RRHS # temp 490 | 491 | 492 | self.S_RHS_ll.append( RRHS ) # store the left-to-left scattering matrix 493 | 494 | 495 | 496 | 497 | fimm.Exec("fpLHS.update()") 498 | 499 | 500 | # update progress bar: 501 | if ( step >= nProg*nWLs/ProgMax ): 502 | sys.stdout.write('*'); sys.stdout.flush(); # print a small progress bar 503 | nProg = nProg+1 504 | if ( step >= nWLs-1 ): 505 | sys.stdout.write('| done\n'); sys.stdout.flush(); # print end of progress bar 506 | 507 | 508 | RLHS = np.zeros([N,N],dtype=complex) 509 | SMAT = [] 510 | for i in range(1,N+1,1): 511 | '''Get Right-to-Right (reflecting) scattering matrix for Left-hand-side of cavity, for each WG mode.''' 512 | SMAT.append( fimm.Exec("fpLHS.cdev.smat.rr["+str(i)+"]") ) 513 | for i in range(1,N+1,1): 514 | for k in range(1,N+1,1): 515 | RLHS[i-1][k-1] = SMAT[i-1][k] # the index "k" is due to the fact that the first element of each line is "None" 516 | 517 | 518 | self.S_LHS_rr.append( RLHS ) # store the right-to-right scattering matrix 519 | 520 | 521 | 522 | ''' Calculate the round-trip matrix R2, by multiplying reflecting Smat's of each side of cavity. ''' 523 | R2 = np.dot(RRHS,RLHS) # combined scattering matrix for cavity round-trip 524 | self.S_RT.append( R2 ) # store round-trip scattering matrix at this wavelength 525 | 526 | 527 | # solve eigenproblem 528 | Eig = np.linalg.eig(R2) # returned in format: (array([e1, e2]), array([v1, v2]) 529 | # eigenvector (coefficient of each WG mode to produce scalar transformation) is in Eig[1] 530 | # eigenvalue (amplitude & phase applied to EigVect upon roundtrip) is in Eig[0] 531 | 532 | ''' 533 | Eig_reorg = [] # we want to achieve an easier format: ([e1,v1],[e2,v2]) 534 | for i in range(0,Nguided,1): 535 | Eig_reorg.append([Eig[0][i],Eig[1][i]]) 536 | ''' 537 | 538 | # 'zip' the two arrays together to rearrange as [ [e1,[v1]], [e2,[v2]]...[eN,[vN]] ] 539 | Eig_reorg = map(list, zip(Eig[0], Eig[1]) ) # also re-map the (tuples) that zip() returns to [lists], so [list].append() will work later 540 | 541 | #if DEBUG(): print "Eig_reorg=" , Eig_reorg 542 | 543 | # now we move on to processing and displaying the results 544 | ''' 545 | # we will display all the modes, ranked by eigenvalue 546 | Eig_ranked = [] 547 | # calculate magnitude of eigenvalue then rank Eigenvalues accordingly 548 | #*** I think these loops can be replaced with more efficient Numpy functions 549 | for i in range(0,Nguided,1): 550 | magnitude = (Eig_reorg[i][0].real)**2+(Eig_reorg[i][0].imag)**2 551 | if len(Eig_ranked)==0: 552 | Eig_ranked.append(Eig_reorg[i]+[magnitude]) 553 | else: 554 | found = 0 555 | for j in range(0,len(Eig_ranked),1): 556 | if magnitude > Eig_ranked[j][2]: 557 | Eig_ranked_temp = Eig_ranked[:j] 558 | Eig_ranked_temp.append(Eig_reorg[i]+[magnitude]) 559 | Eig_ranked = Eig_ranked_temp + Eig_ranked[j:] 560 | found = 1 561 | break 562 | if found == 0: 563 | Eig_ranked.append(Eig_reorg[i]+[magnitude]) 564 | ''' 565 | 566 | 567 | 568 | # Sorting by predominant mode number, instead of max eigenvalue. 569 | ''' 570 | eg. sort eigenvalues according to which mode is largest in the eigenvector: 571 | EigVect_Mode0 = [*0.9983*, 0.003, 3.543e-5] 572 | EigVect_Mode1 = [5.05e-5, *0.9965*, 3.543e-5] 573 | EigVect_Mode2 = [6.23e-5, 0.0041, *0.9912*] 574 | ''' 575 | # sort the list of [EigVal, [EigVect]...] with built-in list sorting via sorted() 576 | Eig_ranked = sorted( Eig_reorg, key= lambda x: np.argmax( np.abs( x[1] ) ) ) 577 | 578 | ''' How the above ``sorted` function works: 579 | The lambda function returns a `key` for sorting - where the key tells sorted() which position to put the element in the new list. 580 | The argument passed to the lambda function, `x`, will be the current element in the list Eig_reorg as sorted() loops through it, which will look like x=[ EigVal, [EigVec0, EigVec1...EigVecN] ]. 581 | We then select only the EigenVector part with `x[1]`. Then the lambda function returns the index to whichever EigVect element has the maximum amplitude (`np.abs()`), generated by `numpy.argmax()` -- the index to that element will be the `key` used for sorting - ie. the vector that has the 1st element as max. ampl. will be sorted to the top of the resulting list. 582 | ''' 583 | 584 | if DEBUG(): print "Eig_ranked=" , Eig_ranked 585 | 586 | 587 | ## To save EigenVector/EigenValue at this wavelength: 588 | EigVect_n = [] 589 | EigVal_n = [] 590 | 591 | # display eigenvalues + save eigvals for each WG mode: 592 | outputstr = str(wavelength) + " " 593 | for i in range(0,Ndisplay,1): 594 | ## Save eigenvector/eigenvalue for this mode 595 | EigVect_n.append(Eig_ranked[i][1]) 596 | EigVal_n.append(Eig_ranked[i][0]) 597 | #if DEBUG(): print "Mode %i: EigVect_n[-1]="%(i) , EigVect_n[-1] 598 | outputstr = outputstr + str(Eig_ranked[i][0].real) + " " + str(Eig_ranked[i][0].imag) + " " 599 | if Display: print outputstr 600 | 601 | 602 | ## Save Eigenvector/Eigenvalue at this wavelength 603 | EigVect.append(EigVect_n) 604 | EigVal.append(EigVal_n) 605 | #if DEBUG(): print "EigVect_n(WL)=", EigVect_n 606 | #end for(wavelengths) 607 | 608 | print # new line 609 | return np.array(EigVal), np.array(EigVect) 610 | #... 611 | # end CavityModeCalc() 612 | 613 | 614 | 615 | def __FindResonance(self, nummodes): 616 | '''Locate the wavelengths where the round-trip phase is zero (imaginary part of Eigenvalue = 0) & Eigenvalue (related to cavity loss) is positive (not lossy). 617 | 618 | From Vincent Brulis @ PhotonDesign: 619 | You can detect the resonances by identifying the wavelengths for which the imaginary part of the eigenvalue (round-trip phase) is zero and the real part is positive (the higher the real part, the less lossy the resonance). The round-trip loss (i.e. the threshold gain) for a given resonance can be obtained from 10*log(real^2). 620 | 621 | Returns 622 | ------- 623 | resWL, resEigVals, resEigVects, loss : lists 624 | List of wavelengths, eigenvalues, eigenvectors and round-trip losses for each mode. List index corresponds to each mode, and will contain `None` if no cavity resonance was found for that mode. 625 | 626 | ''' 627 | 628 | #modenum = self.modenum 629 | 630 | WLs = self.wavelengths 631 | 632 | resWL = [] 633 | resEigVals = [] 634 | resEigVects = [] 635 | losses = [] 636 | 637 | for modenum in range(nummodes): 638 | Eigs_r = self.eigenvalues[:,modenum].real 639 | Eigs_i = self.eigenvalues[:,modenum].imag 640 | 641 | I0 = [] 642 | for i in xrange(len(Eigs_i)-1): 643 | '''xrange() is identical to range() but more efficient with memory, and replaces range() in later Python versions (ver>=3 ?).''' 644 | if (Eigs_i[i] > 0 and Eigs_i[i+1] < 0) or \ 645 | (Eigs_i[i] < 0 and Eigs_i[i+1] > 0): 646 | '''If imaginary crosses zero.''' 647 | 648 | if Eigs_r[i]>0 or Eigs_r[i+1]>0: 649 | '''If real part is positive. 650 | Choose the point with minimum imaginary part.''' 651 | if abs( Eigs_i[i] ) < abs( Eigs_i[i+1] ): 652 | I0.append( i ) 653 | else: 654 | I0.append( i+1 ) 655 | #if DEBUG(): print "Mode %i: "%(modenum) + "crossing between indexes %i and %i"%(i, i+1) 656 | if DEBUG(): print "Mode %i: "%(modenum) + "; Resonance found at Wavelength ", WLs[I0[-1]], " um: " + "Eigs_i=", Eigs_i[I0[-1]], "; Eigs_r=", Eigs_r[I0[-1]] 657 | #end for(Eigs_i) 658 | 659 | if len(I0) == 0: 660 | ''' if no resonance found''' 661 | if DEBUG(): print( "_findres(): Mode=", modenum, " // No Resonance" ) 662 | resWL.append( None ) 663 | resEigVals.append( None ) 664 | resEigVects.append( None ) 665 | losses.append( None ) 666 | else: 667 | if DEBUG(): print( "_findres(): Mode=", modenum, " // I0=", I0 ) 668 | resWL.append( WLs[I0] ) # save all resonance wavelengths for this mode 669 | resEigVals.append( self.eigenvalues[I0,modenum] ) # save all resonance EigVals for this mode 670 | resEigVects.append( self.eigenvectors[I0,modenum] ) # save all resonance EigVects for this mode 671 | 672 | 673 | # normalize the eigenvalue, to the magnitude of the eigenvectors: 674 | loss=[] 675 | if DEBUG(): print("_findres(): len(resEigVals)=", len(resEigVals[-1])) 676 | for ii in range( len(resEigVals[-1]) ): 677 | '''in case multiple resonances for this mode''' 678 | if DEBUG(): print( "_findres(): rVect[", ii, "]=", resEigVects[-1][ii]) 679 | if resEigVects[-1][ii] != None: 680 | MagEigVect = [ np.sum( np.abs( rVect ) ) for rVect in resEigVects[-1][ii] ] # total magnitude of the eigenvector 681 | eVal_norm = np.array(resEigVals[-1][ii]) / np.array(MagEigVect) # normalized eigenvalues 682 | loss.append( 1.0 - np.real(eVal_norm) ) # fractional loss for input mode amplitude 683 | else: 684 | loss.append( None ) 685 | losses.append( np.array(loss) ) 686 | #end for(modenum) 687 | 688 | return (resWL, resEigVals, resEigVects, losses) 689 | 690 | #end __FindResonance 691 | 692 | #end class Cavity 693 | 694 | 695 | 696 | -------------------------------------------------------------------------------- /pyfimm/__CavityMode.py: -------------------------------------------------------------------------------- 1 | ''' 2 | pyFIMM.CavityMode 3 | 4 | Operations on Cavity Modes (modes vs. Z). Created when the user requests: 5 | >>> CavityObj.mode(0) 6 | : returns a 7 | Demis D. John, 2015, Praevium Research Inc. 8 | 9 | To Do: 10 | ------ 11 | - Cavity.plot() 12 | - plot lateral fields? 13 | - zmin & zmax - account for LHS_Dev & RHS_Dev lengths etc. 14 | ''' 15 | 16 | from __globals import * # import global vars & FimmWave connection object 17 | # DEBUG() variable is also set in __globals 18 | 19 | import numpy as np 20 | import math 21 | 22 | #from __pyfimm import DEBUG() # Value is set in __pyfimm.py 23 | from __pyfimm import get_N, set_wavelength # get number of calculated modes 24 | 25 | 26 | 27 | ######## For Cavity.mode(n)... ######## 28 | 29 | class CavityMode(object): 30 | '''CavityMode( CavityObj, ModeNum ) 31 | 32 | Class for selecting a Cavity Mode, similar to the Mode class used in `WG.mode(0)`. 33 | Typically created via Cavity's `mode()` method, like so: 34 | >>> Cavity.mode(0).plot() 35 | Since Cavity.mode() returns a CavityMode object, this calls CavityMode.plot() 36 | 37 | Parameters 38 | ---------- 39 | CavityObj : pyFIMM.Cavity object 40 | The Cavity object to perform operations on. 41 | ModeNum : integer, list of integers, or the string 'all', optional. 42 | The Cavity mode number to work on. Default is 0. 43 | May pass multiple modenumbers in a list, eg. `CavityMode([0,1,2])` 44 | If the string 'all' (case insensitive) is passed, data will be returned for all calculated modes (as specified by get_N() - the number of calculated lateral modes per Section/Circ). 45 | 46 | 47 | Attributes 48 | ---------- 49 | modenum : int or 'all' 50 | Which lateral mode to manipulate. 51 | 52 | wavelengths, eigenvalues, eigenvectors : numpy array 53 | Wavelengths (passed to `Cavity.calc()`) & corresponding eigenvalues/eigenvectors at each. 54 | The eigenvectors are the magnitudes/phases of each lateral mode needed in order to produce the resonant cavity field. The lateral modes (up to get_N() ) are the basis set of the eigenvalue problem. 55 | For eigenvalues & eigenvectors, indexing is like so: 56 | >>> eigenvalues[Imodenum][Iwavelength] 57 | Where `wavelengths[Iwavelength]` tells you which wavelength you're inspecting, and `Imodenum` tells you which mode number you're inspecting. 58 | 59 | 60 | 61 | Methods 62 | ------- 63 | 64 | Please see help on a specific function via `help(CavityMode.theFunc)` for detailed up-to-date info on accepted arguments etc. 65 | 66 | get_resonance_wavelengths(): 67 | Returns resonance wavelength(s) for selected modes. 68 | `get_resonance_wavelength()` is a synonym. 69 | 70 | get_resonance_eigenvalues(): 71 | Returns resonance eigenvalues(s) (the round-trip amplitude & phase applied to a field) for this mode. 72 | `get_resonance_eigenvalue()` is a synonym. 73 | 74 | get_resonance_eigenvectors(): 75 | Returns resonance eigenvectors(s) (the magnitudes/phases of each central-section mode to get the above eigenvalues) for this mode. 76 | `get_resonance_eigenvector()` is a synonym. 77 | 78 | 79 | plot( component ): 80 | Plot a component of this mode. 81 | Supported components include: 82 | 'EigVals' - plot Eigenvalues versus wavelength. 83 | Ex, Ey, Ez - Electric fields versus Z. 84 | Hx, Hy, Hz - Magnetic Fields versus Z. 85 | Px, Py, Pz - Poynting Vectors versus Z. 86 | 'index' or 'rix' - refractive index of cavity versus Z. 87 | See `help(CavityMode.plot)` or `help(CavityObj.mode(0).plot)` for full help on the function, as there are more important details than mentioned here. 88 | 89 | get_cavity_loss(): 90 | NOT IMPLEMENTED YET. 91 | Return the cavity loss (equivalent to threshold gain) for this mode. 92 | 93 | Examples 94 | -------- 95 | CavityMode objects are typically Not called/instantiated from the CavityModes class directly, but instead as a sub-object of a Cavity `mode` method like so: 96 | >>> CavityObj.mode(0).plot() 97 | where `CavityObj.mode(0)` is the method `mode()` of the CavityObject which returns a CavityMode object (initialized with modenum=0), and `.plot()` is a method of this CavityMode object. 98 | ''' 99 | 100 | def __init__(self, CavObj, num): 101 | '''Takes Cavity object `CavObj` as input, and mode number `num` (default=0). 102 | Optionally, if num == 'all' will return data on all modes.''' 103 | 104 | self.Cavity = CavObj 105 | 106 | if isinstance(num, str): 107 | if num.lower() == 'all': 108 | #num = -1 # plot all modes 109 | self.modenum = range(0, get_N() ) # list of each modenumber calc'd 110 | else: 111 | ErrStr = 'CavityMode: Mode Number must be an integer, list of integers, or the string "all".' 112 | raise ValueError(ErrStr) 113 | elif isinstance(num, int): 114 | self.modenum = [num] # put num into a list 115 | else: 116 | try: 117 | self.modenum = [int(x) for x in num] # check that we're able to create a list of integers 118 | except: 119 | ErrStr = 'CavityMode: Mode Number must be an integer, list of integers, or the string "all".' 120 | raise ValueError(ErrStr) 121 | #end if(num) 122 | 123 | 124 | self.eigenvalues = [] 125 | self.eigenvectors = [] 126 | self.wavelengths = [] 127 | self.__resonance_wavelength = [] 128 | self.__resonance_eigenvalue = [] 129 | self.__resonance_eigenvector = [] 130 | self.__resonance_loss = [] 131 | 132 | for num in self.modenum: 133 | '''eigenvalues[i][ corresponds to the modenumber modenum[i]''' 134 | try: 135 | '''Make sure the Cavity has been calculated.''' 136 | CavObj.eigenvalues 137 | CavObj.eigenvectors 138 | except AttributeError: 139 | ErrStr = "EigenValues/EigenVectors not found - not calculated yet? Try calling `Cavity.calc()` first." 140 | raise AttributeError(ErrStr) 141 | self.eigenvalues.append( CavObj.eigenvalues[: , num] ) 142 | self.eigenvectors.append( CavObj.eigenvectors[: , num] ) 143 | self.wavelengths.append( CavObj.wavelengths ) # could just have one entry for this... 144 | self.__resonance_wavelength.append( CavObj.resWLs[num] ) 145 | self.__resonance_eigenvalue.append( CavObj.resEigVals[num] ) 146 | self.__resonance_eigenvector.append( CavObj.resEigVects[num] ) 147 | self.__resonance_loss.append( CavObj.resLosses[num] ) 148 | #end __init__ 149 | 150 | 151 | def get_field(self, component, wavelength, zpoints=3000, zmin=0.0, zmax=None, xcut=0.0, ycut=0.0, direction=None, calc=True): 152 | '''Return the field specified by `component`, versus Z. 153 | 2 arguments are requires, `component` and `wavelength`. 154 | The fields returned are for the Cavity having input field set to the eigenvectors calculated at the given wavelength. 155 | 156 | component : {'Ex' | 'Ey' | 'Ez' | 'Hx' | 'Hy' | 'Hz' | 'Px' | 'Py' | 'Pz' | 'I' }, case-insensitive, required 157 | Return the specified field component along the Z direction. 158 | 'E' is electric field, 'H' is magnetic field, 'P' is the Poynting vector, 'I' is Intensity, and 'x/y/z' chooses the component of each vector to return. 159 | 'index', 'rix' or 'ri' will return the refractive index, a functionality provided by the more convenient function `get_refractive_index()` but otherwise identical to this func. `wavelength` is ignored in this case. 160 | 161 | wavelength : number or the string 'resonance' 162 | If 'resonance' specified, will launch the resonance wavelength with maximum eigenvalue (min loss). Synonyms are 'res' and 'max', and these are all case-insensitive. 163 | If a number is specified, that wavelength will be launched. The wavelength should be found in the list of calculated wavelengths (`Cavity.calc(wavelengths)`), found after `calc()` in the attribute `Cavity.wavelengths`. 164 | 165 | direction = string { 'fwd', 'bwd', 'total' }, case insensitive, optional 166 | DISABLED - now chosen based on LHS or RHS input. 167 | Which field propagation direction to plot. Defaults to 'total'. 168 | Note that the propagation direction should match up with which side the input field was launched. Eg. for `set_input([1,0,0], side="left")` you'll want to use `direction="fwd"`. 169 | Synonyms for 'fwd' include 'forward' & 'f'. 170 | Synonyms for 'bwd' include 'backward' & 'b'. 171 | Synonyms for 'total' include 'tot' & 't'. 172 | 173 | xcut, ycut = float, optional 174 | x & y coords at which to cut the Device along Z. Both default to 0. 175 | 176 | zpoints = integer, optional 177 | Number of points to acquire in the field. Defaults to 3000. 178 | 179 | zmin, zmax = float, optional 180 | min & max z-coorinates. Defaults to 0-->Device Length. 181 | 182 | calc = { True | False } 183 | Tell FimmProp to calculate the fields? Only needs to be done once to store all field components & refractive indices (for a given `zpoints`, `xcut` etc.), so it is useful to prevent re-calculating after the first time. 184 | 185 | cut = tuple of two floats - NOT IMPLEMENTED YET 186 | Specify coordinate plane on which to plot fields. Default (0,0). 187 | If dir='Z', then tuple is (x,y). 188 | If dir='Y', then tuple is (x,z). 189 | If dir='X', then tuple is (y,z). 190 | 191 | Returns 192 | ------- 193 | 2-D List of complex values corresponding to field values, starting at z=0 and ending at specified `zmax`, for each specified modenumber. 194 | 1st dimension of List corresponds to the specified modenumbers. For example: 195 | >>> f = CavObj.mode([1,3]).get_field('Ex', 'resonance') 196 | Will return the list `f` with `f[0]` corresponding to mode(1) & `f[1]` corresponding to mode(3). 197 | >>> f = CavObj.mode(2).get_field('Ex', 'resonance') 198 | Will only have `f[0]`, corresponding to mode(2). 199 | 200 | 201 | Examples 202 | -------- 203 | Get the Total Ex field at x,y=(0,0) along Z, along the whole Cavity. 204 | >>> field = Cav.get_field('Ex') 205 | Get the refractive index at x,y=(0,0) along Z, along the whole Cavity. 206 | >>> field = Cav.fields('index') 207 | ''' 208 | wl = wavelength 209 | zptsL=int(zpoints/2.); zptsR=np.round(zpoints/2.) 210 | 211 | comp = component.lower().strip() 212 | if comp == 'index' or comp == 'rix' or comp == 'ri': 213 | '''Return refractive index - wavelength unimportant''' 214 | Lfield = self.Cavity.LHS_Dev.get_field('rix', zpoints=zptsL, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction='total', calc=calc) 215 | Rfield = self.Cavity.RHS_Dev.get_field('rix', zpoints=zptsR, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction='total', calc=calc) 216 | 217 | Lfield.extend(Rfield) # concatenate the L+R fields 218 | zfield=Lfield 219 | else: 220 | zfield=[] # to hold fields at each mode number 221 | for num,M in enumerate(self.modenum): 222 | '''num goes from 0-># of modes requested. M tells use the actual mode number.''' 223 | 224 | if DEBUG(): print "CavityMode.plot(field): (num, M) = (", num, ",", M, ")" 225 | 226 | 227 | # find index to the spec'd wavelength. 228 | # `wl` is the passed argument, `WL` is the final wavelength 229 | 230 | if isinstance(wl, str): 231 | '''if 2nd arg, wl, is a string: ''' 232 | wl = wl.lower().strip() # to lower case + strip whitespace 233 | if wl == 'resonance' or wl == 'res' or wl == 'max': 234 | '''Find the resonant wavelength/eigval/eigvector''' 235 | if DEBUG(): print "CavityMode.plot('res'): self.get_resonance_eigenvalues() = \n", self.get_resonance_eigenvalues() 236 | if DEBUG(): print "CavityMode.plot('res'): self.get_resonance_wavelengths() = \n", self.get_resonance_wavelengths() 237 | 238 | if self.__resonance_eigenvalue[num]==[None] or self.__resonance_wavelength[num]==None: 239 | '''No resonance found for this mode''' 240 | ErrStr = "No resonance found for mode %i, "%(M) + "can't plot via `resonance`." 241 | raise UserWarning(ErrStr) 242 | 243 | Iwl = np.argmax( np.real( self.__resonance_eigenvalue[num] ) ) 244 | 245 | WL = self.__resonance_wavelength[num][Iwl] 246 | Iwl = np.where( np.array([WL]) == self.wavelengths[:][num] )[0] # set to index of all calc'd WL's, not just resonance WLs 247 | print "CavityMode.plot('res'): Getting field at resonance mode @ %0.3f nm" %( WL ) 248 | if DEBUG(): print "Iwl=%s\nWL=%s"%(Iwl,WL) 249 | else: 250 | raise ValueError("CavityMode.plot(field): Unrecognized wavelength string. Please use 'resonance' or provide a wavelength in microns. See `help(CavityMode.plot)` for more info.") 251 | else: 252 | '''A specific wavelength (number) must have been passed: ''' 253 | WL = wl 254 | Iwl = np.where( np.array([WL]) == self.wavelengths[num] )[0] 255 | if not Iwl: 256 | '''If wavelength not found in calculated WLs: ''' 257 | ErrStr = "CavityMode.plot(field): Wavelength `", WL, "` not found in among list of calculated wavelengths list (chosen during `Cavity.calc(wavelengths)`). See `help(CavityMode.plot)` for more info." 258 | raise ValueError(ErrStr) 259 | if DEBUG(): print "CavityMode.plot(): (num,Iwl)=(",num,",",Iwl,")" 260 | 261 | EigVec = self.eigenvectors[num][Iwl[0]] # find eigenvector at given wavelength 262 | 263 | 264 | # Launch this eigenvector: 265 | norm = False 266 | self.Cavity.RHS_Dev.set_input( EigVec, side='left', normalize=norm ) 267 | self.Cavity.RHS_Dev.set_input( np.zeros( get_N() ), side='right' ) # no input from other side 268 | 269 | # Get mode vector reflected from RHS device & launch it into LHS dev, to accomplish one roundtrip 270 | vec = self.Cavity.RHS_Dev.get_output_vector(side='left', direction='left') 271 | self.Cavity.LHS_Dev.set_input( vec, side='right', normalize=norm ) 272 | self.Cavity.LHS_Dev.set_input( np.zeros( get_N() ), side='left' ) # no input from other side 273 | 274 | 275 | # Get field values: 276 | Lfielddir, Rfielddir = 'total','total' 277 | self.Cavity.LHS_Dev.calc(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut) 278 | Lfield = self.Cavity.LHS_Dev.get_field(comp, zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction=Lfielddir, calc=False) 279 | 280 | self.Cavity.RHS_Dev.calc(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut) 281 | Rfield = self.Cavity.RHS_Dev.get_field(comp, zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction=Rfielddir, calc=False) 282 | 283 | Lfield.extend(Rfield) # concatenate the L+R fields 284 | zfield.append(Lfield) # add field for this mode number 285 | #end for(self.modenum) 286 | #end if(comp==etc.) 287 | 288 | return zfield 289 | #end get_field() 290 | 291 | # Alias for this func: 292 | field = get_field 293 | 294 | 295 | def plot(self, *args, **kwargs): 296 | '''CavityMode.plot(component, [more options]) 297 | CavityMode.plot() 298 | CavityMode.plot( 'EigVals' ) # plot eigenvalues versus wavelength 299 | CavityMode.plot( 'Ex', 1.055 ) # plot cavity field Ex versus Z @ 1.055um wavelength 300 | 301 | Plot the cavity modes. 302 | If no arguments are provided, this will plot the calculated Eigenvalues versus wavelength. 303 | However, if a field component is specified, the function will plot the cavity fields versus Z. 304 | 305 | 306 | Parameters 307 | ---------- 308 | component : string (see below), case-insensitive, optional 309 | Two different plot functionalities may be performed, depending on whether `component` specifies a field component or the eigenvalues of the cavity. The different functionality for either type of `component` specified is as follows: 310 | 311 | component = 'EigVal' : 312 | Plot EigenValues vs. wavelength (at the wavelengths determined by `Cavity.calc(wavelengths)` ). 313 | This is the default if no argument passed. Synonyms for 'EigVal' are 'EigVals' & 'EigV'. 314 | 315 | component = {'Ex' | 'Ey' | 'Ez' | 'Hx' | 'Hy' | 'Hz' | 'I' | 'RIX'} : 316 | Plot the specified field component along a specified direction. 317 | "RIX", "RI" or "index" will plot only the refractive index vs. Z. 318 | The 2nd argument must be the wavelength at which to plot the fields, or the string 'resonance'. The specified wavelength must be in the list of calculated wavelengths passed to `Cavity.calc(wavelengths)`. These wavelengths can be found in the list `CavityObj.wavelengths`. For example, you could get them directly from that list, like so: 319 | >>> CavityObj.mode(0).plot( 'Ex', CavityObj.wavelengths[51] ) 320 | If the string 'resonance' is provided as the wavelength, then the wavelength with dominant resonance (max eigenvalue/min. loss) will be used. Synonyms for 'resonance' are 'res' & 'max', and the string is case-insensitive. 321 | 322 | 323 | Other optional keywords for field plotting that may be provided are: 324 | 325 | refractive_index = { True | False } 326 | If True, will plot the refractive index of the structure on a second axis, with shared X-axis (so zooming etc. zooms both X axes). Default is False. 327 | 328 | field_points = integer, optional 329 | Number of points to acquire in a field plot. Defaults to 3000. The exact number of acquired points may vary by one of two points. 330 | 331 | xcut, ycut = float, optional 332 | x & y coords at which to cut the Device along Z. Both default to 0. 333 | 334 | zmin, zmax = float, optional 335 | min & max z-coorinates. Defaults to 0-->Device Length. 336 | 337 | xpoint, ypoint = float, optional 338 | x & y coords at which to cut the Device along Z. Both default to 0. 339 | 340 | direction = string { 'fwd', 'bwd', 'total' }, case insensitive, optional 341 | DISABLED: direction now chosen based on launch dir. 342 | Which field propagation direction to plot. Defaults to 'bwd'. 343 | 344 | cut = tuple of two floats - NOT IMPLEMENTED YET 345 | Specify coordinate plane on which to plot fields. Default (0,0). 346 | If dir='Z', then tuple is (x,y). 347 | If dir='Y', then tuple is (x,z). 348 | If dir='X', then tuple is (y,z). 349 | 350 | return_handles = { True | False } 351 | If True, will return handles to the figure, axes, legends and lines. False by default. 352 | 353 | title = str, optional 354 | Pre-pend some text to the plot title. 355 | 356 | warn = bool 357 | Display warnings? Defaults to True. 358 | 359 | 360 | Returns 361 | ------- 362 | handles : tuple of (fig1, axes, lines, leg) 363 | If `return_handles=True`, returns matplotlib handles to the plot's objects, as so: 364 | fig1 : main figure object 365 | axes : Each axis. For field plots, if `refractive_index=True` then axes = ( Field_Axis , RI_Axis ), otherwise just = Field_Axis handles (or one axis for EigenValues). 366 | lines : Each curve plotted. If `refractive_index=True` then lines = ( RI_line, Field_Line_Mode_0, Field_Line_Mode_1 , ... Field_Line_Mode_N ), otherwise handle RI_Line is omitted. 367 | For EigenValue plots, `lines = (EigV_real_lines, EigV_imaginary_lines, Resonance_lines)`, with each being a list with a line per mode. Resonance_lines are the vertical lines indicating resonance wavelengths, which itself is a list of lists - `Resonance_lines[modenum][resonance_num]`, since there can be multiple resonances for each mode. 368 | leg : legend of main Field/EigV axis, containing one legend entry for each mode number. 369 | 370 | 371 | Examples 372 | -------- 373 | Typically Not called/instantiated from CavityModes class directly, but instead as a sub-object of a Cavity mode object like so: 374 | >>> CavityObj.mode(0).plot('EigVal') 375 | where `CavityObj.mode(0)` returns a CavityMode object (initialized with modenum=0), and `.plot` is a method of this CavityMode object. 376 | 377 | Plot Eigenvalues vs. Wavelength for a few lateral (waveguide) modes: 378 | >>> CavityObj.mode( [0,2] ).plot('EigVal') 379 | >>> CavityObj.mode( 'all' ).plot('EigVal') # plot all Mode's EigV's on one plot 380 | >>> CavityObj.mode( 0 ).plot('EigVal') # plot only 1st mode's Eigenvalues 381 | 382 | Plot Fields of the Cavity Mode: 383 | >>> CavityObj.mode( 0 ).plot('Ex', 'resonance') # plot Ex for strongest resonance of Mode 0 384 | >>> CavityObj.mode( 'all' ).plot('Hy', 1.550) # plot Hy for all modes on one plot, at wavelength 1.550 (may not be resonant, so fields may be discontinuous @ Cavity cut) 385 | >>> CavityObj.mode( 0 ).plot('Ex', 'resonance', refractive_index=True) # plot Ex for strongest resonance of Mode 0, with Refractive Index profile plotted on separate axis 386 | >>> fig, axis, line, leg = CavityObj.mode( 0 ).plot('Ex', 'res', return_handles=True) # plot Ex for strongest resonance, and return matplotlib handles to the figure's elements 387 | ''' 388 | 389 | import matplotlib.pyplot as plt # there is no extra overhead to re-import a python module 390 | 391 | # parse keyword args: 392 | return_handles = kwargs.pop('return_handles', False) 393 | title = kwargs.pop('title', None) 394 | warn = kwargs.pop('warn', True) 395 | 396 | ''' Unused Kwargs are returned at the end of the plot() func.''' 397 | 398 | 399 | if len(args)==0: 400 | comp = 'eigval' 401 | else: 402 | if isinstance(args[0], str): 403 | comp = args[0].lower().strip() # make lower case, strip whitespace 404 | else: 405 | ErrStr = "CavityMode.plot(component): expected `component` to be a string, but instead got: " + str(type(component)) + " : " + str(component) 406 | raise ValueError(ErrStr) 407 | #end if(args) 408 | 409 | 410 | # Perform different plots depending on requested component `comp`: 411 | #eigvstr = ['eigval', 'eigvals', 'eigv'] # possible strings for EigenValue plotting 412 | fieldstrs = ['ex','ey','ez','hx','hy','hz','i','rix','ri','index'] # possible strings for field plotting 413 | 414 | 415 | 416 | ''' 417 | ----------------------------------- 418 | First case: Plot the Eigenvalues 419 | ----------------------------------- 420 | ''' 421 | if comp == 'eigval' or comp == 'eigvals' or comp == 'eigv': 422 | '''Plot the eigenvalues''' 423 | 424 | fig1, ax1 = plt.subplots(1, 1) 425 | box = ax1.get_position() 426 | ax1.set_position([ box.x0, box.y0, box.width * 0.8, box.height]) # reduce axis width to 80%, to make space for legend 427 | 428 | l1 = []; l2 = [] 429 | vlines_out=[] 430 | for num,M in enumerate(self.modenum): 431 | '''num goes from 0-># of modes requested. M tells use the actual mode number.''' 432 | #if DEBUG(): print "CavityMode.plot: num in modenum = ", num, type(num), " in ", self.modenum, type(self.modenum) 433 | 434 | if len(self.eigenvalues[num]) == 0: raise UserWarning("No EigenValues found for mode %i!" %M +" Cavity modes not calculated yet? Please run Cavity.calc() to do so.") 435 | 436 | EigsArray = self.eigenvalues[num] 437 | WLs = self.wavelengths[num] 438 | 439 | #l1 = []; l2 = []; leg1 = []; leg2=[] 440 | l1.extend( ax1.plot(WLs, EigsArray.real, '-x', label="%i: Real"%self.modenum[num] ) ) 441 | curr_color = l1[-1].get_color() # color for this mode, as selected my MPL 442 | #leg1.append("Real") 443 | l2.extend( ax1.plot(WLs, EigsArray.imag, '-+', label="%i: Imag"%self.modenum[num], color=curr_color ) ) 444 | #leg2.append("Imaginary") 445 | 446 | #ax1.plot(WLs, EigsArray[:,0].real, label="Mode "+str(i)+": real") 447 | #ax2.plot(WLs, EigsArray[:,0].imag, label="Mode "+str(i)+": imag") 448 | 449 | 450 | # add line indicating resonance, if found: 451 | vlines = [] # holds handles of vertical lines 452 | if np.any(self.__resonance_wavelength[num]): 453 | # This line starts at the data coords `xytext` & ends at `xy` 454 | ymin, ymax = ax1.get_ylim() 455 | for ii, resWL in enumerate( self.__resonance_wavelength[num] ): 456 | if ii==0: 457 | '''Only add label once''' 458 | vlines.append( ax1.vlines(resWL, ymin, ymax, linestyles='dashed', colors=curr_color, label="%i: Resonance"%self.modenum[num] ) ) 459 | else: 460 | vlines.append( ax1.vlines(resWL, ymin, ymax, linestyles='dashed', colors=curr_color) ) 461 | #end for(resWL) 462 | #end if(resonance) 463 | vlines_out.append(vlines) 464 | #end for(modenum) 465 | 466 | ax1.set_xlabel(r"Wavelength, ($\mu{}m$)") 467 | ax1.set_ylabel("Eigenvalue") 468 | #ax2.set_ylabel("Imaginary") 469 | titlestr = self.Cavity.name + " Eigenvalues for Mode "+str(self.modenum) 470 | if title: titlestr = title + ": " + titlestr 471 | ax1.set_title( titlestr ) 472 | ax1.grid(axis='both') 473 | #plt.legend() 474 | 475 | #leg = plt.legend() 476 | leg = ax1.legend( loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 477 | 478 | fig1.canvas.draw(); fig1.show() 479 | 480 | # return some figure handles 481 | if return_handles: return fig1, ax1, (l1, l2, vlines_out), leg 482 | 483 | #end if(comp='EigV') 484 | 485 | 486 | 487 | #----------------------------------- 488 | # 2nd case: Plot the Fields 489 | #----------------------------------- 490 | elif np.any( np.array(comp)==np.array(fieldstrs) ): 491 | # check if comp matches Any strings in `fieldstrs`, defined above the if(...), ln. 409 492 | 493 | # -- Plot fields in structure -- 494 | 495 | 496 | # 1st arg: Component string for plot legend: 497 | # (`comp` will be send to `get_field()` for parsing which field) 498 | if comp == 'Ex'.lower(): 499 | compstr='Ex' 500 | elif comp == 'Ey'.lower(): 501 | compstr='Ey' 502 | elif comp == 'Ez'.lower(): 503 | compstr='Ez' 504 | elif comp == 'Hx'.lower(): 505 | compstr='Hx' 506 | elif comp == 'Hy'.lower(): 507 | compstr='Hy' 508 | elif comp == 'Hz'.lower(): 509 | compstr='Hz' 510 | elif comp == 'I'.lower(): 511 | compstr='Intensity' 512 | elif comp=='rix' or comp=='index' or comp=='ri': 513 | compstr='Refr. Index' 514 | else: 515 | raise ValueError("CavityMode.plot(field): Invalid field component requested.") 516 | 517 | 518 | # get keyword arguments, with default: 519 | RIplot = kwargs.pop('refractive_index', False) # plot refractive index? 520 | zpoints = kwargs.pop('field_points', 3000) # number of points in field plot 521 | xcut = kwargs.pop('xpoint', 0.0) 522 | ycut = kwargs.pop('ypoint', 0.0) 523 | zmin = kwargs.pop('zmin', 0.0) 524 | zmax = kwargs.pop('zmax', (self.Cavity.LHS_Dev.get_length() + self.Cavity.RHS_Dev.get_length()) ) # default to total device length 525 | 526 | zpoints = math.ceil( zpoints/2. ) # half as many zpoints in each of the two Devs 527 | 528 | xpoints, ypoints = xcut, ycut # probably not needed - old method 529 | PlotPoints = zpoints # not needed 530 | 531 | """ 532 | dirstr = kwargs.pop('direction', None) 533 | if dirstr == None: 534 | dirstr = 'bwd' 535 | else: 536 | dirstr = dirstr.lower().strip() 537 | 538 | if dirstr=='fwd' or dirstr=='forwards' or dirstr=='f': 539 | dirstr = 'Fwg' 540 | elif dirstr=='bwd' or dirstr=='backwards' or dirstr=='b': 541 | if comp=='i': 542 | '''Due to Fimmwave typo bug: should be Title case. ''' 543 | dirstr = 'bwg' 544 | else: 545 | dirstr = 'Bwg' 546 | elif dirstr=='total' or dirstr=='tot' or dirstr=='t': 547 | dirstr = 'Total' 548 | 549 | fieldstr = compstr + dirstr #attribute of FimmWave `zfieldcomp` object 550 | """ 551 | 552 | # 2nd arg: Figure out array index to proper wavelength 553 | if len(args) >= 2: 554 | wl = args[1] 555 | else: 556 | ErrStr="Cavity.plot(): For plotting a field component, 2nd argument must be the wavelength to plot. Please see `help(CavityMode.plot)` for more info." 557 | raise ValueError(ErrStr) 558 | 559 | #if DEBUG(): print "CavityMode.plot(field): wl= ", wl 560 | 561 | zfield=[] # to hold fields at each mode number 562 | for num,M in enumerate(self.modenum): 563 | '''num goes from 0-># of modes requested. M tells use the actual mode number.''' 564 | 565 | if DEBUG(): print "CavityMode.plot(field): (num, M) = (", num, ",", M, ")" 566 | 567 | 568 | 569 | # find index to the specified wavelength in the list of calc'd wavelengths. 570 | # `wl` is the passed argument, `WL` is the final wavelength 571 | if isinstance(wl, str): 572 | '''if 2nd arg is a string: ''' 573 | wl = wl.lower().strip() # to lower case + strip whitespace 574 | if wl == 'resonance' or wl == 'res' or wl == 'max': 575 | '''Find the resonant wavelength/eigval/eigvector''' 576 | if DEBUG(): print "CavityMode.plot('res'): self.get_resonance_eigenvalues() = \n", self.get_resonance_eigenvalues() 577 | if DEBUG(): print "CavityMode.plot('res'): self.get_resonance_wavelengths() = \n", self.get_resonance_wavelengths() 578 | 579 | if np.all( np.array(self.__resonance_eigenvalue[num])==np.array([None]) ) or np.all( np.array(self.__resonance_wavelength[num])==np.array([None]) ): 580 | '''No resonance found for this mode''' 581 | ErrStr = "No resonance found for mode %i, "%(M) + "can't plot via `resonance`." 582 | raise UserWarning(ErrStr) 583 | 584 | # Find maximum Resonant EigenValue 585 | Iwl = np.argmax( np.real( self.__resonance_eigenvalue[num] ) ) 586 | 587 | WL = self.__resonance_wavelength[num][Iwl] 588 | Iwl = np.where( np.array([WL]) == self.wavelengths[:][num] )[0] # set to index of all calc'd WL's, not just resonance WLs 589 | print "CavityMode.plot('res'): Getting field at resonance mode @ %f nm" %( WL ) 590 | if DEBUG(): print "Iwl=%s\nWL=%s"%(Iwl,WL) 591 | else: 592 | raise ValueError("CavityMode.plot(field): Unrecognized wavelength string. Please use 'resonance' or provide a wavelength in microns. See `help(CavityMode.plot)` for more info.") 593 | else: 594 | '''A specific wavelength (float/number) must have been passed: ''' 595 | WL = wl 596 | Iwl = np.where( np.array([WL]) == self.wavelengths[num] )[0] # get index to specified wl 597 | if not Iwl: 598 | '''If wavelength not found in calculated WLs: ''' 599 | ErrStr = "CavityMode.plot(field): Wavelength `", WL, "` not found in the list of calculated wavelengths list (chosen during `Cavity.calc(wavelengths)`). See `help(CavityMode.plot)` for more info." 600 | raise ValueError(ErrStr) 601 | #end parsing `wl` 602 | 603 | 604 | 605 | if DEBUG(): print "CavityMode.plot(): (num,Iwl)=(",num,",",Iwl,") \n" +\ 606 | "Setting Wavelength to WL=%f um"%WL 607 | 608 | 609 | # Set FimmWave & Device wavelengths to proper value: 610 | print self.Cavity.name + ": Setting Global & Device wavelength to %0.8f."%(WL) 611 | set_wavelength(WL) 612 | self.Cavity.RHS_Dev.set_wavelength(WL) 613 | self.Cavity.LHS_Dev.set_wavelength(WL) 614 | 615 | 616 | EigVec = self.eigenvectors[num][Iwl[0]] # find eigenvector at given wavelength 617 | 618 | 619 | 620 | # Launch this eigenvector: 621 | norm = False # normalize the launch vectors? V.Brulis said to disable this 622 | self.Cavity.RHS_Dev.set_input( EigVec, side='left', normalize=norm ) 623 | self.Cavity.RHS_Dev.set_input( np.zeros( get_N() ), side='right' ) # no input from other side 624 | 625 | # Get mode vector reflected from RHS device & launch it into LHS dev, to accomplish one roundtrip 626 | vec = self.Cavity.RHS_Dev.get_output_vector(side='left', direction='left') 627 | self.Cavity.LHS_Dev.set_input( vec, side='right', normalize=norm ) 628 | self.Cavity.LHS_Dev.set_input( np.zeros( get_N() ), side='left' ) # no input from other side 629 | 630 | 631 | # Get field values: 632 | Lfielddir, Rfielddir = 'total','total' 633 | self.Cavity.LHS_Dev.calc(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut) 634 | Lfield = self.Cavity.LHS_Dev.get_field(comp, zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction=Lfielddir, calc=False) 635 | 636 | self.Cavity.RHS_Dev.calc(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut) 637 | Rfield = self.Cavity.RHS_Dev.get_field(comp, zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, direction=Rfielddir, calc=False) 638 | 639 | Lfield.extend(Rfield) # concatenate the L+R fields 640 | 641 | 642 | zfield.append(Lfield) # add field for this mode number 643 | 644 | #end for(modenums) 645 | 646 | ################################## 647 | # plot the field values versus Z: 648 | zfield = np.array(zfield) 649 | TotalLength = self.Cavity.LHS_Dev.get_length() + self.Cavity.RHS_Dev.get_length() 650 | z = np.linspace( 0, TotalLength, num=len(zfield[0]) ) # Z-coord 651 | 652 | if DEBUG(): print "CavityMode.plot(field): len(zfield[0])=%i"%(len(zfield[0]) ) + \ 653 | "np.shape(zfield)=", np.shape(zfield), "\nz(%i) = "%len(z), z 654 | 655 | lines=[] # to return 656 | if RIplot: 657 | Lindex = self.Cavity.LHS_Dev.get_refractive_index(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, calc=False) 658 | Rindex = self.Cavity.RHS_Dev.get_refractive_index(zpoints=zpoints, zmin=zmin, zmax=zmax, xcut=xcut, ycut=ycut, calc=False) 659 | Lindex.extend(Rindex) # concatenate the L+R indices 660 | 661 | rix=Lindex # add field for this mode number 662 | 663 | 664 | fig1, (ax1,ax2) = plt.subplots(2, sharex=True) # 2 axes 665 | axes=(ax1,ax2) # to return 666 | # Reduce axis width to 80% to accommodate legend: 667 | box = ax2.get_position() 668 | ax2.set_position([ box.x0, box.y0, box.width * 0.8, box.height]) 669 | 670 | l2 = [ ax2.plot(z, np.real( np.array(rix) ), 'g-', label="Refractive Index" ) ] # plot RIX on 2nd sibplot 671 | lines.append(l2) 672 | else: 673 | fig1, ax1 = plt.subplots(1, 1) # 1 axis 674 | axes=ax1 # to return 675 | 676 | # Reduce axis width to 80% to accommodate legend: 677 | box = ax1.get_position() 678 | ax1.set_position([ box.x0, box.y0, box.width * 0.8, box.height]) 679 | 680 | l1 = []; #l2 = [] 681 | for num,M in enumerate(self.modenum): 682 | '''num goes from 0-># of modes requested. M tells us the actual mode number.''' 683 | #if DEBUG(): print "CavityMode.plot(field): num in modenum = ", num, type(num), " in ", self.modenum, type(self.modenum) 684 | 685 | #l1 = []; l2 = []; leg1 = []; leg2=[] 686 | if DEBUG(): print "zfield[%i] = " %(num), zfield[num] 687 | l1.append( ax1.plot(z, np.real(zfield[num]), '-', label="%i: %s"%(self.modenum[num], compstr) ) ) 688 | lines.append(l1[num]) 689 | #leg1.append("Real") 690 | 691 | #end for(modenum) 692 | 693 | ax1.set_ylabel( "Field %s"%(compstr) ) 694 | 695 | titlestr = self.Cavity.name + ": %s vs. Z for Mode @ %0.2f $\mu{}m$"%(compstr,WL) 696 | if title: titlestr = title + ": " + titlestr 697 | fig1.suptitle( titlestr , fontsize=11) 698 | ax1.grid(axis='both') 699 | #plt.legend() 700 | 701 | if RIplot: 702 | ax2.set_ylabel('Refractive Index') 703 | ax2.set_xlabel(r"Z, ($\mu{}m$)") 704 | ax2.grid(axis='both') 705 | else: 706 | ax1.set_xlabel(r"Z, ($\mu{}m$)") 707 | 708 | #leg = plt.legend() 709 | leg = ax1.legend( loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 710 | #leg2 = ax2.legend( loc='upper left', bbox_to_anchor=(1, 1) , fontsize='small' ) 711 | 712 | fig1.canvas.draw(); fig1.show() 713 | 714 | # return some figure handles 715 | if return_handles: 716 | if RIplot: 717 | return fig1, axes, lines, leg 718 | else: 719 | return fig1, axes, lines, leg 720 | 721 | 722 | #end if(comp=='Ex, Ey etc.') 723 | 724 | 725 | else: 726 | '''If component specified is unrecognized: ''' 727 | ErrStr = "CavityMode.plot(): Invalid field component specified: `%s`. \n\tSee `help(pyFIMM.CavityMode.plot)`." %(args[0]) 728 | raise ValueError(ErrStr) 729 | #end if(component) 730 | 731 | 732 | if kwargs: 733 | '''If there are unused key-word arguments''' 734 | ErrStr = "WARNING: Cavity.plot(): Unrecognized keywords provided: {" 735 | for k in kwargs.iterkeys(): 736 | ErrStr += "'" + k + "', " 737 | ErrStr += "}. Continuing..." 738 | if warn: print ErrStr 739 | #end plot 740 | 741 | 742 | 743 | def get_resonance_wavelengths(self, ): 744 | '''Return the resonance wavelength for selected modes, as list, with each list index corresponding to the selected mode. Returns `None` if no resonances found.''' 745 | out = [] 746 | for num, M in enumerate(self.modenum): 747 | out.append( self.__resonance_wavelength[num] ) 748 | return out 749 | # alias to same function: 750 | get_resonance_wavelength = get_resonance_wavelengths 751 | 752 | 753 | def get_resonance_eigenvalues(self, ): 754 | '''Return the eigenvalue at the resonance wavelengths selected modes, as list, with each list index corresponding to the selected mode. Returns `None` if no resonances found.''' 755 | out = [] 756 | for num, M in enumerate(self.modenum): 757 | out.append( self.__resonance_eigenvalue[num] ) 758 | return out 759 | # alias to same function: 760 | get_resonance_eigenvalue = get_resonance_eigenvalues 761 | 762 | 763 | def get_resonance_eigenvectors(self, ): 764 | '''Return the eigenvector at the resonance wavelengths selected modes, as list, with each list index corresponding to the selected mode. Returns `None` if no resonances found.''' 765 | out = [] 766 | for num, M in enumerate(self.modenum): 767 | out.append( self.__resonance_eigenvector[num] ) 768 | return out 769 | # alias to same function: 770 | get_resonance_eigenvector = get_resonance_eigenvectors 771 | 772 | 773 | def get_cavity_losses_frac(self, ): 774 | '''Return the cavity loss (equivalent to threshold gain) for this mode, as a fractional power of the input mode (eigenvector). 775 | Eg. a value of 0.4 means that 40% of the power in this mode was lost. 776 | ''' 777 | #print "get_cavity_loss(): WARNING: Not implemented." 778 | out = [] 779 | for num, M in enumerate(self.modenum): 780 | val = self.__resonance_loss[num]**2 # convert amplitude to power 781 | out.append( val ) 782 | return out 783 | # alias to same function: 784 | get_cavity_loss_frac = get_cavity_losses_frac 785 | 786 | 787 | def get_cavity_losses_dB(self, ): 788 | '''Return the cavity loss (equivalent to threshold gain) for this mode, as a fractional power of the input mode (eigenvector) in dB. 789 | Eg. a value of +3.0 means that 3dB of the power in this mode was lost. 790 | ''' 791 | #print "get_cavity_loss(): WARNING: Not implemented." 792 | out = [] 793 | for L in self.get_cavity_losses_frac(): 794 | val = -10*np.log10( 1.0 - L ) # convert fractional power to dB 795 | out.append( val ) 796 | return out 797 | # alias to same function: 798 | get_cavity_loss_dB = get_cavity_losses_dB 799 | 800 | 801 | def get_cavity_losses_m(self, ): 802 | '''Return the cavity loss (equivalent to threshold gain) for this mode, as meter^-1. 803 | Eg. a value of 0.4 means that the cavity loss for this cavity mode is 0.4m^-1. 804 | ''' 805 | # alpha(cm^-1) = -ln(lambda)/(2*L[cm]) 806 | out = [] 807 | for num, M in enumerate(self.modenum): 808 | val = -1*np.log( (1.0-self.__resonance_loss[num]) ) / ( 2* self.Cavity.get_length() ) # length is in meters 809 | out.append( val ) 810 | return out 811 | # alias to same function: 812 | get_cavity_loss_m = get_cavity_losses_m 813 | 814 | 815 | def get_cavity_losses_cm(self, ): 816 | '''Return the cavity loss (equivalent to threshold gain) for this mode, as centimeter^-1. 817 | Eg. a value of 0.4 means that the cavity loss for this cavity mode is 0.4 cm^-1. 818 | ''' 819 | out = [] 820 | for L in self.get_cavity_losses_m(): 821 | val = L / 100 # convert from m^-1 --> cm^-1 822 | out.append( val ) 823 | return out 824 | # alias to same function: 825 | get_cavity_loss_cm = get_cavity_losses_cm 826 | 827 | --------------------------------------------------------------------------------