# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Julia 1.7.0-rc2
#     language: julia
#     name: julia-1.7
# ---

# # Debugging

# A crucial concept that readers have hopefully started to pick up at this point is *debugging*. Simply put, this is the process by which one fixes code until it accomplishes what it is expected to accomplish. In practice, this can feel like being a detective in a crime movie where you are also the murderer.
#
# There are three types of errors to debug:
#
# * syntax errors
#   * missing parentheses
# * runtime errors
#   * overflow error
# * semantic errors
#   * "this answer is wrong"
#   
# We will explore these one at a time below.

# ## Syntax errors
# Syntax errors are errors that prevent a compiler or interpreter from understanding the source code and generating a working program.
#
# They are often due to typos, and are the easiest of the errors to fix. Thankfully, they appear less frequently as you master a specific programming language, although it is unreasonably to expect them to go away entirely. 
#
# Modular programming (or breaking down the functionality of a larger program into independent and potentially interchangeable modules) helps isolate errors. Throughout this course, this means writing small and readable functions that accomplish only one task, separated into different cells of a Jupyter notebook.
#
# Let's see some syntax error examples.

fnction mySqrt(a)
    return sqrt(a)
end

# Here we see that the stack trace tells us the error is in line 1 of `In[1]`. Specifically the error is 'an extra token "mySqrt" after end of expression'. Putting these two clues together, we should see that the word "function" on the first line was incorrectly spelled.

