Working with PyQt: an empty GUI window

The following code walks you through the steps to create an empty GUI window.

# basic_window.py
# Import necessary modules
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class EmptyWindow(QWidget):
    '''
    Create a new class, EmptyWindow, which inherits from other PyQt modules, 
    in this case from the QWidget class. 
    Inheriting from QWidget means we have access to all the attributes 
    to create GUIs using PyQt, but we also can add our own variables and 
    methods in our new class.
    '''
    def __init__(self):
        super().__init__() 
        '''
        create default constructor for QWidget; super is used to access methods 
        from parent class
        '''
        self.initializeUI()
        
    def initializeUI(self):
        """
        Initialize the window and display its contents to the screen.
        """
        self.setGeometry(100, 100, 400, 300)
        self.setWindowTitle('Empty Window in PyQt')
        self.show()

# Run program
if __name__ == '__main__':
    app = QApplication(sys.argv) # Create instance of QApplication class
    window = EmptyWindow()       # Create instance of EmptyWindow class
    sys.exit(app.exec_())        # Start the event loop

Your initial window should look similar to the one in the following figure depending upon your operating system.

Empty window created with PyQt5

Walking through the code, we first start by importing the sys and PyQt5 modules that we need to create a window. We commonly use sys in order to pass command-line arguments to our applications and to close them.

The QtWidgets module provides a set of UI (User Interfaces) elements that can be used to create desktop-style GUIs. From the QtWidgets module, we import two classes, QApplication and QWidget. You only need to create a single instance of the QApplication class, which manages the application’s main event loop, flow, initialization, and finalization, as well as session management.
Take a quick look at

app = QApplication(sys.argv)

QApplication takes as an argument sys.argv. You can also pass in an empty list if you know that your program will not be taking any command-line arguments using

app = QApplication([])

Next we create a window object that inherits from the class we created, EmptyWindow. Our class actually inherits from QWidget, which is the base class for which all other user interface objects are derived.

We need to call the show() method on the window object to display it to the screen.
This is located inside the initializeUI() function in our EmptyWindow class. You will notice app.exec_() in the final line of the program. This function starts the event loop and will remain here until you quit the application. sys.exit() ensures a clean exit.

If all of this is a little confusing as to why we have to create an application before
we create the window, think of QApplication as the frame that contains our window.

The window, our GUI, is created using QWidget. Before we can create our GUI, we must create an instance of QApplication that we can place window in.

Modifying the Window

The preceding EmptyWindow class contains a function, initializeUI(), that creates the window based upon the parameters we specify.

setGeometry() defines the location of the window on your computer screen and
its dimensions, width and height. So the window we just started is located at x=100,
y=100 in the window and has width=400 and height=300. setWindowTitle() is used to change the title of our window.

Object-Oriented Programming

Python is an object-oriented programming language. The key notion is that of an object. An object consists of two things: data and functions (called methods) that work with that data. As an example, strings in Python are objects. The data of the string object is the actual characters that make up that string. The methods are things like lower, replace, and split. In Python, everything is an object. That includes not only strings and lists, but also integers, floats, and even functions themselves.

Creating your own classes.

A class is a template for objects. It contains the code for all the object’s methods.

Here is a simple example to demonstrate what a class looks like. It does not
do anything interesting.

class Esempio:
    def __init__(self, a,b):
        self.a = a
        self.b = b
    
    def add(self):
        return self.a + self.b

e = Esempio(8,6)
print(e.add())


To create a class, we use the class statement. Class names usually start with a capital.
Most classes will have a method called __init__. The underscores indicate that it is a special kind of method. It is called a constructor, and it is automatically called when someone creates a new object from your class. The constructor is usually used to set up the class’s variables. In the above program, the constructor takes two values, a and b, and assigns the class variables a and b to those values.
The first argument to every method in your class is a special variable called self. Every time your class refers to one of its variables or methods, it must precede them by self. The purpose of self is to distinguish your class’s variables and methods from other variables and functions in the program.
To create a new object from the class, you call the class name along with any values that you want to send to the constructor. You will usually want to assign it to a variable name. This is what the line e=Esempio(8,6) does.
To use the object’s methods, use the dot operator, as in e.add().

Here is a class called Analyzer that performs some simple analysis on a string. There are methods to return how many words are in the string, how many are of a given length, and how many start with a given string.

from string import punctuation

