From 2b33fb0688e1b3133588fb1cbf1bd64d9ff4461a Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 15 Sep 2023 06:55:05 +0000 Subject: [PATCH] site deploy Auto-generated via {sandpaper} Source : ed657e67ce470ea8f6c9ed17b16ec29318eaf49e Branch : md-outputs Author : GitHub Actions Time : 2023-09-15 06:54:41 +0000 Message : markdown source builds Auto-generated via {sandpaper} Source : cc1ed15410f5a8f518b4136f394f85bfc146df38 Branch : main Author : github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Time : 2023-09-15 06:53:33 +0000 Message : [create-pull-request] automated change (#67) Co-authored-by: Simon Christ --- 01_Introduction.html | 66 +- 02_Getting_started.html | 72 +- 03_Julia_type_system.html | 74 +- 04_Using_the_package_manager.html | 90 +- 05_Write_functions.html | 452 +++- 06_Control_flow.html | 92 +- 06_Interfacing_conditions.html | 714 ++++++ 07_Loops.html | 903 ++++++++ 07_Using_Modules.html | 96 +- 08_Creating_Packages.html | 92 +- 08_Using_Modules.html | 705 ++++++ 09_Adding_tests.html | 100 +- 09_Creating_Packages.html | 595 +++++ 10_Adding_tests.html | 611 +++++ 10_Wrapping_Up.html | 86 +- 11_Wrapping_Up.html | 566 +++++ 404.html | 68 +- CODE_OF_CONDUCT.html | 66 +- LICENSE.html | 66 +- aio.html | 2142 ++++++++++++++++-- discuss.html | 66 +- images.html | 98 +- index.html | 74 +- instructor-notes.html | 86 +- instructor/01_Introduction.html | 66 +- instructor/02_Getting_started.html | 72 +- instructor/03_Julia_type_system.html | 74 +- instructor/04_Using_the_package_manager.html | 90 +- instructor/05_Write_functions.html | 452 +++- instructor/06_Control_flow.html | 92 +- instructor/06_Interfacing_conditions.html | 715 ++++++ instructor/07_Loops.html | 904 ++++++++ instructor/07_Using_Modules.html | 96 +- instructor/08_Creating_Packages.html | 92 +- instructor/08_Using_Modules.html | 706 ++++++ instructor/09_Adding_tests.html | 100 +- instructor/09_Creating_Packages.html | 596 +++++ instructor/10_Adding_tests.html | 612 +++++ instructor/10_Wrapping_Up.html | 86 +- instructor/11_Wrapping_Up.html | 567 +++++ instructor/404.html | 68 +- instructor/CODE_OF_CONDUCT.html | 66 +- instructor/LICENSE.html | 66 +- instructor/aio.html | 2126 +++++++++++++++-- instructor/discuss.html | 66 +- instructor/images.html | 98 +- instructor/index.html | 146 +- instructor/instructor-notes.html | 92 +- instructor/key-points.html | 116 +- instructor/profiles.html | 68 +- instructor/reference.html | 66 +- key-points.html | 116 +- md5sum.txt | 10 +- pkgdown.yml | 2 +- profiles.html | 68 +- reference.html | 66 +- sitemap.xml | 36 + 57 files changed, 15626 insertions(+), 950 deletions(-) create mode 100644 06_Interfacing_conditions.html create mode 100644 07_Loops.html create mode 100644 08_Using_Modules.html create mode 100644 09_Creating_Packages.html create mode 100644 10_Adding_tests.html create mode 100644 11_Wrapping_Up.html create mode 100644 instructor/06_Interfacing_conditions.html create mode 100644 instructor/07_Loops.html create mode 100644 instructor/08_Using_Modules.html create mode 100644 instructor/09_Creating_Packages.html create mode 100644 instructor/10_Adding_tests.html create mode 100644 instructor/11_Wrapping_Up.html diff --git a/01_Introduction.html b/01_Introduction.html index 804228ce..e6ccb1d3 100644 --- a/01_Introduction.html +++ b/01_Introduction.html @@ -225,7 +225,7 @@

@@ -234,7 +234,7 @@

@@ -243,7 +243,7 @@

@@ -252,7 +252,61 @@

+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ +
+ +
+
+ + +
+
+ + + +
+ @@ -486,9 +540,9 @@

Keypoints +
+ Programming with Julia +
+ +
+
+ + + + + +
+
+

Interfaces & conditionals

+

Last updated on 2023-09-15 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • “How to use conditionals?”
  • +
  • “What is an interface?”
  • +
+
+
+
+
+
+

Objectives

+
+
+
+
+
+

Conditionals +

+

Before starting to work in a new document, Melissa has to:

+

Activate her environment

+
+

JULIA +

+
using Pkg
+Pkg.activate(joinpath(@__DIR__, "projects", "trebuchet"))
+Pkg.instantiate()
+
+
  Activating project at `~/projects/trebuchet`
+
+

Importing the package under its modified name

+
+

JULIA +

+
import Trebuchet as Trebuchets
+
+

Defining the structures

+
+

JULIA +

+
mutable struct Trebuchet <: AbstractVector{Float64}
+  counterweight::Float64
+  release_angle::Float64
+end
+
+struct Environment
+  wind::Float64
+  target_distance::Float64
+end
+
+Base.size(::Trebuchet) = tuple(2)
+
+

Now that Melissa knows that she has to add a method for

+
getindex(trebuchet::Trebuchet, i::Int)
+

she thinks about the implementation.

+

If the index is 1 she wants to get the +counterweight field and if the index is 2 she +wants to get release_angle and since these are the only two +fields she wants to return an error if anything else comes in. In Julia +the keywords to specify conditions are if, +elseif and else, closed with an +end. Thus she writes

+
+

JULIA +

+
function Base.getindex(trebuchet::Trebuchet, i::Int)
+    if i === 1
+        return trebuchet.counterweight
+    elseif i === 2
+        return trebuchet.release_angle
+    else
+        error("Trebuchet only accepts indices 1 and 2, yours is $i")
+    end
+end
+
+

And tries again:

+
+

JULIA +

+
trebuchet = Trebuchet(500, 0.25pi)
+
+
+

OUTPUT +

+
2-element Trebuchet:
+ 500.0
+   0.7853981633974483
+
+

Notice, that the printing is different from our +trebuchet in the former +episode.

+
+

Interfaces

+

Why is that? By subtyping Trebuchet as +AbstractVector we implicitly opted into a widespread +interface in the Julia language: AbstractArrays. +An interface is a collection of methods that should be implemented by +all subtypes of the interface type in order for generic code to work. +For example, the Julia +manual lists all methods that a subtype of +AbstractArray need to implement to adhere to the +AbstractArray interface:

+
  • +size(A) returns a tuple containing the dimensions of +A +
  • +
  • +getindex(A, i::Int) returns the value associated with +index i +
  • +
  • +setindex!(A, v, i::Int) writes a new value +v at the index i (optional)
  • +

Now, that Melissa implemented the mandatory methods for this +interface for the Trebuchet type, it will work with every +function in Base that accepts an +AbstractArray. She tries a few things that now work without +her writing explicit code for it:

+
+

JULIA +

+
trebuchet + trebuchet
+
+
+

OUTPUT +

+
2-element Vector{Float64}:
+ 1000.0
+    1.5707963267948966
+
+
+

JULIA +

+
using LinearAlgebra
+dot(trebuchet, trebuchet)
+
+
+

OUTPUT +

+
250000.61685027508
+
+
+

JULIA +

+
trebuchet * transpose(trebuchet)
+
+
+

OUTPUT +

+
2×2 Matrix{Float64}:
+ 250000.0    392.699
+    392.699    0.61685
+
+

That is, it now behaves like you would expect from an ordinary +matrix.

+

Now she goes about implementing the missing optional method for +setindex! of the AbstractArray interface.

+
+
+ +
+
+

Implement setindex! +

+
+

Write the missing method for +setindex(trebuchet::Trebuchet, v, i::Int) similar to +Melissas getindex function.

+
+
+
+
+
+ +
+
+
+

JULIA +

+
function Base.setindex!(trebuchet::Trebuchet, v, i::Int)
+     if i === 1
+         trebuchet.counterweight = v
+    elseif i === 2
+        trebuchet.release_angle = v
+    else
+        error("Trebuchet only accepts indices 1 and 2, yours is $i")
+    end
+end
+
+
+
+
+
+

With the new Trebuchet defined with a complete +AbstractArray interface, Melissa tries her new method to +modify a counterweight by index:

+
+

JULIA +

+
trebuchet[1] = 2
+
+
+

OUTPUT +

+
2
+
+
+

JULIA +

+
trebuchet
+
+
+

OUTPUT +

+
2-element Trebuchet:
+ 2.0
+ 0.7853981633974483
+
+
+
+ +
+
+

Keypoints +

+
+
  • “Interfaces are informal”
  • +
  • “Interfaces facilitate code reuse”
  • +
  • “Conditions use if, elseif, +else and end
  • +
+
+
+
+
+
+ + +
+
+ + + diff --git a/07_Loops.html b/07_Loops.html new file mode 100644 index 00000000..953d4539 --- /dev/null +++ b/07_Loops.html @@ -0,0 +1,903 @@ + +Programming with Julia: Loops +
+ Programming with Julia +
+ +
+
+ + + + + +
+
+

Loops

+

Last updated on 2023-09-15 | + + Edit this page

+ + + +
+ +
+ + + +
+

Overview

+
+
+
+
+

Questions

+
  • “What are for and while loops?”
  • +
  • “What is a comprehension?”
  • +
+
+
+
+
+
+

Objectives

+
+
+
+
+
+

Before starting to work in a new document, Melissa has to:

+

Activate her environment

+
+

JULIA +

+
using Pkg
+Pkg.activate(joinpath(@__DIR__, "projects", "trebuchet"))
+Pkg.instantiate()
+
+
  Activating project at `~/projects/trebuchet`
+
+

Importing the package under its modified name

+
+

JULIA +

+
import Trebuchet as Trebuchets
+
+

Defining the structures

+
+

JULIA +

+
mutable struct Trebuchet <: AbstractVector{Float64}
+  counterweight::Float64
+  release_angle::Float64
+end
+
+struct Environment
+  wind::Float64
+  target_distance::Float64
+end
+
+Base.size(::Trebuchet) = tuple(2)
+function Base.getindex(trebuchet::Trebuchet, i::Int)
+    if i === 1
+        return trebuchet.counterweight
+    elseif i === 2
+        return trebuchet.release_angle
+    else
+        error("Trebuchet only accepts indices 1 and 2, yours is $i")
+    end
+end
+function Base.setindex!(trebuchet::Trebuchet, v, i::Int)
+     if i === 1
+         trebuchet.counterweight = v
+    elseif i === 2
+        trebuchet.release_angle = v
+    else
+        error("Trebuchet only accepts indices 1 and 2, yours is $i")
+    end
+end
+function shoot_distance(trebuchet::Trebuchet, env::Environment)
+     shoot_distance(env.wind, trebuchet.release_angle, trebuchet.counterweight)
+end
+function shoot_distance(args...) # slurping
+     Trebuchets.shoot(args...)[2] # splatting
+end
+
+
+

OUTPUT +

+
shoot_distance (generic function with 2 methods)
+
+

Now Melissa knows how to shoot the virtual trebuchet and get the +distance of the projectile, but in order to aim she needs to take a lot +of trial shots in a row. She wants her trebuchet to only shoot a hundred +meters.

+

She could execute the function several times on the REPL with +different parameters, but that gets tiresome quickly. A better way to do +this is to use loops.

+
+ +

The first thing that comes to her mind is to randomly sample points +of the parameter space of the trebuchet. The function +rand() will give her a random number between 0 and 1 that +is uniformly distributed. So

+
+

JULIA +

+
Trebuchet( rand() * 500, rand() * pi/2 )
+
+
+

OUTPUT +

+
2-element Trebuchet:
+ 325.16857513243514
+   1.4214156882736535
+
+

will give her a Trebuchet with a weight between 0 and 500 and a +release angle between 0 and pi/2 radians at random.

+

Now she can store the results of 3 random trebuchets in an array like +this

+
+

JULIA +

+
env = Environment(5, 100)
+distances = [shoot_distance(Trebuchet(rand() * 500, rand() * pi / 2), env) for _ in 1:3]
+
+
+

OUTPUT +

+
3-element Vector{Float64}:
+  95.34228401250319
+ 116.82589218537548
+   0.6815707596179541
+
+

This is called an array comprehension. To get the +information of the parameters and the results in one place she writes +that again a bit differently

+
+

JULIA +

+
N = 10
+weights = [rand() * 500 for _ in 1:N]
+angles = [rand() * pi/2 for _ in 1:N]
+distances = [(w,a) => shoot_distance(Trebuchet(w, a), env) for (w, a) in zip(weights, angles)]
+
+
+

OUTPUT +

+
10-element Vector{Pair{Tuple{Float64, Float64}, Float64}}:
+   (39.64972003383499, 1.4356297441010255) => 11.866149878931637
+  (164.42881179322788, 1.4640276154421406) => 22.174678100790018
+   (367.9434185012257, 1.4375026271475015) => 29.142976299251053
+  (137.99390039219506, 0.9643884432619938) => 84.81262654249787
+   (222.0515164410986, 0.4063767373305969) => 104.28193737621544
+   (436.7263816265339, 1.2139595368028047) => 68.51245438747303
+   (180.62981502392094, 0.966166690591184) => 90.05903638111702
+ (7.5342147342188355, 0.13669385275895293) => 0.6815707596179541
+   (317.4431655220148, 1.2250401309666004) => 64.69560256895292
+  (396.6491533236719, 0.12154258149162844) => 82.50509163157764
+
+
+
+

Gradient descent

+

That is working out so far, but Melissa wonders if she can improve +her parameters more systematically.

+
+
+ +
+
+

Digression: Gradients +

+
+

The shoot_distance function takes three input parameters +and returns one value (the distance). Whenever we change one of the +input parameters, we will get a different distance.

+

The gradient of a +function gives the direction in which the return value will change when +each input value changes.

+

Since the shoot_distance function has three input +parameters, the gradient of shoot_distance will return a +3-element Array: one direction for each input +parameter.

+

Thanks to automatic +differentiation and the Julia package ForwardDiff.jl +gradients can be calculated easily.

+
+
+
+

Melissa uses the gradient function of +ForwardDiff.jl to get the direction in which she needs to +change the parameters to make the largest difference.

+
+
+ +
+
+

Do you remember? +

+
+

What does Melissa need to write into the REPL to install the package +ForwardDiff?

+
  1. ] install ForwardDiff
  2. +
  3. add ForwardDiff
  4. +
  5. ] add ForwardDiff.jl
  6. +
  7. +] add ForwardDiff +
  8. +