function lots_of_math(n)
    var = 0
    for i = 1:n
        var += sqrt(2*pi*i)*(((i-1)+(i+2))/((n+5)*(5*i))
    end
    return var
end

# Another syntax error that is quite common (especially if you're writing complicated mathematical expressions): an uneven amount of parentheses. The stack trace tells us that the error is on line 5 of the above cell, but if we look around line 5, we see that it must be an error carried over from line 4 with the complicated expression.
#
# Here is a helpful tip to zoom in on this issue further. We can track the expression character by character from left to right, counting the number of open parentheses at each character.
#
# ```julia
# sqrt(2*pi*i)*(((i-1)+(i+2))/((n+5)*(5*i))
#
#     1      0 123   2 3   21 23   2 3   21
# ```
# This count confirms that the syntax error will be solved with an extra `)` closing the second half of the expression. The extra `)` will ensure our parenthesis count returns to 0.

function more_math()
    i = 10
    var1
    = sqrt(2*pi*i)*(((i-1)+(i+2))/((i+5)*(5*i)))
    println(var1)
    
    i = 20
    var2 = 
    sqrt(2*pi*i)*(((i-1)+(i+2))/
        ((i+5)*(5*i)))
    println(var2)
end

# Julia is not expecting line 4 to start with an "=". This is not allowed and is fixed by combining line 3 and 4 into one line. However, note that you are allowed to end a line with an "=" as on line 8.
#
# When debugging, it is helpful to turn on line numbers by selecting View -> Toggle Line Numbers in a Jupyter notebook.

function array_init(n)
    array = Array{Float64,2}(n,n)
    for i = 1:size(array,1):
        for j = 1:size(array,2)
            array[i,j] = i*j
        end
    end
end

# A final syntax error to explore. Julia reports that line 4 cannot have a line break in a vectorized expression using ":". If we explore just around that line number, we'll see that again the issue was carried over from the previous line. There is an extra `:` after `size(array,1)`.

# ## Runtime errors
#
# Runtime errors are errors that take place while executing a program. 
#
# These errors can sometimes be caused by typos, but are often more complex than syntax errors. You should fully read the error message(s) before attempting to fix them. If you are unsure how to interpret an error message you have never seen before, please google the exact expression reported in the error message. You are certainly not the first person to cause this error, and there may be a post online from another user who has already solved your exact issue (such as on StackOverflow.com or discourse.julialang.org).
#
# As always, modular programming will help isolate errors. If you have tested other parts of your program and are confident everything else is correct, then the issue is reduced to somewhere only in a few lines of code. 

# +
# Compute the first n terms of the Taylor expansion of e^x centered around x=0
function taylorExpansion(x, n)
    sum = 0
    for i = 1:n
        sum += x^n/factorial(n)
    end
    return sum
end

taylorExpansion(0.5, 25)
# -

# On line 5 of this cell, Julia is reporting that `taylorExpansion()` is calling `factorial()` in a separate Julia file which tries to lookup $25!$, but is instead returning an overflow error. This is because $25! \approx 10^{25}$ and indeed is much too large to store in the variable `sum`. A closer inspection of this algorithm shows that we are dividing two large numbers, $x^n$ and $n!$, and the algorithm can be rewritten so as to avoid computing such large numbers explicitly, avoiding the overflow error entirely. This process was explored in [Section 1.4: For-Loops](for-loops).

# +
# Confuse the audience with a compicated math expression
function lots_of_math2(n)
    var = 0
    for i = 1:n
        var += sqrt(2*pi*i)(((i-1)+(i+2))/((n+5)*(5*i)))
    end
    return var
end

lots_of_math2(5)
# -

# Julia's error message here has dramatically improved in recent versions. While it reports this as a method error "objects of type `Float64` are not callable," it also explains that this can be caused by forgetting an operator such as `*`. Indeed, that's exactly the issue. Comparing this function to `lots_of_math1()` above, we see the issue is a missing multiplication sign between `sqrt(2*pi*i)` and the other expression.
#
# The reason it says this is a method error is because Julia is interpreting line 5 exactly like:
# ```julia
# var1 = sqrt(2*pi*i)
# var2 = ((i-1)+(i+2))/((n+5)*(5*i))
# var += var1(var2)
# ```
# That is to say, Julia is trying to call the function `var1()` with input `var2`. This clearly does not make sense since `var1` is a floating point number, not a function, hence the error message that `Float64`s are not callable.

# +
# Print out a comparison between x and y
function compare(x,y)
    if x < y
        relation = "less than"
    elseif x > y
        relation = "greater than"
    end
    println(x, " is ", relation, " ", y)
end

compare(5,5)
# -

# Apparently, `relation` is not defined on line 8 above. But isn't this variable defined on line 4 or 6 directly above? Well actually not at all. Since we are calling `compare()` with inputs that satisfy $x=y$, neither conditional is evaluated as true and so `relation` is still undefined on line 8. This error can be fixed by adding a third condition for when `x==y` that sets `relation = "equal to"`.

# +
# Initialize a 2d array to hold first m x n integer products
function array_init2(m,n)
    array = Matrix{Float64}(undef,m,n)
    for i = 1:n
        for j = 1:m
            array[i,j] = i*j
        end
    end
    return array
end

array_init2(5,4)
# -

# A classic out-of-bounds error. You will most likely create this error several times throughout this course, and certainly many more times beyond the course! 
#
# To debug this error, we first see that the out-of-bounds error occurs on line 6 where we are accessing `array` at `[i,j]`. Therefore, we can quickly determine that either `i` and `j` are not correct or the overall size of `array` is not correct. At this point, we would have to turn on our brains and actually start figuring out the underlying issue. The matrix is in $\mathbb{R}^{m \times n}$, where $m$ is the number of columns and $n$ is the number of rows - that makes sense. But we are looping our `i` counter over the first `n` columns and the `j` counter over the first `m` rows. There lies the contradiction. Correct code would swap the `i` and `j` variables:
#
# ```julia
# for j = 1:n
#     for i = 1:m
#         array[i,j] = i*j
#     end
# end
# ```

# Let's explore one last runtime error example.

# +
# Divide an input number by 0
function divide_by_zero(input)
    return input/0
end

divide_by_zero("string")
# -

# This error is somewhat unique to Julia because most other programming languages would report dividing a string by the integer zero as a syntax error. However, Julia specifically allows you to write such general code, but will only catch the error when you try and run it. There are many benefits to this approach, but for now let's just explore this one possible error. We see that the error reports that on line 7 above, we are calling `divide_by_zero()` which tries to compute `String/Int64`, something that has not been programmed.
#
# This error can be fixed by writing that functionality ourselves using the tools we will explore in [Section 11: Structs and Objects](structs), or by not trying to divide a string by zero in the first place! This can be enforced by rewriting the function as follows:

# +
# Divide an input number by 0
function divide_by_zero(input::Number) # Only allows an input of type Number
    return input/0
end

divide_by_zero("string")
# -

# Now the error message makes more sense. There correctly is no function `divide_by_zero()` that takes in a `String` as the input.
#
# Note: This fix will not take effect until after you restart the Jupyter kernel so that Julia forgets about the previous function `divide_by_zero()` which allowed `input` to be any type.

# ## Semantic errors
#
# Semantic errors technically are not errors at all. If there are semantic errors in your program, the computer does not generate any error messages, but the program simply does the wrong thing.
#
# A classic example of a semantic error is an infinite loop or infinite recursion. There may not be an error message reported, but an infinite loop can never be intended design.
#
# Debugging semantic errors might take up the majority of your time while programming, so it is important to set yourself up for success in expectation of these errors. Especially as you work on larger programs and projects, start with small test cases to confirm the logic of your program makes sense. Not to sound like a broken record, but modular programming will certainly help isolate the issues; if you have already tested most of your code successfully and can rely upon it, then you only have to struggle to debug the smaller pieces that fail the tests. 
#
# While debugging, use `println` statements to see what is really going on. A simple tip that goes a long way: assume nothing is correct and slowly start to confirm that what you think is going on inside the program is actually going on by looking at a report of the variables. A more effective method than using print statements is to use a debugger. The IDE Atom allows you debug your code carefully, but Jupyter notebooks unfortunately do not play well with debuggers, so we won't discuss them too much in this course.

# Returns the sum of the first n integers
function array_sum(n)
    # Initialization
    array = Vector{Float64}(undef,n)
    for i = 1:n-1
        array[i] = i
    end
    
    # Summation
    sum = 0
    for i = 1:n
        sum += array[i]
    end
    return sum
end

# First try to debug the above algorithm yourself. What tests would you run? Think small at first! What variables would you like to print in order to better understand what is going on under the hood? Is there a more modular way to rewrite this algorithm in order to easier test the different parts?

# Testing cell for array_sum()


# Personally, I would write debugging code as follows:

# +
# Returns the sum of the first n integers
function array_sum(n)
    # Initialization
    array = Vector{Float64}(undef,n)
    for i = 1:n-1
        array[i] = i
    end
    println(array) # TODO: Debugging
    
    # Summation
    sum = 0
    println("Sum before = ", sum) # TODO: Debugging
    for i = 1:n
        sum += array[i]
    end
    println("Sum after = ", sum) # TODO: Debugging
    return sum
end

array_sum(1);
array_sum(2);
array_sum(5);
# -

# Equipped with all this information, it becomes much more simple to debug the algorithm. We see that there is an issue with initializing the array: the last entry of the array remains undefined even after initialization! That information directly leads us to fix the bounds on the first loop from `1:n-1` to `1:n`. Re-running the tests yields the expected answer, and so we can remove the debugging printouts, comfortable that the algorithm is correct.

StarcoderdataJupyterScriptsDedupFiltered