Scientific Computing Using Python - PHYS:4905 - Fall 2018

Lecture Notes #24- 12/4/2018 - Prof. Kaaret


Graphical user interfaces in Python

All of the code that we have written to date uses a command line interface.  This is an extremely useful interface for scientific programming as it allows a tremendous amount of control and also enables you to keep a record of all actions performed.  Most of the time when doing research with Python, you'll want to use the command line interface.

However, sometimes it is nice to interact more rapidly with your calculations.  For that, we can use a graphical user interface or GUI (said "gooey", as in "I prefer gooey brownies" ).  You are all familiar with GUI's as that is how you interact with almost all modern computers, tables, and smartphones.  They are better for interaction, but tend to restrict that interaction.

GUIs tend to be built from elements called widgets.  For instance, if you bring up the dialer on your phone, each numerical digit is a widget, the 'Call' button is a widget, and the display where the phone number builds up as you type it is a widget.  When you press on a widget (or if you are on a computer and point your mouse cursor at the widget and click) then some action occurs.  For example, if you press a digit, then that digit is added to the number in the display.  Some widgets do not have any associated action, but are instead only for display.  I would say that the phone number display is such a widget, but usually pressing on the phone number display brings up a pop-up window that allows you to copy the number.  Note that widgets are also sometimes used to prefer to entire small applications, but here we will use the term only to refer to individual elements in a GUI.

There are lots and lots of different 'toolkits' that provide widgets.  We will use one called Tk.  Tk is free and open-source.  It has been ported to lots of platforms including Linux, Mac OS, Unix, and Microsoft Windows.  Tk was originally written in 1991 by John Ousterhout, who got his bachelors in Physics.  Tk is a toolkit that can be used with many programming languages and has an associated interpreter that carries out commands call Tcl.