class Analizza:
    def __init__(self, s):
        for c in punctuation:
            s = s.replace(c,'')
            s = s.lower()
            self.parole = s.split()
    
    def numeri_di_parole(self):
        return len(self.parole)
    
    def inizia_con(self,s):
        return len([w for w in self.parole if w[:len(s)]==s])
    
    def numero_di_lunghezza(self, n):
        return len([w for w in self.parole if len(w)==n])


s = 'Tanto va la gatta al lardo che ci lascia lo zampino'

analizzatore = Analizza(s)

print(analizzatore.parole)
print('Numero di lettere:', analizzatore.numeri_di_parole())
print('Numero di parole che iniziano con la "c":', analizzatore.inizia_con('c'))
print('Numero di parole formato da due lettere:', analizzatore.numero_di_lunghezza(2))


A few notes about this program:

  • One reason why we would wrap this code up in a class is we can then use it a variety of different programs. It is also good just for organizing things. If all our program is doing is just analyzing some strings, then there’s not too much of a point of writing a class, but if this were to be a part of a larger program, then using a class provides a nice way to separate the Analizza code from the rest of the code. It also means that if we were to change the internals of the Analizza class, the rest of the program would not be affected as long as the interface, the way the rest of the program interacts with the class, does not change. Also, the Analizza class can be imported as-is in other programs.
  • The following line accesses a class variable: print(analizzatore.parole).

You can also change class variables. This is not always a good thing. In some cases this is convenient, but you have to be careful with it. Indiscriminate use of class variables goes against the idea of encapsulation and can lead to programming errors that are hard to fix. Some other object-oriented programming languages have a notion of public and private variables, public variables being those that anyone can access and change, and private variables being only accessible to methods within the class. In Python all variables are public, and it is up to the programmer to be responsible with them. There is a convention where you name those variables that you want to be private with a starting underscore, like _var1. This serves to let others know that this variable is internal to the class and shouldn’t be touched.

Inheritance

In object-oriented programming there is a concept called inheritance where you can create a class that builds off of another class. When you do this, the new class gets all of the variables and methods of the class it is inheriting from (called the base class). It can then define additional variables and methods that are not present in the base class, and it can also override some of the methods of the base class. That is, it can rewrite them to suit its own purposes. Here is a simple example:

class Parent:
    
    def __init__(self, a):
        self.a = a
    
    def method1(self):
        return self.a*2
    
    def method2(self):
        return self.a+'!!!'

class Child(Parent):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def method1(self):
        return self.a*7
    
    def method3(self):
        return self.a + self.b

p = Parent('hi')
c = Child('hi', 'bye')

print('Parent method 1: ', p.method1())
print('Parent method 2: ', p.method2())
print()
print('Child method 1: ', c.method1())
print('Child method 2: ', c.method2())
print('Child method 3: ', c.method3())


We see in the example above that the child has overridden the parent’s method1, causing it to now repeat the string seven times. The child has inherited the parent’s method2, so it can use it without having to define it. The child also adds some features to the parent class, namely a new variable b and a new method, method3.

A note about syntax: when inheriting from a class, you indicate the parent class in parentheses in the class statement.

If the child class adds some new variables, it can call the parent class’s constructor as demonstrated below. Another use is if the child class just wants to add on to one of the parent’s methods. In the example below, the child’s print_var method calls the parent’s print_var method and adds an additional line.

class Parent:
    def __init__(self, a):
        self.a = a
    
    def print_var(self):
        print("The value of this class's variables are:")
        print(self.a)

class Child(Parent):
    def __init__(self, a, b):
        Parent.__init__(self, a)
        self.b = b

def print_var(self):
    Parent.print_var(self)
    print(self.b)

Note – You can also inherit from Python built-in types, like strings (str) and lists (list), as well as any classes defined in the various modules that come with Python.
Note – Your code can inherit from more than one class at a time, though this can be a little tricky.

Come ti scorporo l’iva dal totale della fattura…

Il pretesto è quello di creare un programmino per scorporare l’iva dal totale della fattura. Useremo Python e PyQt6 per creare un’interfaccia grafica al semplice programma. Qui, il repository da cui prelevare i sorgenti.

Qt Designer – progetto interfaccia di imponibile.ui

Al pulsante pB_calcola colleghiamo il signal pB_valutaClick() quando si verifica l’evento clicked().

Di seguito il codice:

#!/usr/bin/python
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6 import uic

