diff --git a/dev/how_to_submit_hw/index.html b/dev/how_to_submit_hw/index.html index 20144bfc..60a75272 100644 --- a/dev/how_to_submit_hw/index.html +++ b/dev/how_to_submit_hw/index.html @@ -1,2 +1,2 @@ -Homework submission · Scientific Programming in Julia
+Homework submission · Scientific Programming in Julia
diff --git a/dev/index.html b/dev/index.html index 53719bf3..248f33e3 100644 --- a/dev/index.html +++ b/dev/index.html @@ -6,4 +6,4 @@ Learn the power of abstraction. Example: The essence of forward mode automatic differentiation. -

Before joining the course, consider reading the following two blog posts to figure out if Julia is a language in which you want to invest your time.

What will you learn?

First and foremost you will learn how to think julia - meaning how write fast, extensible, reusable, and easy-to-read code using things like optional typing, multiple dispatch, and functional programming concepts. The later part of the course will teach you how to use more advanced concepts like language introspection, metaprogramming, and symbolic computing. Amonst others you will implement your own automatic differetiation (the backbone of modern machine learning) package based on these advanced techniques that can transform intermediate representations of Julia code.

Organization

This course webpage contains all information about the course that you need, including lecture notes, lab instructions, and homeworks. The official format of the course is 2+2 (2h lectures/2h labs per week) for 4 credits.

The official course code is: B0M36SPJ and the timetable for the winter semester 2022 can be found here.

The course will be graded based on points from your homework (max. 20 points) and points from a final project (max. 30 points).

Below is a table that shows which lectures have homeworks (and their points).

Homework12345678910111213
Points22222222-2-2-

Hint: The first few homeworks are easier. Use them to fill up your points.

Final project

The final project will be individually agreed on for each student. Ideally you can use this project to solve a problem you have e.g. in your thesis, but don't worry - if you cannot come up with an own project idea, we will suggest one to you. More info and project suggestion can be found here.

Grading

Your points from the homeworks and the final project are summed and graded by the standard grading scale below.

GradeABCDEF
Points45-5040-4435-3930-3425-290-25

Teachers

E-mailRoomRole
Tomáš Pevnýpevnak@protonmail.chKN:E-406Lecturer
Vašek Šmídlsmidlva1@fjfi.cvut.czKN:E-333Lecturer
Matěj Zorekzorekmat@fel.cvut.czKN:E-333Lab Instructor
Niklas Heimheimnikl@fel.cvut.czKN:E-333Lab Instructor

Prerequisites

There are no hard requirements to take the course, but if you are not at all familiar with Julia we recommend you to take Julia for Optimization and Learning before enrolling in this course. The Functional Programming course also contains some helpful concepts for this course. And knowledge about computer hardware, namely basics of how CPU works, how it interacts with memory through caches, and basics of multi-threadding certainly helps.

References

+

Before joining the course, consider reading the following two blog posts to figure out if Julia is a language in which you want to invest your time.

What will you learn?

First and foremost you will learn how to think julia - meaning how write fast, extensible, reusable, and easy-to-read code using things like optional typing, multiple dispatch, and functional programming concepts. The later part of the course will teach you how to use more advanced concepts like language introspection, metaprogramming, and symbolic computing. Amonst others you will implement your own automatic differetiation (the backbone of modern machine learning) package based on these advanced techniques that can transform intermediate representations of Julia code.

Organization

This course webpage contains all information about the course that you need, including lecture notes, lab instructions, and homeworks. The official format of the course is 2+2 (2h lectures/2h labs per week) for 4 credits.

The official course code is: B0M36SPJ and the timetable for the winter semester 2022 can be found here.

The course will be graded based on points from your homework (max. 20 points) and points from a final project (max. 30 points).

Below is a table that shows which lectures have homeworks (and their points).

Homework12345678910111213
Points22222222-2-2-

Hint: The first few homeworks are easier. Use them to fill up your points.

Final project

The final project will be individually agreed on for each student. Ideally you can use this project to solve a problem you have e.g. in your thesis, but don't worry - if you cannot come up with an own project idea, we will suggest one to you. More info and project suggestion can be found here.

Grading

Your points from the homeworks and the final project are summed and graded by the standard grading scale below.

GradeABCDEF
Points45-5040-4435-3930-3425-290-25

Teachers

E-mailRoomRole
Tomáš Pevnýpevnak@protonmail.chKN:E-406Lecturer
Vašek Šmídlsmidlva1@fjfi.cvut.czKN:E-333Lecturer
Matěj Zorekzorekmat@fel.cvut.czKN:E-333Lab Instructor
Niklas Heimheimnikl@fel.cvut.czKN:E-333Lab Instructor

Prerequisites

There are no hard requirements to take the course, but if you are not at all familiar with Julia we recommend you to take Julia for Optimization and Learning before enrolling in this course. The Functional Programming course also contains some helpful concepts for this course. And knowledge about computer hardware, namely basics of how CPU works, how it interacts with memory through caches, and basics of multi-threadding certainly helps.

References

diff --git a/dev/installation/index.html b/dev/installation/index.html index c66f2d60..dde6d497 100644 --- a/dev/installation/index.html +++ b/dev/installation/index.html @@ -14,4 +14,4 @@ _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release |__/ | -julia>

Julia IDE

There is no one way to install/develop and run Julia, which may be strange users coming from MATLAB, but for users of general purpose languages such as Python, C++ this is quite common. Most of the Julia programmers to date are using

This setup is described in a comprehensive step-by-step guide in our bachelor course Julia for Optimization & Learning.

Note that this setup is not a strict requirement for the lectures/labs and any other text editor with the option to send code to the terminal such as Vim (+Tmux), Emacs, or Sublime Text will suffice.

GitHub registration & Git setup

As one of the goals of the course is writing code that can be distributed to others, we recommend a GitHub account, which you can create here (unless you already have one). In order to interact with GitHub repositories, we will be using git. For installation instruction (Windows only) see the section in the bachelor course.

+julia>

Julia IDE

There is no one way to install/develop and run Julia, which may be strange users coming from MATLAB, but for users of general purpose languages such as Python, C++ this is quite common. Most of the Julia programmers to date are using

This setup is described in a comprehensive step-by-step guide in our bachelor course Julia for Optimization & Learning.

Note that this setup is not a strict requirement for the lectures/labs and any other text editor with the option to send code to the terminal such as Vim (+Tmux), Emacs, or Sublime Text will suffice.

GitHub registration & Git setup

As one of the goals of the course is writing code that can be distributed to others, we recommend a GitHub account, which you can create here (unless you already have one). In order to interact with GitHub repositories, we will be using git. For installation instruction (Windows only) see the section in the bachelor course.

diff --git a/dev/lecture_01/basics/index.html b/dev/lecture_01/basics/index.html index f0cdaa12..7f48ca5e 100644 --- a/dev/lecture_01/basics/index.html +++ b/dev/lecture_01/basics/index.html @@ -16,4 +16,4 @@ out = fsum(varargin{1},varargin{2:end}) end

The need to build intuition for function composition.

Dispatch is easier to optimize by the compiler.

Operators are functions

operatorfunction name
[A B C ...]hcat
[A; B; C; ...]vcat
[A B; C D; ...]hvcat
A'adjoint
A[i]getindex
A[i] = xsetindex!
A.ngetproperty
A.n = xsetproperty!
struct Foo end
 
-Base.getproperty(a::Foo, x::Symbol) = x == :a ? 5 : error("does not have property $(x)")

Can be redefined and overloaded for different input types. The getproperty method can define access to the memory structure.

Broadcasting revisited

The a.+b syntax is a syntactic sugar for broadcast(+,a,b).

The special meaning of the dot is that they will be fused into a single call:

The same logic works for lists, tuples, etc.

+Base.getproperty(a::Foo, x::Symbol) = x == :a ? 5 : error("does not have property $(x)")

Can be redefined and overloaded for different input types. The getproperty method can define access to the memory structure.

Broadcasting revisited

The a.+b syntax is a syntactic sugar for broadcast(+,a,b).

The special meaning of the dot is that they will be fused into a single call:

The same logic works for lists, tuples, etc.

diff --git a/dev/lecture_01/demo/index.html b/dev/lecture_01/demo/index.html index 1bfd82b5..51654208 100644 --- a/dev/lecture_01/demo/index.html +++ b/dev/lecture_01/demo/index.html @@ -24,4 +24,4 @@ prob = ODEProblem(lotka_volterra,u0,tspan,p) sol = solve(prob) -plot(sol,denseplot=false)

Integration with other toolkits

Flux: toolkit for modelling Neural Networks. Neural network is a function.

Turing: Probabilistic modelling toolkit

+plot(sol,denseplot=false)

Integration with other toolkits

Flux: toolkit for modelling Neural Networks. Neural network is a function.

Turing: Probabilistic modelling toolkit

diff --git a/dev/lecture_01/hw/index.html b/dev/lecture_01/hw/index.html index 95f43cc6..3faf46ac 100644 --- a/dev/lecture_01/hw/index.html +++ b/dev/lecture_01/hw/index.html @@ -47,61 +47,61 @@ num_edges_nodes: (10, 10) - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -

+

diff --git a/dev/lecture_01/lab/index.html b/dev/lecture_01/lab/index.html index 5cb6aee6..1c22f9ca 100644 --- a/dev/lecture_01/lab/index.html +++ b/dev/lecture_01/lab/index.html @@ -262,4 +262,4 @@ :b => [1, 23]
julia> d[:c]ERROR: KeyError: key :c not found

TypeError

Type assertion failure, or calling an intrinsic function (inside LLVM, where code is strictly typed) with incorrect argument type. In practice this error comes up most often when comparing value of a type against the Bool type as seen in the example bellow.

julia> if 1 end                # calls internally typeassert(1, Bool)ERROR: TypeError: non-boolean (Int64) used in boolean context
julia> typeassert(1, Bool)ERROR: TypeError: non-boolean (Int64) used in boolean context

In order to compare inside conditional statements such as if-elseif-else or the ternary operator x ? a : b the condition has to be always of Bool type, thus the example above can be fixed by the comparison operator: if 1 == 1 end (in reality either the left or the right side of the expression contains an expression or a variable to compare against).

UndefVarError

While this error is quite self-explanatory, the exact causes are often quite puzzling for the user. The reason behind the confusion is to do with code scoping, which comes into play for example when trying to access a local variable from outside of a given function or just updating a global variable from within a simple loop.

In the first example we show the former case, where variable is declared from within a function and accessed from outside afterwards.

julia> function plusone(x)
            uno = 1
            return x + uno
-       endplusone (generic function with 1 method)
julia> uno # defined only within plusoneERROR: UndefVarError: `uno` not defined

Unless there is variable I_am_not_defined in the global scope, the following should throw an error.

julia> I_am_not_definedERROR: UndefVarError: `I_am_not_defined` not defined

Often these kind of errors arise as a result of bad code practices, such as long running sessions of Julia having long forgotten global variables, that do not exist upon new execution (this one in particular has been addressed by the authors of the reactive Julia notebooks Pluto.jl).

For more details on code scoping we recommend particular places in the bachelor course lectures here and there.

ErrorException & error function

ErrorException is the most generic error, which can be thrown/raised just by calling the error function with a chosen string message. As a result developers may be inclined to misuse this for any kind of unexpected behavior a user can run into, often providing out-of-context/uninformative messages.

+ endplusone (generic function with 1 method)
julia> uno # defined only within plusoneERROR: UndefVarError: `uno` not defined

Unless there is variable I_am_not_defined in the global scope, the following should throw an error.

julia> I_am_not_definedERROR: UndefVarError: `I_am_not_defined` not defined

Often these kind of errors arise as a result of bad code practices, such as long running sessions of Julia having long forgotten global variables, that do not exist upon new execution (this one in particular has been addressed by the authors of the reactive Julia notebooks Pluto.jl).

For more details on code scoping we recommend particular places in the bachelor course lectures here and there.

ErrorException & error function

ErrorException is the most generic error, which can be thrown/raised just by calling the error function with a chosen string message. As a result developers may be inclined to misuse this for any kind of unexpected behavior a user can run into, often providing out-of-context/uninformative messages.

diff --git a/dev/lecture_01/motivation/index.html b/dev/lecture_01/motivation/index.html index 153101be..75a29435 100644 --- a/dev/lecture_01/motivation/index.html +++ b/dev/lecture_01/motivation/index.html @@ -35,4 +35,4 @@ m ~ Normal(0, sqrt(s²)) x ~ Normal(m, sqrt(s²)) y ~ Normal(m, sqrt(s²)) -end

Such tools allow building a very convenient user experience on abstract level, and reaching very efficient code.

Reproducibile research

Think about a code that was written some time ago. To run it, you often need to be able to have the same version of the language it was written for.

Environment

Is an independent set of packages that can be local to an individual project or shared and selected by name.

Package

A package is a source tree with a standard layout providing functionality that can be reused by other Julia projects.

This allows Julia to be a rapidly evolving ecosystem with frequent changes due to:

Package manager

Julia from user's point of view

  1. compilation of everything to as specialized as possible

  2. extensibility, Multiple dispatch = multi-functions

+end

Such tools allow building a very convenient user experience on abstract level, and reaching very efficient code.

Reproducibile research

Think about a code that was written some time ago. To run it, you often need to be able to have the same version of the language it was written for.

Environment

Is an independent set of packages that can be local to an individual project or shared and selected by name.

Package

A package is a source tree with a standard layout providing functionality that can be reused by other Julia projects.

This allows Julia to be a rapidly evolving ecosystem with frequent changes due to:

Package manager

Julia from user's point of view

  1. compilation of everything to as specialized as possible

  2. extensibility, Multiple dispatch = multi-functions

diff --git a/dev/lecture_01/outline/index.html b/dev/lecture_01/outline/index.html index 3012ae5a..6d9f817a 100644 --- a/dev/lecture_01/outline/index.html +++ b/dev/lecture_01/outline/index.html @@ -1,2 +1,2 @@ -Outline · Scientific Programming in Julia

Course outline

  1. Introduction

  2. Type system

    • user: tool for abstraction
    • compiler: tool for memory layout
  3. Design patterns (mental setup)

    • Julia is a type-based language
    • multiple-dispatch generalizes OOP and FP
  4. Packages

    • way how to organize code
    • code reuse (alternative to libraries)
    • experiment reproducibility
  5. Benchmarking

    • how to measure code efficiency
  6. Introspection

    • understand how the compiler process the data
  7. Macros

    • automate writing of boring the boilerplate code
    • good macro create cleaner code
  8. Automatic Differentiation

    • Theory: difference between the forward and backward mode
    • Implementation techniques
  9. Intermediate representation

    • how to use internal the representation of the code
    • example in automatic differentiation
  10. Parallel computing

    • threads, processes
  11. Graphics card coding

    • types for GPU
    • specifics of architectures
  12. Ordinary Differential Equations

    • simple solvers
    • error propagation
  13. Data driven ODE

    • combine ODE with optimization
    • automatic differentiation (adjoints)
+Outline · Scientific Programming in Julia

Course outline

  1. Introduction

  2. Type system

    • user: tool for abstraction
    • compiler: tool for memory layout
  3. Design patterns (mental setup)

    • Julia is a type-based language
    • multiple-dispatch generalizes OOP and FP
  4. Packages

    • way how to organize code
    • code reuse (alternative to libraries)
    • experiment reproducibility
  5. Benchmarking

    • how to measure code efficiency
  6. Introspection

    • understand how the compiler process the data
  7. Macros

    • automate writing of boring the boilerplate code
    • good macro create cleaner code
  8. Automatic Differentiation

    • Theory: difference between the forward and backward mode
    • Implementation techniques
  9. Intermediate representation

    • how to use internal the representation of the code
    • example in automatic differentiation
  10. Parallel computing

    • threads, processes
  11. Graphics card coding

    • types for GPU
    • specifics of architectures
  12. Ordinary Differential Equations

    • simple solvers
    • error propagation
  13. Data driven ODE

    • combine ODE with optimization
    • automatic differentiation (adjoints)
diff --git a/dev/lecture_02/hw/index.html b/dev/lecture_02/hw/index.html index b8403022..3aeba47e 100644 --- a/dev/lecture_02/hw/index.html +++ b/dev/lecture_02/hw/index.html @@ -4,4 +4,4 @@
  1. Implement a function agent_count that can be called on a single Agent and returns a number between $(0,1)$ (i.e. always 1 for animals; and size(plant)/max_size(plant) for plants).

  2. Add a method for a vector of agents Vector{<:Agent} which sums all agent counts.

  3. Add a method for a World which returns a dictionary that contains pairs of Symbols and the agent count like below:

julia> grass1 = Grass(1,5,5);
julia> agent_count(grass1)1.0
julia> grass2 = Grass(2,1,5);
julia> agent_count([grass1,grass2]) # one grass is fully grown; the other only 20% => 1.21.2
julia> sheep = Sheep(3,10.0,5.0,1.0,1.0);
julia> wolf = Wolf(4,20.0,10.0,1.0,1.0);
julia> world = World([grass1, grass2, sheep, wolf]);
julia> agent_count(world)Dict{Symbol, Real} with 3 entries: :Wolf => 1 :Grass => 1.2 - :Sheep => 1

Hint: You can get the name of a type by using the nameof function:

julia> nameof(Grass):Grass

Use as much dispatch as you can! ;)

+ :Sheep => 1

Hint: You can get the name of a type by using the nameof function:

julia> nameof(Grass):Grass

Use as much dispatch as you can! ;)

diff --git a/dev/lecture_02/lab/index.html b/dev/lecture_02/lab/index.html index ccd5daac..05392aaf 100644 --- a/dev/lecture_02/lab/index.html +++ b/dev/lecture_02/lab/index.html @@ -30,7 +30,7 @@ # hint: to type the leaf in the julia REPL you can do: # \:herb:<tab> print(io,"🌿 #$(g.id) $(round(Int,x))% grown") -end

Creating a few Grass agents can then look like this:

julia> Grass(1,5)🌿 #1 20% grown
julia> g = Grass(2)🌿 #2 50% grown
julia> g.id = 5ERROR: setfield!: const field .id of type Grass cannot be changed

Sheep and Wolf Agents

Animals are slightly different from plants. They will have an energy $E$, which will be increase (or decrease) if the agent eats (or reproduces) by a certain amount $\Delta E$. Later we will also need a probability to find food $p_f$ and a probability to reproduce $p_r$.c

+end

Creating a few Grass agents can then look like this:

julia> Grass(1,5)🌿 #1 20% grown
julia> g = Grass(2)🌿 #2 70% grown
julia> g.id = 5ERROR: setfield!: const field .id of type Grass cannot be changed

Sheep and Wolf Agents

Animals are slightly different from plants. They will have an energy $E$, which will be increase (or decrease) if the agent eats (or reproduces) by a certain amount $\Delta E$. Later we will also need a probability to find food $p_f$ and a probability to reproduce $p_r$.c

Exercise:
  1. Define two mutable structs Sheep and Wolf that are subtypes of Animal and have the fields id, energy, Δenergy, reprprob, and foodprob.
  2. Define constructors with the following default values:
    • For 🐑: $E=4$, $\Delta E=0.2$, $p_r=0.8$, and $p_f=0.6$.
    • For 🐺: $E=10$, $\Delta E=8$, $p_r=0.1$, and $p_f=0.2$.
  3. Overload Base.show to get pretty printing for your two new animals.
@@ -91,11 +91,11 @@ Solution:

function eat!(sheep::Sheep, grass::Grass, w::World)
     sheep.energy += grass.size * sheep.Δenergy
     grass.size = 0
-end

Below you can see how a fully grown grass is eaten by a sheep. The sheep's energy changes size of the grass is set to zero.

julia> grass = Grass(1)🌿 #1 20% grown
julia> sheep = Sheep(2)🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> world = World([grass, sheep])Main.World{Main.Agent} +end

Below you can see how a fully grown grass is eaten by a sheep. The sheep's energy changes size of the grass is set to zero.

julia> grass = Grass(1)🌿 #1 30% grown
julia> sheep = Sheep(2)🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> world = World([grass, sheep])Main.World{Main.Agent} 🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 - 🌿 #1 20% grown
julia> eat!(sheep,grass,world);ERROR: setfield!: const field .energy of type Sheep cannot be changed
julia> worldMain.World{Main.Agent} + 🌿 #1 30% grown
julia> eat!(sheep,grass,world);ERROR: setfield!: const field .energy of type Sheep cannot be changed
julia> worldMain.World{Main.Agent} 🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 - 🌿 #1 20% grown

Note that the order of the arguments has a meaning here. Calling eat!(grass,sheep,world) results in a MethodError which is great, because Grass cannot eat Sheep.

julia> eat!(grass,sheep,world);ERROR: MethodError: no method matching eat!(::Main.Grass, ::Main.Sheep, ::Main.World{Main.Agent})
+  🌿 #1 30% grown

Note that the order of the arguments has a meaning here. Calling eat!(grass,sheep,world) results in a MethodError which is great, because Grass cannot eat Sheep.

julia> eat!(grass,sheep,world);ERROR: MethodError: no method matching eat!(::Main.Grass, ::Main.Sheep, ::Main.World{Main.Agent})
 
 Closest candidates are:
   eat!(::Main.Sheep, ::Main.Grass, ::Main.World)
@@ -111,9 +111,9 @@
 kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)

With a correct eat! method you should get results like this:

julia> grass = Grass(1);
julia> sheep = Sheep(2);
julia> wolf = Wolf(3);
julia> world = World([grass, sheep, wolf])Main.World{Main.Agent} 🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 🐺 #3 E=10.0 ΔE=8.0 pr=0.1 pf=0.2 - 🌿 #1 10% grown
julia> eat!(wolf,sheep,world);
julia> worldMain.World{Main.Agent} + 🌿 #1 20% grown
julia> eat!(wolf,sheep,world);
julia> worldMain.World{Main.Agent} 🐺 #3 E=42.0 ΔE=8.0 pr=0.1 pf=0.2 - 🌿 #1 10% grown

The sheep is removed from the world and the wolf's energy increased by $\Delta E$.

Reproduction

Currently our animals can only eat. In our simulation we also want them to reproduce. We will do this by adding a reproduce! method to Animal.

+ 🌿 #1 20% grown

The sheep is removed from the world and the wolf's energy increased by $\Delta E$.

Reproduction

Currently our animals can only eat. In our simulation we also want them to reproduce. We will do this by adding a reproduce! method to Animal.

Exercise

Write a function reproduce! that takes an Animal and a World. Reproducing will cost an animal half of its energy and then add an almost identical copy of the given animal to the world. The only thing that is different from parent to child is the ID. You can simply increase the max_id of the world by one and use that as the new ID for the child.

@@ -135,4 +135,4 @@ 🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> reproduce!(s1, w);ERROR: setfield!: const field .energy of type Sheep cannot be changed
julia> wMain.World{Main.Sheep} 🐑 #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 - 🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
+ 🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6 diff --git a/dev/lecture_02/lecture/index.html b/dev/lecture_02/lecture/index.html index 2e69f655..3919bb70 100644 --- a/dev/lecture_02/lecture/index.html +++ b/dev/lecture_02/lecture/index.html @@ -153,17 +153,17 @@ @code_native debuginfo=:none mapreduce(*,+, [1,2,3], [1,2,3])
	.text
 	.file	"mapreduce"
 	.section	.rodata.cst32,"aM",@progbits,32
-	.p2align	5                               # -- Begin function julia_mapreduce_4170
+	.p2align	5                               # -- Begin function julia_mapreduce_4119
 .LCPI0_0:
-	.quad	139881528323360                 # 0x7f38b4ce6d20
-	.quad	139881508015856                 # 0x7f38b3988ef0
-	.quad	139881506625360                 # 0x7f38b3835750
-	.quad	139881506609008                 # 0x7f38b3831770
+	.quad	140176656330016                 # 0x7f7d6bce6d20
+	.quad	140176636022512                 # 0x7f7d6a988ef0
+	.quad	140176634632016                 # 0x7f7d6a835750
+	.quad	140176634615664                 # 0x7f7d6a831770
 	.text
-	.globl	julia_mapreduce_4170
+	.globl	julia_mapreduce_4119
 	.p2align	4, 0x90
-	.type	julia_mapreduce_4170,@function
-julia_mapreduce_4170:                   # @julia_mapreduce_4170
+	.type	julia_mapreduce_4119,@function
+julia_mapreduce_4119:                   # @julia_mapreduce_4119
 	.cfi_startproc
 # %bb.0:                                # %top
 	pushq	%rbp
@@ -177,8 +177,8 @@
 	vmovups	%ymm0, -48(%rbp)
 	movq	%rdi, -16(%rbp)
 	movq	%rsi, -8(%rbp)
-	movabsq	$"j1_#mapreduce#801_4172", %rax
-	movabsq	$139881584604256, %rdi          # imm = 0x7F38B8293460
+	movabsq	$"j1_#mapreduce#801_4121", %rax
+	movabsq	$140176712610912, %rdi          # imm = 0x7F7D6F293460
 	leaq	-48(%rbp), %rsi
 	movl	$6, %edx
 	vzeroupper
@@ -189,16 +189,16 @@
 	.cfi_def_cfa %rsp, 8
 	retq
 .Lfunc_end0:
-	.size	julia_mapreduce_4170, .Lfunc_end0-julia_mapreduce_4170
+	.size	julia_mapreduce_4119, .Lfunc_end0-julia_mapreduce_4119
 	.cfi_endproc
                                         # -- End function
 	.section	".note.GNU-stack","",@progbits

More on the use of types in function definitions

Terminology

A function refers to a set of "methods" for a different combination of type parameters (the term function can be therefore considered as refering to a mere name). Methods define different behavior for different types of arguments for a given function. For example

move(a::Position, b::Position) = Position(a.x + b.x, a.y + b.y)
 move(a::Vector{<:Position}, b::Vector{<:Position}) = move.(a,b)

move refers to a function with methods move(a::Position, b::Position) and move(a::Vector{<:Position}, b::Vector{<:Position}). When different behavior on different types is defined by a programmer, as shown above, it is also called implementation specialization. There is another type of specialization, called compiler specialization, which occurs when the compiler generates different functions for you from a single method. For example for

julia> move(Position(1,1), Position(2,2))Main.Position{Int64}(3, 3)
julia> move(Position(1.0,1.0), Position(2.0,2.0))Main.Position{Float64}(3.0, 3.0)

the compiler generates two methods, one for Position{Int64} and the other for Position{Float64}. Notice that inside generated functions, the compiler needs to use different intrinsic operations, which can be viewed from

@code_native debuginfo=:none move(Position(1,1), Position(2,2))
	.text
 	.file	"move"
-	.globl	julia_move_4212                 # -- Begin function julia_move_4212
+	.globl	julia_move_4161                 # -- Begin function julia_move_4161
 	.p2align	4, 0x90
-	.type	julia_move_4212,@function
-julia_move_4212:                        # @julia_move_4212
+	.type	julia_move_4161,@function
+julia_move_4161:                        # @julia_move_4161
 	.cfi_startproc
 # %bb.0:                                # %top
 	pushq	%rbp
@@ -214,15 +214,15 @@
 	.cfi_def_cfa %rsp, 8
 	retq
 .Lfunc_end0:
-	.size	julia_move_4212, .Lfunc_end0-julia_move_4212
+	.size	julia_move_4161, .Lfunc_end0-julia_move_4161
 	.cfi_endproc
                                         # -- End function
 	.section	".note.GNU-stack","",@progbits

and

@code_native debuginfo=:none move(Position(1.0,1.0), Position(2.0,2.0))
	.text
 	.file	"move"
-	.globl	julia_move_4214                 # -- Begin function julia_move_4214
+	.globl	julia_move_4163                 # -- Begin function julia_move_4163
 	.p2align	4, 0x90
-	.type	julia_move_4214,@function
-julia_move_4214:                        # @julia_move_4214
+	.type	julia_move_4163,@function
+julia_move_4163:                        # @julia_move_4163
 	.cfi_startproc
 # %bb.0:                                # %top
 	pushq	%rbp
@@ -238,7 +238,7 @@
 	.cfi_def_cfa %rsp, 8
 	retq
 .Lfunc_end0:
-	.size	julia_move_4214, .Lfunc_end0-julia_move_4214
+	.size	julia_move_4163, .Lfunc_end0-julia_move_4163
 	.cfi_endproc
                                         # -- End function
 	.section	".note.GNU-stack","",@progbits

Notice that move works on Posits defined in 3rd party libas well

move(Position(Posit8(1),Posit8(1)), Position(Posit8(2),Posit8(2)))

Intermezzo: How does the Julia compiler work?

Let's walk through an example. Consider the following definitions

move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)
@@ -248,7 +248,7 @@
 move(a::Vector{<:Position}, by::Position) = move.(a, by)

and a function call

a = Position(1.0, 1.0)
 by = Position(2.0, 2.0)
 move(a, by)
Main.Position{Float64}(3.0, 3.0)
  1. The compiler knows that you call the function move.
  2. The compiler infers the type of the arguments. You can view the result with
julia> (typeof(a),typeof(by))(Main.Position{Float64}, Main.Position{Float64})
  1. The compiler identifies all move-methods with arguments of type (Position{Float64}, Position{Float64}):
julia> wc = Base.get_world_counter()0x00000000000083e5
julia> m = Base.method_instances(move, (typeof(a), typeof(by)), wc)1-element Vector{Core.MethodInstance}: - MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64})
julia> m = first(m)MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64})

4a. If the method has been specialized (compiled), then the arguments are prepared and the method is invoked. The compiled specialization can be seen from

julia> m.cacheCore.CodeInstance(MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64}), #undef, 0x00000000000083da, 0xffffffffffffffff, Main.Position{Float64}, #undef, UInt8[0x04, 0x00, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08  …  0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x01, 0x00, 0x00], 0x00000ce0, 0x00000ce0, nothing, true, true, 0x00, Ptr{Nothing} @0x00007f38a4188df0, Ptr{Nothing} @0x00007f38a4188dd0)

4b. If the method has not been specialized (compiled), the method is compiled for the given type of arguments and continues as in step 4a. A compiled function is therefore a "blob" of native code living in a particular memory location. When Julia calls a function, it needs to pick the right block corresponding to a function with particular type of parameters.

If the compiler cannot narrow the types of arguments to concrete types, it has to perform the above procedure inside the called function, which has negative effects on performance, as the type resulution and identification of the methods can be slow, especially for methods with many arguments (e.g. 30ns for a method with one argument, 100 ns for method with two arguements). You always want to avoid run-time resolution inside the performant loop!!! Recall the above example

wolfpack_a =  [Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
+ MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64})
julia> m = first(m)MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64})

4a. If the method has been specialized (compiled), then the arguments are prepared and the method is invoked. The compiled specialization can be seen from

julia> m.cacheCore.CodeInstance(MethodInstance for Main.move(::Main.Position{Float64}, ::Main.Position{Float64}), #undef, 0x00000000000083da, 0xffffffffffffffff, Main.Position{Float64}, #undef, UInt8[0x04, 0x00, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08  …  0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x01, 0x00, 0x00], 0x00000ce0, 0x00000ce0, nothing, true, true, 0x00, Ptr{Nothing} @0x00007f7d52125f60, Ptr{Nothing} @0x00007f7d52125f40)

4b. If the method has not been specialized (compiled), the method is compiled for the given type of arguments and continues as in step 4a. A compiled function is therefore a "blob" of native code living in a particular memory location. When Julia calls a function, it needs to pick the right block corresponding to a function with particular type of parameters.

If the compiler cannot narrow the types of arguments to concrete types, it has to perform the above procedure inside the called function, which has negative effects on performance, as the type resulution and identification of the methods can be slow, especially for methods with many arguments (e.g. 30ns for a method with one argument, 100 ns for method with two arguements). You always want to avoid run-time resolution inside the performant loop!!! Recall the above example

wolfpack_a =  [Wolf("1", 1), Wolf("2", 2), Wolf("3", 3)]
 @benchmark energy($(wolfpack_a))
BenchmarkTools.Trial: 10000 samples with 991 evaluations.
  Range (min … max):  40.195 ns … 66.641 ns  ┊ GC (min … max): 0.00% … 0.00%
  Time  (median):     40.742 ns              ┊ GC (median):    0.00%
@@ -314,4 +314,4 @@
   foo(::Vector{Real})
    @ Main REPL[1]:1

Julia's type system is invariant, which means that Vector{Real} is different from Vector{Float64} and from Vector{Float32}, even though Float64 and Float32 are sub-types of Real. Therefore typeof([1.0,2,3]) isa Vector{Float64} which is not subtype of Vector{Real}. For covariant languages, this would be true. For more information on variance in computer languages, see here. If the above definition of foo should be applicable to all vectors which has elements of subtype of Real we have define it as

foo(a::Vector{T}) where {T<:Real} = println("Vector{T} where {T<:Real}")

or equivalently but more tersely as

foo(a::Vector{<:Real}) = println("Vector{T} where {T<:Real}")
  1. Diagonal rule says that a repeated type in a method signature has to be a concrete type (this is to avoid ambinguity if the repeated type is used inside function definition to define a new variable to change type of variables). Consider for example the function below
move(a::T, b::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)

we cannot call it with move(Position(1.0,2.0), Position(1,2)), since in this case Position(1.0,2.0) is of type Position{Float64} while Position(1,2) is of type Position{Int64}.

  1. When debugging why arguments do not match a particular method definition, it is useful to use typeof, isa, and <: commands. For example
julia> typeof(Position(1.0,2.0))Main.Position{Float64}
julia> typeof(Position(1,2))Main.Position{Int64}
julia> Position(1,2) isa Position{Float64}false
julia> Position(1,2) isa Position{Real}false
julia> Position(1,2) isa Position{<:Real}true
julia> typeof(Position(1,2)) <: Position{<:Float64}false
julia> typeof(Position(1,2)) <: Position{<:Real}true

A bizzare definition which you can encounter

The following definition of a one-hot matrix is taken from Flux.jl

struct OneHotArray{T<:Integer, L, N, var"N+1", I<:Union{T,AbstractArray{T, N}}} <: AbstractArray{Bool, var"N+1"}
   indices::I
-end

The parameters of the type carry information about the type used to encode the position of one in each column in T, the dimension of one-hot vectors in L, the dimension of the storage of indices in N (which is zero for OneHotVector and one for OneHotMatrix), number of dimensions of the OneHotArray in var"N+1" and the type of underlying storage of indicies I.

+end

The parameters of the type carry information about the type used to encode the position of one in each column in T, the dimension of one-hot vectors in L, the dimension of the storage of indices in N (which is zero for OneHotVector and one for OneHotMatrix), number of dimensions of the OneHotArray in var"N+1" and the type of underlying storage of indicies I.

diff --git a/dev/lecture_03/hw/index.html b/dev/lecture_03/hw/index.html index 693eae38..c91f326f 100644 --- a/dev/lecture_03/hw/index.html +++ b/dev/lecture_03/hw/index.html @@ -2,11 +2,11 @@ Homework · Scientific Programming in Julia

Homework 3

In this homework we will implement a function find_food and practice the use of closures. The solution of lab 3 can be found here. You can use this file and add the code that you write for the homework to it.

How to submit?

Put all your code (including your or the provided solution of lab 2) in a script named hw.jl. Zip only this file (not its parent folder) and upload it to BRUTE.

Agents looking for food

Homework:

Implement a method find_food(a::Animal, w::World) returns one randomly chosen agent from all w.agents that can be eaten by a or nothing if no food could be found. This means that if e.g. the animal is a Wolf you have to return one random Sheep, etc.

Hint: You can write a general find_food method for all animals and move the parts that are specific to the concrete animal types to a separate function. E.g. you could define a function eats(::Animal{Wolf}, ::Animal{Sheep}) = true, etc.

You can check your solution with the public test:

julia> sheep = Sheep(1,pf=1.0)🐑♂ #1 E=4.0 ΔE=0.2 pr=0.8 pf=1.0
julia> world = World([Grass(2), sheep])Main.World{Main.Agent} - 🌿 #2 80% grown + 🌿 #2 50% grown 🐑♂ #1 E=4.0 ΔE=0.2 pr=0.8 pf=1.0
julia> find_food(sheep, world) isa Plant{Grass}true

Callbacks & Closures

Homework:

Implement a function every_nth(f::Function,n::Int) that takes an inner function f and uses a closure to construct an outer function g that only calls f every nth call to g. For example, if n=3 the inner function f be called at the 3rd, 6th, 9th ... call to g (not at the 1st, 2nd, 4th, 5th, 7th... call).

Hint: You can use splatting via ... to pass on an unknown number of arguments from the outer to the inner function.

You can use every_nth to log (or save) the agent count only every couple of steps of your simulation. Using every_nth will look like this:

julia> w = World([Sheep(1), Grass(2), Wolf(3)])Main.World{Main.Agent}
-  🌿  #2 70% grown
+  🌿  #2 10% grown
   🐺♂ #3 E=10.0 ΔE=8.0 pr=0.1 pf=0.2
   🐑♂ #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> # `@info agent_count(w)` is executed only every 3rd call to logcb(w) - logcb = every_nth(w->(@info agent_count(w)), 3);
julia> logcb(w); # x->(@info agent_count(w)) is not called
julia> logcb(w); # x->(@info agent_count(w)) is not called
julia> logcb(w); # x->(@info agent_count(w)) *is* called[ Info: Dict(:Animal => 2.0, :Plant => 0.7)
+ logcb = every_nth(w->(@info agent_count(w)), 3);
julia> logcb(w); # x->(@info agent_count(w)) is not called
julia> logcb(w); # x->(@info agent_count(w)) is not called
julia> logcb(w); # x->(@info agent_count(w)) *is* called[ Info: Dict(:Animal => 2.0, :Plant => 0.1) diff --git a/dev/lecture_03/lab/index.html b/dev/lecture_03/lab/index.html index 9eb59dd2..e3d25604 100644 --- a/dev/lecture_03/lab/index.html +++ b/dev/lecture_03/lab/index.html @@ -3,7 +3,7 @@ sheep::Sheep sex::Symbol end -⚥Sheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6, sex=rand(Bool) ? :female : :male) = ⚥Sheep(Sheep(id,e,Δe,pr,pf),sex)
julia> sheep = ⚥Sheep(1)Main.⚥Sheep(🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6, :male)
julia> sheep.sheep🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> sheep.sex:male

Instead of littering the whole code with custom getters/setters Julia allows us to overload the sheep.field behaviour by implementing custom getproperty/setproperty! methods.

+⚥Sheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6, sex=rand(Bool) ? :female : :male) = ⚥Sheep(Sheep(id,e,Δe,pr,pf),sex)
julia> sheep = ⚥Sheep(1)Main.⚥Sheep(🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6, :female)
julia> sheep.sheep🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> sheep.sex:female

Instead of littering the whole code with custom getters/setters Julia allows us to overload the sheep.field behaviour by implementing custom getproperty/setproperty! methods.

Exercise:

Implement custom getproperty/setproperty! methods which allow to access the Sheep inside the ⚥Sheep as if we would not be wrapping it.

@@ -23,8 +23,8 @@ else setfield!(s,name,x) end -end

You should be able to do the following with your overloads now

julia> sheep = ⚥Sheep(1)Main.⚥Sheep(🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6, :female)
julia> sheep.id1
julia> sheep.sex:female
julia> sheep.energy += 15.0
julia> sheepMain.⚥Sheep(🐑 #1 E=5.0 ΔE=0.2 pr=0.8 pf=0.6, :female)

In order to make the ⚥Sheep work with the rest of the code we only have to forward the eat! method

julia> eat!(s::⚥Sheep, food, world) = eat!(s.sheep, food, world);
julia> sheep = ⚥Sheep(1);
julia> grass = Grass(2);
julia> world = World([sheep,grass])Main.World{Main.Agent} - 🌿 #2 100% grown +end

You should be able to do the following with your overloads now

julia> sheep = ⚥Sheep(1)Main.⚥Sheep(🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6, :male)
julia> sheep.id1
julia> sheep.sex:male
julia> sheep.energy += 15.0
julia> sheepMain.⚥Sheep(🐑 #1 E=5.0 ΔE=0.2 pr=0.8 pf=0.6, :male)

In order to make the ⚥Sheep work with the rest of the code we only have to forward the eat! method

julia> eat!(s::⚥Sheep, food, world) = eat!(s.sheep, food, world);
julia> sheep = ⚥Sheep(1);
julia> grass = Grass(2);
julia> world = World([sheep,grass])Main.World{Main.Agent} + 🌿 #2 40% grown Main.⚥Sheep(🐑 #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6, :female)
julia> eat!(sheep, grass, world)0

and implement a custom reproduce! method with the behaviour that we want.

However, the extension of Sheep to ⚥Sheep is a very object-oriented approach. With a little bit of rethinking, we can build a much more elegant solution that makes use of Julia's powerful parametric types.

Part II: A new, parametric type hierarchy

First, let us note that there are two fundamentally different types of agents in our world: animals and plants. All species such as grass, sheep, wolves, etc. can be categorized as one of those two. We can use Julia's powerful, parametric type system to define one large abstract type for all agents Agent{S}. The Agent will either be an Animal or a Plant with a type parameter S which will represent the specific animal/plant species we are dealing with.

This new type hiearchy can then look like this:

abstract type Species end
 
 abstract type PlantSpecies <: Species end
@@ -147,8 +147,8 @@
     sheep.energy += grass.size * sheep.Δenergy
     grass.size = 0
 end
-eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0

julia> g = Grass(2)🌿  #2 10% grown
julia> s = Sheep(3)🐑♀ #3 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> w = World([g,s])Main.World{Main.Agent} - 🌿 #2 10% grown - 🐑♀ #3 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> eat!(s,g,w); wMain.World{Main.Agent} +eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0

julia> g = Grass(2)🌿  #2 70% grown
julia> s = Sheep(3)🐑♂ #3 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> w = World([g,s])Main.World{Main.Agent} + 🌿 #2 70% grown + 🐑♂ #3 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> eat!(s,g,w); wMain.World{Main.Agent} 🌿 #2 0% grown - 🐑♀ #3 E=4.2 ΔE=0.2 pr=0.8 pf=0.6
+ 🐑♂ #3 E=5.4 ΔE=0.2 pr=0.8 pf=0.6 diff --git a/dev/lecture_03/lecture/index.html b/dev/lecture_03/lecture/index.html index ad15771d..310a39c4 100644 --- a/dev/lecture_03/lecture/index.html +++ b/dev/lecture_03/lecture/index.html @@ -121,4 +121,4 @@ end end

Is this confusing? What can cb() do and what it can not?

Note that function train! does not have many local variables. The important ones are arguments, i.e. exist in the scope from which the function was invoked.

loss(x,y)=mse(model(x),y)
 cb() = @info "training" loss(x,y)
-train!(loss, ps, data, opt; cb=cb)

Usage

Usage of closures:

Beware: Performance of captured variables

Inference of types may be difficult in closures: https://github.com/JuliaLang/julia/issues/15276

Aditional materials

+train!(loss, ps, data, opt; cb=cb)

Usage

Usage of closures:

Beware: Performance of captured variables

Inference of types may be difficult in closures: https://github.com/JuliaLang/julia/issues/15276

Aditional materials

diff --git a/dev/lecture_04/Lab04Ecosystem.jl b/dev/lecture_04/Lab04Ecosystem.jl new file mode 100644 index 00000000..54a03db0 --- /dev/null +++ b/dev/lecture_04/Lab04Ecosystem.jl @@ -0,0 +1,198 @@ +using StatsBase + +abstract type Species end + +abstract type PlantSpecies <: Species end +abstract type Grass <: PlantSpecies end + +abstract type AnimalSpecies <: Species end +abstract type Sheep <: AnimalSpecies end +abstract type Wolf <: AnimalSpecies end + +abstract type Agent{S<:Species} end + +# instead of Symbols we can use an Enum for the sex field +# using an Enum here makes things easier to extend in case you +# need more than just binary sexes and is also more explicit than +# just a boolean +@enum Sex female male + +########## World ############################################################# + +mutable struct World{A<:Agent} + agents::Dict{Int,A} + max_id::Int +end + +function World(agents::Vector{<:Agent}) + max_id = maximum(a.id for a in agents) + World(Dict(a.id=>a for a in agents), max_id) +end + +# optional: overload Base.show +function Base.show(io::IO, w::World) + println(io, typeof(w)) + for (_,a) in w.agents + println(io," $a") + end +end + + +########## Animals ########################################################### + +mutable struct Animal{A<:AnimalSpecies} <: Agent{A} + const id::Int + energy::Float64 + const Δenergy::Float64 + const reprprob::Float64 + const foodprob::Float64 + const sex::Sex +end + +function (A::Type{<:AnimalSpecies})(id::Int,E::T,ΔE::T,pr::T,pf::T,s::Sex) where T + Animal{A}(id,E,ΔE,pr,pf,s) +end + +# get the per species defaults back +randsex() = rand(instances(Sex)) +Sheep(id; E=4.0, ΔE=0.2, pr=0.8, pf=0.6, s=randsex()) = Sheep(id, E, ΔE, pr, pf, s) +Wolf(id; E=10.0, ΔE=8.0, pr=0.1, pf=0.2, s=randsex()) = Wolf(id, E, ΔE, pr, pf, s) + + +function Base.show(io::IO, a::Animal{A}) where {A<:AnimalSpecies} + e = a.energy + d = a.Δenergy + pr = a.reprprob + pf = a.foodprob + s = a.sex == female ? "♀" : "♂" + print(io, "$A$s #$(a.id) E=$e ΔE=$d pr=$pr pf=$pf") +end + +# note that for new species we will only have to overload `show` on the +# abstract species/sex types like below! +Base.show(io::IO, ::Type{Sheep}) = print(io,"🐑") +Base.show(io::IO, ::Type{Wolf}) = print(io,"🐺") + + +########## Plants ############################################################# + +mutable struct Plant{P<:PlantSpecies} <: Agent{P} + const id::Int + size::Int + const max_size::Int +end + +# constructor for all Plant{<:PlantSpecies} callable as PlantSpecies(...) +(A::Type{<:PlantSpecies})(id, s, m) = Plant{A}(id,s,m) +(A::Type{<:PlantSpecies})(id, m) = (A::Type{<:PlantSpecies})(id,rand(1:m),m) + +# default specific for Grass +Grass(id; max_size=10) = Grass(id, rand(1:max_size), max_size) + +function Base.show(io::IO, p::Plant{P}) where P + x = p.size/p.max_size * 100 + print(io,"$P #$(p.id) $(round(Int,x))% grown") +end + +Base.show(io::IO, ::Type{Grass}) = print(io,"🌿") + + +########## Eating / Dying / Reproducing ######################################## + +function eat!(wolf::Animal{Wolf}, sheep::Animal{Sheep}, w::World) + wolf.energy += sheep.energy * wolf.Δenergy + kill_agent!(sheep,w) +end +function eat!(sheep::Animal{Sheep}, grass::Plant{Grass}, ::World) + sheep.energy += grass.size * sheep.Δenergy + grass.size = 0 +end +eat!(::Animal, ::Nothing, ::World) = nothing + +kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id) + +function find_mate(a::Animal, w::World) + ms = filter(x->mates(x,a), w.agents |> values |> collect) + isempty(ms) ? nothing : sample(ms) +end +mates(a::Animal{A}, b::Animal{A}) where A<:AnimalSpecies = a.sex != b.sex +mates(::Agent, ::Agent) = false + +function reproduce!(a::Animal{A}, w::World) where A + m = find_mate(a,w) + if !isnothing(m) + a.energy = a.energy / 2 + vals = [getproperty(a,n) for n in fieldnames(Animal) if n ∉ [:id, :sex]] + new_id = w.max_id + 1 + ŝ = Animal{A}(new_id, vals..., randsex()) + w.agents[ŝ.id] = ŝ + w.max_id = new_id + end +end + +# finding food / who eats who +function find_food(a::Animal, w::World) + as = filter(x -> eats(a,x), w.agents |> values |> collect) + isempty(as) ? nothing : sample(as) +end +eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0 +eats(::Animal{Wolf},::Animal{Sheep}) = true +eats(::Agent,::Agent) = false + + +########## Stepping through time ############################################# + +function agent_step!(p::Plant, ::World) + if p.size < p.max_size + p.size += 1 + end +end +function agent_step!(a::Animal, w::World) + a.energy -= 1 + if rand() <= a.foodprob + dinner = find_food(a,w) + eat!(a, dinner, w) + end + if a.energy <= 0 + kill_agent!(a,w) + return + end + if rand() <= a.reprprob + reproduce!(a,w) + end + return a +end + +function world_step!(world::World) + # make sure that we only iterate over IDs that already exist in the + # current timestep this lets us safely add agents + ids = copy(keys(world.agents)) + + for id in ids + # agents can be killed by other agents, so make sure that we are + # not stepping dead agents forward + !haskey(world.agents,id) && continue + + a = world.agents[id] + agent_step!(a,world) + end +end + + +########## Counting agents #################################################### + +agent_count(p::Plant) = p.size / p.max_size +agent_count(::Animal) = 1 +agent_count(as::Vector{<:Agent}) = sum(agent_count,as) + +function agent_count(w::World) + function op(d::Dict,a::A) where A<:Agent + if A in keys(d) + d[A] += agent_count(a) + else + d[A] = agent_count(a) + end + return d + end + foldl(op, w.agents |> values |> collect, init=Dict()) +end diff --git a/dev/lecture_04/grass-sheep-wolf.jl b/dev/lecture_04/grass-sheep-wolf.jl new file mode 100644 index 00000000..0dab503e --- /dev/null +++ b/dev/lecture_04/grass-sheep-wolf.jl @@ -0,0 +1,39 @@ +using Plots +include("Lab04Ecosystem.jl") + +function make_counter() + n = 0 + counter() = n += 1 +end + +function create_world() + n_grass = 1_000 + n_sheep = 40 + n_wolves = 4 + + nextid = make_counter() + + World(vcat( + [Grass(nextid()) for _ in 1:n_grass], + [Sheep(nextid()) for _ in 1:n_sheep], + [Wolf(nextid()) for _ in 1:n_wolves], + )) +end +world = create_world(); + +counts = Dict(n=>[c] for (n,c) in agent_count(world)) +for _ in 1:100 + world_step!(world) + for (n,c) in agent_count(world) + push!(counts[n],c) + end +end + +plt = plot() +tolabel(::Type{Animal{Sheep}}) = "Sheep" +tolabel(::Type{Animal{Wolf}}) = "Wolf" +tolabel(::Type{Plant{Grass}}) = "Grass" +for (A,c) in counts + plot!(plt, c, label=tolabel(A), lw=2) +end +display(plt) diff --git a/dev/lecture_04/hw/index.html b/dev/lecture_04/hw/index.html new file mode 100644 index 00000000..30230eb0 --- /dev/null +++ b/dev/lecture_04/hw/index.html @@ -0,0 +1,16 @@ + +Homework 4 · Scientific Programming in Julia

Homework 4

In this homework you will have to write two additional @testsets for the Ecosystem. One testset should be contained in a file test/sheep.jl and verify that the function eat!(::Animal{Sheep}, ::Plant{Grass}, ::World) works correctly. Another testset should be in the file test/wolf.jl and veryfiy that the function eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World) works correctly.

How to submit?

Zip the whole package folder Ecosystem.jl and upload it to BRUTE. The package has to include at least the following files:

├── src
+│   └── Ecosystem.jl
+└── test
+    ├── sheep.jl  # contains only a single @testset
+    ├── wolf.jl   # contains only a single @testset
+    └── runtests.jl

Thet test/runtests.jl file can look like this:

using Test
+using Ecosystem
+
+include("sheep.jl")
+include("wolf.jl")
+# ...

Test Sheep

+
Homework:
+
  1. Create a Sheep with food probability $p_f=1$
  2. Create fully grown Grass and a World with the two agents.
  3. Execute eat!(::Animal{Sheep}, ::Plant{Grass}, ::World)
  4. @test that the size of the Grass now has size == 0

Test Wolf

+
Homework:
+
  1. Create a Wolf with food probability $p_f=1$
  2. Create a Sheep and a World with the two agents.
  3. Execute eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World)
  4. @test that the World only has one agent left in the agents dictionary
diff --git a/dev/lecture_04/lab/index.html b/dev/lecture_04/lab/index.html new file mode 100644 index 00000000..b6a6d383 --- /dev/null +++ b/dev/lecture_04/lab/index.html @@ -0,0 +1,198 @@ + +Lab 04: Packaging · Scientific Programming in Julia

Lab 04: Packaging

Warmup - Stepping through time

We now have all necessary functions in place to make agents perform one step of our simulation. At the beginning of each step an animal looses energy. Afterwards it tries to find some food, which it will subsequently eat. If the animal then has less than zero energy it dies and is removed from the world. If it has positive energy it will try to reproduce.

Plants have a simpler life. They simply grow if they have not reached their maximal size.

+
Exercise:
+
  1. Implement a method agent_step!(::Animal,::World) which performs the following steps:
    • Decrement $E$ of agent by 1.0.
    • With $p_f$, try to find some food and eat it.
    • If $E<0$, the animal dies.
    • With $p_r$, try to reproduce.
  2. Implement a method agent_step!(::Plant,::World) which performs the following steps:
    • If the size of the plant is smaller than max_size, increment the plant's size by one.
+
+Solution:

function agent_step!(p::Plant, w::World)
+    if p.size < p.max_size
+        p.size += 1
+    end
+end
+
+function agent_step!(a::Animal, w::World)
+    a.energy -= 1
+    if rand() <= a.foodprob
+        dinner = find_food(a,w)
+        eat!(a, dinner, w)
+    end
+    if a.energy < 0
+        kill_agent!(a,w)
+        return
+    end
+    if rand() <= a.reprprob
+        reproduce!(a,w)
+    end
+end

An agent_step! of a sheep in a world with a single grass should make it consume the grass, let it reproduce, and eventually die if there is no more food and its energy is at zero:

julia> sheep = Sheep(1,2.0,2.0,1.0,1.0,male);
julia> grass = Grass(2,2,2);
julia> world = World([sheep, grass])Main.World{Main.Agent} + 🌿 #2 100% grown + 🐑♂ #1 E=2.0 ΔE=2.0 pr=1.0 pf=1.0
julia> agent_step!(sheep, world); worldMain.World{Main.Agent} + 🌿 #2 0% grown + 🐑♂ #1 E=5.0 ΔE=2.0 pr=1.0 pf=1.0
julia> # NOTE: The second agent step leads to an error. + # Can you figure out what is the problem here? + agent_step!(sheep, world); worldERROR: MethodError: no method matching eat!(::Main.Animal{🐑}, ::Nothing, ::Main.World{Main.Agent}) + +Closest candidates are: + eat!(::Main.Animal{🐑}, ::Main.Plant{🌿}, ::Main.World) + @ Main ~/work/Scientific-Programming-in-Julia/Scientific-Programming-in-Julia/docs/src/lecture_03/Lab03Ecosystem.jl:106 + eat!(::Main.Animal{🐺}, ::Main.Animal{🐑}, ::Main.World) + @ Main ~/work/Scientific-Programming-in-Julia/Scientific-Programming-in-Julia/docs/src/lecture_03/Lab03Ecosystem.jl:110
+
Exercise:
+

Finally, lets implement a function world_step! which performs one agent_step! for each agent. Note that simply iterating over all agents could lead to problems because we are mutating the agent dictionary. One solution for this is to iterate over a copy of all agent IDs that are present when starting to iterate over agents. Additionally, it could happen that an agent is killed by another one before we apply agent_step! to it. To solve this you can check if a given ID is currently present in the World.

+
+Solution:

# make it possible to eat nothing
+eat!(::Animal, ::Nothing, ::World) = nothing
+
+function world_step!(world::World)
+    # make sure that we only iterate over IDs that already exist in the
+    # current timestep this lets us safely add agents
+    ids = copy(keys(world.agents))
+
+    for id in ids
+        # agents can be killed by other agents, so make sure that we are
+        # not stepping dead agents forward
+        !haskey(world.agents,id) && continue
+
+        a = world.agents[id]
+        agent_step!(a,world)
+    end
+end
world_step! (generic function with 1 method)

julia> w = World([Sheep(1), Sheep(2), Wolf(3)])Main.World{Main.Animal}
+  🐑♂ #2 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
+  🐺♂ #3 E=10.0 ΔE=8.0 pr=0.1 pf=0.2
+  🐑♀ #1 E=4.0 ΔE=0.2 pr=0.8 pf=0.6
julia> world_step!(w); wMain.World{Main.Animal} + 🐑♀ #4 E=1.5 ΔE=0.2 pr=0.8 pf=0.6 + 🐺♂ #3 E=21.0 ΔE=8.0 pr=0.1 pf=0.2 + 🐑♀ #1 E=3.0 ΔE=0.2 pr=0.8 pf=0.6
julia> world_step!(w); wMain.World{Main.Animal} + 🐑♀ #4 E=0.5 ΔE=0.2 pr=0.8 pf=0.6 + 🐺♂ #3 E=20.0 ΔE=8.0 pr=0.1 pf=0.2 + 🐑♀ #1 E=2.0 ΔE=0.2 pr=0.8 pf=0.6
julia> world_step!(w); wMain.World{Main.Animal} + 🐺♂ #3 E=35.0 ΔE=8.0 pr=0.1 pf=0.2

Finally, lets run a few simulation steps and plot the solution

n_grass  = 1_000
+n_sheep  = 40
+n_wolves = 4
+
+gs = [Grass(id) for id in 1:n_grass]
+ss = [Sheep(id) for id in (n_grass+1):(n_grass+n_sheep)]
+ws = [Wolf(id) for id in (n_grass+n_sheep+1):(n_grass+n_sheep+n_wolves)]
+w  = World(vcat(gs,ss,ws))
+
+counts = Dict(n=>[c] for (n,c) in agent_count(w))
+for _ in 1:100
+    world_step!(w)
+    for (n,c) in agent_count(w)
+        push!(counts[n],c)
+    end
+end
+
+using Plots
+plt = plot()
+tolabel(::Type{Animal{Sheep}}) = "Sheep"
+tolabel(::Type{Animal{Wolf}}) = "Wolf"
+tolabel(::Type{Plant{Grass}}) = "Grass"
+for (A,c) in counts
+    plot!(plt, c, label=tolabel(A), lw=2)
+end
+plt

Package: Ecosystem.jl

In the main section of this lab you will create your own Ecosystem.jl package to organize and test (!) the code that we have written so far.

PkgTemplates.jl

+
Exercise:
+

The simplest way to create a new package in Julia is to use PkgTemplates.jl. ]add PkgTemplates to your global julia env and create a new package by running:

using PkgTemplates
+Template(interactive=true)("Ecosystem")

to interactively specify various options for your new package or use the following snippet to generate it programmatically:

using PkgTemplates
+
+# define the package template
+template = Template(;
+    user = "GithubUserName",            # github user name
+    authors = ["Author1", "Author2"],   # list of authors
+    dir = "/path/to/folder/",           # dir in which the package will be created
+    julia = v"1.8",                     # compat version of Julia
+    plugins = [
+        !CompatHelper,                  # disable CompatHelper
+        !TagBot,                        # disable TagBot
+        Readme(; inline_badges = true), # added readme file with badges
+        Tests(; project = true),        # added Project.toml file for unit tests
+        Git(; manifest = false),        # add manifest.toml to .gitignore
+        License(; name = "MIT")         # addedMIT licence
+    ],
+)
+
+# execute the package template (this creates all files/folders)
+template("Ecosystem")
+
+Solution:

This should have created a new folder Ecosystem which looks like below.

.
+├── LICENSE
+├── Project.toml
+├── README.md
+├── src
+│   └── Ecosystem.jl
+└── test
+    ├── Manifest.toml
+    ├── Project.toml
+    └── runtests.jl

If you ]activate /path/to/Ecosystem you should be able to run ]test to run the autogenerated test (which is not doing anything) and get the following output:

(Ecosystem) pkg> test
+     Testing Ecosystem
+      Status `/private/var/folders/6h/l9_skfms2v3dt8z3zfnd2jr00000gn/T/jl_zd5Uai/Project.toml`
+  [e77cd98c] Ecosystem v0.1.0 `~/repos/Ecosystem`
+  [8dfed614] Test `@stdlib/Test`
+      Status `/private/var/folders/6h/l9_skfms2v3dt8z3zfnd2jr00000gn/T/jl_zd5Uai/Manifest.toml`
+  [e77cd98c] Ecosystem v0.1.0 `~/repos/Ecosystem`
+  [2a0f44e3] Base64 `@stdlib/Base64`
+  [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils`
+  [56ddb016] Logging `@stdlib/Logging`
+  [d6f4376e] Markdown `@stdlib/Markdown`
+  [9a3f8284] Random `@stdlib/Random`
+  [ea8e919c] SHA v0.7.0 `@stdlib/SHA`
+  [9e88b42a] Serialization `@stdlib/Serialization`
+  [8dfed614] Test `@stdlib/Test`
+     Testing Running tests...
+Test Summary: |Time
+Ecosystem.jl  | None  0.0s
+     Testing Ecosystem tests passed 

Warning

From now on make sure that you always have the Ecosystem enviroment enabled. Otherwise you will not end up with the correct dependencies in your packages

Adding content to Ecosystem.jl

+
Exercise:
+

Next, let's add the types and functions we have defined so far. You can use include("path/to/file.jl") in the main module file at src/Ecosystem.jl to bring some structure in your code. An exemplary file structure could look like below.

.
+├── LICENSE
+├── Manifest.toml
+├── Project.toml
+├── README.md
+├── src
+│   ├── Ecosystem.jl
+│   ├── animal.jl
+│   ├── plant.jl
+│   └── world.jl
+└── test
+    └── runtests.jl

While you are adding functionality to your package you can make great use of Revise.jl. Loading Revise.jl before your Ecosystem.jl will automatically recompile (and invalidate old methods!) while you develop. You can install it in your global environment and and create a $HOME/.config/startup.jl which always loads Revise. It can look like this:

# try/catch block to make sure you can start julia if Revise should not be installed
+try
+    using Revise
+catch e
+    @warn(e.msg)
+end
Warning

At some point along the way you should run into problems with the sample functions or when trying using StatsBase. This is normal, because you have not added the package to the Ecosystem environment yet. Adding it is as easy as ]add StatsBase. Your Ecosystem environment should now look like this:

(Ecosystem) pkg> status
+Project Ecosystem v0.1.0
+Status `~/repos/Ecosystem/Project.toml`
+  [2913bbd2] StatsBase v0.33.21
+
Exercise:
+

In order to use your new types/functions like below

using Ecosystem
+
+Sheep(2)

you have to export them from your module. Add exports for all important types and functions.

+
+Solution:

# src/Ecosystem.jl
+module Ecosystem
+
+using StatsBase
+
+export World
+export Species, PlantSpecies, AnimalSpecies, Grass, Sheep, Wolf
+export Agent, Plant, Animal
+export agent_step!, eat!, eats, find_food, reproduce!, world_step!, agent_count
+
+# ....
+
+end

Unit tests

Every package should have tests which verify the correctness of your implementation, such that you can make changes to your codebase and remain confident that you did not break anything.

Julia's Test package provides you functionality to easily write unit tests.

+
Exercise:
+

In the file test/runtests.jl, create a new @testset and write three @tests which check that the show methods we defined for Grass, Sheep, and Wolf work as expected.

The function repr(x) == "some string" to check if the string representation we defined in the Base.show overload returns what you expect.

+
+Solution:

julia> # using Ecosystem
+       using Test
julia> @testset "Base.show" begin + g = Grass(1,1,1) + s = Animal{Sheep}(2,1,1,1,1,male) + w = Animal{Wolf}(3,1,1,1,1,female) + @test repr(g) == "🌿 #1 100% grown" + @test repr(s) == "🐑♂ #2 E=1.0 ΔE=1.0 pr=1.0 pf=1.0" + @test repr(w) == "🐺♀ #3 E=1.0 ΔE=1.0 pr=1.0 pf=1.0" + endTest Summary: | Pass Total Time +Base.show | 3 3 0.3s +Test.DefaultTestSet("Base.show", Any[], 3, false, false, true, 1.697700563486736e9, 1.697700563785498e9, false)

Github CI

+
Exercise:
+

If you want you can upload you package to Github and add the julia-runtest Github Action to automatically test your code for every new push you make to the repository.

diff --git a/dev/lecture_04/lecture/index.html b/dev/lecture_04/lecture/index.html index 97768a88..8c194b30 100644 --- a/dev/lecture_04/lecture/index.html +++ b/dev/lecture_04/lecture/index.html @@ -94,4 +94,4 @@ precompile(fsum,(Float64,Float64,Float64)) end

Can be investigated using MethodAnalysis.

using MethodAnalysis
-mi =methodinstances(fsum)

Useful packages:

+mi =methodinstances(fsum)

Useful packages:

diff --git a/dev/projects/index.html b/dev/projects/index.html index 911e0664..ccdb25e5 100644 --- a/dev/projects/index.html +++ b/dev/projects/index.html @@ -14,4 +14,4 @@ │ └── Manifest.toml # usually not committed to git as it is generated on the fly ├── README.md # describes in short what the pkg does and how to install pkg (e.g. some external deps) and run the example ├── Project.toml # lists all the pkg dependencies -└── Manifest.toml # usually not committed to git as the requirements may be to restrictive

The first thing that we will look at is README.md, which should warn us if there are some special installation steps, that cannot be handled with Julia's Pkg system. For example if some 3rd party binary dependency with license is required. Secondly we will try to run tests in the test folder, which should run and not fail and should cover at least some functionality of the pkg. Thirdly and most importantly we will instantiate environment in scripts and test if the example runs correctly. Lastly we will focus on documentation in terms of code readability, docstrings and inline comments.

Only after all this we may look at the extent of the project and it's difficulty, which may help us in deciding between grades.

Nice to have things, which are not strictly required but obviously improves the score.

Here are some examples of how the project could look like:

+└── Manifest.toml # usually not committed to git as the requirements may be to restrictive

The first thing that we will look at is README.md, which should warn us if there are some special installation steps, that cannot be handled with Julia's Pkg system. For example if some 3rd party binary dependency with license is required. Secondly we will try to run tests in the test folder, which should run and not fail and should cover at least some functionality of the pkg. Thirdly and most importantly we will instantiate environment in scripts and test if the example runs correctly. Lastly we will focus on documentation in terms of code readability, docstrings and inline comments.

Only after all this we may look at the extent of the project and it's difficulty, which may help us in deciding between grades.

Nice to have things, which are not strictly required but obviously improves the score.

Here are some examples of how the project could look like:

diff --git a/dev/search/index.html b/dev/search/index.html index 958f61e6..95312ced 100644 --- a/dev/search/index.html +++ b/dev/search/index.html @@ -1,2 +1,2 @@ -Search · Scientific Programming in Julia

Loading search...

    +Search · Scientific Programming in Julia

    Loading search...

      diff --git a/dev/search_index.js b/dev/search_index.js index 31ffe605..61b44b62 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"lecture_03/lab/#lab03","page":"Lab","title":"Lab 3: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_02\",\"Lab02Ecosystem.jl\"))","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In this lab we will look at two different ways of extending our agent simulation to take into account that animals can have two different sexes: female and male.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In the first part of the lab you will re-use the code from lab 2 and create a new type of sheep (⚥Sheep) which has an additional field sex. In the second part you will redesign the type hierarchy from scratch using parametric types to make this agent system much more flexible and julian.","category":"page"},{"location":"lecture_03/lab/#Part-I:-Female-and-Male-Sheep","page":"Lab","title":"Part I: Female & Male Sheep","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"The code from lab 2 that you will need in the first part of this lab can be found here.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"The goal of the first part of the lab is to demonstrate the forwarding method (which is close to how things are done in OOP) by implementing a sheep that can have two different sexes and can only reproduce with another sheep of opposite sex.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"This new type of sheep needs an additonal field sex::Symbol which can be either :male or :female. In OOP we would simply inherit from Sheep and create a ⚥Sheep with an additional field. In Julia there is no inheritance - only subtyping of abstract types. As you cannot inherit from a concrete type in Julia, we will have to create a wrapper type and forward all necessary methods. This is typically a sign of unfortunate type tree design and should be avoided, but if you want to extend a code base by an unforeseen type this forwarding of methods is a nice work-around. Our ⚥Sheep type will simply contain a classic sheep and a sex field","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"struct ⚥Sheep <: Animal\n sheep::Sheep\n sex::Symbol\nend\n⚥Sheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6, sex=rand(Bool) ? :female : :male) = ⚥Sheep(Sheep(id,e,Δe,pr,pf),sex)\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"sheep = ⚥Sheep(1)\nsheep.sheep\nsheep.sex","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Instead of littering the whole code with custom getters/setters Julia allows us to overload the sheep.field behaviour by implementing custom getproperty/setproperty! methods.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement custom getproperty/setproperty! methods which allow to access the Sheep inside the ⚥Sheep as if we would not be wrapping it.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"# NOTE: the @forward macro we will discuss in a later lecture is based on this\n\nfunction Base.getproperty(s::⚥Sheep, name::Symbol)\n if name in fieldnames(Sheep)\n getfield(s.sheep,name)\n else\n getfield(s,name)\n end\nend\n\nfunction Base.setproperty!(s::⚥Sheep, name::Symbol, x)\n if name in fieldnames(Sheep)\n setfield!(s.sheep,name,x)\n else\n setfield!(s,name,x)\n end\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"You should be able to do the following with your overloads now","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"sheep = ⚥Sheep(1)\nsheep.id\nsheep.sex\nsheep.energy += 1\nsheep","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In order to make the ⚥Sheep work with the rest of the code we only have to forward the eat! method","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"eat!(s::⚥Sheep, food, world) = eat!(s.sheep, food, world);\nsheep = ⚥Sheep(1);\ngrass = Grass(2);\nworld = World([sheep,grass])\neat!(sheep, grass, world)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"and implement a custom reproduce! method with the behaviour that we want.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"However, the extension of Sheep to ⚥Sheep is a very object-oriented approach. With a little bit of rethinking, we can build a much more elegant solution that makes use of Julia's powerful parametric types.","category":"page"},{"location":"lecture_03/lab/#Part-II:-A-new,-parametric-type-hierarchy","page":"Lab","title":"Part II: A new, parametric type hierarchy","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"First, let us note that there are two fundamentally different types of agents in our world: animals and plants. All species such as grass, sheep, wolves, etc. can be categorized as one of those two. We can use Julia's powerful, parametric type system to define one large abstract type for all agents Agent{S}. The Agent will either be an Animal or a Plant with a type parameter S which will represent the specific animal/plant species we are dealing with.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"This new type hiearchy can then look like this:","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"abstract type Species end\n\nabstract type PlantSpecies <: Species end\nabstract type Grass <: PlantSpecies end\n\nabstract type AnimalSpecies <: Species end\nabstract type Sheep <: AnimalSpecies end\nabstract type Wolf <: AnimalSpecies end\n\nabstract type Agent{S<:Species} end\n\n# instead of Symbols we can use an Enum for the sex field\n# using an Enum here makes things easier to extend in case you\n# need more than just binary sexes and is also more explicit than\n# just a boolean\n@enum Sex female male","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct World{A<:Agent}\n agents::Dict{Int,A}\n max_id::Int\nend\n\nfunction World(agents::Vector{<:Agent})\n max_id = maximum(a.id for a in agents)\n World(Dict(a.id=>a for a in agents), max_id)\nend\n\n# optional: overload Base.show\nfunction Base.show(io::IO, w::World)\n println(io, typeof(w))\n for (_,a) in w.agents\n println(io,\" $a\")\n end\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Now we can create a concrete type Animal with the two parametric types and the fields that we already know from lab 2.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct Animal{A<:AnimalSpecies} <: Agent{A}\n const id::Int\n energy::Float64\n const Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\n const sex::Sex\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"To create an instance of Animal we have to specify the parametric type while constructing it","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Animal{Wolf}(1,5,5,1,1,female)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Note that we now automatically have animals of any species without additional work. Starting with the overload of the show method we can already see that we can abstract away a lot of repetitive work into the type system. We can implement one single show method for all animal species!","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement Base.show(io::IO, a::Animal) with a single method for all Animals. You can get the pretty (unicode) printing of the Species types with another overload like this: Base.show(io::IO, ::Type{Sheep}) = print(io,\"🐑\")","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function Base.show(io::IO, a::Animal{A}) where {A<:AnimalSpecies}\n e = a.energy\n d = a.Δenergy\n pr = a.reprprob\n pf = a.foodprob\n s = a.sex == female ? \"♀\" : \"♂\"\n print(io, \"$A$s #$(a.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend\n\n# note that for new species/sexes we will only have to overload `show` on the\n# abstract species types like below!\nBase.show(io::IO, ::Type{Sheep}) = print(io,\"🐑\")\nBase.show(io::IO, ::Type{Wolf}) = print(io,\"🐺\")","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Unfortunately we have lost the convenience of creating plants and animals by simply calling their species constructor. For example, Sheep is just an abstract type that we cannot instantiate. However, we can manually define a new constructor that will give us this convenience back. This is done in exactly the same way as defining a constructor for a concrete type:","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Sheep(id,E,ΔE,pr,pf,s=rand(Sex)) = Animal{Sheep}(id,E,ΔE,pr,pf,s)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Ok, so we have a constructor for Sheep now. But what about all the other billions of species that you want to define in your huge master thesis project of ecosystem simulations? Do you have to write them all by hand? Do not despair! Julia has you covered.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Overload all AnimalSpecies types with a constructor. You already know how to write constructors for specific types such as Sheep. Can you manage to sneak in a type variable? Maybe with Type?","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function (A::Type{<:AnimalSpecies})(id::Int,E::T,ΔE::T,pr::T,pf::T,s::Sex) where T\n Animal{A}(id,E,ΔE,pr,pf,s)\nend\n\n# get the per species defaults back\nrandsex() = rand(instances(Sex))\nSheep(id; E=4.0, ΔE=0.2, pr=0.8, pf=0.6, s=randsex()) = Sheep(id, E, ΔE, pr, pf, s)\nWolf(id; E=10.0, ΔE=8.0, pr=0.1, pf=0.2, s=randsex()) = Wolf(id, E, ΔE, pr, pf, s)\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"We have our convenient, high-level behaviour back!","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Sheep(1)\nWolf(2)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Check the methods for eat! and kill_agent! which involve Animals and update their type signatures such that they work for the new type hiearchy.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function eat!(wolf::Animal{Wolf}, sheep::Animal{Sheep}, w::World)\n wolf.energy += sheep.energy * wolf.Δenergy\n kill_agent!(sheep,w)\nend\n\n# no change\n# eat!(::Animal, ::Nothing, ::World) = nothing\n\n# no change\n# kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)\n\neats(::Animal{Wolf},::Animal{Sheep}) = true\neats(::Agent,::Agent) = false\n# this one needs to wait until we have `Plant`s\n# eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\n\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Finally, we can implement the new behaviour for reproduce! which we wanted. Build a function which first finds an animal species of opposite sex and then lets the two reproduce (same behaviour as before).","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mates(a::Animal{A}, b::Animal{A}) where A<:AnimalSpecies = a.sex != b.sex\nmates(::Agent, ::Agent) = false\n\nfunction find_mate(a::Animal, w::World)\n ms = filter(x->mates(x,a), w.agents |> values |> collect)\n isempty(ms) ? nothing : rand(ms)\nend\n\nfunction reproduce!(a::Animal{A}, w::World) where {A}\n m = find_mate(a,w)\n if !isnothing(m)\n a.energy = a.energy / 2\n vals = [getproperty(a,n) for n in fieldnames(Animal) if n ∉ [:id, :sex]]\n new_id = w.max_id + 1\n ŝ = Animal{A}(new_id, vals..., randsex())\n w.agents[ŝ.id] = ŝ\n w.max_id = new_id\n end\nend\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"s1 = Sheep(1, s=female)\ns2 = Sheep(2, s=male)\nw = World([s1, s2])\nreproduce!(s1, w); w","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement the type hiearchy we designed for Plants as well.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct Plant{P<:PlantSpecies} <: Agent{P}\n id::Int\n size::Int\n max_size::Int\nend\n\n# constructor for all Plant{<:PlantSpecies} callable as PlantSpecies(...)\n(A::Type{<:PlantSpecies})(id, s, m) = Plant{A}(id,s,m)\n(A::Type{<:PlantSpecies})(id, m) = (A::Type{<:PlantSpecies})(id,rand(1:m),m)\n\n# default specific for Grass\nGrass(id; max_size=10) = Grass(id, rand(1:max_size), max_size)\n\nfunction Base.show(io::IO, p::Plant{P}) where P\n x = p.size/p.max_size * 100\n print(io,\"$P #$(p.id) $(round(Int,x))% grown\")\nend\n\nBase.show(io::IO, ::Type{Grass}) = print(io,\"🌿\")\n\nfunction eat!(sheep::Animal{Sheep}, grass::Plant{Grass}, w::World)\n sheep.energy += grass.size * sheep.Δenergy\n grass.size = 0\nend\neats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\n\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"g = Grass(2)\ns = Sheep(3)\nw = World([g,s])\neat!(s,g,w); w","category":"page"},{"location":"lecture_02/lecture/#type_lecture","page":"Lecture","title":"Motivation","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Before going into the details of Julia's type system, we will spend a few minutes motivating the roles of a type system, which are:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Structuring code\nCommunicating to the compiler how a type will be used","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The first aspect is important for the convenience of the programmer and enables abstractions in the language, the latter aspect is important for the speed of the generated code. Writing efficient Julia code is best viewed as a dialogue between the programmer and the compiler. [1] ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Type systems according to Wikipedia:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In computer science and computer programming, a data type or simply type is an attribute of data which tells the compiler or interpreter how the programmer intends to use the data.\nA type system is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program, such as variables, expressions, functions or modules. These types formalize and enforce the otherwise implicit categories the programmer uses for algebraic data types, data structures, or other components.","category":"page"},{"location":"lecture_02/lecture/#Structuring-the-code-/-enforcing-the-categories","page":"Lecture","title":"Structuring the code / enforcing the categories","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The role of structuring the code and imposing semantic restriction means that the type system allows you to logically divide your program, and to prevent certain types of errors. Consider for example two types, Wolf and Sheep which share the same definition but the types have different names.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct Wolf\n name::String\n energy::Int\nend\n\nstruct Sheep\n name::String\n energy::Int\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This allows us to define functions applicable only to the corresponding type","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"howl(wolf::Wolf) = println(wolf.name, \" has howled.\")\nbaa(sheep::Sheep) = println(sheep.name, \" has baaed.\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Therefore the compiler (or interpreter) enforces that a wolf can only howl and never baa and vice versa a sheep can only baa. In this sense, it ensures that howl(sheep) and baa(wolf) never happen.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"baa(Sheep(\"Karl\",3))\nbaa(Wolf(\"Karl\",3))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice the type of error of the latter call baa(Wolf(\"Karl\",3)). Julia raises MethodError which states that it has failed to find a function baa for the type Wolf (but there is a function baa for type Sheep).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For comparison, consider an alternative definition which does not have specified types","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"bark(animal) = println(animal.name, \" has howled.\")\nbaa(animal) = println(animal.name, \" has baaed.\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"in which case the burden of ensuring that a wolf will never baa rests upon the programmer which inevitably leads to errors (note that severely constrained type systems are difficult to use).","category":"page"},{"location":"lecture_02/lecture/#Intention-of-use-and-restrictions-on-compilers","page":"Lecture","title":"Intention of use and restrictions on compilers","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Types play an important role in generating efficient code by a compiler, because they tells the compiler which operations are permitted, prohibited, and can indicate invariants of type (e.g. constant size of an array). If compiler knows that something is invariant (constant), it can expoit such information. As an example, consider the following two alternatives to represent a set of animals:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Sheep(\"3\", 3)]\nb = (Wolf(\"1\", 1), Wolf(\"2\", 2), Sheep(\"3\", 3))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where a is an array which can contain arbitrary types and have arbitrary length whereas b is a Tuple which has fixed length in which the first two items are of type Wolf and the third item is of type Sheep. Moreover, consider a function which calculates the energy of all animals as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"energy(animals) = mapreduce(x -> x.energy, +, animals)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"A good compiler makes use of the information provided by the type system to generate efficient code which we can verify by inspecting the compiled code using @code_native macro","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none energy(a)\n@code_native debuginfo=:none energy(b)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"one observes the second version produces more optimal code. Why is that?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In the first representation, a, the animals are stored in an Array{Any} which can have arbitrary size and can contain arbitrary animals. This means that the compiler has to compile energy(a) such that it works on such arrays.\nIn the second representation, b, the animals are stored in a Tuple, which specializes for lengths and types of items. This means that the compiler knows the number of animals and the type of each animal on each position within the tuple, which allows it to specialize.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This difference will indeed have an impact on the time of code execution. On my i5-8279U CPU, the difference (as measured by BenchmarkTools) is","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using BenchmarkTools\n@btime energy($(a))\n@btime energy($(b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":" 70.2 ns (0 allocations: 0 bytes)\n 2.62 ns (0 allocations: 0 bytes)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which nicely demonstrates that the choice of types affects performance. Does it mean that we should always use Tuples instead of Arrays? Surely not, it is just that each is better for different use-cases. Using Tuples means that the compiler will compile a special function for each length of tuple and each combination of types of items it contains, which is clearly wasteful.","category":"page"},{"location":"lecture_02/lecture/#type_system","page":"Lecture","title":"Julia's type system","text":"","category":"section"},{"location":"lecture_02/lecture/#Julia-is-dynamicaly-typed","page":"Lecture","title":"Julia is dynamicaly typed","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia's type system is dynamic, which means that all types are resolved during runtime. But, if the compiler can infer types of all variables of the called function, it can specialize the function for that given type of variables which leads to efficient code. Consider a modified example where we represent two wolfpacks:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\nwolfpack_b = Any[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a carries a type Vector{Wolf} while wolfpack_b has the type Vector{Any}. This means that in the first case, the compiler knows that all items are of the type Wolfand it can specialize functions using this information. In case of wolfpack_b, it does not know which animal it will encounter (although all are of the same type), and therefore it needs to dynamically resolve the type of each item upon its use. This ultimately leads to less performant code.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@benchmark energy($(wolfpack_a))\n@benchmark energy($(wolfpack_b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":" 3.7 ns (0 allocations: 0 bytes)\n 69.4 ns (0 allocations: 0 bytes)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"To conclude, julia is indeed a dynamically typed language, but if the compiler can infer all types in a called function in advance, it does not have to perform the type resolution during execution, which produces performant code. This means and in hot (performance critical) parts of the code, you should be type stable, in other parts, it is not such big deal.","category":"page"},{"location":"lecture_02/lecture/#Classes-of-types","page":"Lecture","title":"Classes of types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia divides types into three classes: primitive, composite, and abstract.","category":"page"},{"location":"lecture_02/lecture/#Primitive-types","page":"Lecture","title":"Primitive types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Citing the documentation: A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive types are integers and floating-point values. Unlike most languages, Julia lets you declare your own primitive types, rather than providing only a fixed set of built-in ones. In fact, the standard primitive types are all defined in the language itself.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The definition of primitive types look as follows","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"primitive type Float16 <: AbstractFloat 16 end\nprimitive type Float32 <: AbstractFloat 32 end\nprimitive type Float64 <: AbstractFloat 64 end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and they are mainly used to jump-start julia's type system. It is rarely needed to define a special primitive type, as it makes sense only if you define special functions operating on its bits. This is almost excusively used for exposing special operations provided by the underlying CPU / LLVM compiler. For example + for Int32 is different from + for Float32 as they call a different intrinsic operations. You can inspect this jump-starting of the type system yourself by looking at Julia's source.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"julia> @which +(1,2)\n+(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"At int.jl:87","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"(+)(x::T, y::T) where {T<:BitInteger} = add_int(x, y)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"we see that + of integers is calling the function add_int(x, y), which is defined in the core part of the compiler in Intrinsics.cpp (yes, in C++), exposed in Core.Intrinsics","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From Julia docs: Core is the module that contains all identifiers considered \"built in\" to the language, i.e. part of the core language and not libraries. Every module implicitly specifies using Core, since you can't do anything without those definitions.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Primitive types are rarely used, and they will not be used in this course. We mention them for the sake of completeness and refer the reader to the official Documentation (and source code of Julia).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An example of use of primitive type is a definition of one-hot vector in the library PrimitiveOneHot as ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"primitive type OneHot{K} <: AbstractOneHotArray{1} 32 end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where K is the dimension of the one-hot vector. ","category":"page"},{"location":"lecture_02/lecture/#Abstract-types","page":"Lecture","title":"Abstract types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An abstract type can be viewed as a set of concrete types. For example, an AbstractFloat represents the set of concrete types (BigFloat,Float64,Float32,Float16). This is used mainly to define general methods for sets of types for which we expect the same behavior (recall the Julia design motivation: if it quacks like a duck, waddles like a duck and looks like a duck, chances are it's a duck). Abstract types are defined with abstract type TypeName end. For example the following set of abstract types defines part of julia's number system.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"abstract type Number end\nabstract type Real <: Number end\nabstract type Complex <: Number end\nabstract type AbstractFloat <: Real end\nabstract type Integer <: Real end\nabstract type Signed <: Integer end\nabstract type Unsigned <: Integer end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where <: means \"is a subtype of\" and it is used in declarations where the right-hand is an immediate sypertype of a given type (Integer has the immediate supertype Real.) If the supertype is not supplied, it is considered to be Any, therefore in the above defition Number has the supertype Any. ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"We can list childrens of an abstract type using function subtypes ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: subtypes # hide\nsubtypes(AbstractFloat)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and we can also list the immediate supertype or climb the ladder all the way to Any using supertypes","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: supertypes # hide\nsupertypes(AbstractFloat)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"supertype and subtypes print only types defined in Modules that are currently loaded to your workspace. For example with Julia without any Modules, subtypes(Number) returns [Complex, Real], whereas if I load Mods package implementing numbers defined over finite field, the same call returns [Complex, Real, AbstractMod].","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"It is relatively simple to print a complete type hierarchy of ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using AbstractTrees\nfunction AbstractTrees.children(t::Type)\n t === Function ? Vector{Type}() : filter!(x -> x !== Any,subtypes(t))\nend\nAbstractTrees.printnode(io::IO,t::Type) = print(io,t)\nprint_tree(Number)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The main role of abstract types allows is in function definitions. They allow to define functions that can be used on variables with types with a given abstract type as a supertype. For example we can define a sgn function for all real numbers as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"sgn(x::Real) = x > 0 ? 1 : x < 0 ? -1 : 0\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and we know it would be correct for all real numbers. This means that if anyone creates a new subtype of Real, the above function can be used. This also means that it is expected that comparison operations are defined for any real number. Also notice that Complex numbers are excluded, since they do not have a total order.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For unsigned numbers, the sgn can be simplified, as it is sufficient to verify if they are different (greater) than zero, therefore the function can read","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"sgn(x::Unsigned) = x > 0 ? 1 : 0\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and again, it applies to all numbers derived from Unsigned. Recall that Unsigned <: Integer <: Real, how does Julia decide, which version of the function sgn to use for UInt8(0)? It chooses the most specific version, and thus for sgn(UInt8(0)) it will use sgn(x::Unsinged). If the compiler cannot decide, typically it encounters an ambiguity, it throws an error and recommends which function you should define to resolve it.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The above behavior allows to define default \"fallback\" implementations and while allowing to specialize for sub-types. A great example is matrix multiplication, which has a generic (and slow) implementation with many specializations, which can take advantage of structure (sparse, banded), or use optimized implementations (e.g. blas implementation for dense matrices with eltype Float32 and Float64).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, Julia does not make a difference between abstract types defined in Base libraries shipped with the language and those defined by you (the user). All are treated the same.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From Julia documentation: Abstract types cannot be instantiated, which means that we cannot create a variable that would have an abstract type (try typeof(Number(1f0))). Also, abstract types cannot have any fields, therefore there is no composition (there are lengthy discussions of why this is so, one of the most definite arguments of creators is that abstract types with fields frequently lead to children types not using some fields (consider circle vs. ellipse)).","category":"page"},{"location":"lecture_02/lecture/#composite_types","page":"Lecture","title":"Composite types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Composite types are similar to struct in C (they even have the same memory layout) as they logically join together other types. It is not a great idea to think about them as objects (in OOP sense), because objects tie together data and functions on owned data. Contrary in Julia (as in C), functions operate on data of structures, but are not tied to them and they are defined outside them. Composite types are workhorses of Julia's type system, as user-defined types are mostly composite (or abstract).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Composite types are defined using struct TypeName [fields] end. To define a position of an animal on the Euclidean plane as a type, we would write","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionF64\n x::Float64\n y::Float64\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"which defines a structure with two fields x and y of type Float64. Julia's compiler creates a default constructor, where both (but generally all) arguments are converted using (convert(Float64, x), convert(Float64, y) to the correct type. This means that we can construct a PositionF64 with numbers of different type that are convertable to Float64, e.g. PositionF64(1,1//2) but we cannot construct PositionF64 where the fields would be of different type (e.g. Int, Float32, etc.) or they are not trivially convertable (e.g. String).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Fields in composite types do not have to have a specified type. We can define a VaguePosition without specifying the type","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct VaguePosition\n x\n y\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This works as the definition above except that the arguments are not converted to Float64 now. One can store different values in x and y, for example String (e.g. VaguePosition(\"Hello\",\"world\")). Although the above definition might be convenient, it limits the compiler's ability to specialize, as the type VaguePosition does not carry information about type of x and y, which has a negative impact on the performance. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using BenchmarkTools\nmove(a,b) = typeof(a)(a.x+b.x, a.y+b.y)\nx = [PositionF64(rand(), rand()) for _ in 1:100]\ny = [VaguePosition(rand(), rand()) for _ in 1:100]\n@benchmark reduce(move, $(x))\n@benchmark reduce(move, $(y))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Giving fields of a composite type an abstract type does not really solve the problem of the compiler not knowing the type. In this example, it still does not know, if it should use instructions for Float64 or Int8.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct LessVaguePosition\n x::Real\n y::Real\nend\n\nz = [LessVaguePosition(rand(), rand()) for _ in 1:100];\n@benchmark reduce(move, $(z))\nnothing #hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From the perspective of generating optimal code, both definitions are equally uninformative to the compiler as it cannot assume anything about the code. However, the LessVaguePosition will ensure that the position will contain only numbers, hence catching trivial errors like instantiating VaguePosition with non-numeric types for which arithmetic operators will not be defined (recall the discussion on the beginning of the lecture).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"All structs defined above are immutable (as we have seen above in the case of Tuple), which means that one cannot change a field (unless the struct wraps a container, like and array, which allows that). For example this raises an error","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = LessVaguePosition(1,2)\na.x = 2","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"If one needs to make a struct mutable, use the keyword mutable before the keyword struct as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct MutablePosition\n x::Float64\n y::Float64\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In mutable structures, we can change the values of fields.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = MutablePosition(1e0, 2e0)\na.x = 2;\na","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Note, that the memory layout of mutable structures is different, as fields now contain references to memory locations, where the actual values are stored (such structures cannot be allocated on stack, which increases the pressure on Garbage Collector).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The difference can be seen from ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a, b = PositionF64(1,2), PositionF64(1,2)\n@code_native debuginfo=:none move(a,b)\na, b = MutablePosition(1,2), MutablePosition(1,2)\n@code_native debuginfo=:none move(a,b)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Why there is just one addition?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Also, the mutability is costly.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"x = [PositionF43(rand(), rand()) for _ in 1:100];\nz = [MutablePosition(rand(), rand()) for _ in 1:100];\n@benchmark reduce(move, $(x))\n@benchmark reduce(move, $(z))","category":"page"},{"location":"lecture_02/lecture/#Parametric-types","page":"Lecture","title":"Parametric types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"So far, we had to trade-off flexibility for generality in type definitions. Can we have both? The answer is affirmative. The way to achieve this flexibility in definitions of the type while being able to generate optimal code is to parametrize the type definition. This is achieved by replacing types with a parameter (typically a single uppercase character) and decorating in definition by specifying different type in curly brackets. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionT{T}\n x::T\n y::T\nend\nu = [PositionT(rand(), rand()) for _ in 1:100]\nu = [PositionT(rand(Float32), rand(Float32)) for _ in 1:100]\n\n@benchmark reduce(move, $(u))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice that the compiler can take advantage of specializing for different types (which does not have an effect here as in modern processors addition of Float and Int takes the same time).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"v = [PositionT(rand(1:100), rand(1:100)) for _ in 1:100]\n@benchmark reduce(move, v)\nnothing #hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The above definition suffers the same problem as VaguePosition, which is that it allows us to instantiate the PositionT with non-numeric types, e.g. String. We solve this by restricting the types T to be children of some supertype, in this case Real","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct Position{T<:Real}\n x::T\n y::T\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"which will throw an error if we try to initialize it with Position(\"1.0\", \"2.0\"). Notice the flexibility we have achieved. We can use Position to store (and later compute) not only over Float32 / Float64 but any real numbers defined by other packages, for example with Posits.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using SoftPosit\nPosition(Posit8(3), Posit8(1))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"also notice that trying to construct the Position with different type of real numbers will fail, example Position(1f0,1e0)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Naturally, fields in structures can be of different types, as is in the below pointless example.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionXY{X<:Real, Y<:Real}\n x::X\n y::Y\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The type can be parametrized by a concrete types. This is usefuyl to communicate the compiler some useful informations, for example size of arrays. ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionZ{T<:Real,Z}\n x::T\n y::T\nend\n\nPositionZ{Int64,1}(1,2)","category":"page"},{"location":"lecture_02/lecture/#Abstract-parametric-types","page":"Lecture","title":"Abstract parametric types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Like Composite types, Abstract types can also have parameters. These parameters define types that are common for all child types. A very good example is Julia's definition of arrays of arbitrary dimension N and type T of its items as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"abstract type AbstractArray{T,N} end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Different T and N give rise to different variants of AbstractArrays, therefore AbstractArray{Float32,2} is different from AbstractArray{Float64,2} and from AbstractArray{Float64,1}. Note that these are still Abstract types, which means you cannot instantiate them. Their purpose is","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"to allow to define operations for broad class of concrete types\nto inform the compiler about constant values, which can be used","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice in the above example that parameters of types do not have to be types, but can also be values of primitive types, as in the above example of AbstractArray N is the number of dimensions which is an integer value.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For convenience, it is common to give some important partially instantiated Abstract types an alias, for example AbstractVector as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"const AbstractVector{T} = AbstractArray{T,1}","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"is defined in array.jl:23 (in Julia 1.6.2), which allows us to define for example general prescription for the dot product of two abstract vectors as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"function dot(a::AbstractVector, b::AbstractVector)\n @assert length(a) == length(b)\n mapreduce(*, +, a, b)\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"You can verify that the above general function can be compiled to performant code if specialized for particular arguments.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: @code_native\n@code_native debuginfo=:none mapreduce(*,+, [1,2,3], [1,2,3])","category":"page"},{"location":"lecture_02/lecture/#More-on-the-use-of-types-in-function-definitions","page":"Lecture","title":"More on the use of types in function definitions","text":"","category":"section"},{"location":"lecture_02/lecture/#Terminology","page":"Lecture","title":"Terminology","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"A function refers to a set of \"methods\" for a different combination of type parameters (the term function can be therefore considered as refering to a mere name). Methods define different behavior for different types of arguments for a given function. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, b::Position) = Position(a.x + b.x, a.y + b.y)\nmove(a::Vector{<:Position}, b::Vector{<:Position}) = move.(a,b)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move refers to a function with methods move(a::Position, b::Position) and move(a::Vector{<:Position}, b::Vector{<:Position}). When different behavior on different types is defined by a programmer, as shown above, it is also called implementation specialization. There is another type of specialization, called compiler specialization, which occurs when the compiler generates different functions for you from a single method. For example for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,1), Position(2,2))\nmove(Position(1.0,1.0), Position(2.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"the compiler generates two methods, one for Position{Int64} and the other for Position{Float64}. Notice that inside generated functions, the compiler needs to use different intrinsic operations, which can be viewed from","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none move(Position(1,1), Position(2,2))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none move(Position(1.0,1.0), Position(2.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice that move works on Posits defined in 3rd party libas well","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(Posit8(1),Posit8(1)), Position(Posit8(2),Posit8(2)))","category":"page"},{"location":"lecture_02/lecture/#Intermezzo:-How-does-the-Julia-compiler-work?","page":"Lecture","title":"Intermezzo: How does the Julia compiler work?","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Let's walk through an example. Consider the following definitions","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)\nmove(a::T, by::T) where {T<:Position} = Position(a.x + by.x, a.y + by.y)\nmove(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nmove(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)\nmove(a::Vector{<:Position}, by::Position) = move.(a, by)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and a function call","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = Position(1.0, 1.0)\nby = Position(2.0, 2.0)\nmove(a, by)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The compiler knows that you call the function move.\nThe compiler infers the type of the arguments. You can view the result with","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"(typeof(a),typeof(by))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The compiler identifies all move-methods with arguments of type (Position{Float64}, Position{Float64}):","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wc = Base.get_world_counter()\nm = Base.method_instances(move, (typeof(a), typeof(by)), wc)\nm = first(m)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"4a. If the method has been specialized (compiled), then the arguments are prepared and the method is invoked. The compiled specialization can be seen from","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"m.cache","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"4b. If the method has not been specialized (compiled), the method is compiled for the given type of arguments and continues as in step 4a. A compiled function is therefore a \"blob\" of native code living in a particular memory location. When Julia calls a function, it needs to pick the right block corresponding to a function with particular type of parameters.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"If the compiler cannot narrow the types of arguments to concrete types, it has to perform the above procedure inside the called function, which has negative effects on performance, as the type resulution and identification of the methods can be slow, especially for methods with many arguments (e.g. 30ns for a method with one argument, 100 ns for method with two arguements). You always want to avoid run-time resolution inside the performant loop!!! Recall the above example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_a))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 991 evaluations.\n Range (min … max): 40.195 ns … 66.641 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 40.742 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 40.824 ns ± 1.025 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n ▂▃ ▃▅▆▅▆█▅▅▃▂▂ ▂\n ▇██████████████▇▇▅▅▁▅▄▁▅▁▄▄▃▄▅▄▅▃▅▃▅▁▃▁▄▄▃▁▁▅▃▃▄▃▄▃▄▆▆▇▇▇▇█ █\n 40.2 ns Histogram: log(frequency) by time 43.7 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_b = Any[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 800 evaluations.\n Range (min … max): 156.406 ns … 212.344 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 157.136 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 158.114 ns ± 4.023 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n ▅█▆▅▄▂ ▃▂▁ ▂\n ██████▆▇██████▇▆▇█▇▆▆▅▅▅▅▅▃▄▄▅▄▄▄▄▅▁▃▄▄▃▃▄▃▃▃▄▄▄▅▅▅▅▁▅▄▃▅▄▄▅▅ █\n 156 ns Histogram: log(frequency) by time 183 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An interesting intermediate between fully abstract and fully concrete type happens, when the compiler knows that arguments have abstract type, which is composed of a small number of concrete types. This case called Union-Splitting, which happens when there is just a little bit of uncertainty. Julia will do something like","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"argtypes = typeof(args)\npush!(execution_stack, args)\nif T == Tuple{Int, Bool}\n @goto compiled_blob_1234\nelse # the only other option is Tuple{Float64, Bool}\n @goto compiled_blob_1236\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"const WolfOrSheep = Union{Wolf, Sheep}\nwolfpack_c = WolfOrSheep[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_c))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 991 evaluations.\n Range (min … max): 43.600 ns … 73.494 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 44.106 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 44.279 ns ± 0.931 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n █ ▁ ▃ \n ▂▂▂▆▃██▅▃▄▄█▅█▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃\n 43.6 ns Histogram: frequency by time 47.4 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Thanks to union splitting, Julia is able to have performant operations on arrays with undefined / missing values for example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"[1, 2, 3, missing] |> typeof","category":"page"},{"location":"lecture_02/lecture/#More-on-matching-methods-and-arguments","page":"Lecture","title":"More on matching methods and arguments","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In the above process, the step, where Julia looks for a method instance with corresponding parameters can be very confusing. The rest of this lecture will focus on this. For those who want to have a formal background, we recommend talk of Francesco Zappa Nardelli and / or the one of Jan Vitek.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"When Julia needs to specialize a method instance, it needs to find it among multiple definitions. A single function can have many method instances, see for example methods(+) which lists all method instances of the +-function. How does Julia select the proper one?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"It finds all methods where the type of arguments match or are subtypes of restrictions on arguments in the method definition.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2a. If there are multiple matches, the compiler selects the most specific definition.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2b. If the compiler cannot decide, which method instance to choose, it throws an error.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"confused_move(a::Position{Float64}, by) = Position(a.x + by.x, a.y + by.y)\nconfused_move(a, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nconfused_move(Position(1.0,2.0), Position(1.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2c. If it cannot find a suitable method, it throws an error.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,2), VaguePosition(\"hello\",\"world\"))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Some examples: Consider following definitions","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)\nmove(a::T, by::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)\nmove(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nmove(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)\nmove(a::Vector{T}, by::Vector{T}) where {T<:Position} = move.(a, by)\nmove(a::Vector{<:Position}, by::Position) = move.(a, by)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1.0,2.0), Position(1.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The first three methods match the types of argumens, but the compiler will select the third one, since it is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,2), Position(1,2))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, the first and second method definitions match the argument, but the second is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will the compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move([Position(1,2)], [Position(1,2)])","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, the fourth and fifth method definitions match the argument, but the fifth is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move([Position(1,2), Position(1.0,2.0)], [Position(1,2), Position(1.0,2.0)])","category":"page"},{"location":"lecture_02/lecture/#Frequent-problems","page":"Lecture","title":"Frequent problems","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Why does the following fail?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{Real}) = println(\"Vector{Real}\")\nfoo([1.0,2,3])","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia's type system is invariant, which means that Vector{Real} is different from Vector{Float64} and from Vector{Float32}, even though Float64 and Float32 are sub-types of Real. Therefore typeof([1.0,2,3]) isa Vector{Float64} which is not subtype of Vector{Real}. For covariant languages, this would be true. For more information on variance in computer languages, see here. If the above definition of foo should be applicable to all vectors which has elements of subtype of Real we have define it as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{T}) where {T<:Real} = println(\"Vector{T} where {T<:Real}\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"or equivalently but more tersely as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{<:Real}) = println(\"Vector{T} where {T<:Real}\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Diagonal rule says that a repeated type in a method signature has to be a concrete type (this is to avoid ambinguity if the repeated type is used inside function definition to define a new variable to change type of variables). Consider for example the function below","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::T, b::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"we cannot call it with move(Position(1.0,2.0), Position(1,2)), since in this case Position(1.0,2.0) is of type Position{Float64} while Position(1,2) is of type Position{Int64}.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"When debugging why arguments do not match a particular method definition, it is useful to use typeof, isa, and <: commands. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"typeof(Position(1.0,2.0))\ntypeof(Position(1,2))\nPosition(1,2) isa Position{Float64}\nPosition(1,2) isa Position{Real}\nPosition(1,2) isa Position{<:Real}\ntypeof(Position(1,2)) <: Position{<:Float64}\ntypeof(Position(1,2)) <: Position{<:Real}","category":"page"},{"location":"lecture_02/lecture/#A-bizzare-definition-which-you-can-encounter","page":"Lecture","title":"A bizzare definition which you can encounter","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The following definition of a one-hot matrix is taken from Flux.jl","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct OneHotArray{T<:Integer, L, N, var\"N+1\", I<:Union{T,AbstractArray{T, N}}} <: AbstractArray{Bool, var\"N+1\"}\n indices::I\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The parameters of the type carry information about the type used to encode the position of one in each column in T, the dimension of one-hot vectors in L, the dimension of the storage of indices in N (which is zero for OneHotVector and one for OneHotMatrix), number of dimensions of the OneHotArray in var\"N+1\" and the type of underlying storage of indicies I.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"[1]: Type Stability in Julia, Pelenitsyn et al., 2021](https://arxiv.org/pdf/2109.01950.pdf)","category":"page"},{"location":"installation/#install","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"In order to participate in the course, everyone should install a recent version of Julia together with some text editor of choice. Furthermore during the course we will introduce some best practices of creating/testing and distributing your own Julia code, for which we will require a GitHub account.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"We recommend to install Julia via juliaup. We are using the latest, stable version of Julia (which at the time of this writing is v1.9). Once you have installed juliaup you can get any Julia version you want via:","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"juliaup add $JULIA_VERSION\n\n# or more concretely:\njuliaup add 1.9\n\n# but please, just use the latest, stable version","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Now you should be able to start Julia an be greated with the following:","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"$ julia\n _\n _ _ _(_)_ | Documentation: https://docs.julialang.org\n (_) | (_) (_) |\n _ _ _| |_ __ _ | Type \"?\" for help, \"]?\" for Pkg help.\n | | | | | | |/ _` | |\n | | |_| | | | (_| | | Version 1.9.2 (2023-07-05)\n _/ |\\__'_|_|_|\\__'_| | Official https://julialang.org/ release\n|__/ |\n\njulia>","category":"page"},{"location":"installation/#Julia-IDE","page":"Installation","title":"Julia IDE","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"There is no one way to install/develop and run Julia, which may be strange users coming from MATLAB, but for users of general purpose languages such as Python, C++ this is quite common. Most of the Julia programmers to date are using","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Visual Studio Code,\nand the corresponding Julia extension.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"This setup is described in a comprehensive step-by-step guide in our bachelor course Julia for Optimization & Learning.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Note that this setup is not a strict requirement for the lectures/labs and any other text editor with the option to send code to the terminal such as Vim (+Tmux), Emacs, or Sublime Text will suffice.","category":"page"},{"location":"installation/#GitHub-registration-and-Git-setup","page":"Installation","title":"GitHub registration & Git setup","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"As one of the goals of the course is writing code that can be distributed to others, we recommend a GitHub account, which you can create here (unless you already have one). In order to interact with GitHub repositories, we will be using git. For installation instruction (Windows only) see the section in the bachelor course.","category":"page"},{"location":"lecture_01/motivation/#Introduction-to-Scientific-Programming","page":"Motivation","title":"Introduction to Scientific Programming","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"note: Loose definition of Scientific Programming\nScientific programming languages are designed and optimized for implementing mathematical formulas and for computing with matrices.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Examples of Scientific programming languages include ALGOL, APL, Fortran, J, Julia, Maple, MATLAB and R.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Key requirements for a Scientific programming language:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Fast execution of the code (complex algorithms).\nEase of code reuse / code restructuring.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"
      \n \n
      \n Julia set.\n Stolen from\n Colorschemes.jl.\n
      \n
      ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"In contrast, to general-purpose language Julia has:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"less concern with standalone executable/libraby compilation \nless concern with Application binary interface (ABI)\nless concern with business models (library + header files)\nless concern with public/private separation","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Example of a scientific task\nIn many applications, we encounter the task of optimization a function given by a routine (e.g. engineering, finance, etc.)using Optim\n\nP(x,y) = x^2 - 3x*y + 5y^2 - 7y + 3 # user defined function\n\nz₀ = [ 0.0\n 0.0 ] # starting point \n\noptimize(z -> P(z...), z₀, ConjugateGradient())\noptimize(z -> P(z...), z₀, Newton())\noptimize(z -> P(z...), z₀, Newton();autodiff = :forward)\n","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Very simple for a user, very complicated for a programmer. The program should:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"pick the right optimization method (easy by config-like approach)\ncompute gradient (Hessian) of a user function","category":"page"},{"location":"lecture_01/motivation/#Classical-approach:-create-a-*fast*-library-and-flexible-calling-enviroment","page":"Motivation","title":"Classical approach: create a fast library and flexible calling enviroment","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Crucial algorithms (sort, least squares...) are relatively small and well defined. Application of these algorithms to real-world problem is typically not well defined and requires more code. Iterative development. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Think of a problem of repeated execution of similar jobs with different options. Different level ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"binary executable with command-line switches\nbinary executable with configuration file\nscripting language/environment (Read-Eval-Print Loop)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"It is not a strict boundary, increasing expresivity of the configuration file will create a new scripting language.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Ending up in the 2 language problem. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Low-level programming = computer centric\nclose to the hardware\nallows excellent optimization for fast execution\nHigh-level programming = user centric\nrunning code with many different modifications as easily as possible\nallowing high level of abstraction","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"In scientific programming, the most well known scripting languages are: Python, Matlab, R","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"If you care about standard \"configurations\" they are just perfect. (PyTorch, BLAS)\nYou hit a problem with more complex experiments, such a modifying the internal algorithms.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The scripting language typically makes decisions (if) at runtime. Becomes slow.","category":"page"},{"location":"lecture_01/motivation/#Examples","page":"Motivation","title":"Examples","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Basic Linear Algebra Subroutines (BLAS)–MKL, OpenBlas–-with bindings (Matlab, NumPy)\nMatlab and Mex (C with pointer arithmetics)\nPython with transcription to C (Cython)","category":"page"},{"location":"lecture_01/motivation/#Convergence-efforts","page":"Motivation","title":"Convergence efforts","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Just-in-time compilation (understands high level and converts to low-level)\nautomatic typing (auto in C++) (extends low-level with high-level concepts)","category":"page"},{"location":"lecture_01/motivation/#Julia-approach:-fresh-thinking","page":"Motivation","title":"Julia approach: fresh thinking","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"A dance between specialization and abstraction. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Specialization allows for custom treatment. The right algorithm for the right circumstance is obtained by Multiple dispatch,\nAbstraction recognizes what remains the same after differences are stripped away. Abstractions in mathematics are captured as code through generic programming.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Why a new language?","category":"page"},{"location":"lecture_01/motivation/#Challenge","page":"Motivation","title":"Challenge","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Translate high-level thinking with as much abstraction as possible into specific fast machine code.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Not so easy!","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"theorem: Indexing array x in Matlab:\nx = [1,2,3]\ny=x(4/2)\ny=x(5/2)In the first case it works, in the second throws an error.type instability \nfunction inde(x,n,m)=x(n/m) can never be fast.\nPoor language design choice!","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Simple solution","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Solved by different floating and integer division operation /,÷\nNot so simple with complex objects, e.g. triangular matrices","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia was designed as a high-level language that allows very high level abstract concepts but propagates as much information about the specifics as possible to help the compiler to generate as fast code as possible. Taking lessons from the inability to achieve fast code compilation (mostly from python).","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"julia is faster than C?","category":"page"},{"location":"lecture_01/motivation/#Julia-way","page":"Motivation","title":"Julia way","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Design principle: abstraction should have zero runtime cost","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"flexible type system with strong typing (abstract types)\nmultiple dispatch\nsingle language from high to low levels (as much as possible) optimize execution as much as you can during compile time\nfunctions as symbolic abstraction layers","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"AST = Abstract Syntax Tree\nIR = Intermediate Representation","category":"page"},{"location":"lecture_01/motivation/#Teaser-example","page":"Motivation","title":"Teaser example","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Function recursion with arbitrary number of arguments:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum(x) = x\nfsum(x,p...) = x+fsum(p...)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Defines essentially a sum of inputs. Nice generic and abstract concept.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Possible in many languages:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Matlab via nargin, varargin using construction if nargin==1, out=varargin{1}, else out=fsum(varargin{2:end}), end","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia solves this if at compile time. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The generated code can be inspected by macro @code_llvm?","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum(1,2,3)\n@code_llvm fsum(1,2,3)\n@code_llvm fsum(1.0,2.0,3.0)\nfz()=fsum(1,2,3)\n@code_llvm fz()","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Note that each call of fsum generates a new and different function.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Functions can act either as regular functions or like templates in C++. Compiler decides.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"This example is relatively simple, many other JIT languages can optimize such code. Julia allows taking this approach further.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Generality of the code:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum('c',1)\nfsum([1,2],[3,4],[5,6])","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Relies on multiple dispatch of the + function.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"More involved example:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"using Zygote\n\nf(x)=3x+1 # user defined function\n@code_llvm f'(10)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The simplification was not achieved by the compiler alone.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia provides tools for AST and IR code manipulation\nautomatic differentiation via IR manipulation is implemented in Zygote.jl\nin a similar way, debugger is implemented in Debugger.jl\nvery simple to design domain specific language\nusing Turing\nusing StatsPlots\n\n@model function gdemo(x, y)\n s² ~ InverseGamma(2, 3)\n m ~ Normal(0, sqrt(s²))\n x ~ Normal(m, sqrt(s²))\n y ~ Normal(m, sqrt(s²))\nend","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Such tools allow building a very convenient user experience on abstract level, and reaching very efficient code.","category":"page"},{"location":"lecture_01/motivation/#Reproducibile-research","page":"Motivation","title":"Reproducibile research","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Think about a code that was written some time ago. To run it, you often need to be able to have the same version of the language it was written for. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Standard way language freezes syntax and guarantees some back-ward compatibility (Matlab), which prevents future improvements\nJulia approach allows easy recreation of the environment in which the code was developed. Every project (e.g. directory) can have its own environment","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Environment\nIs an independent set of packages that can be local to an individual project or shared and selected by name.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Package\nA package is a source tree with a standard layout providing functionality that can be reused by other Julia projects.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"This allows Julia to be a rapidly evolving ecosystem with frequent changes due to:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"built-in package manager\nswitching between multiple versions of packages","category":"page"},{"location":"lecture_01/motivation/#Package-manager","page":"Motivation","title":"Package manager","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"implemented by Pkg.jl\nsource tree have their structure defined by a convention\nhave its own mode in REPL\nallows adding packages for using (add) or development (dev)\nsupporting functions for creation (generate) and activation (activate) and many others","category":"page"},{"location":"lecture_01/motivation/#Julia-from-user's-point-of-view","page":"Motivation","title":"Julia from user's point of view","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"compilation of everything to as specialized as possible\nvery fast code\nslow interaction (caching...)\ngenerating libraries is harder \nthink of fsum, \neverything is \".h\" (Eigen library)\ndebugging is different to matlab/python\nextensibility, Multiple dispatch = multi-functions\nallows great extensibility and code composition\nnot (yet) mainstream thinking\nJulia is not Object-oriented\nJulia is (not pure) functional language","category":"page"},{"location":"how_to_submit_hw/#homeworks","page":"Homework submission","title":"Homework submission","text":"","category":"section"},{"location":"how_to_submit_hw/","page":"Homework submission","title":"Homework submission","text":"This document should describe the homework submission procedure.","category":"page"},{"location":"lecture_01/basics/#Syntax","page":"Basics","title":"Syntax","text":"","category":"section"},{"location":"lecture_01/basics/#Elementary-syntax:-Matlab-heritage","page":"Basics","title":"Elementary syntax: Matlab heritage","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Very much like matlab:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"indexing from 1\narray as first-class A=[1 2 3]","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Cheat sheet: https://cheatsheets.quantecon.org/","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Introduction: https://juliadocs.github.io/Julia-Cheat-Sheet/","category":"page"},{"location":"lecture_01/basics/#Arrays-are-first-class-citizens","page":"Basics","title":"Arrays are first-class citizens","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Many design choices were motivated considering matrix arguments:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"x *= 2 is implemented as x = x*2 causing new allocation (vectors).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The reason is consistency with matrix operations: A *= B works as A = A*B.","category":"page"},{"location":"lecture_01/basics/#Broadcasting-operator","page":"Basics","title":"Broadcasting operator","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Julia generalizes matlabs .+ operation to general use for any function. ","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"a = [1 2 3]\nsin.(a)\nf(x)=x^2+3x+8\nf.(a)","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Solves the problem of inplace multiplication","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"x .*= 2 ","category":"page"},{"location":"lecture_01/basics/#Functional-roots-of-Julia","page":"Basics","title":"Functional roots of Julia","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Function is a first-class citizen.","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Repetition of functional programming:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"function mymap(f::Function,a::AbstractArray)\n b = similar(a)\n for i in eachindex(a)\n b[i]=f(a[i])\n end\n b\nend","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Allows for anonymous functions:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"mymap(x->x^2+2,[1.0,2.0])","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Function properties:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Arguments are passed by reference (change of mutable inputs inside the function is visible outside)\nConvention: function changing inputs have a name ending by \"!\" symbol\nreturn value \nthe last line of the function declaration, \nreturn keyword\nzero cost abstraction","category":"page"},{"location":"lecture_01/basics/#Different-style-of-writing-code","page":"Basics","title":"Different style of writing code","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Definitions of multiple small functions and their composition","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"fsum(x) = x\nfsum(x,p...) = x+fsum(p[1],p[2:end]...)","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"a single methods may not be sufficient to understand the full algorithm. In procedural language, you may write:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"function out=fsum(x,varargin)\n if nargin==2 # TODO: better treatment\n out=x\n else\n out = fsum(varargin{1},varargin{2:end})\n end","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The need to build intuition for function composition.","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Dispatch is easier to optimize by the compiler.","category":"page"},{"location":"lecture_01/basics/#Operators-are-functions","page":"Basics","title":"Operators are functions","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"operator function name\n[A B C ...] hcat\n[A; B; C; ...] vcat\n[A B; C D; ...] hvcat\nA' adjoint\nA[i] getindex\nA[i] = x setindex!\nA.n getproperty\nA.n = x setproperty!","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"struct Foo end\n\nBase.getproperty(a::Foo, x::Symbol) = x == :a ? 5 : error(\"does not have property $(x)\")","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Can be redefined and overloaded for different input types. The getproperty method can define access to the memory structure.","category":"page"},{"location":"lecture_01/basics/#Broadcasting-revisited","page":"Basics","title":"Broadcasting revisited","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The a.+b syntax is a syntactic sugar for broadcast(+,a,b).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The special meaning of the dot is that they will be fused into a single call:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"f.(g.(x .+ 1)) is treated by Julia as broadcast(x -> f(g(x + 1)), x). \nAn assignment y .= f.(g.(x .+ 1)) is treated as in-place operation broadcast!(x -> f(g(x + 1)), y, x).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The same logic works for lists, tuples, etc.","category":"page"},{"location":"lecture_03/lecture/#Design-patterns:-good-practices-and-structured-thinking","page":"Lecture","title":"Design patterns: good practices and structured thinking","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Every software developer has a desire to write better code. A desire to improve system performance. A desire to design software that is easy to maintain, easy to understand and explain.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Design patterns are recommendations and good practices accumulating knowledge of experienced programmers.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The highest level of experience contains the design guiding principles:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface\nSegregation, Dependency Inversion\nDRY: Don't Repeat Yourself\nKISS: Keep It Simple, Stupid!\nPOLA: Principle of Least Astonishment\nYAGNI: You Aren't Gonna Need It (overengineering)\nPOLP: Principle of Least Privilege ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"While these high-level concepts are intuitive, they are too general to give specific answers.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"More detailed patterns arise for programming paradigms (declarative, imperative) with specific instances of functional or object-oriented programming.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The concept of design patterns originates in the OOP paradigm. OOP defines a strict way how to write software. Sometimes it is not clear how to squeeze real world problems into those rules. Cookbook for many practical situations","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Gamma, E., Johnson, R., Helm, R., Johnson, R. E., & Vlissides, J. (1995). Design patterns: elements of reusable object-oriented software. Pearson Deutschland GmbH.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Defining 23 design patterns in three categories. Became extremely popular.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(Image: ) (C) Scott Wlaschin","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is julia OOP or FP? It is different from both, based on:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"types system (polymorphic)\nmultiple dispatch (extending single dispatch of OOP)\nfunctions as first class \ndecoupling of data and functions\nmacros","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Any guidelines to solve real-world problems?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Hands-On Design Patterns and Best Practices with Julia Proven solutions to common problems in software design for Julia 1.x Tom Kwong, CFA","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Fundamental tradeoff: rules vs. freedom","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"freedom: in the C language it is possible to access assembler instructions, use pointer aritmetics:\nit is possible to write extremely efficient code\nit is easy to segfault, leak memory, etc.\nrules: in strict languages (strict OOP, strict functional programing) you lose freedom for certain guarantees:\ne.g. strict functional programing guarantees that the program provably terminates\noperations that are simple e.g. in pointer arithmetics may become clumsy and inefficient in those strict rules.\nthe compiler can validate the rules and complain if the code does not comply with them. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia is again a dance between freedom and strict rules. It is more inclined to freedom. Provides few simple concepts that allow to construct design patterns common in other languages.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"the language does not enforce too many formalisms (via keywords (interface, trait, etc.) but they can be \nthe compiler cannot check for correctness of these \"patterns\"\nthe user has a lot of freedom (and responsibility)\nlots of features can be added by Julia packages (with various level of comfort)\nmacros","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Read: ","category":"page"},{"location":"lecture_03/lecture/#Design-Patterns-of-OOP-from-the-Julia-viewpoint","page":"Lecture","title":"Design Patterns of OOP from the Julia viewpoint","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"OOP is currently very popular concept (C++, Java, Python). It has strenghts and weaknesses. The Julia authors tried to keep the strength and overcome weaknesses. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Key features of OOP:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Encapsulation \nInheritance \nPolymorphism ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Classical OOP languages define classes that bind processing functions to the data. Virtual methods are defined only for the attached methods of the classes.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Encapsulation\nRefers to bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Making Julia to mimic OOP\nThere are many discussions how to make Julia to behave like an OOP. The best implementation to our knowledge is ObjectOriented","category":"page"},{"location":"lecture_03/lecture/#Encapsulation-Advantage:-Consistency-and-Validity","page":"Lecture","title":"Encapsulation Advantage: Consistency and Validity","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"With fields of data structure freely accessible, the information may become inconsistent.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct Grass <: Plant\n id::Int\n size::Int\n max_size::Int\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"What if I create Grass with larger size than max_size?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"grass = Grass(1,50,5)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Freedom over Rules. Maybe I would prefer to introduce some rules.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Some encapsulation may be handy keeping it consistent. Julia has inner constructor.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct Grass2 <: Plant\n id::Int\n size::Int\n max_size::Int\n Grass2(id,sz,msz) = sz > msz ? error(\"size can not be greater that max_size\") : new(id,sz,msz)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"When defined, Julia does not provide the default outer constructor. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"But fields are still accessible:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"grass.size = 10000","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Recall that grass.size=1000 is a syntax of setproperty!(grass,:size,1000), which can be redefined:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function Base.setproperty!(obj::Grass, sym::Symbol, val)\n if sym==:size\n @assert val<=obj.max_size \"size have to be lower than max_size!\"\n end\n setfield!(obj,sym,val)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Function setfield! can not be overloaded.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia has partial encapsulation via a mechanism for consistency checks. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"warn: Array in unmutable struct can be mutated\nThe mutability applies to the structure and not to encapsulated structures.struct Foo\n x::Float64\n y::Vector{Float64}\n z::Dict{Int,Int}\nendIn the structure Foo, x cannot be mutated, but fields of y and key-value pairs of z can be mutated, because they are mutable containers. But I cannot replace y with a different Vector.","category":"page"},{"location":"lecture_03/lecture/#Encapsulation-Disadvantage:-the-Expression-Problem","page":"Lecture","title":"Encapsulation Disadvantage: the Expression Problem","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Encapsulation limits the operations I can do with an object. Sometimes too much. Consider a matrix of methods/types(data-structures)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Consider an existing matrix of data and functions:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"data \\ methods find_food eat! grow! \nWolf \nSheep \nGrass ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"You have a good reason not to modify the original source (maintenance).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Imagine we want to extend the world to use new animals and new methods for all animals.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Object-oriented programming ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"classes are primary objects (hierarchy)\ndefine animals as classes ( inheriting from abstract class)\nadding a new animal is easy\nadding a new method for all animals is hard (without modifying the original code)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Functional programming ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"functions are primary\ndefine operations find_food, eat!\nadding a new operation is easy\nadding new data structure to existing operations is hard","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Solutions:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"multiple-dispatch = julia\nopen classes (monkey patching) = add methods to classes on the fly\nvisitor pattern = partial fix for OOP [extended visitor pattern using dynamic_cast]","category":"page"},{"location":"lecture_03/lecture/#Morale:","page":"Lecture","title":"Morale:","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia does not enforces creation getters/setters by default (setproperty is mapped to setfield)\nit provides tools to enforce access restriction if the user wants it.\ncan be used to imitate objects: ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"https://stackoverflow.com/questions/39133424/how-to-create-a-single-dispatch-object-oriented-class-in-julia-that-behaves-l/39150509#39150509","category":"page"},{"location":"lecture_03/lecture/#Polymorphism:","page":"Lecture","title":"Polymorphism:","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Polymorphism in OOP\nPolymorphism is the method in an object-oriented programming language that performs different things as per the object’s class, which calls it. With Polymorphism, a message is sent to multiple class objects, and every object responds appropriately according to the properties of the class. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Example animals of different classes make different sounds. In Python:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"\nclass Sheep:\n def __init__(self, energy, Denergy):\n self.energy = energy\n self.Denergy = Denergy\n\n def make_sound(self):\n print(\"Baa\")\n\nsheep.make_sound()\nwolf.make_sound()","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Will make distinct sounds (baa, Howl). ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Can we achieve this in Julia?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"make_sound(s::Sheep)=println(\"Baa\")\nmake_sound(w::Wolf)=println(\"Howl\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Implementation of virtual methods\nVirtual methods in OOP are typically implemented using Virtual Method Table, one for each class. (Image: )Julia has a single method table. Dispatch can be either static or dynamic (slow).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Freedom vs. Rules. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Duck typing is a type of polymorphism without static types\nmore programming freedom, less formal guarantees\njulia does not check if make_sound exists for all animals. May result in MethodError. Responsibility of a programmer.\ndefine make_sound(A::AbstractAnimal)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"So far, the polymorphism coincides for OOP and julia becuase the method had only one argument => single argument dispatch.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Multiple dispatch is an extension of the classical first-argument-polymorphism of OOP, to all-argument polymorphism.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Challenge for OOP\nHow to code polymorphic behavior of interaction between two agents, e.g. an agent eating another agent in OOP?Complicated.... You need a \"design pattern\" for it.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"class Sheep(Animal):\n energy: float = 4.0\n denergy: float = 0.2\n reprprob: float = 0.5\n foodprob: float = 0.9\n\n # hard, if not impossible to add behaviour for a new type of food\n def eat(self, a: Agent, w: World):\n if isinstance(a, Grass)\n self.energy += a.size * self.denergy\n a.size = 0\n else:\n raise ValueError(f\"Sheep cannot eat {type(a).__name__}.\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Consider an extension to:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Flower : easy\nPoisonousGrass: harder","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Simple in Julia:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"eat!(w1::Sheep, a::Grass, w::World)=\neat!(w1::Sheep, a::Flower, w::World)=\neat!(w1::Sheep, a::PoisonousGrass, w::World)=","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Boiler-plate code can be automated by macros / meta programming.","category":"page"},{"location":"lecture_03/lecture/#Inheritance","page":"Lecture","title":"Inheritance","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Inheritance\nIs the mechanism of basing one object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Deriving new classes (sub classes) from existing ones such as super class or base class and then forming them into a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance, a \"child object\", acquires all the properties and behaviors of the \"parent object\" , with the exception of: constructors, destructor, overloaded operators.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Most commonly, the sub-class inherits methods and the data.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"For example, in python we can design a sheep with additional field. Think of a situation that we want to refine the reproduction procedure for sheeps by considering differences for male and female. We do not have information about gender in the original implementation. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In OOP, we can use inheritance.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"class Sheep:\n def __init__(self, energy, Denergy):\n self.energy = energy\n self.Denergy = Denergy\n\n def make_sound(self):\n print(\"Baa\")\n\nclass SheepWithGender(Sheep):\n def __init__(self, energy, Denergy,gender):\n super().__init__(energy, Denergy)\n self.gender = gender\n # make_sound is inherited \n\n# Can you do this in Julia?!","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Simple answer: NO, not exactly","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Sheep has fields, is a concrete type, we cannot extend it.\nwith modification of the original code, we can define AbstractSheep with subtypes Sheep and SheepWithGender.\nBut methods for AbstractAnimal works for sheeps! Is this inheritance?","category":"page"},{"location":"lecture_03/lecture/#Inheritance-vs.-Subtyping","page":"Lecture","title":"Inheritance vs. Subtyping","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Subtle difference:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"subtyping = equality of interface \ninheritance = reuse of implementation ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In practice, subtyping reuse methods, not data fields.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"We have seen this in Julia, using type hierarchy: ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"agent_step!(a::Animal, w::World)\nall animals subtype of Animal \"inherit\" this method.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The type hierarchy is only one way of subtyping. Julia allows many variations, e.g. concatenating different parts of hierarchies via the Union{} type:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"fancy_method(O::Union{Sheep,Grass})=println(\"Fancy\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is this a good idea? It can be done completely Ad-hoc! Freedom over Rules.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"There are very good use-cases:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Missing values:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"x::AbstractVector{<:Union{<:Number, Missing}}","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"theorem: SubTyping issues\nWith parametric types, unions and other construction, subtype resolution may become a complicated problem. Julia can even crash. (Jan Vitek's Keynote at JuliaCon 2021)[https://www.youtube.com/watch?v=LT4AP7CUMAw]","category":"page"},{"location":"lecture_03/lecture/#Sharing-of-data-field-via-composition","page":"Lecture","title":"Sharing of data field via composition","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Composition is also recommended in OOP: (Composition over ingeritance)[https://en.wikipedia.org/wiki/Compositionoverinheritance]","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"struct ⚥Sheep <: Animal\n sheep::Sheep\n sex::Symbol\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"If we want our new ⚥Sheep to behave like the original Sheep, we need to forward the corresponding methods.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"eat!(a::⚥Sheep, b::Grass, w::World)=eat!(a.sheep, b, w)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"and all other methods. Routine work. Boring! The whole process can be automated using macros @forward from Lazy.jl.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Why so complicated? Wasn't the original inheritance tree structure better?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"multiple inheritance:\nyou just compose two different \"trees\".\ncommon example with ArmoredVehicle = Vehicle + Weapon\nDo you think there is only one sensible inheritance tree?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Animal World\nThink of an inheritance tree of a full scope Animal world.Idea #1: Split animals by biological taxonomy (Image: )Hold on. Sharks and dolphins can swim very well!\nBoth bats and birds fly similarly!Idea #2: Split by the way they move!Idea #3: Split by way of ...","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In fact, we do not have a tree, but more like a matrix/tensor:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":" swims flies walks\nbirds penguin eagle kiwi\nmammal dolphin bat sheep,wolf\ninsect backswimmer fly beetle","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Single type hierarchy will not work. Other approaches:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"interfaces\nparametric types","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Analyze what features of animals are common and compose the animal:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"abstract type HeatType end\nabstract type MovementType end\nabstract type ChildCare end\n\n\nmutable struct Animal{H<:HeatType,M<:MovementType,C<:ChildCare} \n id::Int\n ...\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Now, we can define methods dispatching on parameters of the main type.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Composition is simpler in such a general case. Composition over inheritance. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"A simple example of parametric approach will be demonstarted in the lab.","category":"page"},{"location":"lecture_03/lecture/#Interfaces:-inheritance/subtyping-without-a-hierarchy-tree","page":"Lecture","title":"Interfaces: inheritance/subtyping without a hierarchy tree","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In OOP languages such as Java, interfaces have a dedicated keyword such that compiler can check correctes of the interface implementation. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In Julia, interfaces can be achived by defining ordinary functions. Not so strict validation by the compiler as in other languages. Freedom...","category":"page"},{"location":"lecture_03/lecture/#Example:-Iterators","page":"Lecture","title":"Example: Iterators","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Many fundamental objects can be iterated: Arrays, Tuples, Data collections...","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"They do not have any common \"predecessor\". They are almost \"primitive\" types.\nthey share just the property of being iterable\nwe do not want to modify them in any way","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Example: of interface Iterators defined by \"duck typing\" via two functions.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Required methods Brief description\niterate(iter) Returns either a tuple of the first item and initial state or nothing if empty\niterate(iter, state) Returns either a tuple of the next item and next state or nothing if no items remain","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Defining these two methods for any object/collection C will make the following work:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"for o in C\n # do something\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The compiler will not check if both functions exist.\nIf one is missing, it will complain about it when it needs it\nThe error message may be less informative than in the case of formal definition","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"even iterators may have different features: they can be finite or infinite\nfor finite iterators we can define useful functions (collect)\nhow to pass this information in an extensible way?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Poor solution: if statements.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function collect(iter)\n if iter isa Tuple...\n\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The compiler can do that for us.","category":"page"},{"location":"lecture_03/lecture/#Traits:-cherry-picking-subtyping","page":"Lecture","title":"Traits: cherry picking subtyping","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Trait mechanism in Julia is build using the existing tools: Type System and Multiple Dispatch.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Traits have a few key parts:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Trait types: the different traits a type can have.\nTrait function: what traits a type has.\nTrait dispatch: using the traits.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"From iterators:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"# trait types:\n\nabstract type IteratorSize end\nstruct SizeUnknown <: IteratorSize end\nstruct HasLength <: IteratorSize end\nstruct IsInfinite <: IteratorSize end\n\n# Trait function: Input is a Type, output is a Type\nIteratorSize(::Type{<:Tuple}) = HasLength()\nIteratorSize(::Type) = HasLength() # HasLength is the default\n\n# ...\n\n# Trait dispatch\nBitArray(itr) = gen_bitarray(IteratorSize(itr), itr)\ngen_bitarray(isz::IteratorSize, itr) = gen_bitarray_from_itr(itr)\ngen_bitarray(::IsInfinite, itr) = throw(ArgumentError(\"infinite-size iterable used in BitArray constructor\"))\n","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"What is needed to define for a new type that I want to iterate over? ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Do you still miss inheritance in the OOP style?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Many packages automating this with more structure:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"https://github.com/andyferris/Traitor.jl\nhttps://github.com/mauro3/SimpleTraits.jl\nhttps://github.com/tk3369/BinaryTraits.jl","category":"page"},{"location":"lecture_03/lecture/#Functional-tools:-Partial-evaluation","page":"Lecture","title":"Functional tools: Partial evaluation","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"It is common to create a new function which \"just\" specify some parameters.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"_prod(x) = reduce(*,x)\n_sum(x) = reduce(+,x)","category":"page"},{"location":"lecture_03/lecture/#Functional-tools:-Closures","page":"Lecture","title":"Functional tools: Closures","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Closure (lexical closure, function closure)\nA technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"originates in functional programming\nnow widespread in many common languages, Python, Matlab, etc..\nmemory management relies on garbage collector in general (can be optimized by compiler)","category":"page"},{"location":"lecture_03/lecture/#Example","page":"Lecture","title":"Example","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function adder(x)\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"creates a function that \"closes\" the argument x. Try: f=adder(5); f(3).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"x = 30;\nfunction adder()\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"creates a function that \"closes\" variable x.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"f = adder(10)\nf(1)\ng = adder()\ng(1)\n","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Such function can be passed as an argument: together with the closed data.","category":"page"},{"location":"lecture_03/lecture/#Implementation-of-closures-in-julia:-documentation","page":"Lecture","title":"Implementation of closures in julia: documentation","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function adder(x)\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"is lowered to (roughly):","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"struct ##1{T}\n x::T\nend\n\n(_::##1)(y) = _.x + y\n\nfunction adder(x)\n return ##1(x)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note that the structure ##1 is not directly accessible. Try f.x and g.x.","category":"page"},{"location":"lecture_03/lecture/#Functor-Function-like-structure","page":"Lecture","title":"Functor = Function-like structure","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Each structure can have a method that is invoked when called as a function.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(_::Sheep)()= println(\"🐑\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"You can think of it as sheep.default_method().","category":"page"},{"location":"lecture_03/lecture/#Coding-style","page":"Lecture","title":"Coding style","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"From Flux.jl:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function train!(loss, ps, data, opt; cb = () -> ())\n ps = Params(ps)\n cb = runall(cb)\n @progress for d in data\n gs = gradient(ps) do\n loss(batchmemaybe(d)...)\n end\n update!(opt, ps, gs)\n cb()\n end\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is this confusing? What can cb() do and what it can not?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note that function train! does not have many local variables. The important ones are arguments, i.e. exist in the scope from which the function was invoked.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"loss(x,y)=mse(model(x),y)\ncb() = @info \"training\" loss(x,y)\ntrain!(loss, ps, data, opt; cb=cb)","category":"page"},{"location":"lecture_03/lecture/#Usage","page":"Lecture","title":"Usage","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Usage of closures:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"callbacks: the function can also modify the enclosed variable.\nabstraction: partial evaluation ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"theorem: Beware: Performance of captured variables\nInference of types may be difficult in closures: https://github.com/JuliaLang/julia/issues/15276 ","category":"page"},{"location":"lecture_03/lecture/#Aditional-materials","page":"Lecture","title":"Aditional materials","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(Functional desighn pattersn)[https://www.youtube.com/watch?v=srQt1NAHYC0]","category":"page"},{"location":"lecture_02/hw/#Homework-2:-Predator-Prey-Agents","page":"Homework","title":"Homework 2: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"In this lab you will continue working on your agent simulation. If you did not manage to finish the homework, do not worry, you can use this script which contains all the functionality we developed in the lab.","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_02\",\"Lab02Ecosystem.jl\"))","category":"page"},{"location":"lecture_02/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Put all your code (including your or the provided solution of lab 2) in a script named hw.jl. Zip only this file (not its parent folder) and upload it to BRUTE. Your file cannot contain any package dependencies. For example, having a using Plots in your code will cause the automatic evaluation to fail.","category":"page"},{"location":"lecture_02/hw/#Counting-Agents","page":"Homework","title":"Counting Agents","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"To monitor the different populations in our world we need a function that counts each type of agent. For Animals we simply have to count how many of each type are currently in our World. In the case of Plants we will use the fraction of size(plant)/max_size(plant) as a measurement quantity.","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"
      \n
      Compulsory Homework (2 points)
      \n
      ","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Implement a function agent_count that can be called on a single Agent and returns a number between (01) (i.e. always 1 for animals; and size(plant)/max_size(plant) for plants).\nAdd a method for a vector of agents Vector{<:Agent} which sums all agent counts.\nAdd a method for a World which returns a dictionary that contains pairs of Symbols and the agent count like below:","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"agent_count(p::Plant) = p.size / p.max_size\nagent_count(::Animal) = 1\nagent_count(as::Vector{<:Agent}) = sum(agent_count,as)\n\nfunction agent_count(w::World)\n function op(d::Dict,a::A) where A<:Agent\n n = nameof(A)\n if n in keys(d)\n d[n] += agent_count(a)\n else\n d[n] = agent_count(a)\n end\n return d\n end\n foldl(op, w.agents |> values |> collect, init=Dict{Symbol,Real}())\nend","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"grass1 = Grass(1,5,5);\nagent_count(grass1)\n\ngrass2 = Grass(2,1,5);\nagent_count([grass1,grass2]) # one grass is fully grown; the other only 20% => 1.2\n\nsheep = Sheep(3,10.0,5.0,1.0,1.0);\nwolf = Wolf(4,20.0,10.0,1.0,1.0);\nworld = World([grass1, grass2, sheep, wolf]);\nagent_count(world)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Hint: You can get the name of a type by using the nameof function:","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"nameof(Grass)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Use as much dispatch as you can! ;)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_01/hw/#Homework-1:-Extending-polynomial-the-other-way","page":"Homework","title":"Homework 1: Extending polynomial the other way","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework (2 points)
      \n
      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Extend the original polynomial function to the case where x is a square matrix. Create a function called circlemat, that returns nxn matrix A(n) with the following elements","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"leftA(n)right_ij = \nbegincases\n 1 textif (i = j-1 land j 1) lor (i = n land j=1) \n 1 textif (i = j+1 land j n) lor (i = 1 land j=n) \n 0 text otherwise\nendcases","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"and evaluate the polynomial","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"f(A) = I + A + A^2 + A^3","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":", at point A = A(10).","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS for matrix definition: You can try one of these options:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"create matrix with all zeros with zeros(n,n), use two nested for loops going in ranges 1:n and if condition with logical or ||, and && \nemploy array comprehension with nested loops [expression for i in 1:n, j in 1:n] and ternary operator condition ? true branch : false","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS for polynomial extension:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"extend the original example (one with for-loop) to initialize the accumulator variable with matrix of proper size (use size function to get the dimension), using argument typing for x is preferred to distinguish individual implementations <: AbstractMatrix","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"or","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"test later defined polynomial methods, that may work out of the box","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n","category":"page"},{"location":"lecture_01/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Put all the code for the exercise above in a file called hw.jl and upload it to BRUTE. If you have any questions, write an email to one of the lab instructors of the course.","category":"page"},{"location":"lecture_01/hw/#Voluntary","page":"Homework","title":"Voluntary","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      Exercise (voluntary)
      \n
      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Install GraphRecipes and Plots packages into the environment defined during the lecture and figure out, how to plot the graph defined by adjacency matrix A from the homework.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"There is help command inside the the pkg mod of the REPL. Type ? add to find out how to install a package. Note that both pkgs are registered.\nFollow a guide in the Plots pkg's documentation, which is accessible through docs icon on top of the README in the GitHub repository. Direct link.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Activate the environment in pkg mode, if it is not currently active.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> activate .","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Installing pkgs is achieved using the add command. Running ] ? add returns a short piece of documentation for this command:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> ? add\n[...]\n Examples\n\n pkg> add Example # most commonly used for registered pkgs (installs usually the latest release)\n pkg> add Example@0.5 # install with some specific version (realized through git tags)\n pkg> add Example#master # install from master branch directly\n pkg> add Example#c37b675 # install from specific git commit\n pkg> add https://github.com/JuliaLang/Example.jl#master # install from specific remote repository (when pkg is not registered)\n pkg> add git@github.com:JuliaLang/Example.jl.git # same as above but using the ssh protocol\n pkg> add Example=7876af07-990d-54b4-ab0e-23690620f79a # when there are multiple pkgs with the same name","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"As the both Plots and GraphRecipes are registered and we don't have any version requirements, we will use the first option.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> add Plots\npkg> add GraphRecipes","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"This process downloads the pkgs and triggers some build steps, if for example some binary dependencies are needed. The process duration depends on the \"freshness\" of Julia installation and the size of each pkg. With Plots being quite dependency heavy, expect few minutes. After the installation is complete we can check the updated environment with the status command.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> status","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"The plotting itself as easy as calling the graphplot function on our adjacency matrix.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"using GraphRecipes, Plots\nA = [ 0 1 0 0 0 0 0 0 0 1; 1 0 1 0 0 0 0 0 0 0; 0 1 0 1 0 0 0 0 0 0; 0 0 1 0 1 0 0 0 0 0; 0 0 0 1 0 1 0 0 0 0; 0 0 0 0 1 0 1 0 0 0; 0 0 0 0 0 1 0 1 0 0; 0 0 0 0 0 0 1 0 1 0; 0 0 0 0 0 0 0 1 0 1; 1 0 0 0 0 0 0 0 1 0]# hide\ngraphplot(A)","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"graphplot(A) #hide","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"

      ","category":"page"},{"location":"lecture_04/lecture/#pkg_lecture","page":"Lecture","title":"Package development","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Organization of the code is more important with the increasing size of the project and the number of contributors and users. Moreover, it will become essential when different codebases are expected to be combined and reused. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Julia was designed from the beginning to encourage code reuse across different codebases as possible\nJulia ecosystem lives on a namespace. From then, it builds projects and environments.","category":"page"},{"location":"lecture_04/lecture/#Namespaces-and-modules","page":"Lecture","title":"Namespaces and modules","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Namespace logically separate fragments of source code so that they can be developed independently without affecting each other. If I define a function in one namespace, I will still be able to define another function in a different namespace even though both functions have the same name.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"prevents confusion when common words are used in different meaning:\nToo general name of functions \"create\", \"extend\", \"loss\", \nor data \"X\", \"y\" (especially in mathematics, think of π)\nmay not be an issue if used with different types\nModules is Julia syntax for a namespace","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Example:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module MySpace\nfunction test1()\n println(\"test1\")\nend\nfunction test2()\n println(\"test2\")\nend\nexport test1\n#include(\"filename.jl\")\nend","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Function include copies content of the file to this location (will be part of the module).","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Creates functions:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"MySpace.test1\nMySpace.test2","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"For easier manipulation, these functions can be \"exported\" to be exposed to the outer world (another namespace).","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Keyword: using exposes the exported functions and structs:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using .MySpace","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The dot means that the module was defined in this scope.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Keyword: import imports function with availability to redefine it.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Combinations:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"usecase results\nusing MySpace MySpace.test1\n MySpace.test2\n test1\nusing MySpace: test1 test1\nimport MySpace MySpace.test1*\n MySpace.test2*\nimport MySpace: test1 test1*\nimport MySpace: test2 test2*","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"symbol \"*\" denotes functions that can be redefined","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":" using .MySpace: test1\n test1()=println(\"new test\")\n import .MySpace: test1\n test1()=println(\"new test\")","category":"page"},{"location":"lecture_04/lecture/#Conflicts:","page":"Lecture","title":"Conflicts:","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"When importing/using functions with name that is already imported/used from another module:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"the imported functions/structs are invalidated. \nboth functions has to be acessed by their full names.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Resolution:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"It may be easier to cherry pick only the functions we need (rather than importing all via using)\nrename some function using keyword as\nimport MySpace2: test1 as t1","category":"page"},{"location":"lecture_04/lecture/#Submodules","page":"Lecture","title":"Submodules","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Modules can be used or included within other modules:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module A\n a=1;\nend\nmodule B\n module C\n c = 2\n end\n b = C.c # you can read from C (by reference)\n using ..A: a\n # a= b # but not write to A\nend;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"REPL of Julia is a module called \"Main\". ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"modules are not copied, but referenced, i.e. B.b===B.C.c\nincluding one module twice (from different packages) is not a problem\nUpcoming Julia 1.9 has the ability to change the contextual module in the REPL: REPL.activate(TestPackage)","category":"page"},{"location":"lecture_04/lecture/#Revise.jl","page":"Lecture","title":"Revise.jl","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The fact that Julia can redefine a function in a Module by importing it is used by package Revise.jl to synchronize REPL with a module or file.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"So far, we have worked in REPL. If you have a file that is loaded and you want to modify it, you would need to either:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"reload the whole file, or\ncopy the changes to REPL","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Revise.jl does the latter automatically.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Example demo:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using Revise.jl\nincludet(\"example.jl\")","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Works with: ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"any package loaded with import or using, \nscript loaded with includet, \nBase julia itself (with Revise.track(Base))\nstandard libraries (with, e.g., using Unicode; Revise.track(Unicode))","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Does not work with variables!","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"How it works: monitors source code for changes and then does:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"for def in setdiff(oldexprs, newexprs)\n # `def` is an expression that defines a method.\n # It was in `oldexprs`, but is no longer present in `newexprs`--delete the method.\n delete_methods_corresponding_to_defexpr(mod, def)\nend\nfor def in setdiff(newexprs, oldexprs)\n # `def` is an expression for a new or modified method. Instantiate it.\n Core.eval(mod, def)\nend","category":"page"},{"location":"lecture_04/lecture/#Namespaces-and-scoping","page":"Lecture","title":"Namespaces & scoping","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Every module introduces a new global scope. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Global scope\nNo variable or function is expected to exist outside of it\nEvery module is equal to a global scope (no single \"global\" exists)\nThe REPL has a global module called Main\nLocal scope\nVariables in Julia do not need to be explicitly declared, they are created by assignments: x=1. \nIn local scope, the compiler checks if variable x does not exist outside. We have seen:\nx=1\nf(y)=x+y\nThe rules for local scope determine how to treat assignment of x. If local x exists, it is used, if it does not:\nin hard scope: new local x is created\nin soft scope: checks if x exists outside (global)\nif not: new local x is created\nif yes: the split is REPL/non-interactive:\nREPL: global x is used (convenience, as of 1.6)\nnon-interactive: local x is created","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"keyword local and global can be used to specify which variable to use","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"From documentation:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Construct Scope type Allowed within\nmodule, baremodule global global\nstruct local (soft) global\nfor, while, try local (soft) global, local\nmacro local (hard) global\nfunctions, do blocks, let blocks, comprehensions, generators local (hard) global, local","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Question:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x=1\nf()= x=3\nf()\n@show x;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x = 1\nfor _ = 1:1\n x=3\nend\n@show x;","category":"page"},{"location":"lecture_04/lecture/#Packages","page":"Lecture","title":"Packages","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Package is a source tree with a standard layout. It provides a module and thus can be loaded with include or using.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Minimimal package:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageName/\n├── src/\n│ └── PackageName.jl\n├── Project.toml","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Contains:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Project.toml file describing basic properties:\nName, does not have to be unique (federated package sources)\nUUID, has to be unique (generated automatically)\noptionally [deps], [targets],...\nfile src/PackageName.jl that defines module PackageName which is executed when loaded.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Many other optional directories:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"directory tests/, (almost mandatory)\ndirectory docs/ (common)\ndirectory scripts/, examples/,... (optional)","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The package typically loads other modules that form package dependencies.","category":"page"},{"location":"lecture_04/lecture/#Project-environments","page":"Lecture","title":"Project environments","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Is a package that does not contain Name and UUID in Project.toml. It's used when you don't need to create a package for your work. It's created by activate some/path in REPL package mode. ","category":"page"},{"location":"lecture_04/lecture/#Project-Manifest","page":"Lecture","title":"Project Manifest","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Both package and environment can contain an additional file Manifest.toml. This file tracks full dependency tree of a project including versions of the packages on which it depends.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"for example:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"# This file is machine-generated - editing it directly is not advised\n\n[[AbstractFFTs]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"485ee0867925449198280d4af84bdb46a2a404d0\"\nuuid = \"621f4979-c628-5d54-868e-fcf4e3e8185c\"\nversion = \"1.0.1\"\n\n[[AbstractTrees]]\ngit-tree-sha1 = \"03e0550477d86222521d254b741d470ba17ea0b5\"\nuuid = \"1520ce14-60c1-5f80-bbc7-55ef81b5835c\"\nversion = \"0.3.4\"","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Content of files Project.toml and Manifest.toml are maintained by PackageManager.","category":"page"},{"location":"lecture_04/lecture/#Package-manager","page":"Lecture","title":"Package manager","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Handles both packages and projects:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"creating a project ]generate PkgName\nadding an existing project add PkgName or add https://github.com/JuliaLang/Example.jl\nNames are resolved by Registrators (public or private).\nremoving ]rm PkgName\nupdating ]update\ndeveloping ]dev http://... \nadd treats packages as being finished, version handling pkg manager. Precompiles!\ndev leaves all operations on the package to the user (git versioning, etc.). Always read content of files","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"By default these operations are related to environment .julia/environments/v1.8","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"E.g. running and updating will update packages in Manifest.toml in this directory. What if the update breaks functionality of some project package that uses special features?","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"There can and should be more than one environment!","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Project environments are based on files with installed packages.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"switching by ]activate Path - creates Project.toml if not existing\nfrom that moment, all package modifications will be relevant only to this project!\nwhen switching to a new project ]instantiate will prepare (download and precompile) the environment\ncreates Manifest.toml = list of all exact versions of all packages \nwhich Packages are visible is determined by LOAD_PATH\ntypically contaings default libraries and default environment\nit is different for REPL and Pkg.tests ! No default env. in tests. ","category":"page"},{"location":"lecture_04/lecture/#Package-hygiene-workflow","page":"Lecture","title":"Package hygiene - workflow","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"theorem: Potential danger\nPackage dependencies may not be compatible: package A requires C@<0.2\npackage B requires C@>0.3\nwhat should happen when ]add A and add B?","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"keep your \"@v#.#\" as clean as possible (recommended are only debugging/profiling packages)\nuse packages as much as you can, even for short work with scripts ]activate .\nadding a package existing elsewhere is cheap (global cache)\nif do you not wish to store any files just test random tricks of a cool package: ]activate --temp","category":"page"},{"location":"lecture_04/lecture/#Package-development-with-Revise","page":"Lecture","title":"Package development with Revise","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Developing a package with interactive test/development:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Create a package/module at one directory MainPackage\nCreate a script at another directory MainScript, and activate it ]activate .\ndev MainPackage in the MainScript environment\nRevise.jl will watch the MainPackage so it is always up to date\nin dev mode you have full control over commits etc.","category":"page"},{"location":"lecture_04/lecture/#Unit-testing,-/test","page":"Lecture","title":"Unit testing, /test","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Without explicit keywords for checking constructs (think missing functions in interfaces), the good quality of the code is guaranteed by detailed unit testing.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"each package should have directory /test\nfile /test/runtest.jl is run by the command ]test of the package manager\nthis file typically contains include of other tests\nno formal structure of tests is prescribed\ntest files are just ordinary julia scripts\nuser is free to choose what to test and how (freedom x formal rules)\ntesting functionality is supported by macros @test and @teststet\n@testset \"trigonometric identities\" begin\n θ = 2/3*π\n @test sin(-θ) ≈ -sin(θ)\n @test cos(-θ) ≈ cos(θ)\n @test sin(2θ) ≈ 2*sin(θ)*cos(θ)\n @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2\nend;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Testset is a collection of tests that will be run and summarized in a common report.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Testsets can be nested: testsets in testsets\ntests can be in loops or functions\nfor i=1:10\n @test a[i]>0\nend\nUseful macro ≈ checks for equality with given tolerance\na=5+1e-8\n@test a≈5\n@test a≈5 atol=1e-10","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"@testset resets RNG to Random.GLOBAL_SEED before and after the test for repeatability \nThe same results of RNG are not guaranteed between Julia versions!\nTest coverage: package Coverage.jl\nCan be run automatically by continuous integration, e.g. GitHub actions\nintegration in VSCode test via package TestItems.jl ","category":"page"},{"location":"lecture_04/lecture/#Documentation-and-Style,-/docs","page":"Lecture","title":"Documentation & Style, /docs","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"A well written package is reusable if it is well documented. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The simpliest kind of documentation is the docstring:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"\"Auxiliary function for printing a hello\"\nhello()=println(\"hello\")\n\n\"\"\"\nMore complex function that adds π to input:\n- x is the input argument (itemize)\n\nCan be written in latex: ``x \\leftarrow x + \\pi``\n\"\"\"\naddπ(x) = x+π","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Yieds:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"tip: Renders as\nMore complex function that adds π to input:x is the input argument (itemize)Can be written in latex: x leftarrow x + pi","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Structure of the document","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageName/\n├── src/\n│ └── SourceFile.jl\n├── docs/\n│ ├── build/\n│ ├── src/\n│ └── make.jl\n...","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Where the line-by-line documentation is in the source files.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"/docs/src folder can contain more detailed information: introductory pages, howtos, tutorials, examples\nrunning make.jl controls which pages are generated in what form (html or latex) documentation in the /build directory\nautomated with GitHub actions","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Documentation is generated by the julia code.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"code in documentation can be evaluated","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x=3\n@show x","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"documentation can be added by code:\nstruct MyType\n value::String\nend\n\nDocs.getdoc(t::MyType) = \"Documentation for MyType with value $(t.value)\"\n\nx = MyType(\"x\")\ny = MyType(\"y\")\nSee ?x and ?y. \nIt uses the same very standard building blocks: multiple dispatch.","category":"page"},{"location":"lecture_04/lecture/#Precompilation","page":"Lecture","title":"Precompilation","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"By default, every package is precompiled when loading and stored in compiled form in a cache.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"If it defines methods that extend previously defined (e.g. from Base), it may affect already loaded packages which need to be recompiled as well. May take time.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Julia has a tracking mechanism that stores information about the whole graph of dependencies. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Faster code can be achieved by the precompile directive:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module FSum\n\nfsum(x) = x\nfsum(x,p...) = x+fsum(p[1],p[2:end]...)\n\nprecompile(fsum,(Float64,Float64,Float64))\nend","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Can be investigated using MethodAnalysis.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using MethodAnalysis\nmi =methodinstances(fsum)","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Useful packages:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageCompiler.jl has three main purposes:\nCreating custom sysimages for reduced latency when working locally with packages that has a high startup time.\nCreating \"apps\" which are a bundle of files including an executable that can be sent and run on other machines without Julia being installed on that machine.\nCreating a relocatable C library bundle form of Julia code.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"AutoSysimages.jl allows easy generation of precompiles images - reduces package loading","category":"page"},{"location":"projects/#projects","page":"Projects","title":"Projects","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of the project should be to create something, which is actually useful. Therefore we offer a lot of freedom in how the project will look like with the condition that you should spent around 60 hours on it (this number was derived as follows: each credit is worth 30 hours minus 13 lectures + labs minus 10 homeworks 2 hours each) and you should demonstrate some skills in solving the project. In general, we can distinguish three types of project depending on the beneficiary:","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"You benefit: Use / try to solve a well known problem using Julia language,\nOur group: work with your tutors on a topic researched in the AIC group, \nJulia community: choose an issue in a registered Julia project you like and fix it (documentation issues are possible but the resulting documentation should be very nice.).","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The project should be of sufficient complexity that verify your skill of the language (to be agreed individually).","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Below, we list some potential projects for inspiration.","category":"page"},{"location":"projects/#Implementing-new-things","page":"Projects","title":"Implementing new things","text":"","category":"section"},{"location":"projects/#Lenia-(Continuous-Game-of-Life)","page":"Projects","title":"Lenia (Continuous Game of Life)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Lenia is a continuous version of Conway's Game of Life. Implement a Julia version. For example, you could focus either on performance compared to the python version, or build nice visualizations with Makie.jl.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Nice tutorial from Conway to Lenia","category":"page"},{"location":"projects/#The-Equation-Learner-And-Its-Symbolic-Representation","page":"Projects","title":"The Equation Learner And Its Symbolic Representation","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"In many scientific and engineering one searches for interpretable (i.e. human-understandable) models instead of the black-box function approximators that neural networks provide. The equation learner (EQL) is one approach that can identify concise equations that describe a given dataset.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The EQL is essentially a neural network with different unary or binary activation functions at each indiviual unit. The network weights are regularized during training to obtain a sparse model which hopefully results in a model that represents a simple equation.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of this project is to implement the EQL, and if there is enough time the improved equation learner (iEQL). The equation learners should be tested on a few toy problems (possibly inspired by the tasks in the papers). Finally, you will implement functionality that can transform the learned model into a symbolic, human readable, and exectuable Julia expression.","category":"page"},{"location":"projects/#Architecture-visualizer","page":"Projects","title":"Architecture visualizer","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Create an extension of Flux / Lux and to visualize architecture of a neural network suitable for publication. Something akin PlotNeuralNet.","category":"page"},{"location":"projects/#Learning-Large-Language-Models-with-reduced-precition-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Learning Large Language Models with reduced precition (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Large Language Models ((Chat) GPT, LLama, Falcon, Palm, ...) are huge. A recent trend is to perform optimization in reduced precision, for example in int8 instead of Float32. Such feature is currently missing in Julia ecosystem and this project should be about bringing this to the community (for an introduction, read these blogs LLM-int8 and emergent features, A gentle introduction to 8-bit Matrix Multiplication). The goal would be to implement this as an additional type of Number / Matrix and overload multiplication on CPU (and ideally on GPU) to make it transparent for neural networks? What I will learn? In this project, you will learn a lot about the (simplicity of) implementation of deep learning libraries and you will practice abstraction of Julia's types. You can furthermore learn about GPU Kernel programming and Transformers.jl library.","category":"page"},{"location":"projects/#Planning-algorithms-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Planning algorithms (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Extend SymbolicPlanners.jl with the mm-ϵ variant of the bi-directional search MM: A bidirectional search algorithm that is guaranteed to meet in the middle. This pull request might be very helpful in understanding better the library.","category":"page"},{"location":"projects/#A-Rule-Learning-Algorithms-(Mentor:-Tomas-Pevny)","page":"Projects","title":"A Rule Learning Algorithms (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Rule-based models are simple and very interpretable models that have been around for a long time and are gaining popularity again. The goal of this project is to implement one of these algorithms","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"sequential covering","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"algorithm called RIPPER and evaluate it on a number of datasets.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Learning Certifiably Optimal Rule Lists for Categorical Data\nBoolean decision rules via column generation\nLearning Optimal Decision Trees with SAT\nA SAT-based approach to learn explainable decision sets","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"To increase the impact of the project, consider interfacing it with MLJ.jl","category":"page"},{"location":"projects/#Parallel-optimization-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Parallel optimization (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Implement one of the following algorithms to train neural networks in parallel. Can be implemented in a separate package or consider extending FluxDistributed.jl. Do not forget to verify that the method actually works!!!","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Hogwild!\nLocal sgd with periodic averaging: Tighter analysis and adaptive synchronization\nDistributed optimization for deep learning with gossip exchange","category":"page"},{"location":"projects/#Solve-issues-in-existing-projects:","page":"Projects","title":"Solve issues in existing projects:","text":"","category":"section"},{"location":"projects/#Create-Yao-backend-for-quantum-simulation-(Mentor:-Niklas-Heim)","page":"Projects","title":"Create Yao backend for quantum simulation (Mentor: Niklas Heim)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The recently published quantum programming library Qadence needs a Julia backend. The tricky quantum parts are already implemented in a library called Yao.jl. The goal of this project is to take the Qadence (Python) representation and translate it to Yao.jl (Julia). You will work with the Python/Julia interfacing library PythonCall.jl to realize this and benchmark the Julia backend in the end to assess if it is faster than the existing python implementation.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If this sounds interesting, talk to Niklas.","category":"page"},{"location":"projects/#Address-issues-in-markov-decision-processes-(Mentor:-Jan-Mrkos)","page":"Projects","title":"Address issues in markov decision processes (Mentor: Jan Mrkos)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Fix type stability issue in MCTS.jl, prepare benchmarks, and evaluate the impact of the changes. Details can be found in this issue. This project will require learnind a little bit about Markov Decision Processes if you don't know them already.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If it sounds interesting, get in touch with lecturer/lab assistant, who will connect you with Jan Mrkos.","category":"page"},{"location":"projects/#Extend-HMil-library-with-Retentative-networks-(mentor-Tomas-Pevny)","page":"Projects","title":"Extend HMil library with Retentative networks (mentor Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Retentative networks were recently proposed as a low-cost alternative to Transformer models without sacrificing performance (according to authors). By implementing Retentative Networks, te HMil library will be able to learn sequences (not just sets), which might nicely extend its applicability.","category":"page"},{"location":"projects/#Address-issues-in-HMil/JsonGrinder-library-(mentor-Simon-Mandlik)","page":"Projects","title":"Address issues in HMil/JsonGrinder library (mentor Simon Mandlik)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"These are open source toolboxes that are used internally in Avast. Lots of general functionality is done, but some love is needed in polishing.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"refactor the codebase using package extensions (e.g. for FillArrays)\nimprove compilation time (tracking down bottlenecks with SnoopCompile and using precompile directives from PrecompileTools.jl)","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Or study new metric learning approach on application in animation description","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"apply machine learning on slides within presentation provide by PowToon","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If it sounds interesting, get in touch with lecturer/lab assistant, who will connect you with Simon Mandlik.","category":"page"},{"location":"projects/#Project-requirements","page":"Projects","title":"Project requirements","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of the semestral project is to create a Julia pkg with reusable, properly tested and documented code. We have given you some options of topics, as well as the freedom to choose something that could be useful for your research or other subjects. In general we are looking for something where performance may be crucial such as data processing, optimization or equation solving.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"In practice the project should follow roughly this tree structure","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":".\n├── scripts\n│\t├── run_example.jl\t\t\t# one or more examples showing the capabilities of the pkg\n│\t├── Project.toml \t\t\t# YOUR_PROJECT should be added here with develop command with rel path\n│\t└── Manifest.toml \t\t\t# should be committed as it allows to reconstruct the environment exactly\n├── src\n│\t├── YOUR_PROJECT.jl \t\t# ideally only some top level code such as imports and exports, rest of the code included from other files\n│\t├── src1.jl \t\t\t\t# source files structured in some logical chunks\n│\t└── src2.jl\n├── test\n│\t├── runtest.jl # contains either all the tests or just includes them from other files\n│\t├── Project.toml \t\t\t# lists some additional test dependencies\n│\t└── Manifest.toml \t\t# usually not committed to git as it is generated on the fly\n├── README.md \t\t\t\t\t# describes in short what the pkg does and how to install pkg (e.g. some external deps) and run the example\n├── Project.toml \t\t\t\t# lists all the pkg dependencies\n└── Manifest.toml \t\t\t\t# usually not committed to git as the requirements may be to restrictive","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The first thing that we will look at is README.md, which should warn us if there are some special installation steps, that cannot be handled with Julia's Pkg system. For example if some 3rd party binary dependency with license is required. Secondly we will try to run tests in the test folder, which should run and not fail and should cover at least some functionality of the pkg. Thirdly and most importantly we will instantiate environment in scripts and test if the example runs correctly. Lastly we will focus on documentation in terms of code readability, docstrings and inline comments. ","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Only after all this we may look at the extent of the project and it's difficulty, which may help us in deciding between grades. ","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Nice to have things, which are not strictly required but obviously improves the score.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Ideally the project should be hosted on GitHub, which could have the continuous integration/testing set up.\nInclude some benchmark and profiling code in your examples, which can show us how well you have dealt with the question of performance.\nSome parallelization attempts either by multi-processing, multi-threadding, or CUDA. Do not forget to show the improvement.\nDocumentation with a webpage using Documenter.jl.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Here are some examples of how the project could look like:","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"ImageInspector","category":"page"},{"location":"lecture_01/outline/#Course-outline","page":"Outline","title":"Course outline","text":"","category":"section"},{"location":"lecture_01/outline/","page":"Outline","title":"Outline","text":"Introduction\nType system\nuser: tool for abstraction\ncompiler: tool for memory layout\nDesign patterns (mental setup)\nJulia is a type-based language\nmultiple-dispatch generalizes OOP and FP\nPackages\nway how to organize code\ncode reuse (alternative to libraries)\nexperiment reproducibility\nBenchmarking\nhow to measure code efficiency\nIntrospection\nunderstand how the compiler process the data\nMacros\nautomate writing of boring the boilerplate code\ngood macro create cleaner code\nAutomatic Differentiation\nTheory: difference between the forward and backward mode\nImplementation techniques\nIntermediate representation\nhow to use internal the representation of the code \nexample in automatic differentiation\nParallel computing\nthreads, processes\nGraphics card coding\ntypes for GPU\nspecifics of architectures\nOrdinary Differential Equations\nsimple solvers\nerror propagation\nData driven ODE\ncombine ODE with optimization\nautomatic differentiation (adjoints)","category":"page"},{"location":"lecture_03/hw/#Homework-3","page":"Homework","title":"Homework 3","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"In this homework we will implement a function find_food and practice the use of closures. The solution of lab 3 can be found here. You can use this file and add the code that you write for the homework to it.","category":"page"},{"location":"lecture_03/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Put all your code (including your or the provided solution of lab 2) in a script named hw.jl. Zip only this file (not its parent folder) and upload it to BRUTE.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_03\",\"Lab03Ecosystem.jl\"))\n\nfunction find_food(a::Animal, w::World)\n as = filter(x -> eats(a,x), w.agents |> values |> collect)\n isempty(as) ? nothing : rand(as)\nend\n\neats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\neats(::Animal{Wolf},::Animal{Sheep}) = true\neats(::Agent,::Agent) = false\n\nfunction every_nth(f::Function, n::Int)\n i = 1\n function callback(args...)\n # display(i) # comment this out to see out the counter increases\n if i == n\n f(args...)\n i = 1\n else\n i += 1\n end\n end\nend\n\nnothing # hide","category":"page"},{"location":"lecture_03/hw/#Agents-looking-for-food","page":"Homework","title":"Agents looking for food","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Implement a method find_food(a::Animal, w::World) returns one randomly chosen agent from all w.agents that can be eaten by a or nothing if no food could be found. This means that if e.g. the animal is a Wolf you have to return one random Sheep, etc.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Hint: You can write a general find_food method for all animals and move the parts that are specific to the concrete animal types to a separate function. E.g. you could define a function eats(::Animal{Wolf}, ::Animal{Sheep}) = true, etc.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"You can check your solution with the public test:","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"sheep = Sheep(1,pf=1.0)\nworld = World([Grass(2), sheep])\nfind_food(sheep, world) isa Plant{Grass}","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_03/hw/#Callbacks-and-Closures","page":"Homework","title":"Callbacks & Closures","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Implement a function every_nth(f::Function,n::Int) that takes an inner function f and uses a closure to construct an outer function g that only calls f every nth call to g. For example, if n=3 the inner function f be called at the 3rd, 6th, 9th ... call to g (not at the 1st, 2nd, 4th, 5th, 7th... call).","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Hint: You can use splatting via ... to pass on an unknown number of arguments from the outer to the inner function.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"You can use every_nth to log (or save) the agent count only every couple of steps of your simulation. Using every_nth will look like this:","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"w = World([Sheep(1), Grass(2), Wolf(3)])\n# `@info agent_count(w)` is executed only every 3rd call to logcb(w)\nlogcb = every_nth(w->(@info agent_count(w)), 3);\n\nlogcb(w); # x->(@info agent_count(w)) is not called\nlogcb(w); # x->(@info agent_count(w)) is not called\nlogcb(w); # x->(@info agent_count(w)) *is* called","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"","category":"page"},{"location":"lecture_01/lab/#Lab-01:-Introduction-to-Julia","page":"Lab","title":"Lab 01: Introduction to Julia","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This lab should get everyone up to speed in the basics of Julia's installation, syntax and basic coding. For more detailed introduction you can check out Lectures 1-3 of the bachelor course.","category":"page"},{"location":"lecture_01/lab/#Testing-Julia-installation-(custom-setup)","page":"Lab","title":"Testing Julia installation (custom setup)","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to proceed further let's run a simple script to see, that the setup described in chapter Installation is working properly. After spawning a terminal/cmdline run this command:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia ./test_setup.jl","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The script does the following ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"\"Tests\" if Julia is added to path and can be run with julia command from anywhere\nPrints Julia version info\nChecks Julia version.\nChecks git configuration (name + email)\nCreates an environment configuration files\nInstalls a basic pkg called BenchmarkTools, which we will use for benchmarking a simple function later in the labs.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"There are some quality of life improvements over long term support versions of Julia and thus throughout this course we will use the latest stable release of Julia 1.6.x.","category":"page"},{"location":"lecture_01/lab/#Polynomial-evaluation-example","page":"Lab","title":"Polynomial evaluation example","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let's consider a common mathematical example for evaluation of nth-degree polynomial","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"f(x) = a_nx^n + a_n-1x^n-1 + dots + a_0x^0","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"where x in mathbbR and veca in mathbbR^n+1.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way of writing this in a generic fashion is realizing that essentially the function f is really implicitly containing argument veca, i.e. f equiv f(veca x), yielding the following Julia code","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Evaluate the code of the function called polynomial in Julia REPL and evaluate the function itself with the following arguments.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6] # list coefficients a from a^0 to a^n\nx = 3 # point of evaluation\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way is to just copy&paste into an already running terminal manually. As opposed to the default Python REPL, Julia can deal with the blocks of code and different indentation much better without installation of an ipython-like REPL. There are ways to make this much easier in different text editors/IDEs:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"VSCode - when using Julia extension is installed and .jl file is opened, Ctrl/Cmd+Enter will spawn Julia REPL\nSublime Text - Ctrl/Cmd+Enter with Send Code pkg (works well with Linux terminal or tmux, support for Windows is poor)\nVim - there is a Julia language plugin, which can be combine with vimcmdline to gain similar functionality","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Either way, you should see the following:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Similarly we enter the arguments of the function a and x:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6]\nx = 3","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Function call intuitively takes the name of the function with round brackets as arguments, i.e. works in the same way as majority of programming languages. The result is printed unless a ; is added at the end of the statement.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) # function call","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Thanks to the high level nature of Julia language it is often the case that examples written in pseudocode are almost directly rewritable into the language itself without major changes and the code can be thus interpreted easily.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"(Image: polynomial_explained)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Due to the existence of the end keyword, indentation is not necessary as opposed to other languages such as Python, however it is strongly recommended to use it, see style guide.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Though there are libraries/IDEs that allow us to step through Julia code (Debugger.jl link and VSCode link), here we will explore the code interactively in REPL by evaluating pieces of code separately.","category":"page"},{"location":"lecture_01/lab/#Basic-types,-assignments-and-variables","page":"Lab","title":"Basic types, assignments and variables","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"When defining a variable through an assignment we get the representation of the right side, again this is different from the default behavior in Python, where the output of assignments a = [-19, 7, -4, 6] or x = 3, prints nothing. Internally Julia returns the result of the display function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6]\ndisplay(a) # should return the same thing as the line above","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As you can see, the string that is being displayed contains information about the contents of a variable along with it's type in this case this is a Vector/Array of Int types. If the output of display is insufficient the type of variable can be checked with the typeof function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(a)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Additionally for collection/iterable types such as Vector there is also the eltype function, which returns the type of elements in the collection.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"eltype(a)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In most cases variables store just a reference to a place in memory either stack/heap (exceptions are primitive types such as Int, Float) and therefore creating an array a, \"storing\" the reference in b with an assignment and changing elements of b, e.g. b[1] = 2, changes also the values in a.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Create variables x and accumulator, storing floating point 3.0 and integer value 0 respectively. Check the type of variables using typeof function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"x = 3.0\naccumulator = 0\ntypeof(x), typeof(accumulator)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#For-cycles-and-ranges","page":"Lab","title":"For cycles and ranges","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Moving further into the polynomial function we encounter the definition of a for cycle, with the de facto standard syntax","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"for iteration_variable in iterator\n # do something\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As an example of iterator we have used an instance of a range type ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"r = length(a):-1:1\ntypeof(r)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to Python, ranges in Julia are inclusive, i.e. they contain number from start to end - in this case running from 4 to 1 with negative step -1, thus counting down. This can be checked with the collect and/or length functions.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"collect(r)\nlength(r)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Create variable c containing an array of even numbers from 2 to 42. Furthermore create variable d that is different from c only at the 7th position, which will contain 13.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINT: Use collect function for creation of c and copy for making a copy of c.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"c = collect(2:2:42)\nd = copy(c)\nd[7] = 13\nd","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Functions-and-operators","page":"Lab","title":"Functions and operators","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let us now move from the function body to the function definition itself. From the picture at the top of the page, we can infer the general syntax for function definition:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function function_name(arguments)\n # do stuff with arguments and define output value `something`\n return something\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The return keyword can be omitted, if the last line being evaluated contains the result.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"By creating the function polynomial we have defined a variable polynomial, that from now on always refers to a function and cannot be reassigned to a different type, like for example Int.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial = 42","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This is caused by the fact that each function defines essentially a new type, the same like Int ~ Int64 or Vector{Int}.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(polynomial)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"You can check that it is a subtype of the Function abstract type, with the subtyping operator <:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(polynomial) <: Function","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"These concepts will be expanded further in the type system lecture, however for now note that this construction is quite useful for example if we wanted to create derivative rules for our function derivativeof(::typeof(polynomial), ...).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Looking at mathematical operators +, *, we can see that in Julia they are also standalone functions. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"+\n*","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The main difference from our polynomial function is that there are multiple methods, for each of these functions. Each one of the methods coresponds to a specific combination of arguments, for which the function can be specialized to using multiple dispatch. You can see the list by calling a methods function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> methods(+)\n# 190 methods for generic function \"+\": \n[1] +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at\n int.jl:87 \n[2] +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigInt) in Base.GMP at gmp.jl:528 \n[3] +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:534\n...","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"One other notable difference is that these functions allow using both infix and postfix notation a + b and +(a,b), which is a specialty of elementary functions such as arithmetic operators or set operation such as ∩, ∪, ∈. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The functionality of methods is complemented with the reverse lookup methodswith, which for a given type returns a list of methods that can be called with it as an argument.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> methodswith(Int)\n[1] +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87\n[2] +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:534\n[3] +(c::Union{Int16, Int32, Int64, Int8}, x::BigFloat) in Base.MPFR at mpfr.jl:384\n[4] +(x::BigFloat, c::Union{Int16, Int32, Int64, Int8}) in Base.MPFR at mpfr.jl:379\n[5] +(x::BigInt, c::Union{Int16, Int32, Int64, Int8}) in Base.GMP at gmp.jl:533\n...","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Define function called addone with one argument, that adds 1 to the argument.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function addone(x)\n x + 1\nend\naddone(1) == 2","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Calling-for-help","page":"Lab","title":"Calling for help","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to better understand some keywords we have encountered so far, we can ask for help in the Julia's REPL itself with the built-in help terminal. Accessing help terminal can be achieved by writing ? with a query keyword after. This searches documentation of all the available source code to find the corresponding keyword. The simplest way to create documentation, that can be accessed in this way, is using so called docstrings, which are multiline strings written above function or type definition. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"\"\"\"\n polynomial(a, x)\n\nReturns value of a polynomial with coefficients `a` at point `x`.\n\"\"\"\nfunction polynomial(a, x)\n # function body\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"More on this in lecture 4 about pkg development.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Lookup docstring for the basic functions that we have introduced in the previous exercises: typeof, eltype, length, collect, copy, methods and methodswith. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Try it with others, for example with the subtyping operator <:.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Example docstring for typeof function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":" typeof(x)\n\n Get the concrete type of x.\n\n Examples\n ≡≡≡≡≡≡≡≡≡≡\n\n julia> a = 1//2;\n \n julia> typeof(a)\n Rational{Int64}\n \n julia> M = [1 2; 3.5 4];\n \n julia> typeof(M)\n Matrix{Float64} (alias for Array{Float64, 2})","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Testing-waters","page":"Lab","title":"Testing waters","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As the arguments of the polynomial functions are untyped, i.e. they do not specify the allowed types like for example polynomial(a, x::Number) does, the following exercise explores which arguments the function accepts, while giving expected result.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Choose one of the variables af to ac representing polynomial coefficients and try to evaluate it with the polynomial function at point x=3 as before. Lookup the type of coefficient collection variable itself with typeof and the items in the collection with eltype. In this case we allow you to consult your solution with the expandable solution bellow to find out more information about a particular example.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"af = [-19.0, 7.0, -4.0, 6.0]\nat = (-19, 7, -4, 6)\nant = (a₀ = -19, a₁ = 7, a₂ = -4, a₃ = 6)\na2d = [-19 -4; 7 6]\nac = [2i^2 + 1 for i in -2:1]\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(af), eltype(af)\npolynomial(af, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to the basic definition of a type the array is filled with Float64 types and the resulting value gets promoted as well to the Float64.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(at), eltype(at)\npolynomial(at, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"With round brackets over a fixed length vector we get the Tuple type, which is so called immutable \"array\" of a fixed size (its elements cannot be changed, unless initialized from scratch). Each element can be of a different type, but here we have only one and thus the Tuple is aliased into NTuple. There are some performance benefits for using immutable structure, which will be discussed later.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Defining key=value pairs inside round brackets creates a structure called NamedTuple, which has the same properties as Tuple and furthermore its elements can be conveniently accessed by dot syntax, e.g. ant.a₀.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ant), eltype(ant)\npolynomial(ant, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Defining a 2D array is a simple change of syntax, which initialized a matrix row by row separated by ; with spaces between individual elements. The function returns the same result because linear indexing works in 2d arrays in the column major order.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(a2d), eltype(a2d)\npolynomial(a2d, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The last example shows so called array comprehension syntax, where we define and array of known length using and for loop iteration. Resulting array/vector has integer elements, however even mixed type is possible yielding Any, if there isn't any other common supertype to promote every entry into. (Use ? to look what promote and promote_type does.)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ac), eltype(ac)\npolynomial(ac, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"So far we have seen that polynomial function accepts a wide variety of arguments, however there are some understandable edge cases that it cannot handle.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Consider first the vector/array of characters ach","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ach = ['1', '2', '3', '4']","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"which themselves have numeric values (you can check by converting them to Int Int('1') or convert(Int, '1')). In spite of that, our untyped function cannot process such input, as there isn't an operation/method that would allow multiplication of Char and Int type. Julia tries to promote the argument types to some common type, however checking the promote_type(Int, Char) returns Any (union of all types), which tells us that the conversion is not possible automatically.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ach), eltype(ach)\npolynomial(ach, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In the stacktrace we can see the location of each function call. If we include the function polynomial from some file poly.jl using include(\"poly.jl\"), we will see that the location changes from REPL[X]:10 to the actual file name.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"By swapping square brackets for round in the array comprehension ac above, we have defined so called generator/iterator, which as opposed to original variable ac does not allocate an array, only the structure that produces it.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ag = (2i^2 + 1 for i in -2:1)\ntypeof(ag), eltype(ag)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"You may notice that the element type in this case is Any, which means that a function using this generator as an argument cannot specialize based on the type and has to infer it every time an element is generated/returned. We will touch on how this affects performance in one of the later lectures.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(ag, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The problem that we face during evaluation is that generator type is missing the getindex operation, as they are made for situations where the size of the collection may be unknown and the only way of obtaining particular elements is through sequential iteration. Generators can be useful for example when creating batches of data for a machine learning training. We can \"fix\" the situation using collect function, mentioned earlier, however that again allocates an array.","category":"page"},{"location":"lecture_01/lab/#Extending/limiting-the-polynomial-example","page":"Lab","title":"Extending/limiting the polynomial example","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Following up on the polynomial example, let's us expand it a little further in order to facilitate the arguments, that have been throwing exceptions. The first direction, which we will move forward to, is providing the user with more detailed error message when an incorrect type of coefficients has been provided.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Design an if-else condition such that the array of Char example throws an error with custom string message, telling the user what went wrong and printing the incorrect input alongside it. Confirm that we have not broken the functionality of other examples from previous exercise.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Throw the ArgumentError(msg) with throw function and string message msg. More details in help mode ? or at the end of this document.\nStrings are defined like this s = \"Hello!\"\nUse string interpolation to create the error message. It allows injecting an expression into a string with the $ syntax b = 1; s = \"Hellow Number $(b)\"\nCompare eltype of the coefficients with Char type.\nThe syntax for if-else:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"if condition\n println(\"true\") # true branch code\nelse\n println(\"false\") # false branch code\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Not equal condition can be written as a != b.\nThrowing an exception automatically returns from the function. Use return inside one of the branches to return the correct value.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way is to wrap the whole function inside an if-else condition and returning only when the input is \"correct\" (it will still fail in some cases).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n if eltype(a) != Char\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\n else\n throw(ArgumentError(\"Invalid coefficients $(a) of type Char!\"))\n end\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Now this should show our predefined error message. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(ach, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Testing on other examples should pass without errors and give the same output as before.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x)\npolynomial(af, x)\npolynomial(at, x)\npolynomial(ant, x)\npolynomial(a2d, x)\npolynomial(ac, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The second direction concerns the limitation to index-able structures, which the generator example is not. For this we will have to rewrite the whole loop in a more functional programming approach using map, anonymous function and other concepts.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Rewrite the following code inside our original polynomial function with map, enumerate and anonymous function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"accumulator = 0\nfor i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"note: Anonymous functions reminder\nx -> x + 1 # unless the reference is stored it cannot be called\nplusone = x -> x + 1 # the reference can be stored inside a variable\nplusone(x) # calling with the same syntax","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Use enumerate to obtain iterator over a that returns a tuple of ia = (i, aᵢ). With Julia 1-based indexing i starts also from 1 and goes up to length(a).\nPass this into a map with either in-place or predefined anonymous function that does the operation of x^(i-1) * aᵢ.\nUse sum to collect the resulting array into accumulator variable or directly into the return command.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Can you figure out how to use the mapreduce function here? See entry in the help mode ?.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Ordered from the longest to the shortest, here are three examples with the same functionality (and there are definitely many more). Using the map(iterable) do itervar ... end syntax, that creates anonymous function from the block of code.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n powers = map(enumerate(a)) do (i, aᵢ)\n x^(i-1) * aᵢ\n end\n accumulator = sum(powers)\n return accumulator\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Using the default syntax for map and storing the anonymous into a variable","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n polypow(i,aᵢ) = x^(i-1) * aᵢ\n powers = map(polypow, enumerate(a))\n return sum(powers)\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As the function polypow is used only once, there is no need to assign it to a local variable. Note the sightly awkward additional parenthesis in the argument of the lambda function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n powers = map(((i,aᵢ),) -> x^(i-1) * aᵢ, enumerate(a))\n sum(powers)\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Checking the behavior on all the inputs.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x)\npolynomial(af, x)\npolynomial(at, x)\npolynomial(ant, x)\npolynomial(a2d, x)\npolynomial(ach, x)\npolynomial(ac, x)\npolynomial(ag, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: You may have noticed that in the example above, the powers variable is allocating an additional, unnecessary vector. With the current, scalar x, this is not such a big deal. But in your homework you will generalize this function to matrix inputs of x, which means that powers becomes a vector of (potentially very large) matrices. This is a very natural use case for the mapreduce: function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) = mapreduce(+, enumerate(a), init=zero(x)) do (i, aᵢ)\n x^(i-1) * aᵢ\nend\n\npolynomial(a, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let's unpack what is happening here. If the function mapreduce(f, op, itr) is called with op=+ it returns the same result as sum(map(f, itr)). In contrast to sum(map(f, itr)) (which allocates a vector as a result of map and then sums) mapreduce applies f to an element in itr and immediately accumulates the result with the given op=+.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) = sum(ia -> x^(ia[1]-1) * ia[2], enumerate(a))\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#How-to-use-code-from-other-people","page":"Lab","title":"How to use code from other people","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The script that we have run at the beginning of this lab has created two new files inside the current folder:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"./\n ├── Manifest.toml\n └── Project.toml","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Every folder with a toml file called Project.toml, can be used by Julia's pkg manager into setting so called environment, which contains a list of pkgs to be installed. Setting up or more often called activating an environment can be done either before starting Julia itself by running julia with the --project XXX flag or from within the Julia REPL, by switching to Pkg mode with ] key (similar to the help mode activated by pressing ?) and running command activate.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"So far we have used the general environment (depending on your setup), which by default does not come with any 3rd party packages and includes only the base and standard libraries - already quite powerful on its own. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to find which environment is currently active, run the following:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"pkg> status","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The output of such command usually indicates the general environment located at .julia/ folder (${HOME}/.julia/ or ${APPDATA}/.julia/ in case of Unix/Windows based systems respectively)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"pkg> status\nStatus `~/.julia/environments/v1.6/Project.toml` (empty project)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Generally one should avoid working in the general environment, with the exception of some generic pkgs, such as PkgTemplates.jl, which is used for generating library templates/folder structure like the one above (link), more on this in the lecture on pkg development. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Activate the environment inside the current folder and check that the BenchmarkTools package has been installed. Use BenchmarkTools pkg's @btime to benchmark our polynomial function with the following arguments.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"aexp = ones(10) ./ factorial.(0:9)\nx = 1.1\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In pkg mode use the command activate and status to check the presence. \nIn order to import the functionality from other package, lookup the keyword using in the repl help mode ?. \nThe functionality that we want to use is the @btime macro (it acts almost like a function but with a different syntax @macro arg1 arg2 arg3 ...). More on macros in lecture 7.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Compare the output of polynomial(aexp, x) with the value of exp(x), which it approximates.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"note: Broadcasting\nIn the assignment's code, we are using quite ubiquitous concept in Julia called broadcasting or simply the dot-syntax - represented here by ./, factorial.. This concept allows to map both simple arithmetic operations as well as custom functions across arrays, with the added benefit of increased performance, when the broadcasting system can merge operations into a more efficient code. More information can be found in the official documentation or section of our bachelor course.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"There are other options to import a function/macro from a different package, however for now let's keep it simple with the using Module syntax, that brings to the REPL, all the variables/function/macros exported by the BenchmarkTools pkg. If @btime is exported, which it is, it can be accessed without specification i.e. just by calling @btime without the need for BenchmarkTools.@btime. More on the architecture of pkg/module loading in the package developement lecture.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> using BenchmarkTools\n\njulia> @btime polynomial(aexp, x)\n 97.119 ns (1 allocation: 16 bytes)\n3.004165230550543","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The output gives us the time of execution averaged over multiple runs (the number of samples is defined automatically based on run time) as well as the number of allocations and the output of the function, that is being benchmarked.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: The difference between our approximation and the \"actual\" function value computed as a difference of the two. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(aexp, x) - exp(x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The apostrophes in the previous sentence are on purpose, because implementation of exp also relies on a finite sum, though much more sophisticated than the basic Taylor expansion.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Discussion-and-future-directions","page":"Lab","title":"Discussion & future directions","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Instead of if-else statements that would throw an error for different types, in Julia, we generally see the pattern of typing the function in a way, that for other than desirable types MethodError is emitted with the information about closest matching methods. This is part of the design process in Julia of a function and for the particular functionality of the polynomial example, we can look into the Julia itself, where it has been implemented in the evalpoly function","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"methods(evalpoly)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Another avenue, that we have only touched with the BenchmarkTools, is performance and will be further explored in the later lectures.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"With the next lecture focused on typing in Julia, it is worth noting that polynomials lend themselves quite nicely to a definition of a custom type, which can help both readability of the code as well further extensions.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"struct Polynom{C}\n coefficients::{C}\nend\n\nfunction (p:Polynom)(x)\n polynomial(p.coefficients, x)\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"","category":"page"},{"location":"lecture_01/lab/#Useful-resources","page":"Lab","title":"Useful resources","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Getting Started tutorial from JuliaLang documentation - Docs\nConverting syntax between MATLAB ↔ Python ↔ Julia - Cheatsheet\nBachelor course for refreshing your knowledge - Course\nStylistic conventions - Style Guide\nReserved keywords - List\nOfficial cheatsheet with basic syntax - link","category":"page"},{"location":"lecture_01/lab/#lab_errors","page":"Lab","title":"Various errors and how to read them","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This section summarizes most commonly encountered types of errors in Julia and how to resolve them or at least understand, what has gone wrong. It expands a little bit the official documentation, which contains the complete list with examples. Keep in mind again, that you can use help mode in the REPL to query error types as well.","category":"page"},{"location":"lecture_01/lab/#MethodError","page":"Lab","title":"MethodError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This type of error is most commonly thrown by Julia's multiple dispatch system with a message like no method matching X(args...), seen in two examples bellow.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"2 * 'a' # many candidates\ngetindex((i for i in 1:4), 3) # no candidates","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Both of these examples have a short stacktrace, showing that the execution failed on the top most level in REPL, however if this code is a part of some function in a separate file, the stacktrace will reflect it. What this error tells us is that the dispatch system could not find a method for a given function, that would be suitable for the type of arguments, that it has been given. In the first case Julia offers also a list of candidate methods, that match at least some of the arguments","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"When dealing with basic Julia functions and types, this behavior can be treated as something given and though one could locally add a method for example for multiplication of Char and Int, there is usually a good reason why Julia does not support such functionality by default. On the other hand when dealing with user defined code, this error may suggest the developer, that either the functions are too strictly typed or that another method definition is needed in order to satisfy the desired functionality.","category":"page"},{"location":"lecture_01/lab/#InexactError","page":"Lab","title":"InexactError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This type of error is most commonly thrown by the type conversion system (centered around convert function), informing the user that it cannot exactly convert a value of some type to match arguments of a function being called.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Int(1.2) # root cause\nappend!([1,2,3], 1.2) # same as above but shows the root cause deeper in the stack trace","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In this case the function being Int and the value a floating point. The second example shows InexactError may be caused deeper inside an inconspicuous function call, where we want to extend an array by another value, which is unfortunately incompatible.","category":"page"},{"location":"lecture_01/lab/#ArgumentError","page":"Lab","title":"ArgumentError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to the previous two errors, ArgumentError can contain user specified error message and thus can serve multiple purposes. It is however recommended to throw this type of error, when the parameters to a function call do not match a valid signature, e.g. when factorial were given negative or non-integer argument (note that this is being handled in Julia by multiple dispatch and specific DomainError).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This example shows a concatenation of two 2d arrays of incompatible sizes 3x3 and 2x2.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"hcat(ones(3,3), zeros(2,2))","category":"page"},{"location":"lecture_01/lab/#KeyError","page":"Lab","title":"KeyError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This error is specific to hash table based objects such as the Dict type and tells the user that and indexing operation into such structure tried to access or delete a non-existent element.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"d = Dict(:a => [1,2,3], :b => [1,23])\nd[:c]","category":"page"},{"location":"lecture_01/lab/#TypeError","page":"Lab","title":"TypeError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Type assertion failure, or calling an intrinsic function (inside LLVM, where code is strictly typed) with incorrect argument type. In practice this error comes up most often when comparing value of a type against the Bool type as seen in the example bellow.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"if 1 end # calls internally typeassert(1, Bool)\ntypeassert(1, Bool)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to compare inside conditional statements such as if-elseif-else or the ternary operator x ? a : b the condition has to be always of Bool type, thus the example above can be fixed by the comparison operator: if 1 == 1 end (in reality either the left or the right side of the expression contains an expression or a variable to compare against).","category":"page"},{"location":"lecture_01/lab/#UndefVarError","page":"Lab","title":"UndefVarError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"While this error is quite self-explanatory, the exact causes are often quite puzzling for the user. The reason behind the confusion is to do with code scoping, which comes into play for example when trying to access a local variable from outside of a given function or just updating a global variable from within a simple loop. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In the first example we show the former case, where variable is declared from within a function and accessed from outside afterwards.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function plusone(x)\n uno = 1\n return x + uno\nend\nuno # defined only within plusone","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Unless there is variable I_am_not_defined in the global scope, the following should throw an error.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"I_am_not_defined","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Often these kind of errors arise as a result of bad code practices, such as long running sessions of Julia having long forgotten global variables, that do not exist upon new execution (this one in particular has been addressed by the authors of the reactive Julia notebooks Pluto.jl).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"For more details on code scoping we recommend particular places in the bachelor course lectures here and there.","category":"page"},{"location":"lecture_01/lab/#ErrorException-and-error-function","page":"Lab","title":"ErrorException & error function","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ErrorException is the most generic error, which can be thrown/raised just by calling the error function with a chosen string message. As a result developers may be inclined to misuse this for any kind of unexpected behavior a user can run into, often providing out-of-context/uninformative messages.","category":"page"},{"location":"lecture_01/demo/#Extensibility-of-the-language","page":"Examples","title":"Extensibility of the language","text":"","category":"section"},{"location":"lecture_01/demo/#DifferentialEquations","page":"Examples","title":"DifferentialEquations","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"A package for solving differential equations, similar to odesolve in Matlab.","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Example:","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using DifferentialEquations\nfunction lotka_volterra(du,u,p,t)\n x, y = u\n α, β, δ, γ = p\n du[1] = dx = α*x - β*x*y\n du[2] = dy = -δ*y + γ*x*y\nend\nu0 = [1.0,1.0]\ntspan = (0.0,10.0)\np = [1.5,1.0,3.0,1.0]\nprob = ODEProblem(lotka_volterra,u0,tspan,p)\n\nsol = solve(prob)\nusing Plots\nplot(sol)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"(Image: )","category":"page"},{"location":"lecture_01/demo/#Measurements","page":"Examples","title":"Measurements","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"A package defining \"numbers with precision\" and complete algebra on these numbers:","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using Measurements\n\na = 4.5 ± 0.1\nb = 3.8 ± 0.4\n\n2a + b\nsin(a)/cos(a) - tan(a)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"It also defines recipes for Plots.jl how to plot such numbers.","category":"page"},{"location":"lecture_01/demo/#Starting-ODE-from-an-interval","page":"Examples","title":"Starting ODE from an interval","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using Measurements\nu0 = [1.0±0.1,1.0±0.01]\n\nprob = ODEProblem(lotka_volterra,u0,tspan,p)\nsol = solve(prob)\nplot(sol,denseplot=false)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"(Image: )","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"all algebraic operations are defined, \npasses all grid refinement techniques\nplot uses the correct plotting for intervals","category":"page"},{"location":"lecture_01/demo/#Integration-with-other-toolkits","page":"Examples","title":"Integration with other toolkits","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Flux: toolkit for modelling Neural Networks. Neural network is a function.","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"integration with Measurements,\nIntegration with ODE (think of NN as part of the ODE)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Turing: Probabilistic modelling toolkit","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"integration with FLux (NN)\ninteration with ODE\nusing arbitrary bijective transformations, Bijectors.jl","category":"page"},{"location":"lecture_02/lab/#lab02","page":"Lab","title":"Lab 2: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"In the next labs you will implement your own predator-prey model. The model will contain wolves, sheep, and - to feed your sheep - some grass. The final simulation will be turn-based and the agents will be able to eat each other, reproduce, and die in every iteration. At every iteration of the simulation each agent will step forward in time via the agent_step! function. The steps for the agent_step! methods of animals and plants are written below in pseudocode.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"# for animals:\nagent_step!(animal, world)\n decrement energy by 1\n find & eat food (with probability pf)\n die if no more energy\n reproduce (with probability pr)\n\n# for plants:\nagent_step!(plant, world)\n grow if not at maximum size","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The world in which the agents live will be the simplest possible world with zero dimensions (i.e. a Dict of ID=>Agent). Running and plotting your final result could look something like the plot below.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"(Image: img)","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We will start implementing the basic functionality for each Agent like eat!ing, reproduce!ing, and a very simplistic World for your agents to live in. In the next lab you will refine both the type hierarchy of your Agents, as well as the design of the World in order to leverage the power of Julia's type system and compiler.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We start with a very basic type hierarchy:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"abstract type Agent end\nabstract type Animal <: Agent end\nabstract type Plant <: Agent end","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We will implement the World for our Agents later, but it will essentially be implemented by a Dict which maps unique IDs to an Agent. Hence, every agent will need an ID.","category":"page"},{"location":"lecture_02/lab/#The-Grass-Agent","page":"Lab","title":"The Grass Agent","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Let's start by implementing some Grass which will later be able to grow during each iteration of our simulation.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Define a mutable struct called Grass which is a subtype of Plant has the fields id (the unique identifier of this Agent - every agent needs one!), size (the current size of the Grass), and max_size. All fields should be integers.\nDefine a constructor for Grass which, given only an ID and a maximum size m, will create an instance of Grass that has a randomly initialized size in the range 1m. It should also be possible to create Grass, just with an ID and a default max_size of 10.\nImplement Base.show(io::IO, g::Grass) to get custom printing of your Grass such that the Grass is displayed with its size in percent of its max_size.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Hint: You can implement a custom show method for a new type MyType like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"struct MyType\n x::Bool\nend\nBase.show(io::IO, a::MyType) = print(io, \"MyType $(a.x)\")","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Since Julia 1.8 we can also declare some fields of mutable structs as const, which can be used both to prevent us from mutating immutable fields (such as the ID) but can also be used by the compiler in certain cases.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Grass <: Plant\n const id::Int\n size::Int\n const max_size::Int\nend\n\nGrass(id,m=10) = Grass(id, rand(1:m), m)\n\nfunction Base.show(io::IO, g::Grass)\n x = g.size/g.max_size * 100\n # hint: to type the leaf in the julia REPL you can do:\n # \\:herb:\n print(io,\"🌿 #$(g.id) $(round(Int,x))% grown\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Creating a few Grass agents can then look like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Grass(1,5)\ng = Grass(2)\ng.id = 5","category":"page"},{"location":"lecture_02/lab/#Sheep-and-Wolf-Agents","page":"Lab","title":"Sheep and Wolf Agents","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Animals are slightly different from plants. They will have an energy E, which will be increase (or decrease) if the agent eats (or reproduces) by a certain amount Delta E. Later we will also need a probability to find food p_f and a probability to reproduce p_r.c","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Define two mutable structs Sheep and Wolf that are subtypes of Animal and have the fields id, energy, Δenergy, reprprob, and foodprob.\nDefine constructors with the following default values:\nFor 🐑: E=4, Delta E=02, p_r=08, and p_f=06.\nFor 🐺: E=10, Delta E=8, p_r=01, and p_f=02.\nOverload Base.show to get pretty printing for your two new animals.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Solution for Sheep","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Sheep <: Animal\n const id::Int\n const energy::Float64\n Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\nend\n\nSheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6) = Sheep(id,e,Δe,pr,pf)\n\nfunction Base.show(io::IO, s::Sheep)\n e = s.energy\n d = s.Δenergy\n pr = s.reprprob\n pf = s.foodprob\n print(io,\"🐑 #$(s.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Solution for Wolf:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Wolf <: Animal\n const id::Int\n energy::Float64\n const Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\nend\n\nWolf(id, e=10.0, Δe=8.0, pr=0.1, pf=0.2) = Wolf(id,e,Δe,pr,pf)\n\nfunction Base.show(io::IO, w::Wolf)\n e = w.energy\n d = w.Δenergy\n pr = w.reprprob\n pf = w.foodprob\n print(io,\"🐺 #$(w.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Sheep(4)\nWolf(5)","category":"page"},{"location":"lecture_02/lab/#The-World","page":"Lab","title":"The World","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Before our agents can eat or reproduce we need to build them a World. The simplest (and as you will later see, somewhat suboptimal) world is essentially a Dict from IDs to agents. Later we will also need the maximum ID, lets define a world with two fields:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct World{A<:Agent}\n agents::Dict{Int,A}\n max_id::Int\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Implement a constructor for the World which accepts a vector of Agents.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function World(agents::Vector{<:Agent})\n max_id = maximum(a.id for a in agents)\n World(Dict(a.id=>a for a in agents), max_id)\nend\n\n# optional: overload Base.show\nfunction Base.show(io::IO, w::World)\n println(io, typeof(w))\n for (_,a) in w.agents\n println(io,\" $a\")\n end\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/#Sheep-eats-Grass","page":"Lab","title":"Sheep eats Grass","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We can implement the behaviour of our various agents with respect to each other by leveraging Julia's multiple dispatch.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Implement a function eat!(::Sheep, ::Grass, ::World) which increases the sheep's energy by Delta E multiplied by the size of the grass.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"After the sheep's energy is updated the grass is eaten and its size counter has to be set to zero.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Note that you do not yet need the world in this function. It is needed later for the case of wolves eating sheep.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function eat!(sheep::Sheep, grass::Grass, w::World)\n sheep.energy += grass.size * sheep.Δenergy\n grass.size = 0\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Below you can see how a fully grown grass is eaten by a sheep. The sheep's energy changes size of the grass is set to zero.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"grass = Grass(1)\nsheep = Sheep(2)\nworld = World([grass, sheep])\neat!(sheep,grass,world);\nworld","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Note that the order of the arguments has a meaning here. Calling eat!(grass,sheep,world) results in a MethodError which is great, because Grass cannot eat Sheep.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"eat!(grass,sheep,world);","category":"page"},{"location":"lecture_02/lab/#Wolf-eats-Sheep","page":"Lab","title":"Wolf eats Sheep","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The eat! method for wolves increases the wolf's energy by sheep.energy * wolf.Δenergy and kills the sheep (i.e. removes the sheep from the world). There are other situationsin which agents die , so it makes sense to implement another function kill_agent!(::Animal,::World).","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Hint: You can use delete! to remove agents from the dictionary in your world.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function eat!(wolf::Wolf, sheep::Sheep, w::World)\n wolf.energy += sheep.energy * wolf.Δenergy\n kill_agent!(sheep,w)\nend\n\nkill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"With a correct eat! method you should get results like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"grass = Grass(1);\nsheep = Sheep(2);\nwolf = Wolf(3);\nworld = World([grass, sheep, wolf])\neat!(wolf,sheep,world);\nworld","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The sheep is removed from the world and the wolf's energy increased by Delta E.","category":"page"},{"location":"lecture_02/lab/#Reproduction","page":"Lab","title":"Reproduction","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Currently our animals can only eat. In our simulation we also want them to reproduce. We will do this by adding a reproduce! method to Animal.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Write a function reproduce! that takes an Animal and a World. Reproducing will cost an animal half of its energy and then add an almost identical copy of the given animal to the world. The only thing that is different from parent to child is the ID. You can simply increase the max_id of the world by one and use that as the new ID for the child.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function reproduce!(a::Animal, w::World)\n a.energy = a.energy/2\n new_id = w.max_id + 1\n â = deepcopy(a)\n â.id = new_id\n w.agents[â.id] = â\n w.max_id = new_id\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"You can avoid mutating the id field (which could be considered bad practice) by reconstructing the child from scratch:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function reproduce!(a::A, w::World) where A<:Animal\n a.energy = a.energy/2\n a_vals = [getproperty(a,n) for n in fieldnames(A) if n!=:id]\n new_id = w.max_id + 1\n â = A(new_id, a_vals...)\n w.agents[â.id] = â\n w.max_id = new_id\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"s1, s2 = Sheep(1), Sheep(2)\nw = World([s1, s2])\nreproduce!(s1, w);\nw","category":"page"},{"location":"","page":"Home","title":"Home","text":"\"Scientific\n\"Scientific","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Plots\nENV[\"GKSwstype\"] = \"100\"\ngr()","category":"page"},{"location":"","page":"Home","title":"Home","text":"Scientific Programming requires the highest performance but we also want to write very high level code to enable rapid prototyping and avoid error prone, low level implementations.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The Julia programming language is designed with exactly those requirements of scientific computing in mind. In this course we will show you how to make use of the tools and advantages that jit-compiled Julia provides over dynamic, high-level languages like Python or lower level languages like C++.","category":"page"},{"location":"","page":"Home","title":"Home","text":"
      \n \n
      \n Learn the power of abstraction.\n Example: The essence of forward mode automatic differentiation.\n
      \n
      ","category":"page"},{"location":"","page":"Home","title":"Home","text":"Before joining the course, consider reading the following two blog posts to figure out if Julia is a language in which you want to invest your time.","category":"page"},{"location":"","page":"Home","title":"Home","text":"What is great about Julia.\nWhat is bad about Julia.","category":"page"},{"location":"#What-will-you-learn?","page":"Home","title":"What will you learn?","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"First and foremost you will learn how to think julia - meaning how write fast, extensible, reusable, and easy-to-read code using things like optional typing, multiple dispatch, and functional programming concepts. The later part of the course will teach you how to use more advanced concepts like language introspection, metaprogramming, and symbolic computing. Amonst others you will implement your own automatic differetiation (the backbone of modern machine learning) package based on these advanced techniques that can transform intermediate representations of Julia code.","category":"page"},{"location":"#Organization","page":"Home","title":"Organization","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This course webpage contains all information about the course that you need, including lecture notes, lab instructions, and homeworks. The official format of the course is 2+2 (2h lectures/2h labs per week) for 4 credits.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The official course code is: B0M36SPJ and the timetable for the winter semester 2022 can be found here.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The course will be graded based on points from your homework (max. 20 points) and points from a final project (max. 30 points).","category":"page"},{"location":"","page":"Home","title":"Home","text":"Below is a table that shows which lectures have homeworks (and their points).","category":"page"},{"location":"","page":"Home","title":"Home","text":"Homework 1 2 3 4 5 6 7 8 9 10 11 12 13\nPoints 2 2 2 2 2 2 2 2 - 2 - 2 -","category":"page"},{"location":"","page":"Home","title":"Home","text":"Hint: The first few homeworks are easier. Use them to fill up your points.","category":"page"},{"location":"#final_project","page":"Home","title":"Final project","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The final project will be individually agreed on for each student. Ideally you can use this project to solve a problem you have e.g. in your thesis, but don't worry - if you cannot come up with an own project idea, we will suggest one to you. More info and project suggestion can be found here.","category":"page"},{"location":"#Grading","page":"Home","title":"Grading","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Your points from the homeworks and the final project are summed and graded by the standard grading scale below.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Grade A B C D E F\nPoints 45-50 40-44 35-39 30-34 25-29 0-25","category":"page"},{"location":"#emails","page":"Home","title":"Teachers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"– E-mail Room Role\nTomáš Pevný pevnak@protonmail.ch KN:E-406 Lecturer\nVašek Šmídl smidlva1@fjfi.cvut.cz KN:E-333 Lecturer\nMatěj Zorek zorekmat@fel.cvut.cz KN:E-333 Lab Instructor\nNiklas Heim heimnikl@fel.cvut.cz KN:E-333 Lab Instructor","category":"page"},{"location":"#Prerequisites","page":"Home","title":"Prerequisites","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"There are no hard requirements to take the course, but if you are not at all familiar with Julia we recommend you to take Julia for Optimization and Learning before enrolling in this course. The Functional Programming course also contains some helpful concepts for this course. And knowledge about computer hardware, namely basics of how CPU works, how it interacts with memory through caches, and basics of multi-threadding certainly helps.","category":"page"},{"location":"#References","page":"Home","title":"References","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Official documentation\nWorkflow tips, and what is new in v1.9\nThink Julia: How to Think Like a Computer Scientist\nFrom Zero to Julia!\nWikiBooks\nJustin Krumbiel's excellent introduction to the package manager.\njuliadatascience.io contains an excellent introduction to plotting with Makie.\nThe art of multiple dispatch\nMIT Course: Julia Computation\nTim Holy's Advanced Scientific Computing","category":"page"}] +[{"location":"lecture_04/lab/#Lab-04:-Packaging","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_03\",\"Lab03Ecosystem.jl\"))\n\nfunction find_food(a::Animal, w::World)\n as = filter(x -> eats(a,x), w.agents |> values |> collect)\n isempty(as) ? nothing : sample(as)\nend\neats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\neats(::Animal{Wolf},::Animal{Sheep}) = true\neats(::Agent,::Agent) = false","category":"page"},{"location":"lecture_04/lab/#Warmup-Stepping-through-time","page":"Lab 04: Packaging","title":"Warmup - Stepping through time","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"We now have all necessary functions in place to make agents perform one step of our simulation. At the beginning of each step an animal looses energy. Afterwards it tries to find some food, which it will subsequently eat. If the animal then has less than zero energy it dies and is removed from the world. If it has positive energy it will try to reproduce.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Plants have a simpler life. They simply grow if they have not reached their maximal size.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Implement a method agent_step!(::Animal,::World) which performs the following steps:\nDecrement E of agent by 1.0.\nWith p_f, try to find some food and eat it.\nIf E0, the animal dies.\nWith p_r, try to reproduce.\nImplement a method agent_step!(::Plant,::World) which performs the following steps:\nIf the size of the plant is smaller than max_size, increment the plant's size by one.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"function agent_step!(p::Plant, w::World)\n if p.size < p.max_size\n p.size += 1\n end\nend\n\nfunction agent_step!(a::Animal, w::World)\n a.energy -= 1\n if rand() <= a.foodprob\n dinner = find_food(a,w)\n eat!(a, dinner, w)\n end\n if a.energy < 0\n kill_agent!(a,w)\n return\n end\n if rand() <= a.reprprob\n reproduce!(a,w)\n end\nend\n\nnothing # hide","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"An agent_step! of a sheep in a world with a single grass should make it consume the grass, let it reproduce, and eventually die if there is no more food and its energy is at zero:","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"sheep = Sheep(1,2.0,2.0,1.0,1.0,male);\ngrass = Grass(2,2,2);\nworld = World([sheep, grass])\nagent_step!(sheep, world); world\n# NOTE: The second agent step leads to an error.\n# Can you figure out what is the problem here?\nagent_step!(sheep, world); world","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Finally, lets implement a function world_step! which performs one agent_step! for each agent. Note that simply iterating over all agents could lead to problems because we are mutating the agent dictionary. One solution for this is to iterate over a copy of all agent IDs that are present when starting to iterate over agents. Additionally, it could happen that an agent is killed by another one before we apply agent_step! to it. To solve this you can check if a given ID is currently present in the World.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"# make it possible to eat nothing\neat!(::Animal, ::Nothing, ::World) = nothing\n\nfunction world_step!(world::World)\n # make sure that we only iterate over IDs that already exist in the\n # current timestep this lets us safely add agents\n ids = copy(keys(world.agents))\n\n for id in ids\n # agents can be killed by other agents, so make sure that we are\n # not stepping dead agents forward\n !haskey(world.agents,id) && continue\n\n a = world.agents[id]\n agent_step!(a,world)\n end\nend","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"w = World([Sheep(1), Sheep(2), Wolf(3)])\nworld_step!(w); w\nworld_step!(w); w\nworld_step!(w); w","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Finally, lets run a few simulation steps and plot the solution","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"n_grass = 1_000\nn_sheep = 40\nn_wolves = 4\n\ngs = [Grass(id) for id in 1:n_grass]\nss = [Sheep(id) for id in (n_grass+1):(n_grass+n_sheep)]\nws = [Wolf(id) for id in (n_grass+n_sheep+1):(n_grass+n_sheep+n_wolves)]\nw = World(vcat(gs,ss,ws))\n\ncounts = Dict(n=>[c] for (n,c) in agent_count(w))\nfor _ in 1:100\n world_step!(w)\n for (n,c) in agent_count(w)\n push!(counts[n],c)\n end\nend\n\nusing Plots\nplt = plot()\ntolabel(::Type{Animal{Sheep}}) = \"Sheep\"\ntolabel(::Type{Animal{Wolf}}) = \"Wolf\"\ntolabel(::Type{Plant{Grass}}) = \"Grass\"\nfor (A,c) in counts\n plot!(plt, c, label=tolabel(A), lw=2)\nend\nplt","category":"page"},{"location":"lecture_04/lab/#Package:-Ecosystem.jl","page":"Lab 04: Packaging","title":"Package: Ecosystem.jl","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"In the main section of this lab you will create your own Ecosystem.jl package to organize and test (!) the code that we have written so far.","category":"page"},{"location":"lecture_04/lab/#PkgTemplates.jl","page":"Lab 04: Packaging","title":"PkgTemplates.jl","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"The simplest way to create a new package in Julia is to use PkgTemplates.jl. ]add PkgTemplates to your global julia env and create a new package by running:","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"using PkgTemplates\nTemplate(interactive=true)(\"Ecosystem\")","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"to interactively specify various options for your new package or use the following snippet to generate it programmatically:","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"using PkgTemplates\n\n# define the package template\ntemplate = Template(;\n user = \"GithubUserName\", # github user name\n authors = [\"Author1\", \"Author2\"], # list of authors\n dir = \"/path/to/folder/\", # dir in which the package will be created\n julia = v\"1.8\", # compat version of Julia\n plugins = [\n !CompatHelper, # disable CompatHelper\n !TagBot, # disable TagBot\n Readme(; inline_badges = true), # added readme file with badges\n Tests(; project = true), # added Project.toml file for unit tests\n Git(; manifest = false), # add manifest.toml to .gitignore\n License(; name = \"MIT\") # addedMIT licence\n ],\n)\n\n# execute the package template (this creates all files/folders)\ntemplate(\"Ecosystem\")","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"This should have created a new folder Ecosystem which looks like below.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":".\n├── LICENSE\n├── Project.toml\n├── README.md\n├── src\n│ └── Ecosystem.jl\n└── test\n ├── Manifest.toml\n ├── Project.toml\n └── runtests.jl","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"If you ]activate /path/to/Ecosystem you should be able to run ]test to run the autogenerated test (which is not doing anything) and get the following output:","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"(Ecosystem) pkg> test\n Testing Ecosystem\n Status `/private/var/folders/6h/l9_skfms2v3dt8z3zfnd2jr00000gn/T/jl_zd5Uai/Project.toml`\n [e77cd98c] Ecosystem v0.1.0 `~/repos/Ecosystem`\n [8dfed614] Test `@stdlib/Test`\n Status `/private/var/folders/6h/l9_skfms2v3dt8z3zfnd2jr00000gn/T/jl_zd5Uai/Manifest.toml`\n [e77cd98c] Ecosystem v0.1.0 `~/repos/Ecosystem`\n [2a0f44e3] Base64 `@stdlib/Base64`\n [b77e0a4c] InteractiveUtils `@stdlib/InteractiveUtils`\n [56ddb016] Logging `@stdlib/Logging`\n [d6f4376e] Markdown `@stdlib/Markdown`\n [9a3f8284] Random `@stdlib/Random`\n [ea8e919c] SHA v0.7.0 `@stdlib/SHA`\n [9e88b42a] Serialization `@stdlib/Serialization`\n [8dfed614] Test `@stdlib/Test`\n Testing Running tests...\nTest Summary: |Time\nEcosystem.jl | None 0.0s\n Testing Ecosystem tests passed ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"warning: Warning\nFrom now on make sure that you always have the Ecosystem enviroment enabled. Otherwise you will not end up with the correct dependencies in your packages","category":"page"},{"location":"lecture_04/lab/#Adding-content-to-Ecosystem.jl","page":"Lab 04: Packaging","title":"Adding content to Ecosystem.jl","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Next, let's add the types and functions we have defined so far. You can use include(\"path/to/file.jl\") in the main module file at src/Ecosystem.jl to bring some structure in your code. An exemplary file structure could look like below.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":".\n├── LICENSE\n├── Manifest.toml\n├── Project.toml\n├── README.md\n├── src\n│ ├── Ecosystem.jl\n│ ├── animal.jl\n│ ├── plant.jl\n│ └── world.jl\n└── test\n └── runtests.jl","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"While you are adding functionality to your package you can make great use of Revise.jl. Loading Revise.jl before your Ecosystem.jl will automatically recompile (and invalidate old methods!) while you develop. You can install it in your global environment and and create a $HOME/.config/startup.jl which always loads Revise. It can look like this:","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"# try/catch block to make sure you can start julia if Revise should not be installed\ntry\n using Revise\ncatch e\n @warn(e.msg)\nend","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"warning: Warning\nAt some point along the way you should run into problems with the sample functions or when trying using StatsBase. This is normal, because you have not added the package to the Ecosystem environment yet. Adding it is as easy as ]add StatsBase. Your Ecosystem environment should now look like this:(Ecosystem) pkg> status\nProject Ecosystem v0.1.0\nStatus `~/repos/Ecosystem/Project.toml`\n [2913bbd2] StatsBase v0.33.21","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"In order to use your new types/functions like below","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"using Ecosystem\n\nSheep(2)","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"you have to export them from your module. Add exports for all important types and functions.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"# src/Ecosystem.jl\nmodule Ecosystem\n\nusing StatsBase\n\nexport World\nexport Species, PlantSpecies, AnimalSpecies, Grass, Sheep, Wolf\nexport Agent, Plant, Animal\nexport agent_step!, eat!, eats, find_food, reproduce!, world_step!, agent_count\n\n# ....\n\nend","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"

      ","category":"page"},{"location":"lecture_04/lab/#Unit-tests","page":"Lab 04: Packaging","title":"Unit tests","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Every package should have tests which verify the correctness of your implementation, such that you can make changes to your codebase and remain confident that you did not break anything.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"Julia's Test package provides you functionality to easily write unit tests.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"In the file test/runtests.jl, create a new @testset and write three @tests which check that the show methods we defined for Grass, Sheep, and Wolf work as expected.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"The function repr(x) == \"some string\" to check if the string representation we defined in the Base.show overload returns what you expect.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"# using Ecosystem\nusing Test\n\n@testset \"Base.show\" begin\n g = Grass(1,1,1)\n s = Animal{Sheep}(2,1,1,1,1,male)\n w = Animal{Wolf}(3,1,1,1,1,female)\n @test repr(g) == \"🌿 #1 100% grown\"\n @test repr(s) == \"🐑♂ #2 E=1.0 ΔE=1.0 pr=1.0 pf=1.0\"\n @test repr(w) == \"🐺♀ #3 E=1.0 ΔE=1.0 pr=1.0 pf=1.0\"\nend","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"

      ","category":"page"},{"location":"lecture_04/lab/#Github-CI","page":"Lab 04: Packaging","title":"Github CI","text":"","category":"section"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"If you want you can upload you package to Github and add the julia-runtest Github Action to automatically test your code for every new push you make to the repository.","category":"page"},{"location":"lecture_04/lab/","page":"Lab 04: Packaging","title":"Lab 04: Packaging","text":"
      ","category":"page"},{"location":"lecture_03/lab/#lab03","page":"Lab","title":"Lab 3: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_02\",\"Lab02Ecosystem.jl\"))","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In this lab we will look at two different ways of extending our agent simulation to take into account that animals can have two different sexes: female and male.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In the first part of the lab you will re-use the code from lab 2 and create a new type of sheep (⚥Sheep) which has an additional field sex. In the second part you will redesign the type hierarchy from scratch using parametric types to make this agent system much more flexible and julian.","category":"page"},{"location":"lecture_03/lab/#Part-I:-Female-and-Male-Sheep","page":"Lab","title":"Part I: Female & Male Sheep","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"The code from lab 2 that you will need in the first part of this lab can be found here.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"The goal of the first part of the lab is to demonstrate the forwarding method (which is close to how things are done in OOP) by implementing a sheep that can have two different sexes and can only reproduce with another sheep of opposite sex.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"This new type of sheep needs an additonal field sex::Symbol which can be either :male or :female. In OOP we would simply inherit from Sheep and create a ⚥Sheep with an additional field. In Julia there is no inheritance - only subtyping of abstract types. As you cannot inherit from a concrete type in Julia, we will have to create a wrapper type and forward all necessary methods. This is typically a sign of unfortunate type tree design and should be avoided, but if you want to extend a code base by an unforeseen type this forwarding of methods is a nice work-around. Our ⚥Sheep type will simply contain a classic sheep and a sex field","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"struct ⚥Sheep <: Animal\n sheep::Sheep\n sex::Symbol\nend\n⚥Sheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6, sex=rand(Bool) ? :female : :male) = ⚥Sheep(Sheep(id,e,Δe,pr,pf),sex)\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"sheep = ⚥Sheep(1)\nsheep.sheep\nsheep.sex","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Instead of littering the whole code with custom getters/setters Julia allows us to overload the sheep.field behaviour by implementing custom getproperty/setproperty! methods.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement custom getproperty/setproperty! methods which allow to access the Sheep inside the ⚥Sheep as if we would not be wrapping it.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"# NOTE: the @forward macro we will discuss in a later lecture is based on this\n\nfunction Base.getproperty(s::⚥Sheep, name::Symbol)\n if name in fieldnames(Sheep)\n getfield(s.sheep,name)\n else\n getfield(s,name)\n end\nend\n\nfunction Base.setproperty!(s::⚥Sheep, name::Symbol, x)\n if name in fieldnames(Sheep)\n setfield!(s.sheep,name,x)\n else\n setfield!(s,name,x)\n end\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"You should be able to do the following with your overloads now","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"sheep = ⚥Sheep(1)\nsheep.id\nsheep.sex\nsheep.energy += 1\nsheep","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"In order to make the ⚥Sheep work with the rest of the code we only have to forward the eat! method","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"eat!(s::⚥Sheep, food, world) = eat!(s.sheep, food, world);\nsheep = ⚥Sheep(1);\ngrass = Grass(2);\nworld = World([sheep,grass])\neat!(sheep, grass, world)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"and implement a custom reproduce! method with the behaviour that we want.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"However, the extension of Sheep to ⚥Sheep is a very object-oriented approach. With a little bit of rethinking, we can build a much more elegant solution that makes use of Julia's powerful parametric types.","category":"page"},{"location":"lecture_03/lab/#Part-II:-A-new,-parametric-type-hierarchy","page":"Lab","title":"Part II: A new, parametric type hierarchy","text":"","category":"section"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"First, let us note that there are two fundamentally different types of agents in our world: animals and plants. All species such as grass, sheep, wolves, etc. can be categorized as one of those two. We can use Julia's powerful, parametric type system to define one large abstract type for all agents Agent{S}. The Agent will either be an Animal or a Plant with a type parameter S which will represent the specific animal/plant species we are dealing with.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"This new type hiearchy can then look like this:","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"abstract type Species end\n\nabstract type PlantSpecies <: Species end\nabstract type Grass <: PlantSpecies end\n\nabstract type AnimalSpecies <: Species end\nabstract type Sheep <: AnimalSpecies end\nabstract type Wolf <: AnimalSpecies end\n\nabstract type Agent{S<:Species} end\n\n# instead of Symbols we can use an Enum for the sex field\n# using an Enum here makes things easier to extend in case you\n# need more than just binary sexes and is also more explicit than\n# just a boolean\n@enum Sex female male","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct World{A<:Agent}\n agents::Dict{Int,A}\n max_id::Int\nend\n\nfunction World(agents::Vector{<:Agent})\n max_id = maximum(a.id for a in agents)\n World(Dict(a.id=>a for a in agents), max_id)\nend\n\n# optional: overload Base.show\nfunction Base.show(io::IO, w::World)\n println(io, typeof(w))\n for (_,a) in w.agents\n println(io,\" $a\")\n end\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Now we can create a concrete type Animal with the two parametric types and the fields that we already know from lab 2.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct Animal{A<:AnimalSpecies} <: Agent{A}\n const id::Int\n energy::Float64\n const Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\n const sex::Sex\nend","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"To create an instance of Animal we have to specify the parametric type while constructing it","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Animal{Wolf}(1,5,5,1,1,female)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Note that we now automatically have animals of any species without additional work. Starting with the overload of the show method we can already see that we can abstract away a lot of repetitive work into the type system. We can implement one single show method for all animal species!","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement Base.show(io::IO, a::Animal) with a single method for all Animals. You can get the pretty (unicode) printing of the Species types with another overload like this: Base.show(io::IO, ::Type{Sheep}) = print(io,\"🐑\")","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function Base.show(io::IO, a::Animal{A}) where {A<:AnimalSpecies}\n e = a.energy\n d = a.Δenergy\n pr = a.reprprob\n pf = a.foodprob\n s = a.sex == female ? \"♀\" : \"♂\"\n print(io, \"$A$s #$(a.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend\n\n# note that for new species/sexes we will only have to overload `show` on the\n# abstract species types like below!\nBase.show(io::IO, ::Type{Sheep}) = print(io,\"🐑\")\nBase.show(io::IO, ::Type{Wolf}) = print(io,\"🐺\")","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Unfortunately we have lost the convenience of creating plants and animals by simply calling their species constructor. For example, Sheep is just an abstract type that we cannot instantiate. However, we can manually define a new constructor that will give us this convenience back. This is done in exactly the same way as defining a constructor for a concrete type:","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Sheep(id,E,ΔE,pr,pf,s=rand(Sex)) = Animal{Sheep}(id,E,ΔE,pr,pf,s)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Ok, so we have a constructor for Sheep now. But what about all the other billions of species that you want to define in your huge master thesis project of ecosystem simulations? Do you have to write them all by hand? Do not despair! Julia has you covered.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Overload all AnimalSpecies types with a constructor. You already know how to write constructors for specific types such as Sheep. Can you manage to sneak in a type variable? Maybe with Type?","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function (A::Type{<:AnimalSpecies})(id::Int,E::T,ΔE::T,pr::T,pf::T,s::Sex) where T\n Animal{A}(id,E,ΔE,pr,pf,s)\nend\n\n# get the per species defaults back\nrandsex() = rand(instances(Sex))\nSheep(id; E=4.0, ΔE=0.2, pr=0.8, pf=0.6, s=randsex()) = Sheep(id, E, ΔE, pr, pf, s)\nWolf(id; E=10.0, ΔE=8.0, pr=0.1, pf=0.2, s=randsex()) = Wolf(id, E, ΔE, pr, pf, s)\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"We have our convenient, high-level behaviour back!","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Sheep(1)\nWolf(2)","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Check the methods for eat! and kill_agent! which involve Animals and update their type signatures such that they work for the new type hiearchy.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"function eat!(wolf::Animal{Wolf}, sheep::Animal{Sheep}, w::World)\n wolf.energy += sheep.energy * wolf.Δenergy\n kill_agent!(sheep,w)\nend\n\n# no change\n# eat!(::Animal, ::Nothing, ::World) = nothing\n\n# no change\n# kill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)\n\neats(::Animal{Wolf},::Animal{Sheep}) = true\neats(::Agent,::Agent) = false\n# this one needs to wait until we have `Plant`s\n# eats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\n\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Finally, we can implement the new behaviour for reproduce! which we wanted. Build a function which first finds an animal species of opposite sex and then lets the two reproduce (same behaviour as before).","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mates(a::Animal{A}, b::Animal{A}) where A<:AnimalSpecies = a.sex != b.sex\nmates(::Agent, ::Agent) = false\n\nfunction find_mate(a::Animal, w::World)\n ms = filter(x->mates(x,a), w.agents |> values |> collect)\n isempty(ms) ? nothing : rand(ms)\nend\n\nfunction reproduce!(a::Animal{A}, w::World) where {A}\n m = find_mate(a,w)\n if !isnothing(m)\n a.energy = a.energy / 2\n vals = [getproperty(a,n) for n in fieldnames(Animal) if n ∉ [:id, :sex]]\n new_id = w.max_id + 1\n ŝ = Animal{A}(new_id, vals..., randsex())\n w.agents[ŝ.id] = ŝ\n w.max_id = new_id\n end\nend\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"s1 = Sheep(1, s=female)\ns2 = Sheep(2, s=male)\nw = World([s1, s2])\nreproduce!(s1, w); w","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"Implement the type hiearchy we designed for Plants as well.","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"mutable struct Plant{P<:PlantSpecies} <: Agent{P}\n id::Int\n size::Int\n max_size::Int\nend\n\n# constructor for all Plant{<:PlantSpecies} callable as PlantSpecies(...)\n(A::Type{<:PlantSpecies})(id, s, m) = Plant{A}(id,s,m)\n(A::Type{<:PlantSpecies})(id, m) = (A::Type{<:PlantSpecies})(id,rand(1:m),m)\n\n# default specific for Grass\nGrass(id; max_size=10) = Grass(id, rand(1:max_size), max_size)\n\nfunction Base.show(io::IO, p::Plant{P}) where P\n x = p.size/p.max_size * 100\n print(io,\"$P #$(p.id) $(round(Int,x))% grown\")\nend\n\nBase.show(io::IO, ::Type{Grass}) = print(io,\"🌿\")\n\nfunction eat!(sheep::Animal{Sheep}, grass::Plant{Grass}, w::World)\n sheep.energy += grass.size * sheep.Δenergy\n grass.size = 0\nend\neats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\n\nnothing # hide","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_03/lab/","page":"Lab","title":"Lab","text":"g = Grass(2)\ns = Sheep(3)\nw = World([g,s])\neat!(s,g,w); w","category":"page"},{"location":"lecture_04/hw/#Homework-4","page":"Homework 4","title":"Homework 4","text":"","category":"section"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"In this homework you will have to write two additional @testsets for the Ecosystem. One testset should be contained in a file test/sheep.jl and verify that the function eat!(::Animal{Sheep}, ::Plant{Grass}, ::World) works correctly. Another testset should be in the file test/wolf.jl and veryfiy that the function eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World) works correctly.","category":"page"},{"location":"lecture_04/hw/#How-to-submit?","page":"Homework 4","title":"How to submit?","text":"","category":"section"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"Zip the whole package folder Ecosystem.jl and upload it to BRUTE. The package has to include at least the following files:","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"├── src\n│ └── Ecosystem.jl\n└── test\n ├── sheep.jl # contains only a single @testset\n ├── wolf.jl # contains only a single @testset\n └── runtests.jl","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"Thet test/runtests.jl file can look like this:","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"using Test\nusing Ecosystem\n\ninclude(\"sheep.jl\")\ninclude(\"wolf.jl\")\n# ...","category":"page"},{"location":"lecture_04/hw/#Test-Sheep","page":"Homework 4","title":"Test Sheep","text":"","category":"section"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"Create a Sheep with food probability p_f=1\nCreate fully grown Grass and a World with the two agents.\nExecute eat!(::Animal{Sheep}, ::Plant{Grass}, ::World)\n@test that the size of the Grass now has size == 0","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"
      ","category":"page"},{"location":"lecture_04/hw/#Test-Wolf","page":"Homework 4","title":"Test Wolf","text":"","category":"section"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"Create a Wolf with food probability p_f=1\nCreate a Sheep and a World with the two agents.\nExecute eat!(::Animal{Wolf}, ::Animal{Sheep}, ::World)\n@test that the World only has one agent left in the agents dictionary","category":"page"},{"location":"lecture_04/hw/","page":"Homework 4","title":"Homework 4","text":"
      ","category":"page"},{"location":"lecture_02/lecture/#type_lecture","page":"Lecture","title":"Motivation","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Before going into the details of Julia's type system, we will spend a few minutes motivating the roles of a type system, which are:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Structuring code\nCommunicating to the compiler how a type will be used","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The first aspect is important for the convenience of the programmer and enables abstractions in the language, the latter aspect is important for the speed of the generated code. Writing efficient Julia code is best viewed as a dialogue between the programmer and the compiler. [1] ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Type systems according to Wikipedia:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In computer science and computer programming, a data type or simply type is an attribute of data which tells the compiler or interpreter how the programmer intends to use the data.\nA type system is a logical system comprising a set of rules that assigns a property called a type to the various constructs of a computer program, such as variables, expressions, functions or modules. These types formalize and enforce the otherwise implicit categories the programmer uses for algebraic data types, data structures, or other components.","category":"page"},{"location":"lecture_02/lecture/#Structuring-the-code-/-enforcing-the-categories","page":"Lecture","title":"Structuring the code / enforcing the categories","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The role of structuring the code and imposing semantic restriction means that the type system allows you to logically divide your program, and to prevent certain types of errors. Consider for example two types, Wolf and Sheep which share the same definition but the types have different names.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct Wolf\n name::String\n energy::Int\nend\n\nstruct Sheep\n name::String\n energy::Int\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This allows us to define functions applicable only to the corresponding type","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"howl(wolf::Wolf) = println(wolf.name, \" has howled.\")\nbaa(sheep::Sheep) = println(sheep.name, \" has baaed.\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Therefore the compiler (or interpreter) enforces that a wolf can only howl and never baa and vice versa a sheep can only baa. In this sense, it ensures that howl(sheep) and baa(wolf) never happen.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"baa(Sheep(\"Karl\",3))\nbaa(Wolf(\"Karl\",3))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice the type of error of the latter call baa(Wolf(\"Karl\",3)). Julia raises MethodError which states that it has failed to find a function baa for the type Wolf (but there is a function baa for type Sheep).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For comparison, consider an alternative definition which does not have specified types","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"bark(animal) = println(animal.name, \" has howled.\")\nbaa(animal) = println(animal.name, \" has baaed.\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"in which case the burden of ensuring that a wolf will never baa rests upon the programmer which inevitably leads to errors (note that severely constrained type systems are difficult to use).","category":"page"},{"location":"lecture_02/lecture/#Intention-of-use-and-restrictions-on-compilers","page":"Lecture","title":"Intention of use and restrictions on compilers","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Types play an important role in generating efficient code by a compiler, because they tells the compiler which operations are permitted, prohibited, and can indicate invariants of type (e.g. constant size of an array). If compiler knows that something is invariant (constant), it can expoit such information. As an example, consider the following two alternatives to represent a set of animals:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Sheep(\"3\", 3)]\nb = (Wolf(\"1\", 1), Wolf(\"2\", 2), Sheep(\"3\", 3))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where a is an array which can contain arbitrary types and have arbitrary length whereas b is a Tuple which has fixed length in which the first two items are of type Wolf and the third item is of type Sheep. Moreover, consider a function which calculates the energy of all animals as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"energy(animals) = mapreduce(x -> x.energy, +, animals)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"A good compiler makes use of the information provided by the type system to generate efficient code which we can verify by inspecting the compiled code using @code_native macro","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none energy(a)\n@code_native debuginfo=:none energy(b)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"one observes the second version produces more optimal code. Why is that?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In the first representation, a, the animals are stored in an Array{Any} which can have arbitrary size and can contain arbitrary animals. This means that the compiler has to compile energy(a) such that it works on such arrays.\nIn the second representation, b, the animals are stored in a Tuple, which specializes for lengths and types of items. This means that the compiler knows the number of animals and the type of each animal on each position within the tuple, which allows it to specialize.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This difference will indeed have an impact on the time of code execution. On my i5-8279U CPU, the difference (as measured by BenchmarkTools) is","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using BenchmarkTools\n@btime energy($(a))\n@btime energy($(b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":" 70.2 ns (0 allocations: 0 bytes)\n 2.62 ns (0 allocations: 0 bytes)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which nicely demonstrates that the choice of types affects performance. Does it mean that we should always use Tuples instead of Arrays? Surely not, it is just that each is better for different use-cases. Using Tuples means that the compiler will compile a special function for each length of tuple and each combination of types of items it contains, which is clearly wasteful.","category":"page"},{"location":"lecture_02/lecture/#type_system","page":"Lecture","title":"Julia's type system","text":"","category":"section"},{"location":"lecture_02/lecture/#Julia-is-dynamicaly-typed","page":"Lecture","title":"Julia is dynamicaly typed","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia's type system is dynamic, which means that all types are resolved during runtime. But, if the compiler can infer types of all variables of the called function, it can specialize the function for that given type of variables which leads to efficient code. Consider a modified example where we represent two wolfpacks:","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\nwolfpack_b = Any[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a carries a type Vector{Wolf} while wolfpack_b has the type Vector{Any}. This means that in the first case, the compiler knows that all items are of the type Wolfand it can specialize functions using this information. In case of wolfpack_b, it does not know which animal it will encounter (although all are of the same type), and therefore it needs to dynamically resolve the type of each item upon its use. This ultimately leads to less performant code.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@benchmark energy($(wolfpack_a))\n@benchmark energy($(wolfpack_b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":" 3.7 ns (0 allocations: 0 bytes)\n 69.4 ns (0 allocations: 0 bytes)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"To conclude, julia is indeed a dynamically typed language, but if the compiler can infer all types in a called function in advance, it does not have to perform the type resolution during execution, which produces performant code. This means and in hot (performance critical) parts of the code, you should be type stable, in other parts, it is not such big deal.","category":"page"},{"location":"lecture_02/lecture/#Classes-of-types","page":"Lecture","title":"Classes of types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia divides types into three classes: primitive, composite, and abstract.","category":"page"},{"location":"lecture_02/lecture/#Primitive-types","page":"Lecture","title":"Primitive types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Citing the documentation: A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive types are integers and floating-point values. Unlike most languages, Julia lets you declare your own primitive types, rather than providing only a fixed set of built-in ones. In fact, the standard primitive types are all defined in the language itself.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The definition of primitive types look as follows","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"primitive type Float16 <: AbstractFloat 16 end\nprimitive type Float32 <: AbstractFloat 32 end\nprimitive type Float64 <: AbstractFloat 64 end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and they are mainly used to jump-start julia's type system. It is rarely needed to define a special primitive type, as it makes sense only if you define special functions operating on its bits. This is almost excusively used for exposing special operations provided by the underlying CPU / LLVM compiler. For example + for Int32 is different from + for Float32 as they call a different intrinsic operations. You can inspect this jump-starting of the type system yourself by looking at Julia's source.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"julia> @which +(1,2)\n+(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"At int.jl:87","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"(+)(x::T, y::T) where {T<:BitInteger} = add_int(x, y)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"we see that + of integers is calling the function add_int(x, y), which is defined in the core part of the compiler in Intrinsics.cpp (yes, in C++), exposed in Core.Intrinsics","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From Julia docs: Core is the module that contains all identifiers considered \"built in\" to the language, i.e. part of the core language and not libraries. Every module implicitly specifies using Core, since you can't do anything without those definitions.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Primitive types are rarely used, and they will not be used in this course. We mention them for the sake of completeness and refer the reader to the official Documentation (and source code of Julia).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An example of use of primitive type is a definition of one-hot vector in the library PrimitiveOneHot as ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"primitive type OneHot{K} <: AbstractOneHotArray{1} 32 end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where K is the dimension of the one-hot vector. ","category":"page"},{"location":"lecture_02/lecture/#Abstract-types","page":"Lecture","title":"Abstract types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An abstract type can be viewed as a set of concrete types. For example, an AbstractFloat represents the set of concrete types (BigFloat,Float64,Float32,Float16). This is used mainly to define general methods for sets of types for which we expect the same behavior (recall the Julia design motivation: if it quacks like a duck, waddles like a duck and looks like a duck, chances are it's a duck). Abstract types are defined with abstract type TypeName end. For example the following set of abstract types defines part of julia's number system.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"abstract type Number end\nabstract type Real <: Number end\nabstract type Complex <: Number end\nabstract type AbstractFloat <: Real end\nabstract type Integer <: Real end\nabstract type Signed <: Integer end\nabstract type Unsigned <: Integer end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"where <: means \"is a subtype of\" and it is used in declarations where the right-hand is an immediate sypertype of a given type (Integer has the immediate supertype Real.) If the supertype is not supplied, it is considered to be Any, therefore in the above defition Number has the supertype Any. ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"We can list childrens of an abstract type using function subtypes ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: subtypes # hide\nsubtypes(AbstractFloat)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and we can also list the immediate supertype or climb the ladder all the way to Any using supertypes","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: supertypes # hide\nsupertypes(AbstractFloat)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"supertype and subtypes print only types defined in Modules that are currently loaded to your workspace. For example with Julia without any Modules, subtypes(Number) returns [Complex, Real], whereas if I load Mods package implementing numbers defined over finite field, the same call returns [Complex, Real, AbstractMod].","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"It is relatively simple to print a complete type hierarchy of ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using AbstractTrees\nfunction AbstractTrees.children(t::Type)\n t === Function ? Vector{Type}() : filter!(x -> x !== Any,subtypes(t))\nend\nAbstractTrees.printnode(io::IO,t::Type) = print(io,t)\nprint_tree(Number)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The main role of abstract types allows is in function definitions. They allow to define functions that can be used on variables with types with a given abstract type as a supertype. For example we can define a sgn function for all real numbers as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"sgn(x::Real) = x > 0 ? 1 : x < 0 ? -1 : 0\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and we know it would be correct for all real numbers. This means that if anyone creates a new subtype of Real, the above function can be used. This also means that it is expected that comparison operations are defined for any real number. Also notice that Complex numbers are excluded, since they do not have a total order.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For unsigned numbers, the sgn can be simplified, as it is sufficient to verify if they are different (greater) than zero, therefore the function can read","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"sgn(x::Unsigned) = x > 0 ? 1 : 0\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and again, it applies to all numbers derived from Unsigned. Recall that Unsigned <: Integer <: Real, how does Julia decide, which version of the function sgn to use for UInt8(0)? It chooses the most specific version, and thus for sgn(UInt8(0)) it will use sgn(x::Unsinged). If the compiler cannot decide, typically it encounters an ambiguity, it throws an error and recommends which function you should define to resolve it.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The above behavior allows to define default \"fallback\" implementations and while allowing to specialize for sub-types. A great example is matrix multiplication, which has a generic (and slow) implementation with many specializations, which can take advantage of structure (sparse, banded), or use optimized implementations (e.g. blas implementation for dense matrices with eltype Float32 and Float64).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, Julia does not make a difference between abstract types defined in Base libraries shipped with the language and those defined by you (the user). All are treated the same.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From Julia documentation: Abstract types cannot be instantiated, which means that we cannot create a variable that would have an abstract type (try typeof(Number(1f0))). Also, abstract types cannot have any fields, therefore there is no composition (there are lengthy discussions of why this is so, one of the most definite arguments of creators is that abstract types with fields frequently lead to children types not using some fields (consider circle vs. ellipse)).","category":"page"},{"location":"lecture_02/lecture/#composite_types","page":"Lecture","title":"Composite types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Composite types are similar to struct in C (they even have the same memory layout) as they logically join together other types. It is not a great idea to think about them as objects (in OOP sense), because objects tie together data and functions on owned data. Contrary in Julia (as in C), functions operate on data of structures, but are not tied to them and they are defined outside them. Composite types are workhorses of Julia's type system, as user-defined types are mostly composite (or abstract).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Composite types are defined using struct TypeName [fields] end. To define a position of an animal on the Euclidean plane as a type, we would write","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionF64\n x::Float64\n y::Float64\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"which defines a structure with two fields x and y of type Float64. Julia's compiler creates a default constructor, where both (but generally all) arguments are converted using (convert(Float64, x), convert(Float64, y) to the correct type. This means that we can construct a PositionF64 with numbers of different type that are convertable to Float64, e.g. PositionF64(1,1//2) but we cannot construct PositionF64 where the fields would be of different type (e.g. Int, Float32, etc.) or they are not trivially convertable (e.g. String).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Fields in composite types do not have to have a specified type. We can define a VaguePosition without specifying the type","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct VaguePosition\n x\n y\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"This works as the definition above except that the arguments are not converted to Float64 now. One can store different values in x and y, for example String (e.g. VaguePosition(\"Hello\",\"world\")). Although the above definition might be convenient, it limits the compiler's ability to specialize, as the type VaguePosition does not carry information about type of x and y, which has a negative impact on the performance. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using BenchmarkTools\nmove(a,b) = typeof(a)(a.x+b.x, a.y+b.y)\nx = [PositionF64(rand(), rand()) for _ in 1:100]\ny = [VaguePosition(rand(), rand()) for _ in 1:100]\n@benchmark reduce(move, $(x))\n@benchmark reduce(move, $(y))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Giving fields of a composite type an abstract type does not really solve the problem of the compiler not knowing the type. In this example, it still does not know, if it should use instructions for Float64 or Int8.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct LessVaguePosition\n x::Real\n y::Real\nend\n\nz = [LessVaguePosition(rand(), rand()) for _ in 1:100];\n@benchmark reduce(move, $(z))\nnothing #hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"From the perspective of generating optimal code, both definitions are equally uninformative to the compiler as it cannot assume anything about the code. However, the LessVaguePosition will ensure that the position will contain only numbers, hence catching trivial errors like instantiating VaguePosition with non-numeric types for which arithmetic operators will not be defined (recall the discussion on the beginning of the lecture).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"All structs defined above are immutable (as we have seen above in the case of Tuple), which means that one cannot change a field (unless the struct wraps a container, like and array, which allows that). For example this raises an error","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = LessVaguePosition(1,2)\na.x = 2","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"If one needs to make a struct mutable, use the keyword mutable before the keyword struct as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct MutablePosition\n x::Float64\n y::Float64\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In mutable structures, we can change the values of fields.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = MutablePosition(1e0, 2e0)\na.x = 2;\na","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Note, that the memory layout of mutable structures is different, as fields now contain references to memory locations, where the actual values are stored (such structures cannot be allocated on stack, which increases the pressure on Garbage Collector).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The difference can be seen from ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a, b = PositionF64(1,2), PositionF64(1,2)\n@code_native debuginfo=:none move(a,b)\na, b = MutablePosition(1,2), MutablePosition(1,2)\n@code_native debuginfo=:none move(a,b)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Why there is just one addition?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Also, the mutability is costly.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"x = [PositionF43(rand(), rand()) for _ in 1:100];\nz = [MutablePosition(rand(), rand()) for _ in 1:100];\n@benchmark reduce(move, $(x))\n@benchmark reduce(move, $(z))","category":"page"},{"location":"lecture_02/lecture/#Parametric-types","page":"Lecture","title":"Parametric types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"So far, we had to trade-off flexibility for generality in type definitions. Can we have both? The answer is affirmative. The way to achieve this flexibility in definitions of the type while being able to generate optimal code is to parametrize the type definition. This is achieved by replacing types with a parameter (typically a single uppercase character) and decorating in definition by specifying different type in curly brackets. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionT{T}\n x::T\n y::T\nend\nu = [PositionT(rand(), rand()) for _ in 1:100]\nu = [PositionT(rand(Float32), rand(Float32)) for _ in 1:100]\n\n@benchmark reduce(move, $(u))\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice that the compiler can take advantage of specializing for different types (which does not have an effect here as in modern processors addition of Float and Int takes the same time).","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"v = [PositionT(rand(1:100), rand(1:100)) for _ in 1:100]\n@benchmark reduce(move, v)\nnothing #hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The above definition suffers the same problem as VaguePosition, which is that it allows us to instantiate the PositionT with non-numeric types, e.g. String. We solve this by restricting the types T to be children of some supertype, in this case Real","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct Position{T<:Real}\n x::T\n y::T\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"which will throw an error if we try to initialize it with Position(\"1.0\", \"2.0\"). Notice the flexibility we have achieved. We can use Position to store (and later compute) not only over Float32 / Float64 but any real numbers defined by other packages, for example with Posits.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using SoftPosit\nPosition(Posit8(3), Posit8(1))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"also notice that trying to construct the Position with different type of real numbers will fail, example Position(1f0,1e0)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Naturally, fields in structures can be of different types, as is in the below pointless example.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionXY{X<:Real, Y<:Real}\n x::X\n y::Y\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The type can be parametrized by a concrete types. This is usefuyl to communicate the compiler some useful informations, for example size of arrays. ","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct PositionZ{T<:Real,Z}\n x::T\n y::T\nend\n\nPositionZ{Int64,1}(1,2)","category":"page"},{"location":"lecture_02/lecture/#Abstract-parametric-types","page":"Lecture","title":"Abstract parametric types","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Like Composite types, Abstract types can also have parameters. These parameters define types that are common for all child types. A very good example is Julia's definition of arrays of arbitrary dimension N and type T of its items as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"abstract type AbstractArray{T,N} end","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Different T and N give rise to different variants of AbstractArrays, therefore AbstractArray{Float32,2} is different from AbstractArray{Float64,2} and from AbstractArray{Float64,1}. Note that these are still Abstract types, which means you cannot instantiate them. Their purpose is","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"to allow to define operations for broad class of concrete types\nto inform the compiler about constant values, which can be used","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice in the above example that parameters of types do not have to be types, but can also be values of primitive types, as in the above example of AbstractArray N is the number of dimensions which is an integer value.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For convenience, it is common to give some important partially instantiated Abstract types an alias, for example AbstractVector as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"const AbstractVector{T} = AbstractArray{T,1}","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"is defined in array.jl:23 (in Julia 1.6.2), which allows us to define for example general prescription for the dot product of two abstract vectors as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"function dot(a::AbstractVector, b::AbstractVector)\n @assert length(a) == length(b)\n mapreduce(*, +, a, b)\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"You can verify that the above general function can be compiled to performant code if specialized for particular arguments.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"using InteractiveUtils: @code_native\n@code_native debuginfo=:none mapreduce(*,+, [1,2,3], [1,2,3])","category":"page"},{"location":"lecture_02/lecture/#More-on-the-use-of-types-in-function-definitions","page":"Lecture","title":"More on the use of types in function definitions","text":"","category":"section"},{"location":"lecture_02/lecture/#Terminology","page":"Lecture","title":"Terminology","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"A function refers to a set of \"methods\" for a different combination of type parameters (the term function can be therefore considered as refering to a mere name). Methods define different behavior for different types of arguments for a given function. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, b::Position) = Position(a.x + b.x, a.y + b.y)\nmove(a::Vector{<:Position}, b::Vector{<:Position}) = move.(a,b)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move refers to a function with methods move(a::Position, b::Position) and move(a::Vector{<:Position}, b::Vector{<:Position}). When different behavior on different types is defined by a programmer, as shown above, it is also called implementation specialization. There is another type of specialization, called compiler specialization, which occurs when the compiler generates different functions for you from a single method. For example for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,1), Position(2,2))\nmove(Position(1.0,1.0), Position(2.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"the compiler generates two methods, one for Position{Int64} and the other for Position{Float64}. Notice that inside generated functions, the compiler needs to use different intrinsic operations, which can be viewed from","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none move(Position(1,1), Position(2,2))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"@code_native debuginfo=:none move(Position(1.0,1.0), Position(2.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Notice that move works on Posits defined in 3rd party libas well","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(Posit8(1),Posit8(1)), Position(Posit8(2),Posit8(2)))","category":"page"},{"location":"lecture_02/lecture/#Intermezzo:-How-does-the-Julia-compiler-work?","page":"Lecture","title":"Intermezzo: How does the Julia compiler work?","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Let's walk through an example. Consider the following definitions","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)\nmove(a::T, by::T) where {T<:Position} = Position(a.x + by.x, a.y + by.y)\nmove(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nmove(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)\nmove(a::Vector{<:Position}, by::Position) = move.(a, by)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and a function call","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"a = Position(1.0, 1.0)\nby = Position(2.0, 2.0)\nmove(a, by)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The compiler knows that you call the function move.\nThe compiler infers the type of the arguments. You can view the result with","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"(typeof(a),typeof(by))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The compiler identifies all move-methods with arguments of type (Position{Float64}, Position{Float64}):","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wc = Base.get_world_counter()\nm = Base.method_instances(move, (typeof(a), typeof(by)), wc)\nm = first(m)","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"4a. If the method has been specialized (compiled), then the arguments are prepared and the method is invoked. The compiled specialization can be seen from","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"m.cache","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"4b. If the method has not been specialized (compiled), the method is compiled for the given type of arguments and continues as in step 4a. A compiled function is therefore a \"blob\" of native code living in a particular memory location. When Julia calls a function, it needs to pick the right block corresponding to a function with particular type of parameters.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"If the compiler cannot narrow the types of arguments to concrete types, it has to perform the above procedure inside the called function, which has negative effects on performance, as the type resulution and identification of the methods can be slow, especially for methods with many arguments (e.g. 30ns for a method with one argument, 100 ns for method with two arguements). You always want to avoid run-time resolution inside the performant loop!!! Recall the above example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_a = [Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_a))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 991 evaluations.\n Range (min … max): 40.195 ns … 66.641 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 40.742 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 40.824 ns ± 1.025 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n ▂▃ ▃▅▆▅▆█▅▅▃▂▂ ▂\n ▇██████████████▇▇▅▅▁▅▄▁▅▁▄▄▃▄▅▄▅▃▅▃▅▁▃▁▄▄▃▁▁▅▃▃▄▃▄▃▄▆▆▇▇▇▇█ █\n 40.2 ns Histogram: log(frequency) by time 43.7 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"and","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"wolfpack_b = Any[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_b))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 800 evaluations.\n Range (min … max): 156.406 ns … 212.344 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 157.136 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 158.114 ns ± 4.023 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n ▅█▆▅▄▂ ▃▂▁ ▂\n ██████▆▇██████▇▆▇█▇▆▆▅▅▅▅▅▃▄▄▅▄▄▄▄▅▁▃▄▄▃▃▄▃▃▃▄▄▄▅▅▅▅▁▅▄▃▅▄▄▅▅ █\n 156 ns Histogram: log(frequency) by time 183 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"An interesting intermediate between fully abstract and fully concrete type happens, when the compiler knows that arguments have abstract type, which is composed of a small number of concrete types. This case called Union-Splitting, which happens when there is just a little bit of uncertainty. Julia will do something like","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"argtypes = typeof(args)\npush!(execution_stack, args)\nif T == Tuple{Int, Bool}\n @goto compiled_blob_1234\nelse # the only other option is Tuple{Float64, Bool}\n @goto compiled_blob_1236\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"const WolfOrSheep = Union{Wolf, Sheep}\nwolfpack_c = WolfOrSheep[Wolf(\"1\", 1), Wolf(\"2\", 2), Wolf(\"3\", 3)]\n@benchmark energy($(wolfpack_c))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"BenchmarkTools.Trial: 10000 samples with 991 evaluations.\n Range (min … max): 43.600 ns … 73.494 ns ┊ GC (min … max): 0.00% … 0.00%\n Time (median): 44.106 ns ┊ GC (median): 0.00%\n Time (mean ± σ): 44.279 ns ± 0.931 ns ┊ GC (mean ± σ): 0.00% ± 0.00%\n\n █ ▁ ▃ \n ▂▂▂▆▃██▅▃▄▄█▅█▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃\n 43.6 ns Histogram: frequency by time 47.4 ns <\n\n Memory estimate: 0 bytes, allocs estimate: 0.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Thanks to union splitting, Julia is able to have performant operations on arrays with undefined / missing values for example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"[1, 2, 3, missing] |> typeof","category":"page"},{"location":"lecture_02/lecture/#More-on-matching-methods-and-arguments","page":"Lecture","title":"More on matching methods and arguments","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"In the above process, the step, where Julia looks for a method instance with corresponding parameters can be very confusing. The rest of this lecture will focus on this. For those who want to have a formal background, we recommend talk of Francesco Zappa Nardelli and / or the one of Jan Vitek.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"When Julia needs to specialize a method instance, it needs to find it among multiple definitions. A single function can have many method instances, see for example methods(+) which lists all method instances of the +-function. How does Julia select the proper one?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"It finds all methods where the type of arguments match or are subtypes of restrictions on arguments in the method definition.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2a. If there are multiple matches, the compiler selects the most specific definition.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2b. If the compiler cannot decide, which method instance to choose, it throws an error.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"confused_move(a::Position{Float64}, by) = Position(a.x + by.x, a.y + by.y)\nconfused_move(a, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nconfused_move(Position(1.0,2.0), Position(1.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"2c. If it cannot find a suitable method, it throws an error.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,2), VaguePosition(\"hello\",\"world\"))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Some examples: Consider following definitions","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::Position, by::Position) = Position(a.x + by.x, a.y + by.y)\nmove(a::T, by::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)\nmove(a::Position{Float64}, by::Position{Float64}) = Position(a.x + by.x, a.y + by.y)\nmove(a::Vector{<:Position}, by::Vector{<:Position}) = move.(a, by)\nmove(a::Vector{T}, by::Vector{T}) where {T<:Position} = move.(a, by)\nmove(a::Vector{<:Position}, by::Position) = move.(a, by)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1.0,2.0), Position(1.0,2.0))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The first three methods match the types of argumens, but the compiler will select the third one, since it is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(Position(1,2), Position(1,2))","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, the first and second method definitions match the argument, but the second is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Which method will the compiler select for","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move([Position(1,2)], [Position(1,2)])","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Again, the fourth and fifth method definitions match the argument, but the fifth is the most specific.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move([Position(1,2), Position(1.0,2.0)], [Position(1,2), Position(1.0,2.0)])","category":"page"},{"location":"lecture_02/lecture/#Frequent-problems","page":"Lecture","title":"Frequent problems","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Why does the following fail?","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{Real}) = println(\"Vector{Real}\")\nfoo([1.0,2,3])","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Julia's type system is invariant, which means that Vector{Real} is different from Vector{Float64} and from Vector{Float32}, even though Float64 and Float32 are sub-types of Real. Therefore typeof([1.0,2,3]) isa Vector{Float64} which is not subtype of Vector{Real}. For covariant languages, this would be true. For more information on variance in computer languages, see here. If the above definition of foo should be applicable to all vectors which has elements of subtype of Real we have define it as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{T}) where {T<:Real} = println(\"Vector{T} where {T<:Real}\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"or equivalently but more tersely as","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"foo(a::Vector{<:Real}) = println(\"Vector{T} where {T<:Real}\")\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"Diagonal rule says that a repeated type in a method signature has to be a concrete type (this is to avoid ambinguity if the repeated type is used inside function definition to define a new variable to change type of variables). Consider for example the function below","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"move(a::T, b::T) where {T<:Position} = T(a.x + by.x, a.y + by.y)\nnothing # hide","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"we cannot call it with move(Position(1.0,2.0), Position(1,2)), since in this case Position(1.0,2.0) is of type Position{Float64} while Position(1,2) is of type Position{Int64}.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"When debugging why arguments do not match a particular method definition, it is useful to use typeof, isa, and <: commands. For example","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"typeof(Position(1.0,2.0))\ntypeof(Position(1,2))\nPosition(1,2) isa Position{Float64}\nPosition(1,2) isa Position{Real}\nPosition(1,2) isa Position{<:Real}\ntypeof(Position(1,2)) <: Position{<:Float64}\ntypeof(Position(1,2)) <: Position{<:Real}","category":"page"},{"location":"lecture_02/lecture/#A-bizzare-definition-which-you-can-encounter","page":"Lecture","title":"A bizzare definition which you can encounter","text":"","category":"section"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The following definition of a one-hot matrix is taken from Flux.jl","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"struct OneHotArray{T<:Integer, L, N, var\"N+1\", I<:Union{T,AbstractArray{T, N}}} <: AbstractArray{Bool, var\"N+1\"}\n indices::I\nend","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"The parameters of the type carry information about the type used to encode the position of one in each column in T, the dimension of one-hot vectors in L, the dimension of the storage of indices in N (which is zero for OneHotVector and one for OneHotMatrix), number of dimensions of the OneHotArray in var\"N+1\" and the type of underlying storage of indicies I.","category":"page"},{"location":"lecture_02/lecture/","page":"Lecture","title":"Lecture","text":"[1]: Type Stability in Julia, Pelenitsyn et al., 2021](https://arxiv.org/pdf/2109.01950.pdf)","category":"page"},{"location":"installation/#install","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"In order to participate in the course, everyone should install a recent version of Julia together with some text editor of choice. Furthermore during the course we will introduce some best practices of creating/testing and distributing your own Julia code, for which we will require a GitHub account.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"We recommend to install Julia via juliaup. We are using the latest, stable version of Julia (which at the time of this writing is v1.9). Once you have installed juliaup you can get any Julia version you want via:","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"juliaup add $JULIA_VERSION\n\n# or more concretely:\njuliaup add 1.9\n\n# but please, just use the latest, stable version","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Now you should be able to start Julia an be greated with the following:","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"$ julia\n _\n _ _ _(_)_ | Documentation: https://docs.julialang.org\n (_) | (_) (_) |\n _ _ _| |_ __ _ | Type \"?\" for help, \"]?\" for Pkg help.\n | | | | | | |/ _` | |\n | | |_| | | | (_| | | Version 1.9.2 (2023-07-05)\n _/ |\\__'_|_|_|\\__'_| | Official https://julialang.org/ release\n|__/ |\n\njulia>","category":"page"},{"location":"installation/#Julia-IDE","page":"Installation","title":"Julia IDE","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"There is no one way to install/develop and run Julia, which may be strange users coming from MATLAB, but for users of general purpose languages such as Python, C++ this is quite common. Most of the Julia programmers to date are using","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Visual Studio Code,\nand the corresponding Julia extension.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"This setup is described in a comprehensive step-by-step guide in our bachelor course Julia for Optimization & Learning.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Note that this setup is not a strict requirement for the lectures/labs and any other text editor with the option to send code to the terminal such as Vim (+Tmux), Emacs, or Sublime Text will suffice.","category":"page"},{"location":"installation/#GitHub-registration-and-Git-setup","page":"Installation","title":"GitHub registration & Git setup","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"As one of the goals of the course is writing code that can be distributed to others, we recommend a GitHub account, which you can create here (unless you already have one). In order to interact with GitHub repositories, we will be using git. For installation instruction (Windows only) see the section in the bachelor course.","category":"page"},{"location":"lecture_01/motivation/#Introduction-to-Scientific-Programming","page":"Motivation","title":"Introduction to Scientific Programming","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"note: Loose definition of Scientific Programming\nScientific programming languages are designed and optimized for implementing mathematical formulas and for computing with matrices.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Examples of Scientific programming languages include ALGOL, APL, Fortran, J, Julia, Maple, MATLAB and R.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Key requirements for a Scientific programming language:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Fast execution of the code (complex algorithms).\nEase of code reuse / code restructuring.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"
      \n \n
      \n Julia set.\n Stolen from\n Colorschemes.jl.\n
      \n
      ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"In contrast, to general-purpose language Julia has:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"less concern with standalone executable/libraby compilation \nless concern with Application binary interface (ABI)\nless concern with business models (library + header files)\nless concern with public/private separation","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Example of a scientific task\nIn many applications, we encounter the task of optimization a function given by a routine (e.g. engineering, finance, etc.)using Optim\n\nP(x,y) = x^2 - 3x*y + 5y^2 - 7y + 3 # user defined function\n\nz₀ = [ 0.0\n 0.0 ] # starting point \n\noptimize(z -> P(z...), z₀, ConjugateGradient())\noptimize(z -> P(z...), z₀, Newton())\noptimize(z -> P(z...), z₀, Newton();autodiff = :forward)\n","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Very simple for a user, very complicated for a programmer. The program should:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"pick the right optimization method (easy by config-like approach)\ncompute gradient (Hessian) of a user function","category":"page"},{"location":"lecture_01/motivation/#Classical-approach:-create-a-*fast*-library-and-flexible-calling-enviroment","page":"Motivation","title":"Classical approach: create a fast library and flexible calling enviroment","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Crucial algorithms (sort, least squares...) are relatively small and well defined. Application of these algorithms to real-world problem is typically not well defined and requires more code. Iterative development. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Think of a problem of repeated execution of similar jobs with different options. Different level ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"binary executable with command-line switches\nbinary executable with configuration file\nscripting language/environment (Read-Eval-Print Loop)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"It is not a strict boundary, increasing expresivity of the configuration file will create a new scripting language.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Ending up in the 2 language problem. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Low-level programming = computer centric\nclose to the hardware\nallows excellent optimization for fast execution\nHigh-level programming = user centric\nrunning code with many different modifications as easily as possible\nallowing high level of abstraction","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"In scientific programming, the most well known scripting languages are: Python, Matlab, R","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"If you care about standard \"configurations\" they are just perfect. (PyTorch, BLAS)\nYou hit a problem with more complex experiments, such a modifying the internal algorithms.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The scripting language typically makes decisions (if) at runtime. Becomes slow.","category":"page"},{"location":"lecture_01/motivation/#Examples","page":"Motivation","title":"Examples","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Basic Linear Algebra Subroutines (BLAS)–MKL, OpenBlas–-with bindings (Matlab, NumPy)\nMatlab and Mex (C with pointer arithmetics)\nPython with transcription to C (Cython)","category":"page"},{"location":"lecture_01/motivation/#Convergence-efforts","page":"Motivation","title":"Convergence efforts","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Just-in-time compilation (understands high level and converts to low-level)\nautomatic typing (auto in C++) (extends low-level with high-level concepts)","category":"page"},{"location":"lecture_01/motivation/#Julia-approach:-fresh-thinking","page":"Motivation","title":"Julia approach: fresh thinking","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"A dance between specialization and abstraction. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Specialization allows for custom treatment. The right algorithm for the right circumstance is obtained by Multiple dispatch,\nAbstraction recognizes what remains the same after differences are stripped away. Abstractions in mathematics are captured as code through generic programming.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Why a new language?","category":"page"},{"location":"lecture_01/motivation/#Challenge","page":"Motivation","title":"Challenge","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Translate high-level thinking with as much abstraction as possible into specific fast machine code.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Not so easy!","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"theorem: Indexing array x in Matlab:\nx = [1,2,3]\ny=x(4/2)\ny=x(5/2)In the first case it works, in the second throws an error.type instability \nfunction inde(x,n,m)=x(n/m) can never be fast.\nPoor language design choice!","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Simple solution","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Solved by different floating and integer division operation /,÷\nNot so simple with complex objects, e.g. triangular matrices","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia was designed as a high-level language that allows very high level abstract concepts but propagates as much information about the specifics as possible to help the compiler to generate as fast code as possible. Taking lessons from the inability to achieve fast code compilation (mostly from python).","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"julia is faster than C?","category":"page"},{"location":"lecture_01/motivation/#Julia-way","page":"Motivation","title":"Julia way","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Design principle: abstraction should have zero runtime cost","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"flexible type system with strong typing (abstract types)\nmultiple dispatch\nsingle language from high to low levels (as much as possible) optimize execution as much as you can during compile time\nfunctions as symbolic abstraction layers","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"(Image: )","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"AST = Abstract Syntax Tree\nIR = Intermediate Representation","category":"page"},{"location":"lecture_01/motivation/#Teaser-example","page":"Motivation","title":"Teaser example","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Function recursion with arbitrary number of arguments:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum(x) = x\nfsum(x,p...) = x+fsum(p...)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Defines essentially a sum of inputs. Nice generic and abstract concept.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Possible in many languages:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Matlab via nargin, varargin using construction if nargin==1, out=varargin{1}, else out=fsum(varargin{2:end}), end","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia solves this if at compile time. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The generated code can be inspected by macro @code_llvm?","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum(1,2,3)\n@code_llvm fsum(1,2,3)\n@code_llvm fsum(1.0,2.0,3.0)\nfz()=fsum(1,2,3)\n@code_llvm fz()","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Note that each call of fsum generates a new and different function.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Functions can act either as regular functions or like templates in C++. Compiler decides.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"This example is relatively simple, many other JIT languages can optimize such code. Julia allows taking this approach further.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Generality of the code:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"fsum('c',1)\nfsum([1,2],[3,4],[5,6])","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Relies on multiple dispatch of the + function.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"More involved example:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"using Zygote\n\nf(x)=3x+1 # user defined function\n@code_llvm f'(10)","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"The simplification was not achieved by the compiler alone.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Julia provides tools for AST and IR code manipulation\nautomatic differentiation via IR manipulation is implemented in Zygote.jl\nin a similar way, debugger is implemented in Debugger.jl\nvery simple to design domain specific language\nusing Turing\nusing StatsPlots\n\n@model function gdemo(x, y)\n s² ~ InverseGamma(2, 3)\n m ~ Normal(0, sqrt(s²))\n x ~ Normal(m, sqrt(s²))\n y ~ Normal(m, sqrt(s²))\nend","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Such tools allow building a very convenient user experience on abstract level, and reaching very efficient code.","category":"page"},{"location":"lecture_01/motivation/#Reproducibile-research","page":"Motivation","title":"Reproducibile research","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Think about a code that was written some time ago. To run it, you often need to be able to have the same version of the language it was written for. ","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"Standard way language freezes syntax and guarantees some back-ward compatibility (Matlab), which prevents future improvements\nJulia approach allows easy recreation of the environment in which the code was developed. Every project (e.g. directory) can have its own environment","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Environment\nIs an independent set of packages that can be local to an individual project or shared and selected by name.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"tip: Package\nA package is a source tree with a standard layout providing functionality that can be reused by other Julia projects.","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"This allows Julia to be a rapidly evolving ecosystem with frequent changes due to:","category":"page"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"built-in package manager\nswitching between multiple versions of packages","category":"page"},{"location":"lecture_01/motivation/#Package-manager","page":"Motivation","title":"Package manager","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"implemented by Pkg.jl\nsource tree have their structure defined by a convention\nhave its own mode in REPL\nallows adding packages for using (add) or development (dev)\nsupporting functions for creation (generate) and activation (activate) and many others","category":"page"},{"location":"lecture_01/motivation/#Julia-from-user's-point-of-view","page":"Motivation","title":"Julia from user's point of view","text":"","category":"section"},{"location":"lecture_01/motivation/","page":"Motivation","title":"Motivation","text":"compilation of everything to as specialized as possible\nvery fast code\nslow interaction (caching...)\ngenerating libraries is harder \nthink of fsum, \neverything is \".h\" (Eigen library)\ndebugging is different to matlab/python\nextensibility, Multiple dispatch = multi-functions\nallows great extensibility and code composition\nnot (yet) mainstream thinking\nJulia is not Object-oriented\nJulia is (not pure) functional language","category":"page"},{"location":"how_to_submit_hw/#homeworks","page":"Homework submission","title":"Homework submission","text":"","category":"section"},{"location":"how_to_submit_hw/","page":"Homework submission","title":"Homework submission","text":"This document should describe the homework submission procedure.","category":"page"},{"location":"lecture_01/basics/#Syntax","page":"Basics","title":"Syntax","text":"","category":"section"},{"location":"lecture_01/basics/#Elementary-syntax:-Matlab-heritage","page":"Basics","title":"Elementary syntax: Matlab heritage","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Very much like matlab:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"indexing from 1\narray as first-class A=[1 2 3]","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Cheat sheet: https://cheatsheets.quantecon.org/","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Introduction: https://juliadocs.github.io/Julia-Cheat-Sheet/","category":"page"},{"location":"lecture_01/basics/#Arrays-are-first-class-citizens","page":"Basics","title":"Arrays are first-class citizens","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Many design choices were motivated considering matrix arguments:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"x *= 2 is implemented as x = x*2 causing new allocation (vectors).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The reason is consistency with matrix operations: A *= B works as A = A*B.","category":"page"},{"location":"lecture_01/basics/#Broadcasting-operator","page":"Basics","title":"Broadcasting operator","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Julia generalizes matlabs .+ operation to general use for any function. ","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"a = [1 2 3]\nsin.(a)\nf(x)=x^2+3x+8\nf.(a)","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Solves the problem of inplace multiplication","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"x .*= 2 ","category":"page"},{"location":"lecture_01/basics/#Functional-roots-of-Julia","page":"Basics","title":"Functional roots of Julia","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Function is a first-class citizen.","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Repetition of functional programming:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"function mymap(f::Function,a::AbstractArray)\n b = similar(a)\n for i in eachindex(a)\n b[i]=f(a[i])\n end\n b\nend","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Allows for anonymous functions:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"mymap(x->x^2+2,[1.0,2.0])","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Function properties:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Arguments are passed by reference (change of mutable inputs inside the function is visible outside)\nConvention: function changing inputs have a name ending by \"!\" symbol\nreturn value \nthe last line of the function declaration, \nreturn keyword\nzero cost abstraction","category":"page"},{"location":"lecture_01/basics/#Different-style-of-writing-code","page":"Basics","title":"Different style of writing code","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Definitions of multiple small functions and their composition","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"fsum(x) = x\nfsum(x,p...) = x+fsum(p[1],p[2:end]...)","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"a single methods may not be sufficient to understand the full algorithm. In procedural language, you may write:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"function out=fsum(x,varargin)\n if nargin==2 # TODO: better treatment\n out=x\n else\n out = fsum(varargin{1},varargin{2:end})\n end","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The need to build intuition for function composition.","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Dispatch is easier to optimize by the compiler.","category":"page"},{"location":"lecture_01/basics/#Operators-are-functions","page":"Basics","title":"Operators are functions","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"operator function name\n[A B C ...] hcat\n[A; B; C; ...] vcat\n[A B; C D; ...] hvcat\nA' adjoint\nA[i] getindex\nA[i] = x setindex!\nA.n getproperty\nA.n = x setproperty!","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"struct Foo end\n\nBase.getproperty(a::Foo, x::Symbol) = x == :a ? 5 : error(\"does not have property $(x)\")","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"Can be redefined and overloaded for different input types. The getproperty method can define access to the memory structure.","category":"page"},{"location":"lecture_01/basics/#Broadcasting-revisited","page":"Basics","title":"Broadcasting revisited","text":"","category":"section"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The a.+b syntax is a syntactic sugar for broadcast(+,a,b).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The special meaning of the dot is that they will be fused into a single call:","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"f.(g.(x .+ 1)) is treated by Julia as broadcast(x -> f(g(x + 1)), x). \nAn assignment y .= f.(g.(x .+ 1)) is treated as in-place operation broadcast!(x -> f(g(x + 1)), y, x).","category":"page"},{"location":"lecture_01/basics/","page":"Basics","title":"Basics","text":"The same logic works for lists, tuples, etc.","category":"page"},{"location":"lecture_03/lecture/#Design-patterns:-good-practices-and-structured-thinking","page":"Lecture","title":"Design patterns: good practices and structured thinking","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Every software developer has a desire to write better code. A desire to improve system performance. A desire to design software that is easy to maintain, easy to understand and explain.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Design patterns are recommendations and good practices accumulating knowledge of experienced programmers.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The highest level of experience contains the design guiding principles:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface\nSegregation, Dependency Inversion\nDRY: Don't Repeat Yourself\nKISS: Keep It Simple, Stupid!\nPOLA: Principle of Least Astonishment\nYAGNI: You Aren't Gonna Need It (overengineering)\nPOLP: Principle of Least Privilege ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"While these high-level concepts are intuitive, they are too general to give specific answers.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"More detailed patterns arise for programming paradigms (declarative, imperative) with specific instances of functional or object-oriented programming.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The concept of design patterns originates in the OOP paradigm. OOP defines a strict way how to write software. Sometimes it is not clear how to squeeze real world problems into those rules. Cookbook for many practical situations","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Gamma, E., Johnson, R., Helm, R., Johnson, R. E., & Vlissides, J. (1995). Design patterns: elements of reusable object-oriented software. Pearson Deutschland GmbH.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Defining 23 design patterns in three categories. Became extremely popular.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(Image: ) (C) Scott Wlaschin","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is julia OOP or FP? It is different from both, based on:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"types system (polymorphic)\nmultiple dispatch (extending single dispatch of OOP)\nfunctions as first class \ndecoupling of data and functions\nmacros","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Any guidelines to solve real-world problems?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Hands-On Design Patterns and Best Practices with Julia Proven solutions to common problems in software design for Julia 1.x Tom Kwong, CFA","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Fundamental tradeoff: rules vs. freedom","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"freedom: in the C language it is possible to access assembler instructions, use pointer aritmetics:\nit is possible to write extremely efficient code\nit is easy to segfault, leak memory, etc.\nrules: in strict languages (strict OOP, strict functional programing) you lose freedom for certain guarantees:\ne.g. strict functional programing guarantees that the program provably terminates\noperations that are simple e.g. in pointer arithmetics may become clumsy and inefficient in those strict rules.\nthe compiler can validate the rules and complain if the code does not comply with them. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia is again a dance between freedom and strict rules. It is more inclined to freedom. Provides few simple concepts that allow to construct design patterns common in other languages.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"the language does not enforce too many formalisms (via keywords (interface, trait, etc.) but they can be \nthe compiler cannot check for correctness of these \"patterns\"\nthe user has a lot of freedom (and responsibility)\nlots of features can be added by Julia packages (with various level of comfort)\nmacros","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Read: ","category":"page"},{"location":"lecture_03/lecture/#Design-Patterns-of-OOP-from-the-Julia-viewpoint","page":"Lecture","title":"Design Patterns of OOP from the Julia viewpoint","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"OOP is currently very popular concept (C++, Java, Python). It has strenghts and weaknesses. The Julia authors tried to keep the strength and overcome weaknesses. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Key features of OOP:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Encapsulation \nInheritance \nPolymorphism ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Classical OOP languages define classes that bind processing functions to the data. Virtual methods are defined only for the attached methods of the classes.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Encapsulation\nRefers to bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components. Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Making Julia to mimic OOP\nThere are many discussions how to make Julia to behave like an OOP. The best implementation to our knowledge is ObjectOriented","category":"page"},{"location":"lecture_03/lecture/#Encapsulation-Advantage:-Consistency-and-Validity","page":"Lecture","title":"Encapsulation Advantage: Consistency and Validity","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"With fields of data structure freely accessible, the information may become inconsistent.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct Grass <: Plant\n id::Int\n size::Int\n max_size::Int\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"What if I create Grass with larger size than max_size?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"grass = Grass(1,50,5)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Freedom over Rules. Maybe I would prefer to introduce some rules.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Some encapsulation may be handy keeping it consistent. Julia has inner constructor.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"mutable struct Grass2 <: Plant\n id::Int\n size::Int\n max_size::Int\n Grass2(id,sz,msz) = sz > msz ? error(\"size can not be greater that max_size\") : new(id,sz,msz)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"When defined, Julia does not provide the default outer constructor. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"But fields are still accessible:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"grass.size = 10000","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Recall that grass.size=1000 is a syntax of setproperty!(grass,:size,1000), which can be redefined:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function Base.setproperty!(obj::Grass, sym::Symbol, val)\n if sym==:size\n @assert val<=obj.max_size \"size have to be lower than max_size!\"\n end\n setfield!(obj,sym,val)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Function setfield! can not be overloaded.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia has partial encapsulation via a mechanism for consistency checks. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"warn: Array in unmutable struct can be mutated\nThe mutability applies to the structure and not to encapsulated structures.struct Foo\n x::Float64\n y::Vector{Float64}\n z::Dict{Int,Int}\nendIn the structure Foo, x cannot be mutated, but fields of y and key-value pairs of z can be mutated, because they are mutable containers. But I cannot replace y with a different Vector.","category":"page"},{"location":"lecture_03/lecture/#Encapsulation-Disadvantage:-the-Expression-Problem","page":"Lecture","title":"Encapsulation Disadvantage: the Expression Problem","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Encapsulation limits the operations I can do with an object. Sometimes too much. Consider a matrix of methods/types(data-structures)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Consider an existing matrix of data and functions:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"data \\ methods find_food eat! grow! \nWolf \nSheep \nGrass ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"You have a good reason not to modify the original source (maintenance).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Imagine we want to extend the world to use new animals and new methods for all animals.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Object-oriented programming ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"classes are primary objects (hierarchy)\ndefine animals as classes ( inheriting from abstract class)\nadding a new animal is easy\nadding a new method for all animals is hard (without modifying the original code)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Functional programming ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"functions are primary\ndefine operations find_food, eat!\nadding a new operation is easy\nadding new data structure to existing operations is hard","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Solutions:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"multiple-dispatch = julia\nopen classes (monkey patching) = add methods to classes on the fly\nvisitor pattern = partial fix for OOP [extended visitor pattern using dynamic_cast]","category":"page"},{"location":"lecture_03/lecture/#Morale:","page":"Lecture","title":"Morale:","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Julia does not enforces creation getters/setters by default (setproperty is mapped to setfield)\nit provides tools to enforce access restriction if the user wants it.\ncan be used to imitate objects: ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"https://stackoverflow.com/questions/39133424/how-to-create-a-single-dispatch-object-oriented-class-in-julia-that-behaves-l/39150509#39150509","category":"page"},{"location":"lecture_03/lecture/#Polymorphism:","page":"Lecture","title":"Polymorphism:","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Polymorphism in OOP\nPolymorphism is the method in an object-oriented programming language that performs different things as per the object’s class, which calls it. With Polymorphism, a message is sent to multiple class objects, and every object responds appropriately according to the properties of the class. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Example animals of different classes make different sounds. In Python:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"\nclass Sheep:\n def __init__(self, energy, Denergy):\n self.energy = energy\n self.Denergy = Denergy\n\n def make_sound(self):\n print(\"Baa\")\n\nsheep.make_sound()\nwolf.make_sound()","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Will make distinct sounds (baa, Howl). ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Can we achieve this in Julia?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"make_sound(s::Sheep)=println(\"Baa\")\nmake_sound(w::Wolf)=println(\"Howl\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Implementation of virtual methods\nVirtual methods in OOP are typically implemented using Virtual Method Table, one for each class. (Image: )Julia has a single method table. Dispatch can be either static or dynamic (slow).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Freedom vs. Rules. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Duck typing is a type of polymorphism without static types\nmore programming freedom, less formal guarantees\njulia does not check if make_sound exists for all animals. May result in MethodError. Responsibility of a programmer.\ndefine make_sound(A::AbstractAnimal)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"So far, the polymorphism coincides for OOP and julia becuase the method had only one argument => single argument dispatch.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Multiple dispatch is an extension of the classical first-argument-polymorphism of OOP, to all-argument polymorphism.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Challenge for OOP\nHow to code polymorphic behavior of interaction between two agents, e.g. an agent eating another agent in OOP?Complicated.... You need a \"design pattern\" for it.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"class Sheep(Animal):\n energy: float = 4.0\n denergy: float = 0.2\n reprprob: float = 0.5\n foodprob: float = 0.9\n\n # hard, if not impossible to add behaviour for a new type of food\n def eat(self, a: Agent, w: World):\n if isinstance(a, Grass)\n self.energy += a.size * self.denergy\n a.size = 0\n else:\n raise ValueError(f\"Sheep cannot eat {type(a).__name__}.\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Consider an extension to:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Flower : easy\nPoisonousGrass: harder","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Simple in Julia:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"eat!(w1::Sheep, a::Grass, w::World)=\neat!(w1::Sheep, a::Flower, w::World)=\neat!(w1::Sheep, a::PoisonousGrass, w::World)=","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Boiler-plate code can be automated by macros / meta programming.","category":"page"},{"location":"lecture_03/lecture/#Inheritance","page":"Lecture","title":"Inheritance","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Inheritance\nIs the mechanism of basing one object or class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Deriving new classes (sub classes) from existing ones such as super class or base class and then forming them into a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance, a \"child object\", acquires all the properties and behaviors of the \"parent object\" , with the exception of: constructors, destructor, overloaded operators.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Most commonly, the sub-class inherits methods and the data.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"For example, in python we can design a sheep with additional field. Think of a situation that we want to refine the reproduction procedure for sheeps by considering differences for male and female. We do not have information about gender in the original implementation. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In OOP, we can use inheritance.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"class Sheep:\n def __init__(self, energy, Denergy):\n self.energy = energy\n self.Denergy = Denergy\n\n def make_sound(self):\n print(\"Baa\")\n\nclass SheepWithGender(Sheep):\n def __init__(self, energy, Denergy,gender):\n super().__init__(energy, Denergy)\n self.gender = gender\n # make_sound is inherited \n\n# Can you do this in Julia?!","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Simple answer: NO, not exactly","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Sheep has fields, is a concrete type, we cannot extend it.\nwith modification of the original code, we can define AbstractSheep with subtypes Sheep and SheepWithGender.\nBut methods for AbstractAnimal works for sheeps! Is this inheritance?","category":"page"},{"location":"lecture_03/lecture/#Inheritance-vs.-Subtyping","page":"Lecture","title":"Inheritance vs. Subtyping","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Subtle difference:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"subtyping = equality of interface \ninheritance = reuse of implementation ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In practice, subtyping reuse methods, not data fields.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"We have seen this in Julia, using type hierarchy: ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"agent_step!(a::Animal, w::World)\nall animals subtype of Animal \"inherit\" this method.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The type hierarchy is only one way of subtyping. Julia allows many variations, e.g. concatenating different parts of hierarchies via the Union{} type:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"fancy_method(O::Union{Sheep,Grass})=println(\"Fancy\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is this a good idea? It can be done completely Ad-hoc! Freedom over Rules.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"There are very good use-cases:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Missing values:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"x::AbstractVector{<:Union{<:Number, Missing}}","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"theorem: SubTyping issues\nWith parametric types, unions and other construction, subtype resolution may become a complicated problem. Julia can even crash. (Jan Vitek's Keynote at JuliaCon 2021)[https://www.youtube.com/watch?v=LT4AP7CUMAw]","category":"page"},{"location":"lecture_03/lecture/#Sharing-of-data-field-via-composition","page":"Lecture","title":"Sharing of data field via composition","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Composition is also recommended in OOP: (Composition over ingeritance)[https://en.wikipedia.org/wiki/Compositionoverinheritance]","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"struct ⚥Sheep <: Animal\n sheep::Sheep\n sex::Symbol\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"If we want our new ⚥Sheep to behave like the original Sheep, we need to forward the corresponding methods.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"eat!(a::⚥Sheep, b::Grass, w::World)=eat!(a.sheep, b, w)","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"and all other methods. Routine work. Boring! The whole process can be automated using macros @forward from Lazy.jl.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Why so complicated? Wasn't the original inheritance tree structure better?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"multiple inheritance:\nyou just compose two different \"trees\".\ncommon example with ArmoredVehicle = Vehicle + Weapon\nDo you think there is only one sensible inheritance tree?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Animal World\nThink of an inheritance tree of a full scope Animal world.Idea #1: Split animals by biological taxonomy (Image: )Hold on. Sharks and dolphins can swim very well!\nBoth bats and birds fly similarly!Idea #2: Split by the way they move!Idea #3: Split by way of ...","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In fact, we do not have a tree, but more like a matrix/tensor:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":" swims flies walks\nbirds penguin eagle kiwi\nmammal dolphin bat sheep,wolf\ninsect backswimmer fly beetle","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Single type hierarchy will not work. Other approaches:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"interfaces\nparametric types","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Analyze what features of animals are common and compose the animal:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"abstract type HeatType end\nabstract type MovementType end\nabstract type ChildCare end\n\n\nmutable struct Animal{H<:HeatType,M<:MovementType,C<:ChildCare} \n id::Int\n ...\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Now, we can define methods dispatching on parameters of the main type.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Composition is simpler in such a general case. Composition over inheritance. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"A simple example of parametric approach will be demonstarted in the lab.","category":"page"},{"location":"lecture_03/lecture/#Interfaces:-inheritance/subtyping-without-a-hierarchy-tree","page":"Lecture","title":"Interfaces: inheritance/subtyping without a hierarchy tree","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In OOP languages such as Java, interfaces have a dedicated keyword such that compiler can check correctes of the interface implementation. ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"In Julia, interfaces can be achived by defining ordinary functions. Not so strict validation by the compiler as in other languages. Freedom...","category":"page"},{"location":"lecture_03/lecture/#Example:-Iterators","page":"Lecture","title":"Example: Iterators","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Many fundamental objects can be iterated: Arrays, Tuples, Data collections...","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"They do not have any common \"predecessor\". They are almost \"primitive\" types.\nthey share just the property of being iterable\nwe do not want to modify them in any way","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Example: of interface Iterators defined by \"duck typing\" via two functions.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Required methods Brief description\niterate(iter) Returns either a tuple of the first item and initial state or nothing if empty\niterate(iter, state) Returns either a tuple of the next item and next state or nothing if no items remain","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Defining these two methods for any object/collection C will make the following work:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"for o in C\n # do something\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The compiler will not check if both functions exist.\nIf one is missing, it will complain about it when it needs it\nThe error message may be less informative than in the case of formal definition","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"even iterators may have different features: they can be finite or infinite\nfor finite iterators we can define useful functions (collect)\nhow to pass this information in an extensible way?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Poor solution: if statements.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function collect(iter)\n if iter isa Tuple...\n\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"The compiler can do that for us.","category":"page"},{"location":"lecture_03/lecture/#Traits:-cherry-picking-subtyping","page":"Lecture","title":"Traits: cherry picking subtyping","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Trait mechanism in Julia is build using the existing tools: Type System and Multiple Dispatch.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Traits have a few key parts:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Trait types: the different traits a type can have.\nTrait function: what traits a type has.\nTrait dispatch: using the traits.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"From iterators:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"# trait types:\n\nabstract type IteratorSize end\nstruct SizeUnknown <: IteratorSize end\nstruct HasLength <: IteratorSize end\nstruct IsInfinite <: IteratorSize end\n\n# Trait function: Input is a Type, output is a Type\nIteratorSize(::Type{<:Tuple}) = HasLength()\nIteratorSize(::Type) = HasLength() # HasLength is the default\n\n# ...\n\n# Trait dispatch\nBitArray(itr) = gen_bitarray(IteratorSize(itr), itr)\ngen_bitarray(isz::IteratorSize, itr) = gen_bitarray_from_itr(itr)\ngen_bitarray(::IsInfinite, itr) = throw(ArgumentError(\"infinite-size iterable used in BitArray constructor\"))\n","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"What is needed to define for a new type that I want to iterate over? ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Do you still miss inheritance in the OOP style?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Many packages automating this with more structure:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"https://github.com/andyferris/Traitor.jl\nhttps://github.com/mauro3/SimpleTraits.jl\nhttps://github.com/tk3369/BinaryTraits.jl","category":"page"},{"location":"lecture_03/lecture/#Functional-tools:-Partial-evaluation","page":"Lecture","title":"Functional tools: Partial evaluation","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"It is common to create a new function which \"just\" specify some parameters.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"_prod(x) = reduce(*,x)\n_sum(x) = reduce(+,x)","category":"page"},{"location":"lecture_03/lecture/#Functional-tools:-Closures","page":"Lecture","title":"Functional tools: Closures","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"tip: Closure (lexical closure, function closure)\nA technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"originates in functional programming\nnow widespread in many common languages, Python, Matlab, etc..\nmemory management relies on garbage collector in general (can be optimized by compiler)","category":"page"},{"location":"lecture_03/lecture/#Example","page":"Lecture","title":"Example","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function adder(x)\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"creates a function that \"closes\" the argument x. Try: f=adder(5); f(3).","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"x = 30;\nfunction adder()\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"creates a function that \"closes\" variable x.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"f = adder(10)\nf(1)\ng = adder()\ng(1)\n","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Such function can be passed as an argument: together with the closed data.","category":"page"},{"location":"lecture_03/lecture/#Implementation-of-closures-in-julia:-documentation","page":"Lecture","title":"Implementation of closures in julia: documentation","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function adder(x)\n return y->x+y\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"is lowered to (roughly):","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"struct ##1{T}\n x::T\nend\n\n(_::##1)(y) = _.x + y\n\nfunction adder(x)\n return ##1(x)\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note that the structure ##1 is not directly accessible. Try f.x and g.x.","category":"page"},{"location":"lecture_03/lecture/#Functor-Function-like-structure","page":"Lecture","title":"Functor = Function-like structure","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Each structure can have a method that is invoked when called as a function.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(_::Sheep)()= println(\"🐑\")","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"You can think of it as sheep.default_method().","category":"page"},{"location":"lecture_03/lecture/#Coding-style","page":"Lecture","title":"Coding style","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"From Flux.jl:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"function train!(loss, ps, data, opt; cb = () -> ())\n ps = Params(ps)\n cb = runall(cb)\n @progress for d in data\n gs = gradient(ps) do\n loss(batchmemaybe(d)...)\n end\n update!(opt, ps, gs)\n cb()\n end\nend","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Is this confusing? What can cb() do and what it can not?","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Note that function train! does not have many local variables. The important ones are arguments, i.e. exist in the scope from which the function was invoked.","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"loss(x,y)=mse(model(x),y)\ncb() = @info \"training\" loss(x,y)\ntrain!(loss, ps, data, opt; cb=cb)","category":"page"},{"location":"lecture_03/lecture/#Usage","page":"Lecture","title":"Usage","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"Usage of closures:","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"callbacks: the function can also modify the enclosed variable.\nabstraction: partial evaluation ","category":"page"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"theorem: Beware: Performance of captured variables\nInference of types may be difficult in closures: https://github.com/JuliaLang/julia/issues/15276 ","category":"page"},{"location":"lecture_03/lecture/#Aditional-materials","page":"Lecture","title":"Aditional materials","text":"","category":"section"},{"location":"lecture_03/lecture/","page":"Lecture","title":"Lecture","text":"(Functional desighn pattersn)[https://www.youtube.com/watch?v=srQt1NAHYC0]","category":"page"},{"location":"lecture_02/hw/#Homework-2:-Predator-Prey-Agents","page":"Homework","title":"Homework 2: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"In this lab you will continue working on your agent simulation. If you did not manage to finish the homework, do not worry, you can use this script which contains all the functionality we developed in the lab.","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_02\",\"Lab02Ecosystem.jl\"))","category":"page"},{"location":"lecture_02/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Put all your code (including your or the provided solution of lab 2) in a script named hw.jl. Zip only this file (not its parent folder) and upload it to BRUTE. Your file cannot contain any package dependencies. For example, having a using Plots in your code will cause the automatic evaluation to fail.","category":"page"},{"location":"lecture_02/hw/#Counting-Agents","page":"Homework","title":"Counting Agents","text":"","category":"section"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"To monitor the different populations in our world we need a function that counts each type of agent. For Animals we simply have to count how many of each type are currently in our World. In the case of Plants we will use the fraction of size(plant)/max_size(plant) as a measurement quantity.","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"
      \n
      Compulsory Homework (2 points)
      \n
      ","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Implement a function agent_count that can be called on a single Agent and returns a number between (01) (i.e. always 1 for animals; and size(plant)/max_size(plant) for plants).\nAdd a method for a vector of agents Vector{<:Agent} which sums all agent counts.\nAdd a method for a World which returns a dictionary that contains pairs of Symbols and the agent count like below:","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"agent_count(p::Plant) = p.size / p.max_size\nagent_count(::Animal) = 1\nagent_count(as::Vector{<:Agent}) = sum(agent_count,as)\n\nfunction agent_count(w::World)\n function op(d::Dict,a::A) where A<:Agent\n n = nameof(A)\n if n in keys(d)\n d[n] += agent_count(a)\n else\n d[n] = agent_count(a)\n end\n return d\n end\n foldl(op, w.agents |> values |> collect, init=Dict{Symbol,Real}())\nend","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"grass1 = Grass(1,5,5);\nagent_count(grass1)\n\ngrass2 = Grass(2,1,5);\nagent_count([grass1,grass2]) # one grass is fully grown; the other only 20% => 1.2\n\nsheep = Sheep(3,10.0,5.0,1.0,1.0);\nwolf = Wolf(4,20.0,10.0,1.0,1.0);\nworld = World([grass1, grass2, sheep, wolf]);\nagent_count(world)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Hint: You can get the name of a type by using the nameof function:","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"nameof(Grass)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"Use as much dispatch as you can! ;)","category":"page"},{"location":"lecture_02/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_01/hw/#Homework-1:-Extending-polynomial-the-other-way","page":"Homework","title":"Homework 1: Extending polynomial the other way","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework (2 points)
      \n
      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Extend the original polynomial function to the case where x is a square matrix. Create a function called circlemat, that returns nxn matrix A(n) with the following elements","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"leftA(n)right_ij = \nbegincases\n 1 textif (i = j-1 land j 1) lor (i = n land j=1) \n 1 textif (i = j+1 land j n) lor (i = 1 land j=n) \n 0 text otherwise\nendcases","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"and evaluate the polynomial","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"f(A) = I + A + A^2 + A^3","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":", at point A = A(10).","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS for matrix definition: You can try one of these options:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"create matrix with all zeros with zeros(n,n), use two nested for loops going in ranges 1:n and if condition with logical or ||, and && \nemploy array comprehension with nested loops [expression for i in 1:n, j in 1:n] and ternary operator condition ? true branch : false","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS for polynomial extension:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"extend the original example (one with for-loop) to initialize the accumulator variable with matrix of proper size (use size function to get the dimension), using argument typing for x is preferred to distinguish individual implementations <: AbstractMatrix","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"or","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"test later defined polynomial methods, that may work out of the box","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n","category":"page"},{"location":"lecture_01/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Put all the code for the exercise above in a file called hw.jl and upload it to BRUTE. If you have any questions, write an email to one of the lab instructors of the course.","category":"page"},{"location":"lecture_01/hw/#Voluntary","page":"Homework","title":"Voluntary","text":"","category":"section"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      Exercise (voluntary)
      \n
      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Install GraphRecipes and Plots packages into the environment defined during the lecture and figure out, how to plot the graph defined by adjacency matrix A from the homework.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"HINTS:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"There is help command inside the the pkg mod of the REPL. Type ? add to find out how to install a package. Note that both pkgs are registered.\nFollow a guide in the Plots pkg's documentation, which is accessible through docs icon on top of the README in the GitHub repository. Direct link.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Activate the environment in pkg mode, if it is not currently active.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> activate .","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"Installing pkgs is achieved using the add command. Running ] ? add returns a short piece of documentation for this command:","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> ? add\n[...]\n Examples\n\n pkg> add Example # most commonly used for registered pkgs (installs usually the latest release)\n pkg> add Example@0.5 # install with some specific version (realized through git tags)\n pkg> add Example#master # install from master branch directly\n pkg> add Example#c37b675 # install from specific git commit\n pkg> add https://github.com/JuliaLang/Example.jl#master # install from specific remote repository (when pkg is not registered)\n pkg> add git@github.com:JuliaLang/Example.jl.git # same as above but using the ssh protocol\n pkg> add Example=7876af07-990d-54b4-ab0e-23690620f79a # when there are multiple pkgs with the same name","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"As the both Plots and GraphRecipes are registered and we don't have any version requirements, we will use the first option.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> add Plots\npkg> add GraphRecipes","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"This process downloads the pkgs and triggers some build steps, if for example some binary dependencies are needed. The process duration depends on the \"freshness\" of Julia installation and the size of each pkg. With Plots being quite dependency heavy, expect few minutes. After the installation is complete we can check the updated environment with the status command.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"pkg> status","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"The plotting itself as easy as calling the graphplot function on our adjacency matrix.","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"using GraphRecipes, Plots\nA = [ 0 1 0 0 0 0 0 0 0 1; 1 0 1 0 0 0 0 0 0 0; 0 1 0 1 0 0 0 0 0 0; 0 0 1 0 1 0 0 0 0 0; 0 0 0 1 0 1 0 0 0 0; 0 0 0 0 1 0 1 0 0 0; 0 0 0 0 0 1 0 1 0 0; 0 0 0 0 0 0 1 0 1 0; 0 0 0 0 0 0 0 1 0 1; 1 0 0 0 0 0 0 0 1 0]# hide\ngraphplot(A)","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"graphplot(A) #hide","category":"page"},{"location":"lecture_01/hw/","page":"Homework","title":"Homework","text":"

      ","category":"page"},{"location":"lecture_04/lecture/#pkg_lecture","page":"Lecture","title":"Package development","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Organization of the code is more important with the increasing size of the project and the number of contributors and users. Moreover, it will become essential when different codebases are expected to be combined and reused. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Julia was designed from the beginning to encourage code reuse across different codebases as possible\nJulia ecosystem lives on a namespace. From then, it builds projects and environments.","category":"page"},{"location":"lecture_04/lecture/#Namespaces-and-modules","page":"Lecture","title":"Namespaces and modules","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Namespace logically separate fragments of source code so that they can be developed independently without affecting each other. If I define a function in one namespace, I will still be able to define another function in a different namespace even though both functions have the same name.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"prevents confusion when common words are used in different meaning:\nToo general name of functions \"create\", \"extend\", \"loss\", \nor data \"X\", \"y\" (especially in mathematics, think of π)\nmay not be an issue if used with different types\nModules is Julia syntax for a namespace","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Example:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module MySpace\nfunction test1()\n println(\"test1\")\nend\nfunction test2()\n println(\"test2\")\nend\nexport test1\n#include(\"filename.jl\")\nend","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Function include copies content of the file to this location (will be part of the module).","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Creates functions:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"MySpace.test1\nMySpace.test2","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"For easier manipulation, these functions can be \"exported\" to be exposed to the outer world (another namespace).","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Keyword: using exposes the exported functions and structs:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using .MySpace","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The dot means that the module was defined in this scope.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Keyword: import imports function with availability to redefine it.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Combinations:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"usecase results\nusing MySpace MySpace.test1\n MySpace.test2\n test1\nusing MySpace: test1 test1\nimport MySpace MySpace.test1*\n MySpace.test2*\nimport MySpace: test1 test1*\nimport MySpace: test2 test2*","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"symbol \"*\" denotes functions that can be redefined","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":" using .MySpace: test1\n test1()=println(\"new test\")\n import .MySpace: test1\n test1()=println(\"new test\")","category":"page"},{"location":"lecture_04/lecture/#Conflicts:","page":"Lecture","title":"Conflicts:","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"When importing/using functions with name that is already imported/used from another module:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"the imported functions/structs are invalidated. \nboth functions has to be acessed by their full names.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Resolution:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"It may be easier to cherry pick only the functions we need (rather than importing all via using)\nrename some function using keyword as\nimport MySpace2: test1 as t1","category":"page"},{"location":"lecture_04/lecture/#Submodules","page":"Lecture","title":"Submodules","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Modules can be used or included within other modules:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module A\n a=1;\nend\nmodule B\n module C\n c = 2\n end\n b = C.c # you can read from C (by reference)\n using ..A: a\n # a= b # but not write to A\nend;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"REPL of Julia is a module called \"Main\". ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"modules are not copied, but referenced, i.e. B.b===B.C.c\nincluding one module twice (from different packages) is not a problem\nUpcoming Julia 1.9 has the ability to change the contextual module in the REPL: REPL.activate(TestPackage)","category":"page"},{"location":"lecture_04/lecture/#Revise.jl","page":"Lecture","title":"Revise.jl","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The fact that Julia can redefine a function in a Module by importing it is used by package Revise.jl to synchronize REPL with a module or file.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"So far, we have worked in REPL. If you have a file that is loaded and you want to modify it, you would need to either:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"reload the whole file, or\ncopy the changes to REPL","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Revise.jl does the latter automatically.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Example demo:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using Revise.jl\nincludet(\"example.jl\")","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Works with: ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"any package loaded with import or using, \nscript loaded with includet, \nBase julia itself (with Revise.track(Base))\nstandard libraries (with, e.g., using Unicode; Revise.track(Unicode))","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Does not work with variables!","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"How it works: monitors source code for changes and then does:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"for def in setdiff(oldexprs, newexprs)\n # `def` is an expression that defines a method.\n # It was in `oldexprs`, but is no longer present in `newexprs`--delete the method.\n delete_methods_corresponding_to_defexpr(mod, def)\nend\nfor def in setdiff(newexprs, oldexprs)\n # `def` is an expression for a new or modified method. Instantiate it.\n Core.eval(mod, def)\nend","category":"page"},{"location":"lecture_04/lecture/#Namespaces-and-scoping","page":"Lecture","title":"Namespaces & scoping","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Every module introduces a new global scope. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Global scope\nNo variable or function is expected to exist outside of it\nEvery module is equal to a global scope (no single \"global\" exists)\nThe REPL has a global module called Main\nLocal scope\nVariables in Julia do not need to be explicitly declared, they are created by assignments: x=1. \nIn local scope, the compiler checks if variable x does not exist outside. We have seen:\nx=1\nf(y)=x+y\nThe rules for local scope determine how to treat assignment of x. If local x exists, it is used, if it does not:\nin hard scope: new local x is created\nin soft scope: checks if x exists outside (global)\nif not: new local x is created\nif yes: the split is REPL/non-interactive:\nREPL: global x is used (convenience, as of 1.6)\nnon-interactive: local x is created","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"keyword local and global can be used to specify which variable to use","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"From documentation:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Construct Scope type Allowed within\nmodule, baremodule global global\nstruct local (soft) global\nfor, while, try local (soft) global, local\nmacro local (hard) global\nfunctions, do blocks, let blocks, comprehensions, generators local (hard) global, local","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Question:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x=1\nf()= x=3\nf()\n@show x;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x = 1\nfor _ = 1:1\n x=3\nend\n@show x;","category":"page"},{"location":"lecture_04/lecture/#Packages","page":"Lecture","title":"Packages","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Package is a source tree with a standard layout. It provides a module and thus can be loaded with include or using.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Minimimal package:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageName/\n├── src/\n│ └── PackageName.jl\n├── Project.toml","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Contains:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Project.toml file describing basic properties:\nName, does not have to be unique (federated package sources)\nUUID, has to be unique (generated automatically)\noptionally [deps], [targets],...\nfile src/PackageName.jl that defines module PackageName which is executed when loaded.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Many other optional directories:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"directory tests/, (almost mandatory)\ndirectory docs/ (common)\ndirectory scripts/, examples/,... (optional)","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The package typically loads other modules that form package dependencies.","category":"page"},{"location":"lecture_04/lecture/#Project-environments","page":"Lecture","title":"Project environments","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Is a package that does not contain Name and UUID in Project.toml. It's used when you don't need to create a package for your work. It's created by activate some/path in REPL package mode. ","category":"page"},{"location":"lecture_04/lecture/#Project-Manifest","page":"Lecture","title":"Project Manifest","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Both package and environment can contain an additional file Manifest.toml. This file tracks full dependency tree of a project including versions of the packages on which it depends.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"for example:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"# This file is machine-generated - editing it directly is not advised\n\n[[AbstractFFTs]]\ndeps = [\"LinearAlgebra\"]\ngit-tree-sha1 = \"485ee0867925449198280d4af84bdb46a2a404d0\"\nuuid = \"621f4979-c628-5d54-868e-fcf4e3e8185c\"\nversion = \"1.0.1\"\n\n[[AbstractTrees]]\ngit-tree-sha1 = \"03e0550477d86222521d254b741d470ba17ea0b5\"\nuuid = \"1520ce14-60c1-5f80-bbc7-55ef81b5835c\"\nversion = \"0.3.4\"","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Content of files Project.toml and Manifest.toml are maintained by PackageManager.","category":"page"},{"location":"lecture_04/lecture/#Package-manager","page":"Lecture","title":"Package manager","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Handles both packages and projects:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"creating a project ]generate PkgName\nadding an existing project add PkgName or add https://github.com/JuliaLang/Example.jl\nNames are resolved by Registrators (public or private).\nremoving ]rm PkgName\nupdating ]update\ndeveloping ]dev http://... \nadd treats packages as being finished, version handling pkg manager. Precompiles!\ndev leaves all operations on the package to the user (git versioning, etc.). Always read content of files","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"By default these operations are related to environment .julia/environments/v1.8","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"E.g. running and updating will update packages in Manifest.toml in this directory. What if the update breaks functionality of some project package that uses special features?","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"There can and should be more than one environment!","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Project environments are based on files with installed packages.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"switching by ]activate Path - creates Project.toml if not existing\nfrom that moment, all package modifications will be relevant only to this project!\nwhen switching to a new project ]instantiate will prepare (download and precompile) the environment\ncreates Manifest.toml = list of all exact versions of all packages \nwhich Packages are visible is determined by LOAD_PATH\ntypically contaings default libraries and default environment\nit is different for REPL and Pkg.tests ! No default env. in tests. ","category":"page"},{"location":"lecture_04/lecture/#Package-hygiene-workflow","page":"Lecture","title":"Package hygiene - workflow","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"theorem: Potential danger\nPackage dependencies may not be compatible: package A requires C@<0.2\npackage B requires C@>0.3\nwhat should happen when ]add A and add B?","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"keep your \"@v#.#\" as clean as possible (recommended are only debugging/profiling packages)\nuse packages as much as you can, even for short work with scripts ]activate .\nadding a package existing elsewhere is cheap (global cache)\nif do you not wish to store any files just test random tricks of a cool package: ]activate --temp","category":"page"},{"location":"lecture_04/lecture/#Package-development-with-Revise","page":"Lecture","title":"Package development with Revise","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Developing a package with interactive test/development:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Create a package/module at one directory MainPackage\nCreate a script at another directory MainScript, and activate it ]activate .\ndev MainPackage in the MainScript environment\nRevise.jl will watch the MainPackage so it is always up to date\nin dev mode you have full control over commits etc.","category":"page"},{"location":"lecture_04/lecture/#Unit-testing,-/test","page":"Lecture","title":"Unit testing, /test","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Without explicit keywords for checking constructs (think missing functions in interfaces), the good quality of the code is guaranteed by detailed unit testing.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"each package should have directory /test\nfile /test/runtest.jl is run by the command ]test of the package manager\nthis file typically contains include of other tests\nno formal structure of tests is prescribed\ntest files are just ordinary julia scripts\nuser is free to choose what to test and how (freedom x formal rules)\ntesting functionality is supported by macros @test and @teststet\n@testset \"trigonometric identities\" begin\n θ = 2/3*π\n @test sin(-θ) ≈ -sin(θ)\n @test cos(-θ) ≈ cos(θ)\n @test sin(2θ) ≈ 2*sin(θ)*cos(θ)\n @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2\nend;","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Testset is a collection of tests that will be run and summarized in a common report.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Testsets can be nested: testsets in testsets\ntests can be in loops or functions\nfor i=1:10\n @test a[i]>0\nend\nUseful macro ≈ checks for equality with given tolerance\na=5+1e-8\n@test a≈5\n@test a≈5 atol=1e-10","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"@testset resets RNG to Random.GLOBAL_SEED before and after the test for repeatability \nThe same results of RNG are not guaranteed between Julia versions!\nTest coverage: package Coverage.jl\nCan be run automatically by continuous integration, e.g. GitHub actions\nintegration in VSCode test via package TestItems.jl ","category":"page"},{"location":"lecture_04/lecture/#Documentation-and-Style,-/docs","page":"Lecture","title":"Documentation & Style, /docs","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"A well written package is reusable if it is well documented. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"The simpliest kind of documentation is the docstring:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"\"Auxiliary function for printing a hello\"\nhello()=println(\"hello\")\n\n\"\"\"\nMore complex function that adds π to input:\n- x is the input argument (itemize)\n\nCan be written in latex: ``x \\leftarrow x + \\pi``\n\"\"\"\naddπ(x) = x+π","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Yieds:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"tip: Renders as\nMore complex function that adds π to input:x is the input argument (itemize)Can be written in latex: x leftarrow x + pi","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Structure of the document","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageName/\n├── src/\n│ └── SourceFile.jl\n├── docs/\n│ ├── build/\n│ ├── src/\n│ └── make.jl\n...","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Where the line-by-line documentation is in the source files.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"/docs/src folder can contain more detailed information: introductory pages, howtos, tutorials, examples\nrunning make.jl controls which pages are generated in what form (html or latex) documentation in the /build directory\nautomated with GitHub actions","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Documentation is generated by the julia code.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"code in documentation can be evaluated","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"x=3\n@show x","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"documentation can be added by code:\nstruct MyType\n value::String\nend\n\nDocs.getdoc(t::MyType) = \"Documentation for MyType with value $(t.value)\"\n\nx = MyType(\"x\")\ny = MyType(\"y\")\nSee ?x and ?y. \nIt uses the same very standard building blocks: multiple dispatch.","category":"page"},{"location":"lecture_04/lecture/#Precompilation","page":"Lecture","title":"Precompilation","text":"","category":"section"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"By default, every package is precompiled when loading and stored in compiled form in a cache.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"If it defines methods that extend previously defined (e.g. from Base), it may affect already loaded packages which need to be recompiled as well. May take time.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Julia has a tracking mechanism that stores information about the whole graph of dependencies. ","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Faster code can be achieved by the precompile directive:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"module FSum\n\nfsum(x) = x\nfsum(x,p...) = x+fsum(p[1],p[2:end]...)\n\nprecompile(fsum,(Float64,Float64,Float64))\nend","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Can be investigated using MethodAnalysis.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"using MethodAnalysis\nmi =methodinstances(fsum)","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"Useful packages:","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"PackageCompiler.jl has three main purposes:\nCreating custom sysimages for reduced latency when working locally with packages that has a high startup time.\nCreating \"apps\" which are a bundle of files including an executable that can be sent and run on other machines without Julia being installed on that machine.\nCreating a relocatable C library bundle form of Julia code.","category":"page"},{"location":"lecture_04/lecture/","page":"Lecture","title":"Lecture","text":"AutoSysimages.jl allows easy generation of precompiles images - reduces package loading","category":"page"},{"location":"projects/#projects","page":"Projects","title":"Projects","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of the project should be to create something, which is actually useful. Therefore we offer a lot of freedom in how the project will look like with the condition that you should spent around 60 hours on it (this number was derived as follows: each credit is worth 30 hours minus 13 lectures + labs minus 10 homeworks 2 hours each) and you should demonstrate some skills in solving the project. In general, we can distinguish three types of project depending on the beneficiary:","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"You benefit: Use / try to solve a well known problem using Julia language,\nOur group: work with your tutors on a topic researched in the AIC group, \nJulia community: choose an issue in a registered Julia project you like and fix it (documentation issues are possible but the resulting documentation should be very nice.).","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The project should be of sufficient complexity that verify your skill of the language (to be agreed individually).","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Below, we list some potential projects for inspiration.","category":"page"},{"location":"projects/#Implementing-new-things","page":"Projects","title":"Implementing new things","text":"","category":"section"},{"location":"projects/#Lenia-(Continuous-Game-of-Life)","page":"Projects","title":"Lenia (Continuous Game of Life)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Lenia is a continuous version of Conway's Game of Life. Implement a Julia version. For example, you could focus either on performance compared to the python version, or build nice visualizations with Makie.jl.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Nice tutorial from Conway to Lenia","category":"page"},{"location":"projects/#The-Equation-Learner-And-Its-Symbolic-Representation","page":"Projects","title":"The Equation Learner And Its Symbolic Representation","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"In many scientific and engineering one searches for interpretable (i.e. human-understandable) models instead of the black-box function approximators that neural networks provide. The equation learner (EQL) is one approach that can identify concise equations that describe a given dataset.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The EQL is essentially a neural network with different unary or binary activation functions at each indiviual unit. The network weights are regularized during training to obtain a sparse model which hopefully results in a model that represents a simple equation.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of this project is to implement the EQL, and if there is enough time the improved equation learner (iEQL). The equation learners should be tested on a few toy problems (possibly inspired by the tasks in the papers). Finally, you will implement functionality that can transform the learned model into a symbolic, human readable, and exectuable Julia expression.","category":"page"},{"location":"projects/#Architecture-visualizer","page":"Projects","title":"Architecture visualizer","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Create an extension of Flux / Lux and to visualize architecture of a neural network suitable for publication. Something akin PlotNeuralNet.","category":"page"},{"location":"projects/#Learning-Large-Language-Models-with-reduced-precition-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Learning Large Language Models with reduced precition (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Large Language Models ((Chat) GPT, LLama, Falcon, Palm, ...) are huge. A recent trend is to perform optimization in reduced precision, for example in int8 instead of Float32. Such feature is currently missing in Julia ecosystem and this project should be about bringing this to the community (for an introduction, read these blogs LLM-int8 and emergent features, A gentle introduction to 8-bit Matrix Multiplication). The goal would be to implement this as an additional type of Number / Matrix and overload multiplication on CPU (and ideally on GPU) to make it transparent for neural networks? What I will learn? In this project, you will learn a lot about the (simplicity of) implementation of deep learning libraries and you will practice abstraction of Julia's types. You can furthermore learn about GPU Kernel programming and Transformers.jl library.","category":"page"},{"location":"projects/#Planning-algorithms-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Planning algorithms (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Extend SymbolicPlanners.jl with the mm-ϵ variant of the bi-directional search MM: A bidirectional search algorithm that is guaranteed to meet in the middle. This pull request might be very helpful in understanding better the library.","category":"page"},{"location":"projects/#A-Rule-Learning-Algorithms-(Mentor:-Tomas-Pevny)","page":"Projects","title":"A Rule Learning Algorithms (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Rule-based models are simple and very interpretable models that have been around for a long time and are gaining popularity again. The goal of this project is to implement one of these algorithms","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"sequential covering","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"algorithm called RIPPER and evaluate it on a number of datasets.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Learning Certifiably Optimal Rule Lists for Categorical Data\nBoolean decision rules via column generation\nLearning Optimal Decision Trees with SAT\nA SAT-based approach to learn explainable decision sets","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"To increase the impact of the project, consider interfacing it with MLJ.jl","category":"page"},{"location":"projects/#Parallel-optimization-(Mentor:-Tomas-Pevny)","page":"Projects","title":"Parallel optimization (Mentor: Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Implement one of the following algorithms to train neural networks in parallel. Can be implemented in a separate package or consider extending FluxDistributed.jl. Do not forget to verify that the method actually works!!!","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Hogwild!\nLocal sgd with periodic averaging: Tighter analysis and adaptive synchronization\nDistributed optimization for deep learning with gossip exchange","category":"page"},{"location":"projects/#Solve-issues-in-existing-projects:","page":"Projects","title":"Solve issues in existing projects:","text":"","category":"section"},{"location":"projects/#Create-Yao-backend-for-quantum-simulation-(Mentor:-Niklas-Heim)","page":"Projects","title":"Create Yao backend for quantum simulation (Mentor: Niklas Heim)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The recently published quantum programming library Qadence needs a Julia backend. The tricky quantum parts are already implemented in a library called Yao.jl. The goal of this project is to take the Qadence (Python) representation and translate it to Yao.jl (Julia). You will work with the Python/Julia interfacing library PythonCall.jl to realize this and benchmark the Julia backend in the end to assess if it is faster than the existing python implementation.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If this sounds interesting, talk to Niklas.","category":"page"},{"location":"projects/#Address-issues-in-markov-decision-processes-(Mentor:-Jan-Mrkos)","page":"Projects","title":"Address issues in markov decision processes (Mentor: Jan Mrkos)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Fix type stability issue in MCTS.jl, prepare benchmarks, and evaluate the impact of the changes. Details can be found in this issue. This project will require learnind a little bit about Markov Decision Processes if you don't know them already.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If it sounds interesting, get in touch with lecturer/lab assistant, who will connect you with Jan Mrkos.","category":"page"},{"location":"projects/#Extend-HMil-library-with-Retentative-networks-(mentor-Tomas-Pevny)","page":"Projects","title":"Extend HMil library with Retentative networks (mentor Tomas Pevny)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"Retentative networks were recently proposed as a low-cost alternative to Transformer models without sacrificing performance (according to authors). By implementing Retentative Networks, te HMil library will be able to learn sequences (not just sets), which might nicely extend its applicability.","category":"page"},{"location":"projects/#Address-issues-in-HMil/JsonGrinder-library-(mentor-Simon-Mandlik)","page":"Projects","title":"Address issues in HMil/JsonGrinder library (mentor Simon Mandlik)","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"These are open source toolboxes that are used internally in Avast. Lots of general functionality is done, but some love is needed in polishing.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"refactor the codebase using package extensions (e.g. for FillArrays)\nimprove compilation time (tracking down bottlenecks with SnoopCompile and using precompile directives from PrecompileTools.jl)","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Or study new metric learning approach on application in animation description","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"apply machine learning on slides within presentation provide by PowToon","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"If it sounds interesting, get in touch with lecturer/lab assistant, who will connect you with Simon Mandlik.","category":"page"},{"location":"projects/#Project-requirements","page":"Projects","title":"Project requirements","text":"","category":"section"},{"location":"projects/","page":"Projects","title":"Projects","text":"The goal of the semestral project is to create a Julia pkg with reusable, properly tested and documented code. We have given you some options of topics, as well as the freedom to choose something that could be useful for your research or other subjects. In general we are looking for something where performance may be crucial such as data processing, optimization or equation solving.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"In practice the project should follow roughly this tree structure","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":".\n├── scripts\n│\t├── run_example.jl\t\t\t# one or more examples showing the capabilities of the pkg\n│\t├── Project.toml \t\t\t# YOUR_PROJECT should be added here with develop command with rel path\n│\t└── Manifest.toml \t\t\t# should be committed as it allows to reconstruct the environment exactly\n├── src\n│\t├── YOUR_PROJECT.jl \t\t# ideally only some top level code such as imports and exports, rest of the code included from other files\n│\t├── src1.jl \t\t\t\t# source files structured in some logical chunks\n│\t└── src2.jl\n├── test\n│\t├── runtest.jl # contains either all the tests or just includes them from other files\n│\t├── Project.toml \t\t\t# lists some additional test dependencies\n│\t└── Manifest.toml \t\t# usually not committed to git as it is generated on the fly\n├── README.md \t\t\t\t\t# describes in short what the pkg does and how to install pkg (e.g. some external deps) and run the example\n├── Project.toml \t\t\t\t# lists all the pkg dependencies\n└── Manifest.toml \t\t\t\t# usually not committed to git as the requirements may be to restrictive","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"The first thing that we will look at is README.md, which should warn us if there are some special installation steps, that cannot be handled with Julia's Pkg system. For example if some 3rd party binary dependency with license is required. Secondly we will try to run tests in the test folder, which should run and not fail and should cover at least some functionality of the pkg. Thirdly and most importantly we will instantiate environment in scripts and test if the example runs correctly. Lastly we will focus on documentation in terms of code readability, docstrings and inline comments. ","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Only after all this we may look at the extent of the project and it's difficulty, which may help us in deciding between grades. ","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Nice to have things, which are not strictly required but obviously improves the score.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Ideally the project should be hosted on GitHub, which could have the continuous integration/testing set up.\nInclude some benchmark and profiling code in your examples, which can show us how well you have dealt with the question of performance.\nSome parallelization attempts either by multi-processing, multi-threadding, or CUDA. Do not forget to show the improvement.\nDocumentation with a webpage using Documenter.jl.","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"Here are some examples of how the project could look like:","category":"page"},{"location":"projects/","page":"Projects","title":"Projects","text":"ImageInspector","category":"page"},{"location":"lecture_01/outline/#Course-outline","page":"Outline","title":"Course outline","text":"","category":"section"},{"location":"lecture_01/outline/","page":"Outline","title":"Outline","text":"Introduction\nType system\nuser: tool for abstraction\ncompiler: tool for memory layout\nDesign patterns (mental setup)\nJulia is a type-based language\nmultiple-dispatch generalizes OOP and FP\nPackages\nway how to organize code\ncode reuse (alternative to libraries)\nexperiment reproducibility\nBenchmarking\nhow to measure code efficiency\nIntrospection\nunderstand how the compiler process the data\nMacros\nautomate writing of boring the boilerplate code\ngood macro create cleaner code\nAutomatic Differentiation\nTheory: difference between the forward and backward mode\nImplementation techniques\nIntermediate representation\nhow to use internal the representation of the code \nexample in automatic differentiation\nParallel computing\nthreads, processes\nGraphics card coding\ntypes for GPU\nspecifics of architectures\nOrdinary Differential Equations\nsimple solvers\nerror propagation\nData driven ODE\ncombine ODE with optimization\nautomatic differentiation (adjoints)","category":"page"},{"location":"lecture_03/hw/#Homework-3","page":"Homework","title":"Homework 3","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"In this homework we will implement a function find_food and practice the use of closures. The solution of lab 3 can be found here. You can use this file and add the code that you write for the homework to it.","category":"page"},{"location":"lecture_03/hw/#How-to-submit?","page":"Homework","title":"How to submit?","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Put all your code (including your or the provided solution of lab 2) in a script named hw.jl. Zip only this file (not its parent folder) and upload it to BRUTE.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"projdir = dirname(Base.active_project())\ninclude(joinpath(projdir,\"src\",\"lecture_03\",\"Lab03Ecosystem.jl\"))\n\nfunction find_food(a::Animal, w::World)\n as = filter(x -> eats(a,x), w.agents |> values |> collect)\n isempty(as) ? nothing : rand(as)\nend\n\neats(::Animal{Sheep},g::Plant{Grass}) = g.size > 0\neats(::Animal{Wolf},::Animal{Sheep}) = true\neats(::Agent,::Agent) = false\n\nfunction every_nth(f::Function, n::Int)\n i = 1\n function callback(args...)\n # display(i) # comment this out to see out the counter increases\n if i == n\n f(args...)\n i = 1\n else\n i += 1\n end\n end\nend\n\nnothing # hide","category":"page"},{"location":"lecture_03/hw/#Agents-looking-for-food","page":"Homework","title":"Agents looking for food","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Implement a method find_food(a::Animal, w::World) returns one randomly chosen agent from all w.agents that can be eaten by a or nothing if no food could be found. This means that if e.g. the animal is a Wolf you have to return one random Sheep, etc.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Hint: You can write a general find_food method for all animals and move the parts that are specific to the concrete animal types to a separate function. E.g. you could define a function eats(::Animal{Wolf}, ::Animal{Sheep}) = true, etc.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"You can check your solution with the public test:","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"sheep = Sheep(1,pf=1.0)\nworld = World([Grass(2), sheep])\nfind_food(sheep, world) isa Plant{Grass}","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_03/hw/#Callbacks-and-Closures","page":"Homework","title":"Callbacks & Closures","text":"","category":"section"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      \n
      Homework:
      \n
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Implement a function every_nth(f::Function,n::Int) that takes an inner function f and uses a closure to construct an outer function g that only calls f every nth call to g. For example, if n=3 the inner function f be called at the 3rd, 6th, 9th ... call to g (not at the 1st, 2nd, 4th, 5th, 7th... call).","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"Hint: You can use splatting via ... to pass on an unknown number of arguments from the outer to the inner function.","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"
      ","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"You can use every_nth to log (or save) the agent count only every couple of steps of your simulation. Using every_nth will look like this:","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"w = World([Sheep(1), Grass(2), Wolf(3)])\n# `@info agent_count(w)` is executed only every 3rd call to logcb(w)\nlogcb = every_nth(w->(@info agent_count(w)), 3);\n\nlogcb(w); # x->(@info agent_count(w)) is not called\nlogcb(w); # x->(@info agent_count(w)) is not called\nlogcb(w); # x->(@info agent_count(w)) *is* called","category":"page"},{"location":"lecture_03/hw/","page":"Homework","title":"Homework","text":"","category":"page"},{"location":"lecture_01/lab/#Lab-01:-Introduction-to-Julia","page":"Lab","title":"Lab 01: Introduction to Julia","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This lab should get everyone up to speed in the basics of Julia's installation, syntax and basic coding. For more detailed introduction you can check out Lectures 1-3 of the bachelor course.","category":"page"},{"location":"lecture_01/lab/#Testing-Julia-installation-(custom-setup)","page":"Lab","title":"Testing Julia installation (custom setup)","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to proceed further let's run a simple script to see, that the setup described in chapter Installation is working properly. After spawning a terminal/cmdline run this command:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia ./test_setup.jl","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The script does the following ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"\"Tests\" if Julia is added to path and can be run with julia command from anywhere\nPrints Julia version info\nChecks Julia version.\nChecks git configuration (name + email)\nCreates an environment configuration files\nInstalls a basic pkg called BenchmarkTools, which we will use for benchmarking a simple function later in the labs.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"There are some quality of life improvements over long term support versions of Julia and thus throughout this course we will use the latest stable release of Julia 1.6.x.","category":"page"},{"location":"lecture_01/lab/#Polynomial-evaluation-example","page":"Lab","title":"Polynomial evaluation example","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let's consider a common mathematical example for evaluation of nth-degree polynomial","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"f(x) = a_nx^n + a_n-1x^n-1 + dots + a_0x^0","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"where x in mathbbR and veca in mathbbR^n+1.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way of writing this in a generic fashion is realizing that essentially the function f is really implicitly containing argument veca, i.e. f equiv f(veca x), yielding the following Julia code","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Evaluate the code of the function called polynomial in Julia REPL and evaluate the function itself with the following arguments.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6] # list coefficients a from a^0 to a^n\nx = 3 # point of evaluation\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way is to just copy&paste into an already running terminal manually. As opposed to the default Python REPL, Julia can deal with the blocks of code and different indentation much better without installation of an ipython-like REPL. There are ways to make this much easier in different text editors/IDEs:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"VSCode - when using Julia extension is installed and .jl file is opened, Ctrl/Cmd+Enter will spawn Julia REPL\nSublime Text - Ctrl/Cmd+Enter with Send Code pkg (works well with Linux terminal or tmux, support for Windows is poor)\nVim - there is a Julia language plugin, which can be combine with vimcmdline to gain similar functionality","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Either way, you should see the following:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Similarly we enter the arguments of the function a and x:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6]\nx = 3","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Function call intuitively takes the name of the function with round brackets as arguments, i.e. works in the same way as majority of programming languages. The result is printed unless a ; is added at the end of the statement.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) # function call","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Thanks to the high level nature of Julia language it is often the case that examples written in pseudocode are almost directly rewritable into the language itself without major changes and the code can be thus interpreted easily.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"(Image: polynomial_explained)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Due to the existence of the end keyword, indentation is not necessary as opposed to other languages such as Python, however it is strongly recommended to use it, see style guide.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Though there are libraries/IDEs that allow us to step through Julia code (Debugger.jl link and VSCode link), here we will explore the code interactively in REPL by evaluating pieces of code separately.","category":"page"},{"location":"lecture_01/lab/#Basic-types,-assignments-and-variables","page":"Lab","title":"Basic types, assignments and variables","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"When defining a variable through an assignment we get the representation of the right side, again this is different from the default behavior in Python, where the output of assignments a = [-19, 7, -4, 6] or x = 3, prints nothing. Internally Julia returns the result of the display function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"a = [-19, 7, -4, 6]\ndisplay(a) # should return the same thing as the line above","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As you can see, the string that is being displayed contains information about the contents of a variable along with it's type in this case this is a Vector/Array of Int types. If the output of display is insufficient the type of variable can be checked with the typeof function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(a)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Additionally for collection/iterable types such as Vector there is also the eltype function, which returns the type of elements in the collection.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"eltype(a)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In most cases variables store just a reference to a place in memory either stack/heap (exceptions are primitive types such as Int, Float) and therefore creating an array a, \"storing\" the reference in b with an assignment and changing elements of b, e.g. b[1] = 2, changes also the values in a.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Create variables x and accumulator, storing floating point 3.0 and integer value 0 respectively. Check the type of variables using typeof function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"x = 3.0\naccumulator = 0\ntypeof(x), typeof(accumulator)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#For-cycles-and-ranges","page":"Lab","title":"For cycles and ranges","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Moving further into the polynomial function we encounter the definition of a for cycle, with the de facto standard syntax","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"for iteration_variable in iterator\n # do something\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As an example of iterator we have used an instance of a range type ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"r = length(a):-1:1\ntypeof(r)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to Python, ranges in Julia are inclusive, i.e. they contain number from start to end - in this case running from 4 to 1 with negative step -1, thus counting down. This can be checked with the collect and/or length functions.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"collect(r)\nlength(r)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Create variable c containing an array of even numbers from 2 to 42. Furthermore create variable d that is different from c only at the 7th position, which will contain 13.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINT: Use collect function for creation of c and copy for making a copy of c.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"c = collect(2:2:42)\nd = copy(c)\nd[7] = 13\nd","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Functions-and-operators","page":"Lab","title":"Functions and operators","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let us now move from the function body to the function definition itself. From the picture at the top of the page, we can infer the general syntax for function definition:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function function_name(arguments)\n # do stuff with arguments and define output value `something`\n return something\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The return keyword can be omitted, if the last line being evaluated contains the result.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"By creating the function polynomial we have defined a variable polynomial, that from now on always refers to a function and cannot be reassigned to a different type, like for example Int.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial = 42","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This is caused by the fact that each function defines essentially a new type, the same like Int ~ Int64 or Vector{Int}.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(polynomial)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"You can check that it is a subtype of the Function abstract type, with the subtyping operator <:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(polynomial) <: Function","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"These concepts will be expanded further in the type system lecture, however for now note that this construction is quite useful for example if we wanted to create derivative rules for our function derivativeof(::typeof(polynomial), ...).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Looking at mathematical operators +, *, we can see that in Julia they are also standalone functions. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"+\n*","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The main difference from our polynomial function is that there are multiple methods, for each of these functions. Each one of the methods coresponds to a specific combination of arguments, for which the function can be specialized to using multiple dispatch. You can see the list by calling a methods function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> methods(+)\n# 190 methods for generic function \"+\": \n[1] +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at\n int.jl:87 \n[2] +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigInt) in Base.GMP at gmp.jl:528 \n[3] +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:534\n...","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"One other notable difference is that these functions allow using both infix and postfix notation a + b and +(a,b), which is a specialty of elementary functions such as arithmetic operators or set operation such as ∩, ∪, ∈. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The functionality of methods is complemented with the reverse lookup methodswith, which for a given type returns a list of methods that can be called with it as an argument.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> methodswith(Int)\n[1] +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87\n[2] +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:534\n[3] +(c::Union{Int16, Int32, Int64, Int8}, x::BigFloat) in Base.MPFR at mpfr.jl:384\n[4] +(x::BigFloat, c::Union{Int16, Int32, Int64, Int8}) in Base.MPFR at mpfr.jl:379\n[5] +(x::BigInt, c::Union{Int16, Int32, Int64, Int8}) in Base.GMP at gmp.jl:533\n...","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Define function called addone with one argument, that adds 1 to the argument.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function addone(x)\n x + 1\nend\naddone(1) == 2","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Calling-for-help","page":"Lab","title":"Calling for help","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to better understand some keywords we have encountered so far, we can ask for help in the Julia's REPL itself with the built-in help terminal. Accessing help terminal can be achieved by writing ? with a query keyword after. This searches documentation of all the available source code to find the corresponding keyword. The simplest way to create documentation, that can be accessed in this way, is using so called docstrings, which are multiline strings written above function or type definition. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"\"\"\"\n polynomial(a, x)\n\nReturns value of a polynomial with coefficients `a` at point `x`.\n\"\"\"\nfunction polynomial(a, x)\n # function body\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"More on this in lecture 4 about pkg development.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Lookup docstring for the basic functions that we have introduced in the previous exercises: typeof, eltype, length, collect, copy, methods and methodswith. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Try it with others, for example with the subtyping operator <:.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Example docstring for typeof function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":" typeof(x)\n\n Get the concrete type of x.\n\n Examples\n ≡≡≡≡≡≡≡≡≡≡\n\n julia> a = 1//2;\n \n julia> typeof(a)\n Rational{Int64}\n \n julia> M = [1 2; 3.5 4];\n \n julia> typeof(M)\n Matrix{Float64} (alias for Array{Float64, 2})","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Testing-waters","page":"Lab","title":"Testing waters","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As the arguments of the polynomial functions are untyped, i.e. they do not specify the allowed types like for example polynomial(a, x::Number) does, the following exercise explores which arguments the function accepts, while giving expected result.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Choose one of the variables af to ac representing polynomial coefficients and try to evaluate it with the polynomial function at point x=3 as before. Lookup the type of coefficient collection variable itself with typeof and the items in the collection with eltype. In this case we allow you to consult your solution with the expandable solution bellow to find out more information about a particular example.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"af = [-19.0, 7.0, -4.0, 6.0]\nat = (-19, 7, -4, 6)\nant = (a₀ = -19, a₁ = 7, a₂ = -4, a₃ = 6)\na2d = [-19 -4; 7 6]\nac = [2i^2 + 1 for i in -2:1]\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(af), eltype(af)\npolynomial(af, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to the basic definition of a type the array is filled with Float64 types and the resulting value gets promoted as well to the Float64.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(at), eltype(at)\npolynomial(at, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"With round brackets over a fixed length vector we get the Tuple type, which is so called immutable \"array\" of a fixed size (its elements cannot be changed, unless initialized from scratch). Each element can be of a different type, but here we have only one and thus the Tuple is aliased into NTuple. There are some performance benefits for using immutable structure, which will be discussed later.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Defining key=value pairs inside round brackets creates a structure called NamedTuple, which has the same properties as Tuple and furthermore its elements can be conveniently accessed by dot syntax, e.g. ant.a₀.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ant), eltype(ant)\npolynomial(ant, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Defining a 2D array is a simple change of syntax, which initialized a matrix row by row separated by ; with spaces between individual elements. The function returns the same result because linear indexing works in 2d arrays in the column major order.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(a2d), eltype(a2d)\npolynomial(a2d, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The last example shows so called array comprehension syntax, where we define and array of known length using and for loop iteration. Resulting array/vector has integer elements, however even mixed type is possible yielding Any, if there isn't any other common supertype to promote every entry into. (Use ? to look what promote and promote_type does.)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ac), eltype(ac)\npolynomial(ac, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"So far we have seen that polynomial function accepts a wide variety of arguments, however there are some understandable edge cases that it cannot handle.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Consider first the vector/array of characters ach","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ach = ['1', '2', '3', '4']","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"which themselves have numeric values (you can check by converting them to Int Int('1') or convert(Int, '1')). In spite of that, our untyped function cannot process such input, as there isn't an operation/method that would allow multiplication of Char and Int type. Julia tries to promote the argument types to some common type, however checking the promote_type(Int, Char) returns Any (union of all types), which tells us that the conversion is not possible automatically.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"typeof(ach), eltype(ach)\npolynomial(ach, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In the stacktrace we can see the location of each function call. If we include the function polynomial from some file poly.jl using include(\"poly.jl\"), we will see that the location changes from REPL[X]:10 to the actual file name.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"By swapping square brackets for round in the array comprehension ac above, we have defined so called generator/iterator, which as opposed to original variable ac does not allocate an array, only the structure that produces it.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ag = (2i^2 + 1 for i in -2:1)\ntypeof(ag), eltype(ag)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"You may notice that the element type in this case is Any, which means that a function using this generator as an argument cannot specialize based on the type and has to infer it every time an element is generated/returned. We will touch on how this affects performance in one of the later lectures.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(ag, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The problem that we face during evaluation is that generator type is missing the getindex operation, as they are made for situations where the size of the collection may be unknown and the only way of obtaining particular elements is through sequential iteration. Generators can be useful for example when creating batches of data for a machine learning training. We can \"fix\" the situation using collect function, mentioned earlier, however that again allocates an array.","category":"page"},{"location":"lecture_01/lab/#Extending/limiting-the-polynomial-example","page":"Lab","title":"Extending/limiting the polynomial example","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Following up on the polynomial example, let's us expand it a little further in order to facilitate the arguments, that have been throwing exceptions. The first direction, which we will move forward to, is providing the user with more detailed error message when an incorrect type of coefficients has been provided.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Design an if-else condition such that the array of Char example throws an error with custom string message, telling the user what went wrong and printing the incorrect input alongside it. Confirm that we have not broken the functionality of other examples from previous exercise.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Throw the ArgumentError(msg) with throw function and string message msg. More details in help mode ? or at the end of this document.\nStrings are defined like this s = \"Hello!\"\nUse string interpolation to create the error message. It allows injecting an expression into a string with the $ syntax b = 1; s = \"Hellow Number $(b)\"\nCompare eltype of the coefficients with Char type.\nThe syntax for if-else:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"if condition\n println(\"true\") # true branch code\nelse\n println(\"false\") # false branch code\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Not equal condition can be written as a != b.\nThrowing an exception automatically returns from the function. Use return inside one of the branches to return the correct value.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The simplest way is to wrap the whole function inside an if-else condition and returning only when the input is \"correct\" (it will still fail in some cases).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n if eltype(a) != Char\n accumulator = 0\n for i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\n end\n return accumulator\n else\n throw(ArgumentError(\"Invalid coefficients $(a) of type Char!\"))\n end\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Now this should show our predefined error message. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(ach, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Testing on other examples should pass without errors and give the same output as before.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x)\npolynomial(af, x)\npolynomial(at, x)\npolynomial(ant, x)\npolynomial(a2d, x)\npolynomial(ac, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The second direction concerns the limitation to index-able structures, which the generator example is not. For this we will have to rewrite the whole loop in a more functional programming approach using map, anonymous function and other concepts.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Rewrite the following code inside our original polynomial function with map, enumerate and anonymous function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"accumulator = 0\nfor i in length(a):-1:1\n accumulator += x^(i-1) * a[i] # ! 1-based indexing for arrays\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"note: Anonymous functions reminder\nx -> x + 1 # unless the reference is stored it cannot be called\nplusone = x -> x + 1 # the reference can be stored inside a variable\nplusone(x) # calling with the same syntax","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Use enumerate to obtain iterator over a that returns a tuple of ia = (i, aᵢ). With Julia 1-based indexing i starts also from 1 and goes up to length(a).\nPass this into a map with either in-place or predefined anonymous function that does the operation of x^(i-1) * aᵢ.\nUse sum to collect the resulting array into accumulator variable or directly into the return command.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Can you figure out how to use the mapreduce function here? See entry in the help mode ?.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Ordered from the longest to the shortest, here are three examples with the same functionality (and there are definitely many more). Using the map(iterable) do itervar ... end syntax, that creates anonymous function from the block of code.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n powers = map(enumerate(a)) do (i, aᵢ)\n x^(i-1) * aᵢ\n end\n accumulator = sum(powers)\n return accumulator\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Using the default syntax for map and storing the anonymous into a variable","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n polypow(i,aᵢ) = x^(i-1) * aᵢ\n powers = map(polypow, enumerate(a))\n return sum(powers)\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As the function polypow is used only once, there is no need to assign it to a local variable. Note the sightly awkward additional parenthesis in the argument of the lambda function.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function polynomial(a, x)\n powers = map(((i,aᵢ),) -> x^(i-1) * aᵢ, enumerate(a))\n sum(powers)\nend\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Checking the behavior on all the inputs.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x)\npolynomial(af, x)\npolynomial(at, x)\npolynomial(ant, x)\npolynomial(a2d, x)\npolynomial(ach, x)\npolynomial(ac, x)\npolynomial(ag, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: You may have noticed that in the example above, the powers variable is allocating an additional, unnecessary vector. With the current, scalar x, this is not such a big deal. But in your homework you will generalize this function to matrix inputs of x, which means that powers becomes a vector of (potentially very large) matrices. This is a very natural use case for the mapreduce: function:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) = mapreduce(+, enumerate(a), init=zero(x)) do (i, aᵢ)\n x^(i-1) * aᵢ\nend\n\npolynomial(a, x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Let's unpack what is happening here. If the function mapreduce(f, op, itr) is called with op=+ it returns the same result as sum(map(f, itr)). In contrast to sum(map(f, itr)) (which allocates a vector as a result of map and then sums) mapreduce applies f to an element in itr and immediately accumulates the result with the given op=+.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(a, x) = sum(ia -> x^(ia[1]-1) * ia[2], enumerate(a))\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#How-to-use-code-from-other-people","page":"Lab","title":"How to use code from other people","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The script that we have run at the beginning of this lab has created two new files inside the current folder:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"./\n ├── Manifest.toml\n └── Project.toml","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Every folder with a toml file called Project.toml, can be used by Julia's pkg manager into setting so called environment, which contains a list of pkgs to be installed. Setting up or more often called activating an environment can be done either before starting Julia itself by running julia with the --project XXX flag or from within the Julia REPL, by switching to Pkg mode with ] key (similar to the help mode activated by pressing ?) and running command activate.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"So far we have used the general environment (depending on your setup), which by default does not come with any 3rd party packages and includes only the base and standard libraries - already quite powerful on its own. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to find which environment is currently active, run the following:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"pkg> status","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The output of such command usually indicates the general environment located at .julia/ folder (${HOME}/.julia/ or ${APPDATA}/.julia/ in case of Unix/Windows based systems respectively)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"pkg> status\nStatus `~/.julia/environments/v1.6/Project.toml` (empty project)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Generally one should avoid working in the general environment, with the exception of some generic pkgs, such as PkgTemplates.jl, which is used for generating library templates/folder structure like the one above (link), more on this in the lecture on pkg development. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Activate the environment inside the current folder and check that the BenchmarkTools package has been installed. Use BenchmarkTools pkg's @btime to benchmark our polynomial function with the following arguments.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"aexp = ones(10) ./ factorial.(0:9)\nx = 1.1\nnothing #hide","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"HINTS:","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In pkg mode use the command activate and status to check the presence. \nIn order to import the functionality from other package, lookup the keyword using in the repl help mode ?. \nThe functionality that we want to use is the @btime macro (it acts almost like a function but with a different syntax @macro arg1 arg2 arg3 ...). More on macros in lecture 7.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: Compare the output of polynomial(aexp, x) with the value of exp(x), which it approximates.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"note: Broadcasting\nIn the assignment's code, we are using quite ubiquitous concept in Julia called broadcasting or simply the dot-syntax - represented here by ./, factorial.. This concept allows to map both simple arithmetic operations as well as custom functions across arrays, with the added benefit of increased performance, when the broadcasting system can merge operations into a more efficient code. More information can be found in the official documentation or section of our bachelor course.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"There are other options to import a function/macro from a different package, however for now let's keep it simple with the using Module syntax, that brings to the REPL, all the variables/function/macros exported by the BenchmarkTools pkg. If @btime is exported, which it is, it can be accessed without specification i.e. just by calling @btime without the need for BenchmarkTools.@btime. More on the architecture of pkg/module loading in the package developement lecture.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"julia> using BenchmarkTools\n\njulia> @btime polynomial(aexp, x)\n 97.119 ns (1 allocation: 16 bytes)\n3.004165230550543","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The output gives us the time of execution averaged over multiple runs (the number of samples is defined automatically based on run time) as well as the number of allocations and the output of the function, that is being benchmarked.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"BONUS: The difference between our approximation and the \"actual\" function value computed as a difference of the two. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"polynomial(aexp, x) - exp(x)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"The apostrophes in the previous sentence are on purpose, because implementation of exp also relies on a finite sum, though much more sophisticated than the basic Taylor expansion.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_01/lab/#Discussion-and-future-directions","page":"Lab","title":"Discussion & future directions","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Instead of if-else statements that would throw an error for different types, in Julia, we generally see the pattern of typing the function in a way, that for other than desirable types MethodError is emitted with the information about closest matching methods. This is part of the design process in Julia of a function and for the particular functionality of the polynomial example, we can look into the Julia itself, where it has been implemented in the evalpoly function","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"methods(evalpoly)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Another avenue, that we have only touched with the BenchmarkTools, is performance and will be further explored in the later lectures.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"With the next lecture focused on typing in Julia, it is worth noting that polynomials lend themselves quite nicely to a definition of a custom type, which can help both readability of the code as well further extensions.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"struct Polynom{C}\n coefficients::{C}\nend\n\nfunction (p:Polynom)(x)\n polynomial(p.coefficients, x)\nend","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"","category":"page"},{"location":"lecture_01/lab/#Useful-resources","page":"Lab","title":"Useful resources","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Getting Started tutorial from JuliaLang documentation - Docs\nConverting syntax between MATLAB ↔ Python ↔ Julia - Cheatsheet\nBachelor course for refreshing your knowledge - Course\nStylistic conventions - Style Guide\nReserved keywords - List\nOfficial cheatsheet with basic syntax - link","category":"page"},{"location":"lecture_01/lab/#lab_errors","page":"Lab","title":"Various errors and how to read them","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This section summarizes most commonly encountered types of errors in Julia and how to resolve them or at least understand, what has gone wrong. It expands a little bit the official documentation, which contains the complete list with examples. Keep in mind again, that you can use help mode in the REPL to query error types as well.","category":"page"},{"location":"lecture_01/lab/#MethodError","page":"Lab","title":"MethodError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This type of error is most commonly thrown by Julia's multiple dispatch system with a message like no method matching X(args...), seen in two examples bellow.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"2 * 'a' # many candidates\ngetindex((i for i in 1:4), 3) # no candidates","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Both of these examples have a short stacktrace, showing that the execution failed on the top most level in REPL, however if this code is a part of some function in a separate file, the stacktrace will reflect it. What this error tells us is that the dispatch system could not find a method for a given function, that would be suitable for the type of arguments, that it has been given. In the first case Julia offers also a list of candidate methods, that match at least some of the arguments","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"When dealing with basic Julia functions and types, this behavior can be treated as something given and though one could locally add a method for example for multiplication of Char and Int, there is usually a good reason why Julia does not support such functionality by default. On the other hand when dealing with user defined code, this error may suggest the developer, that either the functions are too strictly typed or that another method definition is needed in order to satisfy the desired functionality.","category":"page"},{"location":"lecture_01/lab/#InexactError","page":"Lab","title":"InexactError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This type of error is most commonly thrown by the type conversion system (centered around convert function), informing the user that it cannot exactly convert a value of some type to match arguments of a function being called.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Int(1.2) # root cause\nappend!([1,2,3], 1.2) # same as above but shows the root cause deeper in the stack trace","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In this case the function being Int and the value a floating point. The second example shows InexactError may be caused deeper inside an inconspicuous function call, where we want to extend an array by another value, which is unfortunately incompatible.","category":"page"},{"location":"lecture_01/lab/#ArgumentError","page":"Lab","title":"ArgumentError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"As opposed to the previous two errors, ArgumentError can contain user specified error message and thus can serve multiple purposes. It is however recommended to throw this type of error, when the parameters to a function call do not match a valid signature, e.g. when factorial were given negative or non-integer argument (note that this is being handled in Julia by multiple dispatch and specific DomainError).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This example shows a concatenation of two 2d arrays of incompatible sizes 3x3 and 2x2.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"hcat(ones(3,3), zeros(2,2))","category":"page"},{"location":"lecture_01/lab/#KeyError","page":"Lab","title":"KeyError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"This error is specific to hash table based objects such as the Dict type and tells the user that and indexing operation into such structure tried to access or delete a non-existent element.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"d = Dict(:a => [1,2,3], :b => [1,23])\nd[:c]","category":"page"},{"location":"lecture_01/lab/#TypeError","page":"Lab","title":"TypeError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Type assertion failure, or calling an intrinsic function (inside LLVM, where code is strictly typed) with incorrect argument type. In practice this error comes up most often when comparing value of a type against the Bool type as seen in the example bellow.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"if 1 end # calls internally typeassert(1, Bool)\ntypeassert(1, Bool)","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In order to compare inside conditional statements such as if-elseif-else or the ternary operator x ? a : b the condition has to be always of Bool type, thus the example above can be fixed by the comparison operator: if 1 == 1 end (in reality either the left or the right side of the expression contains an expression or a variable to compare against).","category":"page"},{"location":"lecture_01/lab/#UndefVarError","page":"Lab","title":"UndefVarError","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"While this error is quite self-explanatory, the exact causes are often quite puzzling for the user. The reason behind the confusion is to do with code scoping, which comes into play for example when trying to access a local variable from outside of a given function or just updating a global variable from within a simple loop. ","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"In the first example we show the former case, where variable is declared from within a function and accessed from outside afterwards.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"function plusone(x)\n uno = 1\n return x + uno\nend\nuno # defined only within plusone","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Unless there is variable I_am_not_defined in the global scope, the following should throw an error.","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"I_am_not_defined","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"Often these kind of errors arise as a result of bad code practices, such as long running sessions of Julia having long forgotten global variables, that do not exist upon new execution (this one in particular has been addressed by the authors of the reactive Julia notebooks Pluto.jl).","category":"page"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"For more details on code scoping we recommend particular places in the bachelor course lectures here and there.","category":"page"},{"location":"lecture_01/lab/#ErrorException-and-error-function","page":"Lab","title":"ErrorException & error function","text":"","category":"section"},{"location":"lecture_01/lab/","page":"Lab","title":"Lab","text":"ErrorException is the most generic error, which can be thrown/raised just by calling the error function with a chosen string message. As a result developers may be inclined to misuse this for any kind of unexpected behavior a user can run into, often providing out-of-context/uninformative messages.","category":"page"},{"location":"lecture_01/demo/#Extensibility-of-the-language","page":"Examples","title":"Extensibility of the language","text":"","category":"section"},{"location":"lecture_01/demo/#DifferentialEquations","page":"Examples","title":"DifferentialEquations","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"A package for solving differential equations, similar to odesolve in Matlab.","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Example:","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using DifferentialEquations\nfunction lotka_volterra(du,u,p,t)\n x, y = u\n α, β, δ, γ = p\n du[1] = dx = α*x - β*x*y\n du[2] = dy = -δ*y + γ*x*y\nend\nu0 = [1.0,1.0]\ntspan = (0.0,10.0)\np = [1.5,1.0,3.0,1.0]\nprob = ODEProblem(lotka_volterra,u0,tspan,p)\n\nsol = solve(prob)\nusing Plots\nplot(sol)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"(Image: )","category":"page"},{"location":"lecture_01/demo/#Measurements","page":"Examples","title":"Measurements","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"A package defining \"numbers with precision\" and complete algebra on these numbers:","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using Measurements\n\na = 4.5 ± 0.1\nb = 3.8 ± 0.4\n\n2a + b\nsin(a)/cos(a) - tan(a)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"It also defines recipes for Plots.jl how to plot such numbers.","category":"page"},{"location":"lecture_01/demo/#Starting-ODE-from-an-interval","page":"Examples","title":"Starting ODE from an interval","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"using Measurements\nu0 = [1.0±0.1,1.0±0.01]\n\nprob = ODEProblem(lotka_volterra,u0,tspan,p)\nsol = solve(prob)\nplot(sol,denseplot=false)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"(Image: )","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"all algebraic operations are defined, \npasses all grid refinement techniques\nplot uses the correct plotting for intervals","category":"page"},{"location":"lecture_01/demo/#Integration-with-other-toolkits","page":"Examples","title":"Integration with other toolkits","text":"","category":"section"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Flux: toolkit for modelling Neural Networks. Neural network is a function.","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"integration with Measurements,\nIntegration with ODE (think of NN as part of the ODE)","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"Turing: Probabilistic modelling toolkit","category":"page"},{"location":"lecture_01/demo/","page":"Examples","title":"Examples","text":"integration with FLux (NN)\ninteration with ODE\nusing arbitrary bijective transformations, Bijectors.jl","category":"page"},{"location":"lecture_02/lab/#lab02","page":"Lab","title":"Lab 2: Predator-Prey Agents","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"In the next labs you will implement your own predator-prey model. The model will contain wolves, sheep, and - to feed your sheep - some grass. The final simulation will be turn-based and the agents will be able to eat each other, reproduce, and die in every iteration. At every iteration of the simulation each agent will step forward in time via the agent_step! function. The steps for the agent_step! methods of animals and plants are written below in pseudocode.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"# for animals:\nagent_step!(animal, world)\n decrement energy by 1\n find & eat food (with probability pf)\n die if no more energy\n reproduce (with probability pr)\n\n# for plants:\nagent_step!(plant, world)\n grow if not at maximum size","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The world in which the agents live will be the simplest possible world with zero dimensions (i.e. a Dict of ID=>Agent). Running and plotting your final result could look something like the plot below.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"(Image: img)","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We will start implementing the basic functionality for each Agent like eat!ing, reproduce!ing, and a very simplistic World for your agents to live in. In the next lab you will refine both the type hierarchy of your Agents, as well as the design of the World in order to leverage the power of Julia's type system and compiler.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We start with a very basic type hierarchy:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"abstract type Agent end\nabstract type Animal <: Agent end\nabstract type Plant <: Agent end","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We will implement the World for our Agents later, but it will essentially be implemented by a Dict which maps unique IDs to an Agent. Hence, every agent will need an ID.","category":"page"},{"location":"lecture_02/lab/#The-Grass-Agent","page":"Lab","title":"The Grass Agent","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Let's start by implementing some Grass which will later be able to grow during each iteration of our simulation.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Define a mutable struct called Grass which is a subtype of Plant has the fields id (the unique identifier of this Agent - every agent needs one!), size (the current size of the Grass), and max_size. All fields should be integers.\nDefine a constructor for Grass which, given only an ID and a maximum size m, will create an instance of Grass that has a randomly initialized size in the range 1m. It should also be possible to create Grass, just with an ID and a default max_size of 10.\nImplement Base.show(io::IO, g::Grass) to get custom printing of your Grass such that the Grass is displayed with its size in percent of its max_size.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Hint: You can implement a custom show method for a new type MyType like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"struct MyType\n x::Bool\nend\nBase.show(io::IO, a::MyType) = print(io, \"MyType $(a.x)\")","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Since Julia 1.8 we can also declare some fields of mutable structs as const, which can be used both to prevent us from mutating immutable fields (such as the ID) but can also be used by the compiler in certain cases.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Grass <: Plant\n const id::Int\n size::Int\n const max_size::Int\nend\n\nGrass(id,m=10) = Grass(id, rand(1:m), m)\n\nfunction Base.show(io::IO, g::Grass)\n x = g.size/g.max_size * 100\n # hint: to type the leaf in the julia REPL you can do:\n # \\:herb:\n print(io,\"🌿 #$(g.id) $(round(Int,x))% grown\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Creating a few Grass agents can then look like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Grass(1,5)\ng = Grass(2)\ng.id = 5","category":"page"},{"location":"lecture_02/lab/#Sheep-and-Wolf-Agents","page":"Lab","title":"Sheep and Wolf Agents","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Animals are slightly different from plants. They will have an energy E, which will be increase (or decrease) if the agent eats (or reproduces) by a certain amount Delta E. Later we will also need a probability to find food p_f and a probability to reproduce p_r.c","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Define two mutable structs Sheep and Wolf that are subtypes of Animal and have the fields id, energy, Δenergy, reprprob, and foodprob.\nDefine constructors with the following default values:\nFor 🐑: E=4, Delta E=02, p_r=08, and p_f=06.\nFor 🐺: E=10, Delta E=8, p_r=01, and p_f=02.\nOverload Base.show to get pretty printing for your two new animals.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Solution for Sheep","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Sheep <: Animal\n const id::Int\n const energy::Float64\n Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\nend\n\nSheep(id, e=4.0, Δe=0.2, pr=0.8, pf=0.6) = Sheep(id,e,Δe,pr,pf)\n\nfunction Base.show(io::IO, s::Sheep)\n e = s.energy\n d = s.Δenergy\n pr = s.reprprob\n pf = s.foodprob\n print(io,\"🐑 #$(s.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Solution for Wolf:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct Wolf <: Animal\n const id::Int\n energy::Float64\n const Δenergy::Float64\n const reprprob::Float64\n const foodprob::Float64\nend\n\nWolf(id, e=10.0, Δe=8.0, pr=0.1, pf=0.2) = Wolf(id,e,Δe,pr,pf)\n\nfunction Base.show(io::IO, w::Wolf)\n e = w.energy\n d = w.Δenergy\n pr = w.reprprob\n pf = w.foodprob\n print(io,\"🐺 #$(w.id) E=$e ΔE=$d pr=$pr pf=$pf\")\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Sheep(4)\nWolf(5)","category":"page"},{"location":"lecture_02/lab/#The-World","page":"Lab","title":"The World","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Before our agents can eat or reproduce we need to build them a World. The simplest (and as you will later see, somewhat suboptimal) world is essentially a Dict from IDs to agents. Later we will also need the maximum ID, lets define a world with two fields:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"mutable struct World{A<:Agent}\n agents::Dict{Int,A}\n max_id::Int\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise:
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Implement a constructor for the World which accepts a vector of Agents.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function World(agents::Vector{<:Agent})\n max_id = maximum(a.id for a in agents)\n World(Dict(a.id=>a for a in agents), max_id)\nend\n\n# optional: overload Base.show\nfunction Base.show(io::IO, w::World)\n println(io, typeof(w))\n for (_,a) in w.agents\n println(io,\" $a\")\n end\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/#Sheep-eats-Grass","page":"Lab","title":"Sheep eats Grass","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"We can implement the behaviour of our various agents with respect to each other by leveraging Julia's multiple dispatch.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Implement a function eat!(::Sheep, ::Grass, ::World) which increases the sheep's energy by Delta E multiplied by the size of the grass.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"After the sheep's energy is updated the grass is eaten and its size counter has to be set to zero.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Note that you do not yet need the world in this function. It is needed later for the case of wolves eating sheep.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function eat!(sheep::Sheep, grass::Grass, w::World)\n sheep.energy += grass.size * sheep.Δenergy\n grass.size = 0\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Below you can see how a fully grown grass is eaten by a sheep. The sheep's energy changes size of the grass is set to zero.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"grass = Grass(1)\nsheep = Sheep(2)\nworld = World([grass, sheep])\neat!(sheep,grass,world);\nworld","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Note that the order of the arguments has a meaning here. Calling eat!(grass,sheep,world) results in a MethodError which is great, because Grass cannot eat Sheep.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"eat!(grass,sheep,world);","category":"page"},{"location":"lecture_02/lab/#Wolf-eats-Sheep","page":"Lab","title":"Wolf eats Sheep","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The eat! method for wolves increases the wolf's energy by sheep.energy * wolf.Δenergy and kills the sheep (i.e. removes the sheep from the world). There are other situationsin which agents die , so it makes sense to implement another function kill_agent!(::Animal,::World).","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Hint: You can use delete! to remove agents from the dictionary in your world.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function eat!(wolf::Wolf, sheep::Sheep, w::World)\n wolf.energy += sheep.energy * wolf.Δenergy\n kill_agent!(sheep,w)\nend\n\nkill_agent!(a::Agent, w::World) = delete!(w.agents, a.id)\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"With a correct eat! method you should get results like this:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"grass = Grass(1);\nsheep = Sheep(2);\nwolf = Wolf(3);\nworld = World([grass, sheep, wolf])\neat!(wolf,sheep,world);\nworld","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"The sheep is removed from the world and the wolf's energy increased by Delta E.","category":"page"},{"location":"lecture_02/lab/#Reproduction","page":"Lab","title":"Reproduction","text":"","category":"section"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Currently our animals can only eat. In our simulation we also want them to reproduce. We will do this by adding a reproduce! method to Animal.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      Exercise
      \n
      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"Write a function reproduce! that takes an Animal and a World. Reproducing will cost an animal half of its energy and then add an almost identical copy of the given animal to the world. The only thing that is different from parent to child is the ID. You can simply increase the max_id of the world by one and use that as the new ID for the child.","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"
      \n
      \nSolution:

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function reproduce!(a::Animal, w::World)\n a.energy = a.energy/2\n new_id = w.max_id + 1\n â = deepcopy(a)\n â.id = new_id\n w.agents[â.id] = â\n w.max_id = new_id\nend","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"You can avoid mutating the id field (which could be considered bad practice) by reconstructing the child from scratch:","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"function reproduce!(a::A, w::World) where A<:Animal\n a.energy = a.energy/2\n a_vals = [getproperty(a,n) for n in fieldnames(A) if n!=:id]\n new_id = w.max_id + 1\n â = A(new_id, a_vals...)\n w.agents[â.id] = â\n w.max_id = new_id\nend\nnothing # hide","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"

      ","category":"page"},{"location":"lecture_02/lab/","page":"Lab","title":"Lab","text":"s1, s2 = Sheep(1), Sheep(2)\nw = World([s1, s2])\nreproduce!(s1, w);\nw","category":"page"},{"location":"","page":"Home","title":"Home","text":"\"Scientific\n\"Scientific","category":"page"},{"location":"","page":"Home","title":"Home","text":"","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Plots\nENV[\"GKSwstype\"] = \"100\"\ngr()","category":"page"},{"location":"","page":"Home","title":"Home","text":"Scientific Programming requires the highest performance but we also want to write very high level code to enable rapid prototyping and avoid error prone, low level implementations.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The Julia programming language is designed with exactly those requirements of scientific computing in mind. In this course we will show you how to make use of the tools and advantages that jit-compiled Julia provides over dynamic, high-level languages like Python or lower level languages like C++.","category":"page"},{"location":"","page":"Home","title":"Home","text":"
      \n \n
      \n Learn the power of abstraction.\n Example: The essence of forward mode automatic differentiation.\n
      \n
      ","category":"page"},{"location":"","page":"Home","title":"Home","text":"Before joining the course, consider reading the following two blog posts to figure out if Julia is a language in which you want to invest your time.","category":"page"},{"location":"","page":"Home","title":"Home","text":"What is great about Julia.\nWhat is bad about Julia.","category":"page"},{"location":"#What-will-you-learn?","page":"Home","title":"What will you learn?","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"First and foremost you will learn how to think julia - meaning how write fast, extensible, reusable, and easy-to-read code using things like optional typing, multiple dispatch, and functional programming concepts. The later part of the course will teach you how to use more advanced concepts like language introspection, metaprogramming, and symbolic computing. Amonst others you will implement your own automatic differetiation (the backbone of modern machine learning) package based on these advanced techniques that can transform intermediate representations of Julia code.","category":"page"},{"location":"#Organization","page":"Home","title":"Organization","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"This course webpage contains all information about the course that you need, including lecture notes, lab instructions, and homeworks. The official format of the course is 2+2 (2h lectures/2h labs per week) for 4 credits.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The official course code is: B0M36SPJ and the timetable for the winter semester 2022 can be found here.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The course will be graded based on points from your homework (max. 20 points) and points from a final project (max. 30 points).","category":"page"},{"location":"","page":"Home","title":"Home","text":"Below is a table that shows which lectures have homeworks (and their points).","category":"page"},{"location":"","page":"Home","title":"Home","text":"Homework 1 2 3 4 5 6 7 8 9 10 11 12 13\nPoints 2 2 2 2 2 2 2 2 - 2 - 2 -","category":"page"},{"location":"","page":"Home","title":"Home","text":"Hint: The first few homeworks are easier. Use them to fill up your points.","category":"page"},{"location":"#final_project","page":"Home","title":"Final project","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The final project will be individually agreed on for each student. Ideally you can use this project to solve a problem you have e.g. in your thesis, but don't worry - if you cannot come up with an own project idea, we will suggest one to you. More info and project suggestion can be found here.","category":"page"},{"location":"#Grading","page":"Home","title":"Grading","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Your points from the homeworks and the final project are summed and graded by the standard grading scale below.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Grade A B C D E F\nPoints 45-50 40-44 35-39 30-34 25-29 0-25","category":"page"},{"location":"#emails","page":"Home","title":"Teachers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"– E-mail Room Role\nTomáš Pevný pevnak@protonmail.ch KN:E-406 Lecturer\nVašek Šmídl smidlva1@fjfi.cvut.cz KN:E-333 Lecturer\nMatěj Zorek zorekmat@fel.cvut.cz KN:E-333 Lab Instructor\nNiklas Heim heimnikl@fel.cvut.cz KN:E-333 Lab Instructor","category":"page"},{"location":"#Prerequisites","page":"Home","title":"Prerequisites","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"There are no hard requirements to take the course, but if you are not at all familiar with Julia we recommend you to take Julia for Optimization and Learning before enrolling in this course. The Functional Programming course also contains some helpful concepts for this course. And knowledge about computer hardware, namely basics of how CPU works, how it interacts with memory through caches, and basics of multi-threadding certainly helps.","category":"page"},{"location":"#References","page":"Home","title":"References","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Official documentation\nWorkflow tips, and what is new in v1.9\nThink Julia: How to Think Like a Computer Scientist\nFrom Zero to Julia!\nWikiBooks\nJustin Krumbiel's excellent introduction to the package manager.\njuliadatascience.io contains an excellent introduction to plotting with Makie.\nThe art of multiple dispatch\nMIT Course: Julia Computation\nTim Holy's Advanced Scientific Computing","category":"page"}] }