#!/usr/bin/env python # -*- coding: utf-8 -*- """ This program produces a graphical demonstration of the reflection and refraction from an interface between two complex valued materials. Daniel Sjoberg, 2011-09-03 """ import wx ############################### # Quote from Eli Bendersky's code wx_mpl_bars.py 2008-07-30: "The # recommended way to use wx with mpl is with the WXAgg backend." # It seems the backend must be set before importing pylab. import matplotlib matplotlib.use('WXAgg') from matplotlib.figure import Figure from matplotlib.backends.backend_wxagg import \ FigureCanvasWxAgg as FigCanvas, \ NavigationToolbar2WxAgg as NavigationToolbar ############################### from pylab import * from numpy.lib.scimath import sqrt # Use complex sqrt by default matplotlib.interactive(True) from enthought.mayavi import mlab from enthought.tvtk.tools import visual # Necessary to get greek letters in figure xlabel rcParams['font.sans-serif'] = 'DejaVu Sans' # Set up some unicode strings so that labels using greek letters # and subscripts display properly epsilonchar = u'\N{GREEK SMALL LETTER EPSILON}' muchar = u'\N{GREEK SMALL LETTER MU}' betachar = u'\N{GREEK SMALL LETTER BETA}' alphachar = u'\N{GREEK SMALL LETTER ALPHA}' sub0char = u'\N{SUBSCRIPT ZERO}' sub1char = u'\N{SUBSCRIPT ONE}' sub2char = u'\N{SUBSCRIPT TWO}' subxchar = u'\N{LATIN SUBSCRIPT SMALL LETTER X}' eps1abstitle = 'abs(' + epsilonchar + sub1char + ')' eps1phititle = '-arg(' + epsilonchar + sub1char + ')' mu1abstitle = 'abs(' + muchar + sub1char + ')' mu1phititle = '-arg(' + muchar + sub1char + ')' eps2abstitle = 'abs(' + epsilonchar + sub2char + ')' eps2phititle = '-arg(' + epsilonchar + sub2char + ')' mu2abstitle = 'abs(' + muchar + sub2char + ')' mu2phititle = '-arg(' + muchar + sub2char + ')' betaxoverk0title = betachar + subxchar + '/k' + sub0char alphaxoverk0title = alphachar + subxchar + '/k' + sub0char k0title = 'k' + sub0char # Data describing the variables: # (name, unit, scale, start value, end value, initial value) variables_info = [(eps1abstitle, '', 1, 1, 2, 1.0), (eps1phititle, '', 1, 0, 180, 0.0), (mu1abstitle, '', 1, 1, 2, 1.0), (mu1phititle, '', 1, 0, 180, 0.0), (eps2abstitle, '', 1, 1, 2, 2.0), (eps2phititle, '', 1, 0, 180, 0.0), (mu2abstitle, '', 1, 1, 2, 1.0), (mu2phititle, '', 1, 0, 180, 0.0), (betaxoverk0title, '', 1, 0, 1, 0.5), (alphaxoverk0title, '', 1, 0, 1, 0.0), (k0title, '', 1, 0, 3, 1.5)] class PlotPanel(wx.Panel): """This class holds the plot window, using matplotlib as backend.""" def __init__(self, parent): wx.Panel.__init__(self, parent) self.parent = parent self.Npoints = 1000 self.ydata = linspace(0, 1, self.Npoints) self.xdata = linspace(0, 1, self.Npoints) # Variable to plot against self.kx = 1 + 0j self.kzi = 1 + 0j self.kzr = -1 + 0j self.kzt = 1 + 0j self.rhoTE = 0 + 0j self.rhoTM = 0 + 0j self.phi = linspace(0, 2*pi, 30) self.dpi = 100 self.fig = Figure((4.0, 4.0), dpi=self.dpi) self.canvas = FigCanvas(self, -1, self.fig) self.axes = self.fig.add_subplot(111) self.fig.subplots_adjust(hspace=0.8) self.toolbar = NavigationToolbar(self.canvas) self.xlabel = '' self.ylabel = '' self.GridOn = True vSizer = wx.BoxSizer(wx.VERTICAL) vSizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) vSizer.Add(self.toolbar, 0, wx.EXPAND) vSizer.AddSpacer(10) self.SetSizer(vSizer) vSizer.Fit(self.parent) self.Update() def PlotArrow(self, dx, dy, side='left', col='blue', fill=True): w = 0.03 if side == 'left': if dx>0: self.axes.arrow(-dx, -dy, dx, dy, color=col, width=w, fill=fill) else: self.axes.arrow(0, 0, dx, dy, color=col, width=w, fill=fill) else: if dx>0: self.axes.arrow(0, 0, dx, dy, color=col, width=w, fill=fill) else: self.axes.arrow(-dx, -dy, dx, dy, color=col, width=w, fill=fill) def Update(self): self.axes.clear() self.axes.grid(self.GridOn) # self.axes.plot(self.xdata, self.ydata, 'b-', linewidth=2) self.axes.plot([0, 0], [-1, 1], 'k-', linewidth=2) # Add arrows for the wave vectors self.PlotArrow(real(self.kzi), real(self.kx), side='left', col='blue', fill=True) self.PlotArrow(real(self.kzr), real(self.kx), side='left', col='green', fill=True) self.PlotArrow(real(self.kzt), real(self.kx), side='right', col='red', fill=True) self.PlotArrow(-imag(self.kzi), -imag(self.kx), side='left', col='blue', fill=False) self.PlotArrow(-imag(self.kzr), -imag(self.kx), side='left', col='green', fill=False) self.PlotArrow(-imag(self.kzt), -imag(self.kx), side='right', col='red', fill=False) # Add reflection coefficients in Smith chart self.axes.plot(0.75 + 0.25*cos(self.phi), -0.75 + 0.25*sin(self.phi), 'k-', linewidth=1) self.axes.plot(0.75, -0.75, 'k+', linewidth=1) self.axes.plot(0.75 + 0.25*real(self.rhoTE), -0.75 + 0.25*imag(self.rhoTE), 'cx', linewidth=1) self.axes.plot(0.75 + 0.25*real(self.rhoTM), -0.75 + 0.25*imag(self.rhoTM), 'mx', linewidth=1) self.axes.set_xlim([-1, 1]) self.axes.set_ylim([-1, 1]) # self.axes.set_xlabel(self.xlabel) # self.axes.set_ylabel(self.ylabel) # self.axes.legend(loc='best') self.canvas.draw() class TextPanel(wx.Panel): """A simple panel where a parameter can be set.""" def __init__(self, parent, title, value): wx.Panel.__init__(self, parent) self.parent = parent self.label = wx.StaticText(self, -1, title) self.input = wx.TextCtrl(self, wx.ID_ANY, str(value), style=wx.TE_PROCESS_ENTER) self.input.Bind(wx.EVT_KILL_FOCUS, self.Update) self.input.Bind(wx.EVT_TEXT_ENTER, self.Update) vsizer = wx.BoxSizer(wx.VERTICAL) vsizer.Add(self.label, 0, wx.ALIGN_CENTER | wx.ALL, 0) vsizer.Add(self.input, 0, wx.ALIGN_CENTER | wx.ALL, 0) self.SetSizer(vsizer) def GetValue(self): return float(self.input.GetValue()) def Update(self, event=None): event.Skip() class MySlider(wx.Panel): """Create a slider where min and max values can be set.""" def __init__(self, parent, labelname, min, max, rbstyle=None): wx.Panel.__init__(self, parent) self.parent = parent self.labelname = labelname self.labelformat = "%s = %.2f" self.width = 250 labeltext = self.labelformat % (self.labelname, (min+max)/2.0) self.label = wx.StaticText(self, -1, labeltext) self.min = min self.max = max self.slider = wx.Slider(self, -1, 50, 0, 100, (0,0), (self.width,20)) self.mininput = wx.TextCtrl(self, wx.ID_ANY, str(min), style=wx.TE_PROCESS_ENTER, size=(50,-1)) self.maxinput = wx.TextCtrl(self, wx.ID_ANY, str(max), style=wx.TE_PROCESS_ENTER, size=(50,-1)) self.slider.Bind(wx.EVT_SLIDER, self.Update) self.mininput.Bind(wx.EVT_KILL_FOCUS, self.Update) self.maxinput.Bind(wx.EVT_KILL_FOCUS, self.Update) self.mininput.Bind(wx.EVT_TEXT_ENTER, self.Update) self.maxinput.Bind(wx.EVT_TEXT_ENTER, self.Update) self.slider.SetToolTip(wx.ToolTip('Slide to set variable')) self.mininput.SetToolTip(wx.ToolTip('Minimum value of variable')) self.maxinput.SetToolTip(wx.ToolTip('Maximum value of variable')) vsizer = wx.BoxSizer(wx.VERTICAL) vsizer.Add(self.slider, 0, wx.ALIGN_CENTER | wx.ALL, 0) hsizer = wx.BoxSizer(wx.HORIZONTAL) hsizer.SetMinSize((self.width,20)) hsizer.Add(self.mininput, 0, wx.ALIGN_LEFT | wx.ALL, 0) hsizer.AddStretchSpacer() hsizer.Add(self.label, 0, wx.ALIGN_CENTER | wx.ALL, 0) hsizer.AddStretchSpacer() hsizer.Add(self.maxinput, 0, wx.ALIGN_RIGHT | wx.ALL, 0) vsizer.Add(hsizer, 0, wx.ALIGN_CENTER | wx.ALL, 0) self.SetSizer(vsizer) def GetValue(self): slidervalue = float(wx.Slider.GetValue(self.slider)) scale = (self.max-self.min)/100.0 value = slidervalue*scale+self.min return value def SetValue(self, value): slidervalue = round((value-self.min)/(self.max-self.min)*100.) if slidervalue < 0: slidervalue = 0 elif slidervalue > 100: slidervalue = 100 self.slider.SetValue(slidervalue) self.Update() def Update(self, event=None): self.min = float(self.mininput.GetValue()) self.max = float(self.maxinput.GetValue()) self.value = self.GetValue() labeltext = self.labelformat % (self.labelname, self.value) self.label.Label = labeltext if event: event.Skip() class VariablesPanel(wx.Panel): """Create a panel with input widgets.""" def __init__(self, parent): wx.Panel.__init__(self, parent) self.parent = parent Nvars = len(variables_info) self.Sliders = [] self.variableSizers = [] for n in arange(0, Nvars): name, unit, scale, start, stop, initial = variables_info[n] self.Sliders.append(MySlider(self, name, start, stop)) self.variableSizers.append(wx.BoxSizer(wx.HORIZONTAL)) self.Sliders[n].Bind(wx.EVT_SLIDER, self.Update) self.Sliders[n].SetValue(initial) self.variableSizers[n].Add(self.Sliders[n]) vSizer = wx.BoxSizer(wx.VERTICAL) title = wx.StaticText(self, -1, 'Variables') title.SetFont(wx.Font(10, -1, wx.NORMAL, wx.BOLD)) vSizer.Add(title, 0, wx.ALIGN_CENTER | wx.ALL, 5) vSizer1 = wx.BoxSizer(wx.VERTICAL) for n in arange(0, 4): vSizer1.Add(self.variableSizers[n], 0, wx.ALIGN_CENTER | wx.ALL, 5) vSizer2 = wx.BoxSizer(wx.VERTICAL) for n in arange(4, 8): vSizer2.Add(self.variableSizers[n], 0, wx.ALIGN_CENTER | wx.ALL, 5) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(vSizer1, 0, wx.ALIGN_CENTER | wx.ALL, 5) hSizer.Add(vSizer2, 0, wx.ALIGN_CENTER | wx.ALL, 5) vSizer.Add(hSizer, 0, wx.ALIGN_CENTER | wx.ALL, 5) for n in arange(8, Nvars): vSizer.Add(self.variableSizers[n], 0, wx.ALIGN_CENTER | wx.ALL, 5) self.SetSizer(vSizer) self.variables = [] for n in arange(0, Nvars): self.variables.append(self.Sliders[n].GetValue()) self.Update() def Update(self, event=None): for n in arange(0, len(variables_info)): self.variables[n] = self.Sliders[n].GetValue() if event: event.Skip() class InputPanel(wx.Panel): """Create a panel with input widgets.""" def __init__(self, parent): wx.Panel.__init__(self, parent) self.parent = parent self.VariablesPanel = VariablesPanel(self) vSizer = wx.BoxSizer(wx.VERTICAL) vSizer.Add(self.VariablesPanel, 0, wx.ALIGN_CENTER | wx.ALL, 5) self.SetSizer(vSizer) class MyFrame(wx.Frame): def __init__(self, parent, title): """Build the GUI.""" wx.Frame.__init__(self, parent, -1, title, size=(1000,600)) self.SetBackgroundColour('Normal') MenuBar = wx.MenuBar() FileMenu = wx.Menu() item = FileMenu.Append(wx.ID_EXIT, text="&Quit") self.Bind(wx.EVT_MENU, self.OnQuit, item) MenuBar.Append(FileMenu, "&File") ApplicationsMenu = wx.Menu() item = ApplicationsMenu.Append(-1, text="&Zenneck") self.Bind(wx.EVT_MENU, self.OnZenneck, item) item = ApplicationsMenu.Append(-1, text="&Plasmon") self.Bind(wx.EVT_MENU, self.OnPlasmon, item) MenuBar.Append(ApplicationsMenu, "&Applications") HelpMenu = wx.Menu() item = HelpMenu.Append(wx.ID_HELP, text="&About") self.Bind(wx.EVT_MENU, self.OnAbout, item) MenuBar.Append(HelpMenu, "&Help") self.SetMenuBar(MenuBar) self.ScrollWindow = wx.ScrolledWindow(self) self.MainPanel = MainPanel(self.ScrollWindow) width, height = self.MainPanel.GetSize() dpixel = 20 self.ScrollWindow.SetScrollbars(dpixel, dpixel, round(width/dpixel), round(height/dpixel)) def OnAbout(self, event=None): """Display some information about the program.""" wx.MessageBox(u"""This program helps visualize the oblique incidence of a plane wave on an interface at fixed frequency. The arrows correspond to the wave vectors, filled arrow for real part (beta), contoured arrow for imaginary part (alpha). The TE and TM reflection coefficients are plotted in the circle in the lower right corner. The waves can be visualized using the button below the sliders. Daniel Sjöberg , 2011-09-03.""", 'Info', wx.OK, self) def OnQuit(self, event=None): """End the GUI.""" ret = wx.MessageBox('Are you sure you want to quit?', 'Question', wx.YES_NO | wx.NO_DEFAULT, self) if ret == wx.YES: self.Close() if event: event.Skip() def OnZenneck(self, event=None): """Set parameters for Zenneck wave.""" eps1abs = 1 eps1phi = 0 mu1abs = 1 mu1phi = 0 eps2abs = self.MainPanel.InputPanel.VariablesPanel.variables[4] eps2phi = self.MainPanel.InputPanel.VariablesPanel.variables[5] mu2abs = 1 mu2phi = 0 eps1 = eps1abs*exp(-1j*eps1phi*pi/180.) eps2 = eps2abs*exp(-1j*eps2phi*pi/180.) kxoverk0 = sqrt(eps1*eps2)/sqrt(eps1+eps2) betaxoverk0 = real(kxoverk0) alphaxoverk0 = -imag(kxoverk0) k0 = 1.5 # Set the parameters locvars = [eps1abs, eps1phi, mu1abs, mu1phi, eps2abs, eps2phi, mu2abs, mu2phi, betaxoverk0, alphaxoverk0, k0] for n in arange(0, len(variables_info)): self.MainPanel.InputPanel.VariablesPanel.Sliders[n].SetValue(locvars[n]) self.MainPanel.Update() self.MainPanel.InputPanel.VariablesPanel.Update() self.MainPanel.PlotPanel.Update() if event: event.Skip() def OnPlasmon(self, event=None): """Set parameters for a plasmon wave.""" eps1abs = 1 eps1phi = 0 mu1abs = 1 mu1phi = 0 eps2abs = self.MainPanel.InputPanel.VariablesPanel.variables[4] eps2phi = 180 mu2abs = 1 mu2phi = 0 eps1 = eps1abs*exp(-1j*eps1phi*pi/180.) eps2 = eps2abs*exp(-1j*eps2phi*pi/180.) kxoverk0 = sqrt((eps1*eps2)/(eps1+eps2)) betaxoverk0 = real(kxoverk0) alphaxoverk0 = -imag(kxoverk0) k0 = 1.5 # Set the parameters locvars = [eps1abs, eps1phi, mu1abs, mu1phi, eps2abs, eps2phi, mu2abs, mu2phi, betaxoverk0, alphaxoverk0, k0] for n in arange(0, len(variables_info)): self.MainPanel.InputPanel.VariablesPanel.Sliders[n].SetValue(locvars[n]) self.MainPanel.Update() self.MainPanel.InputPanel.VariablesPanel.Update() self.MainPanel.PlotPanel.Update() if event: event.Skip() class MainPanel(wx.Panel): def __init__(self, parent): """Build the top panel to hold everything.""" wx.Panel.__init__(self, parent) self.parent = parent self.parent.Bind(wx.EVT_MENU, self.Update) self.InputPanel = InputPanel(self) self.InputPanel.Bind(wx.EVT_SLIDER, self.Update) self.InputPanel.Bind(wx.EVT_RADIOBUTTON, self.Update) self.InputPanel.Bind(wx.EVT_KILL_FOCUS, self.Update) self.InputPanel.Bind(wx.EVT_TEXT_ENTER, self.Update) self.InputPanel.Bind(wx.EVT_CHECKBOX, self.Update) self.PlotPanel = PlotPanel(self) self.Title = wx.StaticText(self, -1, 'Oblique incidence on a boundary') self.Title.SetFont(wx.Font(12, -1, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) self.PlotInc = wx.CheckBox(self, -1, 'Incident', (10,10)) self.PlotRef = wx.CheckBox(self, -1, 'Reflected', (10,10)) self.PlotTra = wx.CheckBox(self, -1, 'Transmitted', (10,10)) self.PlotInc.SetValue(True) self.PlotRef.SetValue(True) self.PlotTra.SetValue(True) WaveSizer = wx.BoxSizer(wx.VERTICAL) WaveSizer.Add(wx.StaticText(self, -1, 'Waves to plot'), 0,wx.ALIGN_LEFT) WaveSizer.Add(self.PlotInc, 0, wx.ALIGN_LEFT) WaveSizer.Add(self.PlotRef, 0, wx.ALIGN_LEFT) WaveSizer.Add(self.PlotTra, 0, wx.ALIGN_LEFT) self.TE = wx.RadioButton(self, -1, 'TE', (10,10)) self.TM = wx.RadioButton(self, -1, 'TM', (10,10)) PolSizer = wx.BoxSizer(wx.VERTICAL) PolSizer.Add(wx.StaticText(self, -1, 'Polarization'), 0, wx.ALIGN_LEFT) PolSizer.Add(self.TE, 0, wx.ALIGN_LEFT) PolSizer.Add(self.TM, 0, wx.ALIGN_LEFT) self.RunButton = wx.Button(self, id=-1, label='Run!') self.RunButton.Bind(wx.EVT_BUTTON, self.Simulate) SimSizer = wx.BoxSizer(wx.HORIZONTAL) SimSizer.Add(WaveSizer, 10, wx.ALIGN_TOP) SimSizer.Add(PolSizer, 10, wx.ALIGN_TOP) SimSizer.Add(self.RunButton, 10, wx.ALIGN_CENTER) InputSizer = wx.BoxSizer(wx.VERTICAL) InputSizer.Add(self.InputPanel, 0, wx.ALIGN_CENTER) InputSizer.Add(SimSizer, 0, wx.ALIGN_CENTER) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(self.PlotPanel, 0, wx.ALIGN_CENTER) hSizer.Add(InputSizer, 0, wx.ALIGN_CENTER) vSizer = wx.BoxSizer(wx.VERTICAL) vSizer.Add(self.Title, 0, wx.ALIGN_CENTER | wx.ALL, 10) vSizer.Add(hSizer, 0, wx.ALIGN_CENTER) self.SetSizerAndFit(vSizer) self.KeyPressed = False self.StopButtonPressed = False self.Update() def ComputeWaveParameters(self, event=None): """Compute the wave parameters.""" locvars = [] for nn in arange(0, len(variables_info)): locvars.append(self.InputPanel.VariablesPanel.variables[nn]) eps1abs, eps1phi, mu1abs, mu1phi, \ eps2abs, eps2phi, mu2abs, mu2phi, \ betaxoverk0, alphaxoverk0, k0 = locvars eps1 = eps1abs*exp(-1j*eps1phi*pi/180) mu1 = mu1abs*exp(-1j*mu1phi*pi/180) eps2 = eps2abs*exp(-1j*eps2phi*pi/180) mu2 = mu2abs*exp(-1j*mu2phi*pi/180) kx = k0*(betaxoverk0 - 1j*alphaxoverk0) kz1 = -1j*sqrt(-k0**2*eps1*mu1 + kx**2) kz2 = -1j*sqrt(-k0**2*eps2*mu2 + kx**2) eta1TE = k0*mu1/kz1 eta2TE = k0*mu2/kz2 eta1TM = kz1/(k0*eps1) eta2TM = kz2/(k0*eps2) rhoTE = (eta2TE - eta1TE)/(eta2TE + eta1TE) rhoTM = (eta2TM - eta1TM)/(eta2TM + eta1TM) return(kx, kz1, kz2, rhoTE, rhoTM) def Simulate(self, event=None): """Run the 3D visualization.""" kx, kz1, kz2, rhoTE, rhoTM = self.ComputeWaveParameters(self) if self.TE.GetValue(): rho = rhoTE else: rho = rhoTM tau = 1 + rho if self.PlotInc.GetValue(): PlotInc = 1 else: PlotInc = 0 if self.PlotRef.GetValue(): PlotRef = 1 else: PlotRef = 0 if self.PlotTra.GetValue(): PlotTra = 1 else: PlotTra = 0 x0 = 10 dx = 0.1 z0 = 10 dz = 0.1 [z1, x1] = mgrid[-z0:0+dz:dz, -x0:x0:dx] [z2, x2] = mgrid[ 0:z0+dz:dz, -x0:x0:dx] wave1 = PlotInc*exp(-1j*(kz1*z1 + kx*x1)) \ + PlotRef*rho*exp(-1j*(-kz1*z1 + kx*x1)) wave2 = PlotTra*tau*exp(-1j*(kz2*z2 + kx*x2)) scale = amax(abs(array([wave1, wave2]))) fig = mlab.figure(size=(500,500)) visual.set_viewer(fig) s1 = mlab.surf(z1, x1, real(wave1)/scale, vmin=-1, vmax=1) s2 = mlab.surf(z2, x2, real(wave2)/scale, vmin=-1, vmax=1) mlab.outline(extent=[0, 0, -x0, x0, -1, 1]) mlab.view(-70, 50) @mlab.show @mlab.animate(delay=20) def anim(): while True: for phi in linspace(0, 2*2*pi, 20): fig.scene.disable_render = True # For smoother rendering s1.mlab_source.scalars = real(wave1*exp(1j*phi))/scale s2.mlab_source.scalars = real(wave2*exp(1j*phi))/scale fig.scene.disable_render = False # For smoother rendering yield anim() if event: event.Skip() def OnKey(self, event): keycode = event.GetKeyCode() print keycode if keycode == wx.WXK_ESCAPE: self.KeyPressed = True event.Skip() def Update(self, event=None): """Send parameters to the plotting window.""" kx, kz1, kz2, rhoTE, rhoTM = self.ComputeWaveParameters(self) self.PlotPanel.kx = kx self.PlotPanel.kzi = kz1 self.PlotPanel.kzr = -kz1 self.PlotPanel.kzt = kz2 self.PlotPanel.rhoTE = rhoTE self.PlotPanel.rhoTM = rhoTM self.PlotPanel.Update() if event: event.Skip() if __name__ == '__main__': app = wx.PySimpleApp() frame = MyFrame(None, title='Oblique incidence on interface') frame.Show() app.MainLoop()