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#

  1. 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!

  2. Test often

    • Test every time you add a new feature

    • Test every time you change something

  3. 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

  4. 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

  5. Simplify your code

    • Comment out code that may not be needed

    • Break up code into functions that can be tested separately

  6. Check your answer doing it a different way of from a reputable source.

Bug Types#

  1. 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

  2. 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

  3. 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).

\[ f = \frac{1}{\left( -1.8 \log_{10} \left[ \frac{6.9}{Re} + \left( \frac{\epsilon}{3.7D} \right)^{1.11} \right] \right)^2} \]

or

\[ f = 64/Re \text{ if Re} < 2000 \]

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:

  1. Use assert statements

    • Assert statements are logical checks that you can use

  2. 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()
../_images/2270feecdbfa5c82df1e48b82ee9224c6acaebf7950f77cd5a70cc115ee54b44.png
---------------------------------------------------------------------------
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.