+
+
+
+
+ +
+
+

The correct solution is 4: ] to enter pkg mode, then

+
+

JULIA +

+
pkg> add ForwardDiff
+
+
+
+
+
+
+

JULIA +

+
using ForwardDiff: gradient
+
+
+imprecise_trebuchet = Trebuchet(500.0, 0.25pi);
+environment = Environment(5.0, 100.0);
+
+grad = gradient(x ->(shoot_distance([environment.wind, x[2], x[1]])
+                      - environment.target_distance),
+                imprecise_trebuchet)
+
+
+

OUTPUT +

+
2-element Vector{Float64}:
+  -0.12516519503998055
+ -49.443442438172205
+
+

Melissa now changes her arguments a little bit in the direction of +the gradient and checks the new distance.

+
+

JULIA +

+
better_trebuchet = imprecise_trebuchet - 0.05 * grad;
+
+shoot_distance([5, better_trebuchet[2], better_trebuchet[1]])
+
+
+

OUTPUT +

+
-2.785549535224487
+
+

Great! That didn’t shoot past the target, but instead it landed a bit +too short.

+
+
+ +
+
+

Experiment +

+
+

How far can you change the parameters in the direction of the +gradient, such that it still improves the distance?

+
+
+
+
+
+ +
+
+
+

JULIA +