class Ui(QWidget):
    def __init__(self):
        super().__init__()
        uic.loadUi('imponibile.ui', self)
        self.setFixedSize(399, 177)

    def pB_valutaClick(self):
        try:
            n1 = float(self.lE_totFattura.text())
            n2 = float(self.lE_aliquota.text())
            n3 = n1*100/(n2+100)
            n4 = n1-n3
            self.lE_imponibile.setText(str(format(n3,'>12.2f')))
            self.lE_iva.setText(str(format(n4,'>12.2f')))
            self.l_errore.setText('')
        except:
            self.l_errore.setText('Valore numerico non accettato')
            self.lE_imponibile.setText('')
            self.lE_iva.setText('')
            self.lE_aliquota.setText('22')



app = QApplication([])
window = Ui()
window.show()
app.exec()

Lo screencast seguente mostra il funzionamento del programma:

…la speranza

Un gracchio affamato si posò su un fico e siccome vi trovò i frutti ancora acerbi si mise ad aspettare che maturassero. Ma una volpe, che lo aveva visto là fermo, quando ne seppe da lui la ragione osservò: «Sbagli, amico mio, ad attaccarti alla speranza, che, come un pastore, sa condurre al pascolo, ma non può assolutamente riempire lo stomaco».

– da “Il gracchio e la volpe”, Esopo.

Dio per me non è neanche un’ ipotesi…

Lei crede in Dio? «Dio per me non è neanche un’ ipotesi». In un lontano incontro, Cabibbo disse che la fede è comunque un bel vantaggio. «Non dubito, se uno crede. Ma uno non è che può credere per vivere meglio. Cabibbo è stato Presidente dell’ Accademia Pontificia, ma non so quanto credesse».

  • Da Giorgio Parisi: “Ecco perché la fisica moderna è assurda come un testo di Beckett“, la Repubblica, 31 dicembre 2010.

Uniform flow past a doublet…

Flow over a cylinder

A doublet alone does not give so much information about how it can be used to represent a practical flow pattern in aerodynamics. But let’s use our superposition powers: our doublet in a uniform flow turns out to be a very interesting flow pattern. Let’s first define a uniform horizontal flow.

u_inf = 1.0        # freestream speed

Now, we can calculate velocities and stream function values for all points in our grid. And as we now know, we can calculate them all together with one line of code per array.

u_freestream = u_inf * numpy.ones((N, N), dtype=float)
v_freestream = numpy.zeros((N, N), dtype=float)

psi_freestream = u_inf * Y

Below, the stream function of the flow created by superposition of a doublet in a free stream is obtained by simple addition.

The plot shows that this pattern can represent the flow around a cylinder with center at the location of the doublet. All the streamlines remaining outside the cylinder originated from the uniform flow. All the streamlines inside the cylinder can be ignored and this area assumed to be a solid object. This will turn out to be more useful than you may think.

# superposition of the doublet on the freestream flow
u = u_freestream + u_doublet
v = v_freestream + v_doublet
psi = psi_freestream + psi_doublet

# plot the streamlines
width = 10
height = (y_end - y_start) / (x_end - x_start) * width
pyplot.figure(figsize=(width, height))
pyplot.xlabel('x', fontsize=16)
pyplot.ylabel('y', fontsize=16)
pyplot.xlim(x_start, x_end)
pyplot.ylim(y_start, y_end)
pyplot.streamplot(X, Y, u, v,
                  density=2, linewidth=1, arrowsize=1, arrowstyle='->')
pyplot.contour(X, Y, psi,
               levels=[0.], colors='#CD2305', linewidths=2, linestyles='solid')
pyplot.scatter(x_doublet, y_doublet, color='#CD2305', s=80, marker='o')

# calculate the stagnation points
x_stagn1, y_stagn1 = +math.sqrt(kappa / (2 * math.pi * u_inf)), 0.0
x_stagn2, y_stagn2 = -math.sqrt(kappa / (2 * math.pi * u_inf)), 0.0

# display the stagnation points
pyplot.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2],
               color='g', s=80, marker='o');

Bernoulli’s equation and the pressure coefficient

A very useful measurement of a flow around a body is the coefficient of pressureCp. To evaluate the pressure coefficient, we apply Bernoulli’s equation for ideal flow and with simple mathematical steps:

\(C_{p}=1-\left(\frac{U}{U_{\infty}}\right)^{2}\)

In an incompressible flow, Cp=1 at a stagnation point. Let’s plot the pressure coefficient in the whole domain.

# compute the pressure coefficient field
cp = 1.0 - (u**2 + v**2) / u_inf**2

# plot the pressure coefficient field
width = 10
height = (y_end - y_start) / (x_end - x_start) * width
pyplot.figure(figsize=(1.1 * width, height))
pyplot.xlabel('x', fontsize=16)
pyplot.ylabel('y', fontsize=16)
pyplot.xlim(x_start, x_end)
pyplot.ylim(y_start, y_end)
contf = pyplot.contourf(X, Y, cp,
                        levels=numpy.linspace(-2.0, 1.0, 100), extend='both')
