06 - Debugging#
When you code, you debug. It’s inevitable. You will make mistakes, and you will have to fix them. This lecture will cover the approach to writing code that makes debugging easier and some of the tools that you can use to debug your code.
Debugging Principles#
Write code in small pieces
Start with a simple task and test it
Add complexity and test it
Comment your code so it’s easy to understand for you and others -Time spend being careful and avoiding bugs saves you time debugging!
Test often
Test every time you add a new feature
Test every time you change something
Use print statements to check the values of variables
Print the value of a variable before and after a line of code
Print the value of a variable inside a loop
Print the value of a variable inside a function
Check errors and warnings
Read the warning/error message
Google the warning/error message
Copy and paste the code into Google Gemini, ChatGPT, or another AI assistant
Simplify your code
Comment out code that may not be needed
Break up code into functions that can be tested separately
Check your answer doing it a different way of from a reputable source.
Bug Types#
Syntax Errors (code won’t run)
Errors that occur when you violate the rules of Python
Usually easy to fix
Python will tell you where the error is and what the error is
Run-time or Execution errors (code won’t run or gives an overflow error)
Divide by zero, Bad array call (array index out of bounds), bad variable call/assignment (variable not defined)
Easy to locate, sometimes harder to fix than syntax errors
Logic errors (code runs, but doesn’t do what you want or gives the wrong answer)
Hard to locate, hard to fix
The code will do exactly what you told it to do, but not what you want it to do
Sometimes the error persists for a while before it’s noticed
Use print statements to locate logic errors
Use the debugger to locate logic errors
Example#
Maybe you have to program the Haaland Equation for the friction factor from an array of Reynolds numbers (and also plot the result as a function of the Reynolds number).
or
where \(f\) is the friction factor, \(Re\) is the Reynolds number, \(\epsilon\) is the roughness of the pipe, and \(D\) is the diameter of the pipe.
#first import needed packages
import numpi as np
import mathplotlib.pyplot as plt
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 2
1 #first import needed packages
----> 2 import numpi as np
3 import mathplotlib.pyplot as plt
ModuleNotFoundError: No module named 'numpi'
#Now set array of Reynolds numbers
Re = np.linespace(2e2, 1e7,100000) # or np.arange(1e-1, 1e4, 1)
#print or show my Reynolds numbers
Re # or print(Re)
#maybe print the first and last values
Re(0), Re(-1)
#maybe just print the first 10 and last 10
Re[:10], Re[-10:]
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[96], line 2
1 #Now set array of Reynolds numbers
----> 2 Re = np.linespace(2e2, 1e7,100000) # or np.arange(1e-1, 1e4, 1)
3 #print or show my Reynolds numbers
4 Re # or print(Re)
File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/numpy/__init__.py:322, in __getattr__(attr)
319 "Removed in NumPy 1.25.0"
320 raise RuntimeError("Tester was removed in NumPy 1.25.")
--> 322 raise AttributeError("module {!r} has no attribute "
323 "{!r}".format(__name__, attr))
AttributeError: module 'numpy' has no attribute 'linespace'
#Now set a function to calculate the friction factor
def friction_factor(Re=1, eoD=2e-4):
if Re < 2000:
f = 64/Re
else:
f = 1/(-1.8*np.log((6.9/Re) + ((eoD)/3.7)**1.11))**2
return f
#Now calculate the friction factor for each Reynolds number
ff = [friction_factor(each,Re=1,eoD=2e-4) for each in Re]
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 2
1 #Now calculate the friction factor for each Reynolds number
----> 2 ff = [friction_factor(each,Re=1,eoD=2e-4) for each in Re]
NameError: name 'Re' is not defined
#Now plot the friction factor vs Reynolds number
plt.loglog(Re, ffd)
plt.grid(which='major');plt.grid(which='minor',linestyle='--',alpha=0.2)
plt.xlabel('Reynolds Number');plt.ylabel('Friction Factor')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[3], line 2
1 #Now plot the friction factor vs Reynolds number
----> 2 plt.loglog(Re, ffd)
3 plt.grid(which='major');plt.grid(which='minor',linestyle='--',alpha=0.2)
4 plt.xlabel('Reynolds Number');plt.ylabel('Friction Factor')
NameError: name 'plt' is not defined
Less often used debugging tools:
Use assert statements
Assert statements are logical checks that you can use
Use the debugger to step through your code
Set a breakpoint
Step through your code
Check the value of variables
Check the value of variables inside a loop
Check the value of variables inside a function
Examples#
Example 1: Syntax, Execution, and Logic Errors#
Take the transpose of a matrix
# Debug the following code which is supposed to find
# the transpose of the matrix
A = np.array([[ 5, 8, 4, 2],
[ 1, -6, 5, -7],
[-2, 2, -9, 1],
[-3, 5, -8, 7]])
#Print the size of the matrix
print(A.shape)
A_transpose = np.zeros(4) #initialize a transpose matrix with zeroes (to fill out manually)
for i in range(2):
for j in range(4):
A_transpose[j] = A[i]
print(A_transpose-A.T) #check if the transpose is correct, should be array of zeros
#A.T is the transpose of A
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 4
1 # Debug the following code which is supposed to find
2 # the transpose of the matrix
----> 4 A = np.array([[ 5, 8, 4, 2],
5 [ 1, -6, 5, -7],
6 [-2, 2, -9, 1],
7 [-3, 5, -8, 7]])
9 #Print the size of the matrix
10 print(A.shape)
NameError: name 'np' is not defined
Example 2: More errors#
This calculates the next value of a function by multiplying the derivative by the step size. Specifically we want: $\( \begin{align} t_{n+1} &= t_n + dt \\ A_{n+1} &= A_n + dt\cdot\left[ (A_{in} - A_n)/\tau - k A_n^2 \right] \end{align} \)$
Debug the following code:
#use these constants
Ain = 1
tau = 2
k = 0.1
dt = 0.1
t_final = 10
Nsteps = int(t_final/dt)+1
A = np.zeros(Nsteps)
t = np.zeros(Nsteps)
for n in range(Nsteps):
A(n+1) = A(n) + dt * ( Ain-A(n)/tau - A(n)**(1/2) )
t(n+1) = t(n) + dt
plt.plot(t, A, 'k-')
plt.xlabel('t')
plt.ylabel('A')
plt.show()
Cell In[5], line 13
A(n+1) = A(n) + dt * ( Ain-A(n)/tau - A(n)**(1/2) )
^
SyntaxError: cannot assign to function call
Example 3: Sympy equation solver#
The below code is attempting to solve for the roots for: $\( x^3 + 15x^2 + 3x - 10 = 0 \)$
import sympy as sp
sp.init_printing() #for pretty printing
x = sp.symbols('x')
ex = sp.Eq(x**3 + 15*x*x, 3*x - 10)
roots = sp.solve(ex,x)
display(roots)
roots.evalf()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[6], line 8
6 roots = sp.solve(ex,x)
7 display(roots)
----> 8 roots.evalf()
AttributeError: 'list' object has no attribute 'evalf'
Example 4: Assert statements#
Assert statements are logical checks that you can use to check that your code is doing what you expect it to do. If the assert statement is true, then nothing happens. If the assert statement is false, then an error is raised. An example is shown below:
# we'll define a function to calculate the the log of the factorial of n
import numpy as np
def factorial(n):
assert type(n) == int and n>0, "n must be an integer and greater than zero"
factorial = n
while n>1:
factorial = factorial*(n-1); n = n-1
return factorial
def log_factorial(n):
return np.log(float(factorial(n)))
log_factorial('hello')
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[11], line 1
----> 1 log_factorial('hello')
Cell In[9], line 10, in log_factorial(n)
9 def log_factorial(n):
---> 10 return np.log(float(factorial(n)))
Cell In[9], line 4, in factorial(n)
3 def factorial(n):
----> 4 assert type(n) == int and n>0, "n must be an integer and greater than zero"
5 factorial = n
6 while n>1:
AssertionError: n must be an integer and greater than zero
Example 5: Using the debugger#
There is a debugger that’s associated with most IDEs (Integrated Development Environments) that allows you to step through your code and check the value of variables.