+
better_trebuchet = imprecise_trebuchet - 0.04 * grad
+shoot_distance([environment.wind, better_trebuchet[2], better_trebuchet[1]])
+120.48753521261001
+
+
+

JULIA +

+
better_trebuchet = imprecise_trebuchet - 0.03 * grad
+shoot_distance([environment.wind, better_trebuchet[2], better_trebuchet[1]])
+107.80646596787481
+
+
+

JULIA +

+
better_trebuchet = imprecise_trebuchet - 0.02 * grad
+shoot_distance([environment.wind, better_trebuchet[2], better_trebuchet[1]])
+33.90699307740854
+
+
+

JULIA +

+
better_trebuchet = imprecise_trebuchet - 0.025 * grad
+shoot_distance([environment.wind, better_trebuchet[2], better_trebuchet[1]])
+75.87613276409223
+
+

Looks like the “best” trebuchet for a target 100 m away will be +between 2.5% and 3% down the gradient from the imprecise trebuchet.

+
+
+
+
+
+
+

For loops

+

Now that Melissa knows it is going in the right direction she wants +to automate the additional iterations. She writes a new function +aim, that performs the application of the gradient +N times.

+
+

JULIA +

+
function aim(trebuchet, environment; N = 5, η = 0.05)
+           better_trebuchet = copy(trebuchet)
+           for _ in 1:N
+               grad = gradient(x -> (shoot_distance([environment.wind, x[2], x[1]])
+                                     - environment.target_distance),
+                               better_trebuchet)
+               better_trebuchet -= η * grad
+           end
+           return Trebuchet(better_trebuchet[1], better_trebuchet[2])
+       end
+
+better_trebuchet  = aim(imprecise_trebuchet, environment);
+
+shoot_distance(environment.wind, better_trebuchet[2], better_trebuchet[1])
+
+
+