To use Tk with Python, we need a Python library that provides an interface between Python and Tk/Tcl.  We will use 'Tkinter' which is short for Tk interface.  There are several GUIs available for Python, but Tkinter is the most commonly used.  It is relatively straightforward to use (you will disagree with me on that for the rest of today's lecture, but may agree at some point in the future after you get familiar with it.  Tkinter is included with most Linux, Microsoft Windows and Mac OS X installs of Python.  One the best introductions to Tkinter is An Introduction to Tkinter at
http://effbot.org/tkinterbook/.  Oddly, it was last updated in 2005, but is still marked as a work in progress.  Fortunatly, Tkinter hasn't changed much since 2005.

Your first GUI

We have to set up Spyder to work with Tkinter.  Go to Tools -> Preferences -> IPython console -> Graphics, and select Tkinter from the Graphics backend drop down item.  Then quit Spyder and restart it.

Now copy the following program into Spyder.

# make Python 2.7 act like Python 3
from __future__ import division, print_function
input = raw_input

import Tkinter as tk # import tkinter library

root = tk.Tk() # setup tk root window

# define a scale widget that allows you to adjust a value graphically
var = tk.DoubleVar() # define the variable controlled by the widget
var.set(1) # set the scale widget to 1 at the start
scale = tk.Scale(root, variable = var, label='Scale') # create the scale widget
scale.pack(anchor=tk.CENTER) # put the widget into the window

# define a button widget that prints the value of the scale widget when pressed
# define the action carried out the by button
def button_action():
  print('Value = ', scale.get())
# create the button widget
button = tk.Button(root, text="Get Scale Value", command=button_action)
button.pack(anchor=tk.CENTER) # put the widget into the window

# define a label widget to display the value of the scale widget
label = tk.Label(root, textvariable=var)
label.pack() # put the widget into the window

# the following command displays the GUI and
# loops to carry out interaction with the user
root.mainloop()

The essential parts of this program are:
Each of these bullets except for the third is one line of code.  Defining the widgets takes up most of the program.  We define three widgets. 

The first is a scale widget that allows you to set a value by moving a slider.  We first create a variable to hold the output of the scale (tk.DoubleVar), then create a Python object for the scale widget (tk.Scale), and finally put that widget into the main Tk window (scale.pack).  To read more about scale widgets, see http://effbot.org/tkinterbook/scale.htm.

The second is a button widget that we have set up to print out the value of the scale widget when it is pressed.  In our example, this is a bit redundant because we have set up the scale widget to display the value itself.  However, it is an example of how to write your own code linked to user interactions.  You need to define a function that carries out the action (def button_action) and then reference that function when you define the button widget.

The last widget is a label widget.  This doesn't perform any action, but just displays the value of the scale widget, yet again.

Try running the code in Spyder.  Note that you need to set up the graphics interface of Spyder to get it to work.  Also, there is no way to exit the GUI.  If you want to stop the GUI from running, you need to click the options gear at the right edge of the Spyder console pane and then click on 'Restart kernel'.  Unfortunately, this seems to be true even if you include a quit function in your GUI as we do in the next example.  So, please remember how to restart your kernel.


Coupled oscillator GUI

Let's look at a program that provides a graphical interface to a program that numerically solves the equations of motion for two coupled oscillators, plots the solution, fits the eigenfunctions to the solution, and then prints the amplitude and phase of each eigenfunction.

Let's review the setup, which is from lecture #22.   There are two masses, each with mass m, attached by springs with spring constants as shown in the figure below with x1 being the displacement of the first mass from its equilibrium position and x2 the displacement of the second mass from its equilibrium position.
 


In lecture #23, we wrote code to numerically calculate code to solve the equations of motion of this system.  We set the problem as an initial value problem with initial values being the positions and velocities of the two masses.  We reuse that code here and add a graphical interface that allows you to set the initial values using sliders (scales in Tkinter terminology) and set the time interval for the calculation using another slider.  The code then calculates the solution to the equations of motion and plots the output from the numerical integration as points.

The code is here in gui_oscillators.py.  Download it to your machine and give it a try.  In the code, we define m = 1 kg, k = 2.0 N/m, and κ = 2.0 N/m.

Decomposing the motion as eigenfuctions

In lecture #22, we found that this system has two eigenvalues,

ω1=km,ω2=k+2κmω_1 = \sqrt{\frac{k}{m}} \; , \;\; ω_2 = \sqrt{\frac{k+2κ}{m}}
And that the eigenvectors are

v1=(c1c2)=(11),v2=(1-1)v_1 = \begin{pmatrix} c_1 \\ c_2 \end{pmatrix} = \begin{pmatrix} 1 \\ 1 \end{pmatrix} \, , \;\;\;\; v_2 = \begin{pmatrix} 1 \\ -1 \end{pmatrix}
that describe sinusoidal motions of the form x1=c1eiωt,x2=c2eiωtx_1 = c_1 e^{i ω t}, \;\; x_2 = c_2 e^{i ω t}.  Note that in these solutions, ci is a complex number and we take the only real part of the right hand side of the equation.  Allowing complex values for c and taking the real part is same as writing the function in the form

x(t)=Acos[ω(t-t0)]x(t) = A \, \cos[ω(t-t_0)]
but the math is easier.  The complex part of c is a phase factor that tells us the time of maximum displacement.  This form of the displacement breaks c up into the amplitude, A, and the phase, t0,  which is the time of maximum displacement.  Our GUI is set up so that it takes the calculated position of the first mass, fits those (simulated) data to the sum of two cosine function with the form given above and with the eigenfrequencies calculated from the equations above, and then displays the amplitudes and phases for each eigenmode.  This provides us with a way to decompose the motion in terms of the two eigenmodes.  There is a line of text for each eigenmode with the calculated frequency and the amplitude and phase from the fit to the data.  To graphically check the fit, the GUI also plots the motion calculated from the eigenmode fit and plots it as a continuous curve (only for mass #1).  If the fit is good, then the curve should lie on top of the data points.
 
The first eigenvalue/vector pair is the slower mode of oscillation where the masses move in the same direction and with the same amplitude.  The second is the faster mode of oscillation where the masses move in opposite directions, but with the same amplitude. 

From the masses and spring constants given above, what do you calculate for the eigenfrequencies?  Do your calculations match the values displayed in the GUI?

Set up the GUI so that the masses move in the first (slower) eigenmode.  What are the amplitudes for the two modes?  Does that make sense?

Set up the GUI so that the masses move in the second (facter) eigenmode.  What are the amplitudes for the two modes?  Does that make sense?

Take some time to inspect the code.  The main body of the code sets up the widgets.  Instead of the pack function, we use the grid function to place the widgets on the main Tk windows because the GUI is more complex than our first example.  In general, you'll be less frustrated for GUIs with more than a few elements if you use grid.  We use the same scale, button, and label widgets as in the simple example.  We also use a more complex canvas widget to hold the plot.  In setting this sort of stuff up, it is best to work from an example.  Hopefully, you'll be able to use this example as a starting point for your own GUIs.  If you need more functionality, it's best to search on the collection of tubes known as the internet for examples, but you can read the Tkinter documentation as a last resort.

The body of our code is the function update.  This function
Note there is an asterisk in the definition of the arguments for update.  The asterisk allows a variable number of parameters.  This is needed because the command functions for scale widgets are passed one argument (the scale value), while the command functions for button widgets are passed zero parameters.

Also, the code is a bit unclean in that the parameters of the system (mass and spring constants) are passed as global variables from the main body of the code into the update function.  A better way to code this would be to set up the GUI as a class.  That would allow multiple windows to be defined simultaneously with different physical parameters.  We have run out of time this semester, but the next logical topic in Python programming is the use of classes and object oriented programming.