cbar = pyplot.colorbar(contf)
cbar.set_label('$C_p$', fontsize=16)
cbar.set_ticks([-2.0, -1.0, 0.0, 1.0])
pyplot.scatter(x_doublet, y_doublet,
               color='#CD2305', s=80, marker='o')
pyplot.contour(X,Y,psi,
               levels=[0.], colors='#CD2305', linewidths=2, linestyles='solid')
pyplot.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2],
               color='g', s=80, marker='o');
pyplot.streamplot(X, Y, u, v,
                  density=2, linewidth=1, arrowsize=1, arrowstyle='->')

Below, we report the complete Python code:

import math as mt
import numpy as np
from matplotlib import pyplot as pyp

N = 50                                # Number of points in each direction
x_start, x_end = -2.0, 2.0            # x-direction boundaries
y_start, y_end = -1.0, 1.0            # y-direction boundaries
x = np.linspace(x_start, x_end, N)    # creates a 1D-array for x
y = np.linspace(y_start, y_end, N)    # creates a 1D-array for y
X, Y = np.meshgrid(x, y)              # generates a mesh grid

kappa = 1.0                        # strength of the doublet
x_doublet, y_doublet = 0.0, 0.0    # location of the doublet

def get_velocity_doublet(strength, xd, yd, X, Y):
    
    u = (- strength / (2 * mt.pi) *
         ((X - xd)**2 - (Y - yd)**2) /
         ((X - xd)**2 + (Y - yd)**2)**2)
    v = (- strength / (2 * mt.pi) *
         2 * (X - xd) * (Y - yd) /
         ((X - xd)**2 + (Y - yd)**2)**2)
    
    return u, v

def get_stream_function_doublet(strength, xd, yd, X, Y):
    
    psi = - strength / (2 * mt.pi) * (Y - yd) / ((X - xd)**2 + (Y - yd)**2)
    
    return psi

# compute the velocity field on the mesh grid
u_doublet, v_doublet = get_velocity_doublet(kappa, x_doublet, y_doublet, X, Y)

# compute the stream-function on the mesh grid
psi_doublet = get_stream_function_doublet(kappa, x_doublet, y_doublet, X, Y)

# plot the streamlines
width = 10
height = (y_end - y_start) / (x_end - x_start) * width
pyp.figure(figsize=(width, height))
pyp.xlabel('x', fontsize=16)
pyp.ylabel('y', fontsize=16)
pyp.xlim(x_start, x_end)
pyp.ylim(y_start, y_end)
pyp.streamplot(X, Y, u_doublet, v_doublet,
                  density=2, linewidth=1, arrowsize=1, arrowstyle='->')
pyp.scatter(x_doublet, y_doublet, color='#CD2305', s=80, marker='o');



u_inf = 1.0        # freestream speed

u_freestream = u_inf * np.ones((N, N), dtype=float)
v_freestream = np.zeros((N, N), dtype=float)

psi_freestream = u_inf * Y

# superposition of the doublet on the freestream flow
u = u_freestream + u_doublet
v = v_freestream + v_doublet
psi = psi_freestream + psi_doublet

# plot the streamlines
width = 10
height = (y_end - y_start) / (x_end - x_start) * width
pyp.figure(figsize=(width, height))
pyp.xlabel('x', fontsize=16)
pyp.ylabel('y', fontsize=16)
pyp.xlim(x_start, x_end)
pyp.ylim(y_start, y_end)
pyp.streamplot(X, Y, u, v,
                  density=2, linewidth=1, arrowsize=1, arrowstyle='->')
pyp.contour(X, Y, psi,
               levels=[0.], colors='#CD2305', linewidths=2, linestyles='solid')
pyp.scatter(x_doublet, y_doublet, color='#CD2305', s=80, marker='o')

# calculate the stagnation points
x_stagn1, y_stagn1 = +mt.sqrt(kappa / (2 * mt.pi * u_inf)), 0.0
x_stagn2, y_stagn2 = -mt.sqrt(kappa / (2 * mt.pi * u_inf)), 0.0

# display the stagnation points
pyp.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2],
               color='g', s=80, marker='o');

# compute the pressure coefficient field
cp = 1.0 - (u**2 + v**2) / u_inf**2

