{ "cells": [ { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-e39dce2363a3b28b", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "# Programming in Julia\n", "\n", "This notebook will teach you some of the basic programming routines in Julia. You will need these skills to complete the probabilistic programming assignments later on in the course. We will assume basic familiarity with programming, such as for-loops, if-else statements and function definitions.\n", "\n", "Resources:\n", "- [Julia documentation](https://docs.julialang.org/en/v1/)\n", "- [Differences to Python, Matlab, C and Java](https://docs.julialang.org/en/v1/manual/noteworthy-differences/)\n", "- [Video on getting started](https://www.youtube.com/watch?v=4igzy3bGVkQ&list=PLP8iPy9hna6SCcFv3FvY_qjAmtTsNYHQE)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-3c2e8eba10e12225", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Data types\n", "\n", "- References: [Numbers](https://docs.julialang.org/en/v1/base/numbers/), [Integers and Float](https://docs.julialang.org/en/v1/manual/integers-and-floating-point-numbers/), [Strings](https://docs.julialang.org/en/v1/base/strings/), [Symbols](https://docs.julialang.org/en/v1/manual/metaprogramming/).\n", "\n", "Numbers in Julia have specific types, most notably `Integer`, `Real` and `Complex`. It is important to be aware of what type your numbers are because many functions operate differently on different number types. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Int64" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "a = 3\n", "typeof(a)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-2778002073c87b29", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "`Int64` is a 64-byte integer. Other options are 32-,16-, or 8-bit integers and they can be unsigned as well. The default real-valued numbers is a 64-bit floating-point number:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Float64" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "a = 3.0\n", "typeof(a)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-b4f29642d811571a", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Converting number types is easy:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Float64" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "a = convert(Float64, 2)\n", "typeof(a)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-ed3bc3a833acccd0", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Strings are constructed by enclosing symbols within double parentheses." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "String" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "a = \"3\"\n", "typeof(a)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-c1be2e1ea2b90c34", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "They can be concatenated by multiplication (which is an example of a function, namely `*`, which acts differently on different input argument types): " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"ab\"" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "ab = \"a\"*\"b\"" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-76b62f36bf62f546", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "You can incorporate numbers into strings through a `$` call:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"a = 3\"" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "\"a = $a\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Julia also has a `Symbol` type, which is used for [meta-programming](https://docs.julialang.org/en/v1/manual/metaprogramming/) (not important for this course). A `Symbol` type is characterized by a `:` in front of a word. For example:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Symbol" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "s = :foo\n", "typeof(s)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You will not have to use `Symbol`s in the course, but you may see one every once in a while in the lecture notes. For example, the `plot` command (see Visualization section), may have a `markershape=:cross` argument." ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-597f95a479025e8a", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Array manipulation\n", "\n", "- References: [Arrays](https://docs.julialang.org/en/v1/base/arrays/)\n", "\n", "Arrays are indexed with square brackets, `A[i,j]`. You can construct a matrix by enclosing a set of numbers with square brackets. If you separate your numbers with comma's, then you will get a column vector:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4-element Vector{Int64}:\n", " 1\n", " 2\n", " 3\n", " 4" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x = [1,2,3,4]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-55802261c54cb78c", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "If you use spaces, then you will construct a row vector (i.e., a matrix of dimensions 1 by n):" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×4 Matrix{Int64}:\n", " 1 2 3 4" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x = [1 2 3 4]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-0797fa9c8ec1c27d", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Matrices can be constructed by separating elements with spaces and rows by semicolons:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2×2 Matrix{Int64}:\n", " 1 2\n", " 3 4" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "X = [1 2; 3 4]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-752e17ff7e221dc6", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Common array constructors are:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2×3 Matrix{Float64}:\n", " 1.89942 1.58604 0.790389\n", " -0.243699 0.464712 -0.0802384" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "X = zeros(2,3)\n", "Y = ones(2,3)\n", "Z = randn(2,3)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-2842d71d3b6fef26", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Matrix operations are intuitive and similar to the mathematical notation:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Vector{Int64}:\n", " 4\n", " 7\n", " 8" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "A = [3 2 1; 2 3 2; 1 2 3]\n", "x = [0, 1, 2]\n", "b = A*x" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-5ef3ae2aa92ab37c", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "A matrix can be transposed by a single parenthesis, `A'`. Note that this does not mutate the array in memory. It just tells functions defined for matrices that it should change how it indexes the matrix's elements." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "23" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "c = x'*A*x" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-07efb0f4ef442665", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Broadcasting\n", "\n", "- Reference: [Broadcasting](https://docs.julialang.org/en/v1/manual/arrays/#Broadcasting)\n", "\n", "You can apply functions to elements in an array by placing a dot in front:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×3 Matrix{Int64}:\n", " 3 6 9" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ " 3 .*[1 2 3]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-31680dda8e3c1644", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "This also works for named functions:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3-element Vector{Float64}:\n", " 0.8414709848078965\n", " 0.9092974268256817\n", " 0.1411200080598672" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sin.([1., 2., 3.])" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-86a74a70e6d21c8d", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Iteration\n", "\n", "- Reference: [Collections](https://docs.julialang.org/en/v1/base/collections/)\n", "\n", "For-loops are one of the simplest forms of iteration and can be defined in a number of ways. First, the matlab way:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "3\n", "5\n" ] } ], "source": [ "for n = 1:2:5\n", " println(n)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-f584f54f4771da9f", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Next, we can use the `range` command to construct an array of numbers and then use the `in` command to loop over elements in the array:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0\n", "4.0\n" ] } ], "source": [ "num_range = range(0, stop=4, length=2)\n", "for n in num_range\n", " println(n)\n", "end" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-3dcbd8a0da6ff030", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "If you need both the index and the value of the array element, you can use the `enumerate` command:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1, 0.0\n", "2, 4.0\n" ] } ], "source": [ "for (j,n) in enumerate(num_range)\n", " println(\"$j, $n\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-009da1637a8bd0de", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "You may be familiar with \"list comprehension\", which is a shortened form of iterating through a collection:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2-element Vector{String}:\n", " \"n = 0.0\"\n", " \"n = 4.0\"" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "[\"n = $n\" for n in num_range]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-46cbf7bfca3c6d76", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Control flow\n", "\n", "- References: [Control flow](https://docs.julialang.org/en/v1/manual/control-flow/), [Logical Operators](https://docs.julialang.org/en/v1/manual/missing/#Logical-operators)\n", "\n", "Control flow refers to redirecting how a compiler goes through a program. Instead of traversing it line-by-line, things like `if-else` statements can make a compiler skip steps. These require logical operations: you can use `==` to check if two variables have the same value, `===` to check if they are actually the same object (i.e., same memory reference) and `!=` to check if they're not equal." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Positive\n" ] } ], "source": [ "a = 3.0\n", "if a < 0 \n", " println(\"Negative\")\n", "elseif a == 0\n", " println(\"0.0\")\n", "else \n", " println(\"Positive\")\n", "end" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-44cf3744dc8b7ea8", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Simple `if-else` statements can often be replaced by `ternary` checks. Essentially, you ask a question (a logical operation followed by `?`) and then tell the program what to do when the answer is yes (written immediately after the question) or no (written after the yes-answer followed by a `:`)." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Positive\n" ] } ], "source": [ "a > 0 ? println(\"Positive\") : println(\"Not positive\")" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-2bd8fafa975e39df", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Functions\n", "\n", "- References: [Functions](https://docs.julialang.org/en/v1/manual/functions/), [Mutation](https://docs.julialang.org/en/v1/manual/style-guide/#bang-convention)\n", "\n", "Function and expressions are a core component of the julia language. Its \"multiple dispatch\" feature means that you can define multiple functions with the same name but with behaviour that depends on the input argument types. When you're starting out, you may not notice this so much, but you will start to appreciate this feature tremendously when you begin to professionally develop software." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\"Bah!\"" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "function foo(bar::Float64)\n", " message = \"Boo!\"\n", " return message\n", "end\n", "\n", "function foo(bar::Integer)\n", " message = \"Bah!\"\n", " return message\n", "end\n", "\n", "foo(1)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-89334d50f79b3b6e", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Note that the `return` argument does not need to be at the end of a function ([the return keyword](https://docs.julialang.org/en/v1/manual/functions/#The-return-Keyword)).\n", "\n", "You don't actually need the `function` keyword if you have simple enough functions:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.25" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fn(x::Number) = 1/x\n", "fn(4)" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-52be8f7c7644d2ae", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "You can add keyword arguments to a function, which are input arguments with default values:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1×2 Matrix{Float64}:\n", " 1.0 0.333333" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fn(; number::Number = 1) = 1/number\n", "[fn() fn(number=3)]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-fdc3777677dd70d6", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Functions that modify their input arguments instead of creating new output variables are typically marked with an `!`. Below I have defined an unsorted vector and I call `sort` to sort it in increasing order. If I call the sort function and the original vector, then they will be different:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×2 Matrix{Int64}:\n", " 1 1\n", " 2 3\n", " 3 2" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "x = [1, 3, 2]\n", "[sort(x) x]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-4d68be5af3628fca", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "But if I call the `sort!` function and the original vector, you'll see that the original vector is now also sorted." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3×2 Matrix{Int64}:\n", " 1 1\n", " 2 2\n", " 3 3" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "[sort!(x) x]" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-e1a8e25a1191dbc5", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "## Software packages\n", "\n", "- Reference: [Packages and modules](https://docs.julialang.org/en/v1/manual/faq/#Packages-and-Modules)\n", "\n", "Just like with Python, there are thousands of additional software packages that can be imported to provide specific functionalities. You'll encounter some of the most common ones throughout the course. \n", "\n", "We have provided an \"environment\" that automatically downloads all the packages you need throughout the course. The three lines of code below point to the environment specification itself, a Project.toml file containing a list of packages, and construct the environment. If the specified packages are not installed, then these will be automatically added. This will take a bit of time the first time you run it, but it will only take a few seconds afterwards." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-108da214ac7e1c99", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m\u001b[1m Activating\u001b[22m\u001b[39m new project at `~/syndr/Wouter/Onderwijs/Vakken/tueindhoven/5SSD0 - Bayesian Machine Learning & Information Processing/2024-2025 Q2/BMLIP/lessons/notebooks`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/syndr/Wouter/Onderwijs/Vakken/tueindhoven/5SSD0 - Bayesian Machine Learning & Information Processing/2024-2025 Q2/BMLIP/lessons/notebooks/Project.toml`\n", "\u001b[32m\u001b[1m No Changes\u001b[22m\u001b[39m to `~/syndr/Wouter/Onderwijs/Vakken/tueindhoven/5SSD0 - Bayesian Machine Learning & Information Processing/2024-2025 Q2/BMLIP/lessons/notebooks/Manifest.toml`\n" ] } ], "source": [ "using Pkg\n", "Pkg.activate(\".\")\n", "Pkg.instantiate();" ] }, { "cell_type": "markdown", "metadata": { "nbgrader": { "grade": false, "grade_id": "cell-7697dde5851e63b9", "locked": true, "schema_version": 3, "solution": false, "task": false } }, "source": [ "Below are some examples of importing packages and using their added functionalities." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}\n", "values:\n", "2-element Vector{Float64}:\n", " -0.6853720883753125\n", " 4.085372088375313\n", "vectors:\n", "2×2 Matrix{Float64}:\n", " 0.476976 -0.878916\n", " -0.878916 -0.476976" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "using LinearAlgebra\n", "\n", "E,V = eigen([3. 2.;2. 0.4])" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
Row | a | b | c |
---|---|---|---|
Int64 | Int64 | Int64 | |
1 | 1 | 2 | 3 |