diff --git a/.gitignore b/.gitignore index 9edfe639..a834e783 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ site/ venv/ +*.pyc diff --git a/docs/community/README.md b/docs/community/README.md index f8a2d85b..e4d6ec93 100644 --- a/docs/community/README.md +++ b/docs/community/README.md @@ -16,3 +16,8 @@ The [three characteristics](https://www.communityofpractice.ca/background/what-i 3. Domain: A shared interest, problem, or concern. We regularly meet as a community, report [meeting summaries](meetings/README.md), and collect [case studies](case-studies/README.md) that showcase good practices. + +## Training events + +To support skill development, we have the capacity to prepare and deliver bespoke training events as standalone session and as part of larger meetings and conferences. +See our [Training events](training/README.md) page for further details. diff --git a/docs/community/training/README.md b/docs/community/training/README.md new file mode 100644 index 00000000..546671a6 --- /dev/null +++ b/docs/community/training/README.md @@ -0,0 +1,3 @@ +# Training events + +We will be running an [Introduction to Debugging](debugging/README.md) workshop at the [SPECTRUM](https://spectrum.edu.au/) Annual Meeting 2024 (23-25 September). diff --git a/docs/community/training/debugging/README.md b/docs/community/training/debugging/README.md new file mode 100644 index 00000000..54002252 --- /dev/null +++ b/docs/community/training/debugging/README.md @@ -0,0 +1,11 @@ +# Introduction to Debugging + +This workshop was prepared for the [SPECTRUM](https://spectrum.edu.au/) Annual Meeting 2024 (23-25 September). + +!!! tip + + **We all make mistakes** when writing code and introduce errors. + + Having good debugging skills means that you can spend **less time fixing your code**. + +See the discussion in our [August 2024 meeting](../../meetings/2024-08-08.md#debugging) for further background. diff --git a/docs/community/training/debugging/debugging-manifesto-poster.jpg b/docs/community/training/debugging/debugging-manifesto-poster.jpg new file mode 100644 index 00000000..4dd03eee Binary files /dev/null and b/docs/community/training/debugging/debugging-manifesto-poster.jpg differ diff --git a/docs/community/training/debugging/example-perfect-numbers.md b/docs/community/training/debugging/example-perfect-numbers.md new file mode 100644 index 00000000..64bf908f --- /dev/null +++ b/docs/community/training/debugging/example-perfect-numbers.md @@ -0,0 +1,45 @@ +# Perfect numbers + +=== "Overview" + + [Perfect numbers](https://en.wikipedia.org/wiki/Perfect_number) are positive integers that are equal to the sum of their divisors. + Here we have provided example Python and R scripts that should print all of the perfect numbers up to 1,000. + + You can download each script to debug on your own computer: + + - [perfect_numbers.py](perfect_numbers.py) + - [perfect_numbers.R](perfect_numbers.R) + +=== "Python" + + ```py title="perfect_numbers.py" linenums="1" + --8<-- "perfect_numbers.py" + ``` + +=== "R" + + ```R title="perfect_numbers.R" linenums="1" + --8<-- "perfect_numbers.R" + ``` + +??? bug "But there's a problem ..." + + If we run these scripts, we see that **they don't print anything**: + +
+ + How should we begin investigating? + +??? note "Some initial thoughts ..." + + - Are we actually running the `main()` function at all? + + - The `main()` function is almost certainly not the cause of this error. + + - The `is_perfect()` function is very simple, so it's unlikely to be the cause of this error. + + - The `divisors_of()` function doesn't look obviously wrong. + + - But there must be a mistake **somewhere**! + + - Let's **use a debugger** to investigate. diff --git a/docs/community/training/debugging/example-python-vs-r.md b/docs/community/training/debugging/example-python-vs-r.md new file mode 100644 index 00000000..3f4a8326 --- /dev/null +++ b/docs/community/training/debugging/example-python-vs-r.md @@ -0,0 +1,112 @@ +# Example: Python vs R + +=== "Overview" + + Here we have provided SIR ODE model implementations in Python and in R. + Each script runs several scenarios and produces a plot of infection prevalence for each scenario. + + You can download each script to debug on your computer: + + - [sir_ode.py](sir_ode.py) + - [sir_ode.R](sir_ode.R) + +=== "Python" + + ```py title="sir_ode.py" linenums="1" + --8<-- "sir_ode.py" + ``` + +=== "R" + + ```R title="sir_ode.R" linenums="1" + --8<-- "sir_ode.R" + ``` + +??? bug "The model outputs differ!" + + Here are prevalence time-series plots produced by each script: + + === "Python plot" + +
+ ![Python outputs](sir_ode_python.png) +
Model outputs for the Python script.
+
+ + === "R plot" + +
+ ![R outputs](sir_ode_r.png) +
Model outputs for the R script.
+
+ +??? note "Some initial thoughts ..." + + - Is it obvious whether one of the figures is correct and the other is wrong? + + - The `sir_rhs()` functions in the two scripts appear to be equivalent — but are they? + + - The `default_settings()` functions appear to be equivalent — but are they? + + - The `run_model_scaled_beta()` and `run_model_scaled_gamma()` functions also appear to be equivalent. + + - Where might you begin looking? + +??? info "Commentary" + + > I've heard that dicts (and other mutable data types) are passed by reference, as such a function could change the “local” value of a variable, which causes an error when using the “global” variable in another function. + > + > This would also be good for showing that your test cases aren’t perfect. + > + > You can run function 1 tests and get good answers, and then run function 2 test and get good answers, but this is because they are not sharing the state of the mutable variable. + > Sounds like a great inclusion as a mini chapter. + + These are great ideas! The one drawback of the mutable data example is that it won't work in R, because it's copy-on-write. But that's no big deal. In fact, now that I think about, this difference us surely worth highlighting! People who are familiar with only one of these two languages need to be aware of this important difference if they begin using the other language. + + I've come up with an example of shared mutable state resulting in incorrect model outputs. + + !!! quote "The R Language Definition" + + The semantics of invoking a function in R argument are **call-by-value**. + In general, supplied arguments behave as if they are **local variables** initialized with the value supplied and the name of the corresponding formal argument. + Changing the value of a supplied argument within a function **will not affect** the value of the variable in the calling frame. + + — [Argument Evaluation](https://cran.r-project.org/doc/manuals/r-patched/R-lang.html#Argument-evaluation) + + !!! quote "Python Programming FAQ" + + Remember that arguments are **passed by assignment** in Python. + Since assignment just **creates references** to objects, there's no alias between an argument name in the caller and callee, and so no call-by-reference per se. + + — [How do I write a function with output parameters (call by reference)?](https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference) + + ??? info "Output messages" + + Here are the output messages printed by each script: + + === "Python" + + ```text + beta = 1.0 gamma = 0.5 + beta = 1.5 gamma = 0.5 + beta = 1.5 gamma = 0.35 + ``` + + === "R" + + ```text + beta = 1.0 gamma = 0.5 + beta = 1.5 gamma = 0.5 + beta = 1.0 gamma = 0.35 + ``` + + ??? bug "Parameters differ!" + + There's a difference in the parameter values for the third scenario: + + ```diff + beta = 1.0 gamma = 0.5 + beta = 1.5 gamma = 0.5 + - beta = 1.5 gamma = 0.35 + + beta = 1.0 gamma = 0.35 + ``` diff --git a/docs/community/training/debugging/headless-horse.jpg b/docs/community/training/debugging/headless-horse.jpg new file mode 100644 index 00000000..3f2bf4fa Binary files /dev/null and b/docs/community/training/debugging/headless-horse.jpg differ diff --git a/docs/community/training/debugging/learning-objectives.md b/docs/community/training/debugging/learning-objectives.md new file mode 100644 index 00000000..f3cc8893 --- /dev/null +++ b/docs/community/training/debugging/learning-objectives.md @@ -0,0 +1,17 @@ +# Learning objectives + +In this workshop, we will introduce the concept of "debugging", and demonstrate techniques and tools that can help us efficiently identify and remove errors from our code. + +After completing this workshop, participants will: + ++ Understand that debugging can be divided into a sequence of actions; + ++ Understand the purpose of each of these actions; + ++ Be familiar with techniques and tools that can help perform these actions; + ++ Be able to apply these techniques and tools to their own code. + +!!! info + + By achieving these learning objectives, participants should be able to find and correct errors in their code more quickly and with greater confidence. diff --git a/docs/community/training/debugging/manifesto.md b/docs/community/training/debugging/manifesto.md new file mode 100644 index 00000000..d753a911 --- /dev/null +++ b/docs/community/training/debugging/manifesto.md @@ -0,0 +1,12 @@ +# Debugging manifesto + +
+ ![A debugging manifesto poster.](debugging-manifesto-poster.jpg){ align=left, width="50%" } +
+ [Julia Evans](https://jvns.ca/) and [Tanya Brassie](https://tanyabrassie.com/): [Debugging Manifesto Poster](https://store.wizardzines.com/products/poster-debugging-manifesto), 2024. +
+
+ +!!! info + + See the [Resources](resourced.md) page for links to more of Julia Evans' articles, stories, and zines about debugging. diff --git a/docs/community/training/debugging/perfect-numbers-first-run.cast b/docs/community/training/debugging/perfect-numbers-first-run.cast new file mode 100644 index 00000000..8b34ffc1 --- /dev/null +++ b/docs/community/training/debugging/perfect-numbers-first-run.cast @@ -0,0 +1,48 @@ +{"version": 2, "width": 80, "height": 4, "timestamp": 1723508323, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} +[0.106254, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] +[1.439464, "o", "."] +[1.511348, "o", "/"] +[1.735208, "o", "p"] +[1.815337, "o", "e"] +[1.879152, "o", "r"] +[2.063205, "o", "f"] +[2.151301, "o", "e"] +[2.271034, "o", "c"] +[2.46335, "o", "t"] +[2.663166, "o", "_"] +[2.823218, "o", "n"] +[3.007567, "o", "u"] +[3.199264, "o", "m"] +[3.271363, "o", "b"] +[3.439513, "o", "e"] +[3.487438, "o", "r"] +[3.607177, "o", "s"] +[3.687383, "o", "."] +[3.847072, "o", "p"] +[3.991135, "o", "y"] +[4.967213, "o", "\r\n"] +[4.967292, "o", "\u001b[?2004l\r"] +[5.065072, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] +[6.879247, "o", "."] +[7.079536, "o", "/"] +[7.319153, "o", "p"] +[7.415356, "o", "e"] +[7.479395, "o", "r"] +[7.639271, "o", "f"] +[7.727076, "o", "e"] +[7.823333, "o", "c"] +[8.00743, "o", "t"] +[8.151241, "o", "_"] +[8.407055, "o", "n"] +[8.527353, "o", "u"] +[8.71911, "o", "m"] +[8.783422, "o", "b"] +[8.967451, "o", "e"] +[9.015325, "o", "r"] +[9.151235, "o", "s"] +[9.247291, "o", "."] +[9.575237, "o", "R"] +[10.167232, "o", "\r\n"] +[10.167313, "o", "\u001b[?2004l\r"] +[10.450741, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] +[12.800104, "o", "\u001b[?2004l"] diff --git a/docs/community/training/debugging/perfect_numbers.R b/docs/community/training/debugging/perfect_numbers.R new file mode 100755 index 00000000..5a5be50c --- /dev/null +++ b/docs/community/training/debugging/perfect_numbers.R @@ -0,0 +1,40 @@ +#!/usr/bin/env -S Rscript --vanilla +# +# This script prints perfect numbers. +# +# Perfect numbers are positive integers that are equal to the sum of their +# divisors. +# + + +main <- function() { + start <- 2 + end <- 1000 + for (value in seq.int(start, end)) { + if (is_perfect(value)) { + print(value) + } + } +} + + +is_perfect <- function(value) { + divisors <- divisors_of(value) + sum(divisors) == value +} + + +divisors_of <- function(value) { + divisors <- c() + candidate <- 2 + while (candidate < value) { + if (value %% candidate == 0) { + divisors <- c(divisors, candidate) + } + candidate <- candidate + 1 + } + divisors +} + + +main() diff --git a/docs/community/training/debugging/perfect_numbers.py b/docs/community/training/debugging/perfect_numbers.py new file mode 100755 index 00000000..149a2919 --- /dev/null +++ b/docs/community/training/debugging/perfect_numbers.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +This script prints perfect numbers. + +Perfect numbers are positive integers that are equal to the sum of their +divisors. +""" + + +def main(): + start = 2 + end = 1_000 + for value in range(start, end + 1): + if is_perfect(value): + print(value) + + +def is_perfect(value): + divisors = divisors_of(value) + return sum(divisors) == value + + +def divisors_of(value): + divisors = [] + candidate = 2 + while candidate < value: + if value % candidate == 0: + divisors.append(candidate) + candidate += 1 + return divisors + + +if __name__ == '__main__': + main() diff --git a/docs/community/training/debugging/resources.md b/docs/community/training/debugging/resources.md new file mode 100644 index 00000000..91ebea1b --- /dev/null +++ b/docs/community/training/debugging/resources.md @@ -0,0 +1,10 @@ +# Resources + +- Julie Evans: + - [Debugging articles](https://jvns.ca/#debugging) + - [Debugging stories](https://jvns.ca/#debugging-stories) + - [The Pocket Guide to Debugging](https://jvns.ca/blog/2022/12/21/new-zine--the-pocket-guide-to-debugging/) (see the [table of contents](https://jvns.ca/images/debugging-guide-toc.png)) + - [Notes on building debugging puzzles](https://jvns.ca/blog/2021/04/16/notes-on-debugging-puzzles/) +- [Learn Debugging](https://www.cse.unsw.edu.au/~learn/debugging/) +- [Debug with pdb and breakpoint](https://hamatti.org/posts/debug-with-pdb-and-breakpoint/) +- [Advanced R: Debugging](https://adv-r.hadley.nz/debugging.html) diff --git a/docs/community/training/debugging/sir_ode.R b/docs/community/training/debugging/sir_ode.R new file mode 100755 index 00000000..fc892f0b --- /dev/null +++ b/docs/community/training/debugging/sir_ode.R @@ -0,0 +1,109 @@ +#!/usr/bin/env -S Rscript --vanilla + +library(deSolve) +suppressPackageStartupMessages(library(dplyr)) +suppressPackageStartupMessages(library(ggplot2)) + + +# The right-hand side for the vanilla SIR compartmental model. +sir_rhs <- function(time, state, params) { + s_to_i <- params$beta * state["I"] * state["S"] / params$popn + i_to_r <- params$gamma * state["I"] + list(c(-s_to_i, s_to_i - i_to_r, i_to_r)) +} + + +# Return the SIR ODE solution for the given model settings. +run_model <- function(settings) { + # Define the time span and evaluation times. + times <- seq(0, settings$sim_days) + # Define the initial state. + popn <- settings$population + exposures <- settings$exposures + initial_state <- c(S = popn - exposures, I = exposures, R = 0) + # Define the SIR parameters. + params <- list( + popn = settings$population, + beta = settings$beta, + gamma = settings$gamma + ) + # Return the daily number of people in S, I, and R. + ode(initial_state, times, sir_rhs, params) +} + + +# The default model settings. +default_settings <- function() { + list( + sim_days = 20, + population = 100, + exposures = 2, + beta = 1.0, + gamma = 0.5 + ) +} + + +# Adjust the value of ``beta`` before running the model. +run_model_scaled_beta <- function(settings, scale) { + settings$beta <- scale * settings$beta + run_model(settings) +} + + +# Adjust the value of ``gamma`` before running the model. +run_model_scaled_gamma <- function(settings, scale) { + settings$gamma <- scale * settings$gamma + run_model(settings) +} + + +# Plot daily prevalence of infectious individuals for one or more scenarios. +plot_prevalence_time_series <- function(solutions) { + df <- lapply( + names(solutions), + function(name) { + solutions[[name]] |> + as.data.frame() |> + mutate(scenario = name) + } + ) |> + bind_rows() |> + mutate( + scenario = factor(scenario, levels = names(solutions), ordered = TRUE) + ) + fig <- ggplot() + + geom_line(aes(time, I), df) + + xlab(NULL) + + scale_y_continuous( + name = NULL, + limits = c(0, 40), + breaks = c(0, 20, 40) + ) + + facet_wrap(~ scenario, ncol = 1) + + theme_bw(base_size = 10) + + theme( + strip.background = element_blank(), + panel.grid = element_blank(), + ) + png_file <- "sir_ode_r.png" + ggsave(png_file, fig, width = 640, height = 480, units = "px", dpi = 150) +} + + +demonstration <- function() { + settings <- default_settings() + default_scenario <- run_model(settings) + scaled_beta_scenario <- run_model_scaled_beta(settings, scale=1.5) + scaled_gamma_scenario <- run_model_scaled_gamma(settings, scale=0.7) + + plot_prevalence_time_series( + list( + Default = default_scenario, + `Scaled Beta` = scaled_beta_scenario, + `Scaled Gamma` = scaled_gamma_scenario + ) + ) +} + +demonstration() diff --git a/docs/community/training/debugging/sir_ode.py b/docs/community/training/debugging/sir_ode.py new file mode 100755 index 00000000..21308275 --- /dev/null +++ b/docs/community/training/debugging/sir_ode.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +import numpy as np +from scipy.integrate import solve_ivp + + +def sir_rhs(time, state, popn, beta, gamma): + """ + The right-hand side for the vanilla SIR compartmental model. + """ + s_to_i = beta * state[1] * state[0] / popn # beta * I(t) * S(t) / N + i_to_r = gamma * state[1] # gamma * I(t) + return [-s_to_i, s_to_i - i_to_r, i_to_r] + + +def run_model(settings): + """ + Return the SIR ODE solution for the given model settings. + """ + # Define the time span and evaluation times. + sim_days = settings['sim_days'] + time_span = [0, sim_days] + times = np.linspace(0, sim_days, num=sim_days + 1) + # Define the initial state. + popn = settings['population'] + exposures = settings['exposures'] + initial_state = [popn - exposures, exposures, 0] + # Define the SIR parameters. + params = (popn, settings['beta'], settings['gamma']) + # Return the daily number of people in S, I, and R. + return solve_ivp( + sir_rhs, time_span, initial_state, t_eval=times, args=params + ) + + +def default_settings(): + """ + The default model settings. + """ + return { + 'sim_days': 20, + 'population': 100, + 'exposures': 2, + 'beta': 1.0, + 'gamma': 0.5, + } + + +def run_model_scaled_beta(settings, scale): + """ + Adjust the value of ``beta`` before running the model. + """ + settings['beta'] = scale * settings['beta'] + return run_model(settings) + + +def run_model_scaled_gamma(settings, scale): + """ + Adjust the value of ``gamma`` before running the model. + """ + settings['gamma'] = scale * settings['gamma'] + return run_model(settings) + + +def plot_prevalence_time_series(solutions): + """ + Plot daily prevalence of infectious individuals for one or more scenarios. + """ + fig, axes = plt.subplots( + constrained_layout=True, + nrows=len(solutions), + sharex=True, + sharey=True, + ) + for ix, (scenario_name, solution) in enumerate(solutions.items()): + ax = axes[ix] + ax.title.set_text(scenario_name) + ax.plot(solution.y[1], label='I') + ax.set_xticks([0, 5, 10, 15, 20]) + # Save the figure. + png_file = 'sir_ode_python.png' + fig.savefig(png_file, format='png', metadata={'Software': None}) + + +def demonstration(): + settings = default_settings() + default_scenario = run_model(settings) + scaled_beta_scenario = run_model_scaled_beta(settings, scale=1.5) + scaled_gamma_scenario = run_model_scaled_gamma(settings, scale=0.7) + + plot_prevalence_time_series( + { + 'Default': default_scenario, + 'Scaled Beta': scaled_beta_scenario, + 'Scaled Gamma': scaled_gamma_scenario, + } + ) + + +if __name__ == '__main__': + demonstration() diff --git a/docs/community/training/debugging/sir_ode_python.png b/docs/community/training/debugging/sir_ode_python.png new file mode 100644 index 00000000..d618e02c Binary files /dev/null and b/docs/community/training/debugging/sir_ode_python.png differ diff --git a/docs/community/training/debugging/sir_ode_r.png b/docs/community/training/debugging/sir_ode_r.png new file mode 100644 index 00000000..3bd40c2f Binary files /dev/null and b/docs/community/training/debugging/sir_ode_r.png differ diff --git a/docs/community/training/debugging/square-numbers-demo.cast b/docs/community/training/debugging/square-numbers-demo.cast new file mode 100644 index 00000000..646eb6de --- /dev/null +++ b/docs/community/training/debugging/square-numbers-demo.cast @@ -0,0 +1,46 @@ +{"version": 2, "width": 80, "height": 5, "timestamp": 1724287506, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} +[0.085762, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] +[0.304444, "o", "."] +[0.450345, "o", "/"] +[0.613627, "o", "s"] +[0.873455, "o", "q"] +[1.108823, "o", "u"] +[1.287488, "o", "a"] +[1.454263, "o", "r"] +[1.674393, "o", "e"] +[1.812098, "o", "_"] +[2.047097, "o", "n"] +[2.335861, "o", "u"] +[2.485753, "o", "m"] +[2.776119, "o", "b"] +[3.009771, "o", "e"] +[3.129275, "o", "r"] +[3.317859, "o", "s"] +[3.595679, "o", "."] +[3.835811, "o", "p"] +[4.001456, "o", "y"] +[4.448924, "o", "\r\n\u001b[?2004l\r"] +[4.475384, "o", "[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]\r\n"] +[4.490576, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] +[5.316432, "o", "."] +[5.448534, "o", "/"] +[5.617079, "o", "s"] +[5.810295, "o", "q"] +[5.963872, "o", "u"] +[6.227657, "o", "a"] +[6.366643, "o", "r"] +[6.492842, "o", "e"] +[6.611395, "o", "_"] +[6.831541, "o", "n"] +[7.102805, "o", "u"] +[7.323411, "o", "m"] +[7.610398, "o", "b"] +[7.855683, "o", "e"] +[8.127994, "o", "r"] +[8.414289, "o", "s"] +[8.623942, "o", "."] +[8.911873, "o", "R"] +[9.311594, "o", "\r\n"] +[9.311712, "o", "\u001b[?2004l\r"] +[9.489246, "o", " [1] 1 4 9 16 25 36 49 64 81 100\r\n"] +[9.507689, "o", "\u001b[?2004h\u001b]2;(x1::debugging)\u0007\u001b[32;1m(x1::debugging)\u001b[0m "] diff --git a/docs/community/training/debugging/square-numbers-demo.toml b/docs/community/training/debugging/square-numbers-demo.toml new file mode 100644 index 00000000..680b8668 --- /dev/null +++ b/docs/community/training/debugging/square-numbers-demo.toml @@ -0,0 +1,29 @@ +output_file = "square-numbers-demo.cast" +start_delay = 0.3 +end_delay = 0.5 +typing_delay = [ + 0.05, + 0.25, +] +pre_nl_delay = [ + 0.2, + 0.2, +] +post_nl_delay = [ + 0.8, + 1.0, +] +with_comments = false +comments_at_top = false +lines = [ + "./square_numbers.py", + "./square_numbers.R", + { action_id = "Marker", label = "END" }, + "exit", +] +cols = 80 +rows = 5 + +[[filters]] +filter_id = "EndMarkerFilter" +end_label = "END" diff --git a/docs/community/training/debugging/square-numbers-pdb.cast b/docs/community/training/debugging/square-numbers-pdb.cast new file mode 100644 index 00000000..1bb6254d --- /dev/null +++ b/docs/community/training/debugging/square-numbers-pdb.cast @@ -0,0 +1,349 @@ +{"version": 2, "width": 80, "height": 24, "timestamp": 1724216836, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} +[0.004525, "o", "\u001b[!p\u001b[?3;4l\u001b[4l\u001b>\u001b[?1049h\u001b[22;0;0t\u001b[4l\u001b[?1h\u001b=\u001b[0m\u001b(B\u001b[1;24r"] +[0.006047, "o", "\u001b[H\u001b[2J\u001b[H\u001b[2J\u001b[23B\u001b[7m \u001b[H\u001b[27m\u001b[23B\u001b[7m \u001b[H\u001b[27m\u001b[23B\u001b[7m \u001b[H\u001b[27m\u001b[23B\u001b[7m \u001b[H\u001b[27m\u001b[H\u001b[2J\u001b[23B\u001b[7m \u001b[H\u001b[27m\u001b[23B\u001b[7m \u001b[H\u001b[27m"] +[0.111888, "o", "\u001b[22;2t\u001b]2;(x1::debugging)\u0007\u001b[1m\u001b[32m(x1::debugging)\u001b[0m "] +[0.297472, "o", "p"] +[0.408534, "o", "y"] +[0.524753, "o", "t"] +[0.66504, "o", "h"] +[0.798985, "o", "o"] +[0.918783, "o", "n"] +[1.035745, "o", "3"] +[1.166345, "o", " "] +[1.275735, "o", "-"] +[1.4096, "o", "m"] +[1.556987, "o", " "] +[1.669553, "o", "p"] +[1.817613, "o", "d"] +[1.951816, "o", "b"] +[2.056371, "o", " "] +[2.179294, "o", "s"] +[2.32357, "o", "q"] +[2.458568, "o", "u"] +[2.575096, "o", "a"] +[2.712077, "o", "r"] +[2.823579, "o", "e"] +[2.927899, "o", "_"] +[3.035931, "o", "n"] +[3.153274, "o", "u"] +[3.276966, "o", "m"] +[3.390595, "o", "b"] +[3.531795, "o", "e"] +[3.641283, "o", "r"] +[3.748397, "o", "s"] +[3.853112, "o", "."] +[3.98345, "o", "p"] +[4.126279, "o", "y"] +[4.456845, "o", "\r\n"] +[4.494598, "o", "> debugging/square_numbers.py(2)()\r\n-> \"\"\"\r\n(Pdb) "] +[4.7, "m", "Set a breakpoint"] +[5.452301, "o", "b"] +[5.595708, "o", "r"] +[5.742767, "o", "e"] +[5.870233, "o", "a"] +[6.017347, "o", "k"] +[6.14264, "o", " "] +[6.256639, "o", "1"] +[6.379398, "o", "6"] +[6.71338, "o", "\r\n"] +[6.713562, "o", "Breakpoint 1 at debugging/square_numbers.py:16\r\n(Pdb) "] +[7.745539, "o", "c"] +[7.85873, "o", "o"] +[7.976148, "o", "n"] +[8.090055, "o", "t"] +[8.207778, "o", "i"] +[8.308467, "o", "n"] +[8.440298, "o", "u"] +[8.554699, "o", "e"] +[8.85907, "o", "\r\n"] +[8.85921, "o", "> debugging/square_numbers.py(16)find_squares()\r\n-> if is_square(value):\r\n"] +[8.85924, "o", "(Pdb) "] +[9.081, "m", "Show current location"] +[9.745612, "o", "l"] +[9.860581, "o", "i"] +[9.983146, "o", "s"] +[10.090868, "o", "t"] +[11.203442, "o", "\r\n"] +[11.203536, "o", " 11 \r\n 12 def find_squares(lower_bound, upper_bound):\r\n 13 squares = []\r\n 14 value = lower_bound\r\n 15 while value <= upper_bound:\r\n 16 B-> if is_square(value):\r\n 17 squares.append(value)\r\n 18 value += 1\r\n 19 return squares\r\n 20 \r\n 21 \r\u001b[1;23r\u001b[23;1H\n(Pdb) "] +[13.256525, "o", "p"] +[13.368927, "o", "r"] +[13.483868, "o", "i"] +[13.598561, "o", "n"] +[13.711684, "o", "t"] +[13.835765, "o", "("] +[13.946968, "o", "v"] +[14.071736, "o", "a"] +[14.184584, "o", "l"] +[14.326651, "o", "u"] +[14.435913, "o", "e"] +[14.579051, "o", ")"] +[15.689609, "o", "\r\n"] +[15.689692, "o", "1\b\n"] +[15.689811, "o", "(Pdb) "] +[16.146, "m", "Step into `is_square()`"] +[17.742407, "o", "s"] +[17.853405, "o", "t"] +[17.99157, "o", "e"] +[18.104392, "o", "p"] +[18.408988, "o", "\r\n"] +[18.409075, "o", "--Call--\r\n"] +[18.409113, "o", "> debugging/square_numbers.py(22)is_square()\r\n-> def is_square(value):\r\n"] +[18.409141, "o", "(Pdb) "] +[19.367903, "o", "s"] +[19.499585, "o", "t"] +[19.608708, "o", "e"] +[19.721867, "o", "p"] +[20.057163, "o", "\r\n"] +[20.057264, "o", "> debugging/square_numbers.py(23)is_square()\r\n-> for i in range(1, value + 1):\r\n"] +[20.057397, "o", "(Pdb) "] +[21.083079, "o", "l"] +[21.205252, "o", "i"] +[21.336407, "o", "s"] +[21.452416, "o", "t"] +[22.562739, "o", "\r\n"] +[22.56286, "o", " 18 value += 1\r\n 19 return squares\r\n 20 \r\n 21 \r\n 22 def is_square(value):\r\n 23 -> for i in range(1, value + 1):\r\n 24 if i * i == value:\r\n 25 return True\r\n 26 return False\r\n 27 \r\n "] +[22.562907, "o", "28 \r\n(Pdb) "] +[24.615103, "o", "u"] +[24.759021, "o", "p"] +[25.107984, "o", "\r\n"] +[25.108063, "o", "> debugging/square_numbers.py(16)find_squares()\r\n-> if is_square(value):\r\n(Pdb) "] +[26.034808, "o", "l"] +[26.149422, "o", "i"] +[26.298029, "o", "s"] +[26.400998, "o", "t"] +[27.523337, "o", "\r\n"] +[27.523401, "o", " 11 \r\n 12 def find_squares(lower_bound, upper_bound):\r\n 13 squares = []\r\n 14 value = lower_bound\r\n 15 while value <= upper_bound:\r\n 16 B-> if is_square(value):\r\n 17 squares.append(value)\r\n 18 value += 1\r\n 19 return squares\r\n 20 \r\n 21 \r\n(Pdb) "] +[27.979, "o", "\u001b[s\u001b[24;1H\u001b[7m we can check the value of squares \u001b[m\u001b[u"] +[29.575679, "o", "p"] +[29.715048, "o", "r"] +[29.825091, "o", "i"] +[29.95373, "o", "n"] +[30.072316, "o", "t"] +[30.198288, "o", "("] +[30.315144, "o", "s"] +[30.429754, "o", "q"] +[30.54417, "o", "u"] +[30.648343, "o", "a"] +[30.772575, "o", "r"] +[30.916994, "o", "e"] +[31.064668, "o", "s"] +[31.166977, "o", ")"] +[31.513016, "o", "\r\n"] +[31.513096, "o", "[]\r\n"] +[31.513132, "o", "(Pdb) "] +[32.513611, "o", "d"] +[32.659259, "o", "o"] +[32.767921, "o", "w"] +[32.884711, "o", "n"] +[33.204342, "o", "\r\n"] +[33.204452, "o", "> debugging/square_numbers.py(23)is_square()\r\n-> for i in range(1, value + 1):\r\n(Pdb) "] +[33.44, "o", "\u001b[s\u001b[24;1H\u001b[7m \u001b[m\u001b[u"] +[34.15866, "o", "s"] +[34.259468, "o", "t"] +[34.380843, "o", "e"] +[34.525734, "o", "p"] +[34.830548, "o", "\r\n> debugging/square_numbers.py(24)is_square()\r\n-> if i * i == value:\r\n(Pdb) "] +[35.777766, "o", "s"] +[35.916978, "o", "t"] +[36.065687, "o", "e"] +[36.201439, "o", "p"] +[36.51544, "o", "\r\n"] +[36.515526, "o", "> debugging/square_numbers.py(25)is_square()\r\n-> return True\r\n"] +[36.515557, "o", "(Pdb) "] +[36.744, "m", "Return from `is_square()`"] +[37.43589, "o", "s"] +[37.574683, "o", "t"] +[37.708649, "o", "e"] +[37.857879, "o", "p"] +[38.201763, "o", "\r\n"] +[38.20196, "o", "--Return--\r\n> debugging/square_numbers.py(25)is_square()->True\r\n-> return True\r\n(Pdb) "] +[39.111629, "o", "s"] +[39.254985, "o", "t"] +[39.385075, "o", "e"] +[39.502855, "o", "p"] +[39.806283, "o", "\r\n"] +[39.806416, "o", "> debugging/square_numbers.py(17)find_squares()\r\n-> squares.append(value)\r\n(Pdb) "] +[40.021, "m", "Show updated `squares` list"] +[40.657363, "o", "p"] +[40.762232, "o", "r"] +[40.912148, "o", "i"] +[41.051649, "o", "n"] +[41.195581, "o", "t"] +[41.319649, "o", "("] +[41.428762, "o", "s"] +[41.55563, "o", "q"] +[41.676762, "o", "u"] +[41.785445, "o", "a"] +[41.893803, "o", "r"] +[42.00703, "o", "e"] +[42.154767, "o", "s"] +[42.304376, "o", ")"] +[42.640528, "o", "\r\n[]\r\n"] +[42.640681, "o", "(Pdb) "] +[43.63773, "o", "s"] +[43.764392, "o", "t"] +[43.87365, "o", "e"] +[44.017718, "o", "p"] +[44.362371, "o", "\r\n"] +[44.362648, "o", "> debugging/square_numbers.py(18)find_squares()\r\n-> value += 1\r\n(Pdb) "] +[45.399711, "o", "p"] +[45.549327, "o", "r"] +[45.674679, "o", "i"] +[45.812784, "o", "n"] +[45.943807, "o", "t"] +[46.049968, "o", "("] +[46.166126, "o", "s"] +[46.269687, "o", "q"] +[46.414211, "o", "u"] +[46.515515, "o", "a"] +[46.642921, "o", "r"] +[46.751832, "o", "e"] +[46.859613, "o", "s"] +[46.967873, "o", ")"] +[47.312719, "o", "\r\n"] +[47.312858, "o", "[1]\r\n(Pdb) "] +[47.541, "o", "\u001b[s\u001b[24;1H\u001b[7m the value of squares has changed! \u001b[m\u001b[u"] +[48.231614, "o", "s"] +[48.382485, "o", "t"] +[48.509991, "o", "e"] +[48.625551, "o", "p"] +[48.944535, "o", "\r\n"] +[48.944639, "o", "> debugging/square_numbers.py(15)find_squares()\r\n-> while value <= upper_bound:\r\n"] +[48.944703, "o", "(Pdb) "] +[49.819614, "o", "s"] +[49.957695, "o", "t"] +[50.102433, "o", "e"] +[50.226523, "o", "p"] +[50.548852, "o", "\r\n"] +[50.548947, "o", "> debugging/square_numbers.py(16)find_squares()\r\n-> if is_square(value):\r\n(Pdb) "] +[51.484711, "o", "n"] +[51.623307, "o", "e"] +[51.743909, "o", "x"] +[51.875723, "o", "t"] +[52.178744, "o", "\r\n> debugging/square_numbers.py(18)find_squares()\r\n-> value += 1\r\n(Pdb) "] +[52.402, "o", "\u001b[s\u001b[24;1H\u001b[7m \u001b[m\u001b[u"] +[53.071081, "o", "p"] +[53.219911, "o", "r"] +[53.322578, "o", "i"] +[53.433516, "o", "n"] +[53.535592, "o", "t"] +[53.668051, "o", "("] +[53.800433, "o", "v"] +[53.91804, "o", "a"] +[54.02024, "o", "l"] +[54.142308, "o", "u"] +[54.251735, "o", "e"] +[54.37299, "o", ")"] +[54.692186, "o", "\r\n"] +[54.692295, "o", "2\b\n"] +[54.692321, "o", "(Pdb) "] +[55.711625, "o", "p"] +[55.846289, "o", "r"] +[55.97021, "o", "i"] +[56.0905, "o", "n"] +[56.196059, "o", "t"] +[56.326526, "o", "("] +[56.451928, "o", "s"] +[56.597488, "o", "q"] +[56.746219, "o", "u"] +[56.887395, "o", "a"] +[57.030623, "o", "r"] +[57.155689, "o", "e"] +[57.275173, "o", "s"] +[57.398813, "o", ")"] +[57.726129, "o", "\r\n"] +[57.726207, "o", "[1]\r\n(Pdb) "] +[57.947, "o", "\u001b[s\u001b[24;1H\u001b[7m the value of squares has not changed! \u001b[m\u001b[u"] +[57.947, "m", "Add a conditional breakpoint"] +[58.607099, "o", "c"] +[58.737681, "o", "l"] +[58.883347, "o", "e"] +[58.994825, "o", "a"] +[59.109159, "o", "r"] +[59.251787, "o", " "] +[59.384621, "o", "1"] +[60.534392, "o", "\r\n"] +[60.534425, "o", "Deleted breakpoint 1 at debugging/square_numbers.py:16\r\n(Pdb) "] +[62.586635, "o", "b"] +[62.728743, "o", "r"] +[62.863095, "o", "e"] +[62.972939, "o", "a"] +[63.083646, "o", "k"] +[63.227127, "o", " "] +[63.344054, "o", "1"] +[63.478808, "o", "8"] +[63.600774, "o", ","] +[63.724199, "o", " "] +[63.824876, "o", "v"] +[63.966245, "o", "a"] +[64.082401, "o", "l"] +[64.199346, "o", "u"] +[64.303515, "o", "e"] +[64.435082, "o", " "] +[64.574237, "o", "="] +[64.700165, "o", "="] +[64.824254, "o", " "] +[64.963366, "o", "4"] +[65.06824, "o", "9"] +[66.196277, "o", "\r\nBreakpoint 2 at debugging/square_numbers.py:18\r\n"] +[66.196373, "o", "(Pdb) "] +[66.652, "o", "\u001b[s\u001b[24;1H\u001b[7m \u001b[m\u001b[u"] +[68.248328, "o", "c"] +[68.392739, "o", "o"] +[68.516071, "o", "n"] +[68.621905, "o", "t"] +[68.771138, "o", "i"] +[68.894353, "o", "n"] +[69.031926, "o", "u"] +[69.136275, "o", "e"] +[69.477046, "o", "\r\n"] +[69.479688, "o", "> debugging/square_numbers.py(18)find_squares()\r\n-> value += 1\r\n(Pdb) "] +[69.697, "m", "Stop at the conditional breakpoint"] +[70.352751, "o", "p"] +[70.466919, "o", "r"] +[70.588438, "o", "i"] +[70.713067, "o", "n"] +[70.835375, "o", "t"] +[70.937003, "o", "("] +[71.067244, "o", "v"] +[71.204682, "o", "a"] +[71.354011, "o", "l"] +[71.48225, "o", "u"] +[71.628841, "o", "e"] +[71.77667, "o", ")"] +[72.08829, "o", "\r\n"] +[72.088374, "o", "49\r\n(Pdb) "] +[72.972917, "o", "p"] +[73.11125, "o", "r"] +[73.224163, "o", "i"] +[73.330112, "o", "n"] +[73.477669, "o", "t"] +[73.612016, "o", "("] +[73.76012, "o", "s"] +[73.876853, "o", "q"] +[73.987119, "o", "u"] +[74.094043, "o", "a"] +[74.241785, "o", "r"] +[74.350153, "o", "e"] +[74.490289, "o", "s"] +[74.601531, "o", ")"] +[75.745992, "o", "\r\n"] +[75.746119, "o", "[1, 4, 9, 16, 25, 36, 49]\r\n(Pdb) "] +[76.202, "m", "Continue until the script ends"] +[77.798742, "o", "c"] +[77.919531, "o", "o"] +[78.069038, "o", "n"] +[78.214485, "o", "t"] +[78.333852, "o", "i"] +[78.455246, "o", "n"] +[78.591434, "o", "u"] +[78.723284, "o", "e"] +[79.86969, "o", "\r\n"] +[79.87667, "o", "[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]\r\nThe program finished and will be restarted\r\n"] +[79.876846, "o", "> debugging/square_numbers.py(2)()\r\n-> \"\"\"\r\n(Pdb) "] +[81.921356, "o", "q"] +[82.066705, "o", "u"] +[82.183706, "o", "i"] +[82.302386, "o", "t"] +[83.421588, "o", "\r\n"] +[83.453566, "o", "\u001b[1m\u001b[32m(x1::debugging)\u001b[0m "] diff --git a/docs/community/training/debugging/square-numbers-pdb.toml b/docs/community/training/debugging/square-numbers-pdb.toml new file mode 100644 index 00000000..38b7a0d8 --- /dev/null +++ b/docs/community/training/debugging/square-numbers-pdb.toml @@ -0,0 +1,80 @@ +output_file = "square-numbers-pdb.cast" +start_delay = 0.3 +end_delay = 0.5 +typing_delay = [ + 0.05, + 0.1, +] +pre_nl_delay = [ + 0.2, + 0.2, +] +post_nl_delay = [ + 0.8, + 1.0, +] +with_comments = true +comments_at_top = false +lines = [ + "python3 -m pdb square_numbers.py", + { action_id = "Marker", label = "Set a breakpoint" }, + "break 16", + "continue", + { action_id = "Marker", label = "Show current location" }, + { action_id = "Input", text = "list", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Input", text = "print(value)", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Marker", label = "Step into `is_square()`" }, + "step", + "step", + { action_id = "Input", text = "list", pre_nl_delay = 1, post_nl_delay = 2 }, + "up", + { action_id = "Input", text = "list", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Comment", comment = "we can check the value of squares" }, + "print(squares)", + "down", + { action_id = "Comment", comment = "" }, + "step", + "step", + { action_id = "Marker", label = "Return from `is_square()`" }, + "step", + "step", + { action_id = "Marker", label = "Show updated `squares` list" }, + "print(squares)", + "step", + "print(squares)", + { action_id = "Comment", comment = "the value of squares has changed!" }, + "step", + "step", + "next", + { action_id = "Comment", comment = "" }, + "print(value)", + "print(squares)", + { action_id = "Comment", comment = "the value of squares has not changed!" }, + { action_id = "Marker", label = "Add a conditional breakpoint" }, + { action_id = "Input", text = "clear 1", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Input", text = "break 18, value == 49", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Comment", comment = "" }, + "continue", + { action_id = "Marker", label = "Stop at the conditional breakpoint" }, + "print(value)", + { action_id = "Input", text = "print(squares)", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Marker", label = "Continue until the script ends" }, + { action_id = "Input", text = "continue", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Input", text = "quit", pre_nl_delay = 1, post_nl_delay = 2 }, + { action_id = "Marker", label = "END" }, + "exit", +] +cols = 80 +rows = 24 + +[[filters]] +filter_id = "RegexReplacementFilter" +regex = "/home/.*/git-is-my-lab-book/docs/community/training/" +replacement = "" + +[[filters]] +filter_id = "EndMarkerFilter" +end_label = "END" + +[[filters]] +filter_id = "CommentFilter" diff --git a/docs/community/training/debugging/square_numbers.R b/docs/community/training/debugging/square_numbers.R new file mode 100755 index 00000000..177978aa --- /dev/null +++ b/docs/community/training/debugging/square_numbers.R @@ -0,0 +1,35 @@ +#!/usr/bin/env -S Rscript --vanilla +# +# Print the square numbers between 1 and 100. +# + + +main <- function() { + squares <- find_squares(1, 100) + print(squares) +} + + +find_squares <- function(lower_bound, upper_bound) { + squares <- c() + value <- lower_bound + while (value <= upper_bound) { + if (is_square(value)) { + squares <- c(squares, value) + } + value <- value + 1 + } + squares +} + + +is_square <- function(value) { + for (i in seq(value)) { + if (i * i == value) { + return(TRUE) + } + } + FALSE +} + +main() diff --git a/docs/community/training/debugging/square_numbers.py b/docs/community/training/debugging/square_numbers.py new file mode 100755 index 00000000..dd300fde --- /dev/null +++ b/docs/community/training/debugging/square_numbers.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +Print the square numbers between 1 and 100. +""" + + +def main(): + squares = find_squares(1, 100) + print(squares) + + +def find_squares(lower_bound, upper_bound): + squares = [] + value = lower_bound + while value <= upper_bound: + if is_square(value): + squares.append(value) + value += 1 + return squares + + +def is_square(value): + for i in range(1, value + 1): + if i * i == value: + return True + return False + + +if __name__ == '__main__': + main() diff --git a/docs/community/training/debugging/techniques-and-tools.md b/docs/community/training/debugging/techniques-and-tools.md new file mode 100644 index 00000000..0c90567f --- /dev/null +++ b/docs/community/training/debugging/techniques-and-tools.md @@ -0,0 +1,25 @@ +# Techniques and tools + +## Identifying errors + +How to read tracebacks? + +## Developing a plan + +Version control, last known good version, `git bisect`. + +## Searching for the root cause + +Print statements, debuggers, REPLs, last good version, recent changes, assert statements, comment out code ... + +## Fixing the root cause + +Is there an optimal solution? +Smallest change or least effect on the rest of your code? +Does the best solution involve restructuring or modifying other parts of your code? + +## After it's fixed + +Test cases, defensive programming, shorten the feedback loop, what coding practices might make such a mistake easier to find next time (e.g., small functions, frequent commits). + +Document your experience! diff --git a/docs/community/training/debugging/using-a-debugger.md b/docs/community/training/debugging/using-a-debugger.md new file mode 100644 index 00000000..a66a57c2 --- /dev/null +++ b/docs/community/training/debugging/using-a-debugger.md @@ -0,0 +1,97 @@ +# Using a debugger + +!!! tip + + A debugger is a tool for examining the state of a running program. + + Debuggers can help us to find errors because they show us what the code **is actually doing**. + +!!! info + + Editors such as [RStudio](https://support.posit.co/hc/en-us/articles/205612627-Debugging-with-the-RStudio-IDE), [PyCharm](https://www.jetbrains.com/pycharm/features/debugger.html), and [Spyder](https://docs.spyder-ide.org/current/panes/debugging.html) allow you to run a debugger inside the editor itself. + +!!! question + + What is the "state" of a running program? + +How to use a debugger to examine the program state: + +- [`breakpoint()`](https://docs.python.org/3/library/functions.html#breakpoint) and [`pdb`](https://docs.python.org/3/library/pdb.html#module-pdb) for Python +- [`browser()`](https://rdrr.io/r/base/browser.html), [`debug()`](https://rdrr.io/r/base/debug.html) and [`traceback()`](https://rdrr.io/r/base/traceback.html) for base R +- step over, step into, etc +- Advanced R, chapter 22: [Debugging](https://adv-r.hadley.nz/debugging.html) + +How to move up and down the call stack! + +- Start with some toy examples and some simple diagrams and asciinema videos. + +- Relate this to tracebacks! + When you know where a failure occurs, you can set a breakpoint and inspect the current state of the program, but also walk up the stack! + +## Example: Square numbers + +[Square numbers](https://en.wikipedia.org/wiki/Square_number) are positive integers that are equal to the square of an integer. +Here we have provided example Python and R scripts that print all of the square numbers between 1 and 100: + +
+ +You can download these scripts to run on your own computer: + +- [square_numbers.py](square_numbers.py) +- [square_numbers.R](square_numbers.R) + +Each script contains three functions: + +- `main()` +- `find_squares(lower_bound, upper_bound)` +- `is_square(value)` + +The diagram below shows how `main()` calls `find_squares()`, which in turn calls `is_square()` many times. + +```mermaid +sequenceDiagram + participant M as main() + participant F as find_squares() + participant I as is_square() + activate M + M ->>+ F: lower_bound = 1, upper_bound = 100 + Note over F: squares = [ ] + F ->>+ I: value = 1 + I ->>- F: True/False + F ->>+ I: value = 2 + I ->>- F: True/False + F -->>+ I: ... + I -->>- F: ... + F ->>+ I: value = 100 + I ->>- F: True/False + F ->>- M: squares = [...] + Note over M: print(squares) + deactivate M +``` + +??? info "Source code" + + === "Python" + + ```py title="square_numbers.py" linenums="1" + --8<-- "square_numbers.py" + ``` + + === "R" + + ```R title="square_numbers.R" linenums="1" + --8<-- "square_numbers.R" + ``` + +
+ +Video timeline: + +1. Set a breakpoint +2. Show current location +3. Step into `is_square()` +4. Return from `is_square()` +5. Show updated `squares` list +6. Add a conditional breakpoint +7. Stop at the conditional breakpoint +8. Continue until the script ends diff --git a/docs/community/training/debugging/what-is-debugging.md b/docs/community/training/debugging/what-is-debugging.md new file mode 100644 index 00000000..56ccb57f --- /dev/null +++ b/docs/community/training/debugging/what-is-debugging.md @@ -0,0 +1,82 @@ +# What is debugging? + +!!! info + + Debugging is the process of identifying and removing errors from computer software. + +You need to **identify (and reproduce) the problem** and **only then** begin fixing it (ideally writing a test case first, to check that (a) you can identify the problem; and (b) to identify if you accidentally introduce the same, or similar, mistake in the future). + +## Action 1: Identify the error + +!!! tip + + First make sure that you can reproduce the error. + +What observations or outputs indicate the presence of this error? + +Is the error reproducible, or does it come and go? + +Write a failing test? + +## Action 2: Develop a plan + +!!! tip + + The visible error and its root cause may be located in very different parts of your code. + +Identify like and unlikely suspects, what can we rule in/out? +What parts of your code recently changed? +When was the last time you might have noticed this error? + +## Action 3: Search for the root cause + +!!! tip + + As much as possible, the search should be guided by **facts**, not **assumptions**. + +Our assumptions about the code can help us to develop a plan, but we need to verify whether our assumptions are actually true. + +For example: + +```text +Simple errors can often hide +hide in plain sight and be +surprisingly difficult to +discover without assistance. +``` + +Thinking "this looks right" is **not a reliable indicator** of whether a piece of code contains an error. + +
+ ![A horse with its head buried in a haystack.](headless-horse.jpg){ align=left, width="50%" } +
Searching at random is like looking for a needle in a haystack. +
+ ([Perry McKenna](https://flickr.com/photos/63567936@N00/4210167891/), Flickr, 2009; CC BY 2.0) +
+
+ +Better approaches involve confirming what the code is **actually doing**. + +- This can be done using indirect approaches, such as adding print statements or writing test cases. + +- It can also be done by directly inspecting the state of the program with a debugger — more on that shortly! + +## Action 4: Fix the root cause + +!!! tip + + It's worth considering if the root cause is a result of deliberate decisions or unintentional mistakes. + +Don't start modifying/adding/removing lines based on suspicions or on the off chance that it might work. +Without identifying the **root cause** of the error, there is no guarantee that making the error seem to disappear will actually have fixed the root cause. + +## Action 5: After it's fixed + +!!! tip + + This is the perfect time to reflect on your experience! + +What can you learn from this experience? +Can you avoid this mistake in the future? +What parts of the process were the hardest or took the longest? +Are the tools and techniques that might help you next time? diff --git a/docs/community/training/debugging/why-is-debugging-useful.md b/docs/community/training/debugging/why-is-debugging-useful.md new file mode 100644 index 00000000..26684769 --- /dev/null +++ b/docs/community/training/debugging/why-is-debugging-useful.md @@ -0,0 +1,23 @@ +# Why is debugging useful? + +Many of the errors that take a long time for us to find are relatively simple **once we find them**. + +We usually have a hard time finding these errors because: + +1. We read what we expect to see, rather than what is actually written; and + +2. We rely on assumptions about where the mistake might be, and our intuition is often wrong. + +!!! info + + Debugging forces us to verify our assumptions and to observe what the code is actually doing. + +Here are some common mistakes that can be difficult to identify when reading through your own code: + +- Using an incorrect index into an array, matrix, list, etc; + +- Using incorrect bounds on a loop or sequence; + +- Confusing the digit "1" with letter "l"; + +- Confusing the digit "0" with letter "O". diff --git a/mkdocs.yml b/mkdocs.yml index 851e036b..da9c3767 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -103,6 +103,19 @@ nav: - "Fixing a bug in pypfilt": community/case-studies/moss-pypfilt-earlier-states.md - "Incorrect data in a pre-print figure": community/case-studies/moss-incorrect-data-pre-print.md - "Pen and paper version control": community/case-studies/campbell-pen-and-paper-version-control.md + - "Training events": + - community/training/README.md + - "Introduction to Debugging": + - community/training/debugging/README.md + - "Learning objectives": community/training/debugging/learning-objectives.md + - "Debugging manifesto": community/training/debugging/manifesto.md + - "What is debugging?": community/training/debugging/what-is-debugging.md + - "Why is debugging useful?": community/training/debugging/why-is-debugging-useful.md + - "Using a debugger": community/training/debugging/using-a-debugger.md + - "Example: Perfect numbers": community/training/debugging/example-perfect-numbers.md + - "Example: Python vs R": community/training/debugging/example-python-vs-r.md + - "Techniques and tools": community/training/debugging/techniques-and-tools.md + - "Resources": community/training/debugging/resources.md markdown_extensions: - admonition @@ -115,7 +128,18 @@ markdown_extensions: emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.highlight - pymdownx.inlinehilite - - pymdownx.superfences + - pymdownx.snippets: + base_path: [ + # Locate snippets relative to the documentation root directory. + "docs/", + # Locate snippets in the 2024 debugging workshop directory. + "docs/community/training/debugging", + ] + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - toc: @@ -168,3 +192,7 @@ extra_css: extra_javascript: - extra/asciinema-player.min.js - extra/wrapper.js + +# Ignore TOML scripts for generating asciinema recordings. +exclude_docs: | + *.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..db9e5b31 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +# Formatter and linter settings for Python code examples. + +[tool.ruff] +line-length = 78 +target-version = "py310" + +[tool.ruff.lint] +# Enable pyflakes (F), pycodestyle (E, W), flake8-bugbear (B), pyupgrade (UP), +# flake8-debugger (T10), and NumPy (NPY). +select = ["F", "E", "W", "B", "UP", "T10", "NPY"] + +[tool.ruff.format] +quote-style = "single" +docstring-code-format = true