# plot the pressure coefficient field
width = 10
height = (y_end - y_start) / (x_end - x_start) * width
pyp.figure(figsize=(1.1 * width, height))
pyp.xlabel('x', fontsize=16)
pyp.ylabel('y', fontsize=16)
pyp.xlim(x_start, x_end)
pyp.ylim(y_start, y_end)
contf = pyp.contourf(X, Y, cp,
                        levels=np.linspace(-2.0, 1.0, 100), extend='both')
cbar = pyp.colorbar(contf)
cbar.set_label('$C_p$', fontsize=16)
cbar.set_ticks([-2.0, -1.0, 0.0, 1.0])
pyp.scatter(x_doublet, y_doublet,
               color='#CD2305', s=80, marker='o')
pyp.contour(X,Y,psi,
               levels=[0.], colors='#CD2305', linewidths=2, linestyles='solid')
pyp.scatter([x_stagn1, x_stagn2], [y_stagn1, y_stagn2],
               color='g', s=80, marker='o');
pyp.streamplot(X, Y, u, v,
                  density=2, linewidth=1, arrowsize=1, arrowstyle='->')


pyp.show()

Solving 2D Heat Equation…

Before we do the Python code, let’s talk about the heat equation and finite-difference method. Heat equation is basically a partial differential equation, it is

If we want to solve it in 2D (Cartesian), we can write the heat equation above like this:

where u is the quantity that we want to know, t is for temporal variable, x and y are for spatial variables, and α is diffusivity constant. So basically we want to find the solution u everywhere in x and y, and over time t.

We can write the heat equation above using finite-difference method like this:

If we arrange the equation above by taking Δx = Δy, we get this final equation:

where

\(\gamma = \alpha \frac{{\Delta t}}{{\Delta {x^2}}}\)

We use explicit method to get the solution for the heat equation, so it will be numerically stable whenever

\(\Delta t \leq \frac{{\Delta {x^2}}}{{4\alpha }}\)

Everything is ready. Now we can solve the original heat equation approximated by algebraic equation above, which is computer-friendly.

Let’s suppose a thin square plate with the side of 50 unit length. The temperature everywhere inside the plate is originally 0 degree (at t = 0), let’s see the diagram below:

For our model, let’s take Δx = 1 and α = 2.0.
Now we can use Python code to solve this problem numerically to see the temperature everywhere (denoted by i and j) and over time (denoted by k). Let’s first import all of the necessary libraries, and then set up the boundary and initial conditions.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation

print("2D heat equation solver")

plate_length = 50
max_iter_time = 750

alpha = 2
delta_x = 1

delta_t = (delta_x ** 2)/(4 * alpha)
gamma = (alpha * delta_t) / (delta_x ** 2)

# Initialize solution: the grid of u(k, i, j)
u = np.empty((max_iter_time, plate_length, plate_length))

# Initial condition everywhere inside the grid
u_initial = 0

# Boundary conditions
u_top = 100.0
u_left = 0.0
u_bottom = 0.0
u_right = 0.0

# Set the initial condition
u.fill(u_initial)

# Set the boundary conditions
u[:, (plate_length-1):, :] = u_top
u[:, :, :1] = u_left
u[:, :1, 1:] = u_bottom
u[:, :, (plate_length-1):] = u_right

We’ve set up the initial and boundary conditions, let’s write the calculation function based on finite-difference method that we’ve derived above.

def calculate(u):
    for k in range(0, max_iter_time-1, 1):
        for i in range(1, plate_length-1, delta_x):
            for j in range(1, plate_length-1, delta_x):
                u[k + 1, i, j] = gamma * (u[k][i+1][j] + u[k][i-1][j] + u[k][i][j+1] + u[k][i][j-1] - 4*u[k][i][j]) + u[k][i][j]

    return u

Let’s prepare the plot function so we can visualize the solution (for each k) as a heat map. We use Matplotlib library, it’s easy to use.

def plotheatmap(u_k, k):
    # Clear the current plot figure
    plt.clf()

    plt.title(f"Temperature at t = {k*delta_t:.3f} unit time")
    plt.xlabel("x")
    plt.ylabel("y")

    # This is to plot u_k (u at time-step k)
    plt.pcolormesh(u_k, cmap=plt.cm.jet, vmin=0, vmax=100)
    plt.colorbar()

    return plt

One more thing that we need is to animate the result because we want to see the temperature points inside the plate change over time. So let’s create the function to animate the solution.

def animate(k):
  plotheatmap(u[k], k)

anim = animation.FuncAnimation(plt.figure(), animate, interval=1, frames=max_iter_time, repeat=False)
anim.save("heat_equation_solution.gif")

That’s it! And here’s the result: