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:
- Import the Tkinter library.
- Create the main Tk window.
- Define a bunch of widgets and add them to the main Tk window.
- Start the main loop that draws the widgets and performs
actions when the user interacts with the widgets.
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,
And that the eigenvectors are
that describe sinusoidal motions of the form
.
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
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
- extracts values from the scale widgets to set the initial
values and duration of the integration interval,
- does the numerical integration by calling solve_ivp
and using the function dfun, which should be familiar
from a previous lecture,
- translated the integration outputs into more convenient
variables,
- calls curve_fit to fit the position versus time data for mass
#1 to the cosine form discussed above and defined in the
function eigenf,
- writes the fitted amplitudes and phases to the label widgets,
- plots the output from the numerical integration as points,
- plots the fitted eigenmodes for mass #1 as a curve.
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.