OUTPUT +

+
-2.2195176928658915
+
+
+
+ +
+
+

Explore +

+
+

Play around with different inputs of N and +η. How close can you come?

+
+
+
+
+
+ +
+
+

This is a highly non-linear system and thus very sensitive. The +distances across different values for the counterweight and the release +angle α look like this:

+
+
+
+
+
+
+ +
+
+

Aborting programs +

+
+

If a call takes too long, you can abort it with +Ctrl-c

+
+
+
+
+
+

While loops

+

Melissa finds the output of the above aim function too +unpredictable to be useful. That’s why she decides to change it a bit. +This time she uses a while-loop to run the iterations until +she is sufficiently near her target.

+

(Hint: ε is +\epsilontab, and η is +\etatab.)

+
+

JULIA +

+
function aim(trebuchet, environment; ε = 0.1, η = 0.05)
+    better_trebuchet = copy(trebuchet)
+    hit = x -> (shoot_distance([environment.wind, x[2], x[1]])
+                          - environment.target_distance)
+            while abs(hit(better_trebuchet)) > ε
+                grad = gradient(hit, better_trebuchet)
+                better_trebuchet -= η * grad
+            end
+            return Trebuchet(better_trebuchet[1], better_trebuchet[2])
+        end
+
+better_trebuchet = aim(imprecise_trebuchet, environment);
+
+shoot_distance(better_trebuchet, environment)
+
+
+

OUTPUT +

+
100.05601729579894
+
+

That is more what she had in mind. Your trebuchet may be tuned +differently, but it should hit just as close as hers.

+
+
+ +
+
+

Keypoints +

+
+
  • “Use for loops for a known number of iterations and while loops for +an unknown number of iterations.”
  • +
+
+
+
+ + + +
+
+ + +
+
+ + + diff --git a/07_Using_Modules.html b/07_Using_Modules.html index ea8dcde1..60cc42dd 100644 --- a/07_Using_Modules.html +++ b/07_Using_Modules.html @@ -98,11 +98,11 @@ Programming with Julia

-