diff --git a/src/4_Arrays_Linear_Algebra.ipynb b/src/4_Arrays_Linear_Algebra.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..b8f7db6981a294945950e681589c1cb7298395af
--- /dev/null
+++ b/src/4_Arrays_Linear_Algebra.ipynb
@@ -0,0 +1,1141 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "4f17d479-4a7d-430c-a1ee-92fcd5ec0c16",
+   "metadata": {},
+   "source": [
+    "# Arrays, Vectors, Matrices and Linear Algebra"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c02d808e-3b3d-46c6-8199-320d6fa778aa",
+   "metadata": {},
+   "source": [
+    "## Arrays\n",
+    "\n",
+    "An array is a sequence of values that can be of any type. The values in an array are called elements or sometimes items. \n",
+    "\n",
+    "\n",
+    "There are several ways to create a new array; the simplest is to enclose the elements in square brackets (`[ ]`):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ecf8d02-ba1c-4296-a9bd-d0a747068e85",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a1 = [10, 20, 30, 40]\n",
+    "a2 = [\"hi\", \"this\", \"is\", \"an\", \"array\"]\n",
+    "@show a1 a2;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f9bbdffd-c37b-4317-b07d-d75003b2f568",
+   "metadata": {},
+   "source": [
+    "The first example is an array of four integers. The second is an array of five strings. The elements of an array don’t have to be the same type. The following array contains a string, a float, an integer, and another array:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "52cb6c55-90c0-459b-86cf-ae35ab43abcf",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "a3 = [\"spam\", 2.0, 5, [10, 20]]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a364229e-ed7a-4b7d-b87f-40d073f24682",
+   "metadata": {},
+   "source": [
+    "An array within another array is nested.\n",
+    "An array that contains no elements is called an empty array; you can create one with empty brackets, `[]`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4e661e5d-b43b-4d1f-a805-32f96d2c741f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cheeses = [\"Cheddar\", \"Edam\", \"Gouda\"];\n",
+    "numbers = [42, 123];\n",
+    "empty = [];\n",
+    "print(cheeses, \" \", numbers, \" \", empty)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0275af11-5f69-49aa-8b8a-794c82f27590",
+   "metadata": {},
+   "source": [
+    "The kind of the array is specified between curly braces and is composed of a type and a number. The number indicates the dimensions. The array empty contains values of type `Any`., i.e. it can hold values of all types.\n",
+    "\n",
+    "The syntax for accessing the elements of an array is the bracket operator. The expression inside the brackets specifies the index. Remember that the indices start at 1:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0ab05daa-9cb1-4dcd-9a01-03dda978de6f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cheeses[1]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7983a3ae-f9cc-410d-888b-f281812add8c",
+   "metadata": {},
+   "source": [
+    "Unlike tuples, arrays are **mutable**. When the bracket operator appears on the left side of an assignment, it identifies the element of the array that will be assigned: "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "01a4d81f-ddd4-4fa0-a1de-e5cfa13b496f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cheeses[2] = \"Gorgonzola\"\n",
+    "print(cheeses)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "918c617e-ffb9-4b90-b9e5-fea8e960ad1e",
+   "metadata": {},
+   "source": [
+    "Array indexes follow these rules:\n",
+    "  - Any integer expression can be used as an index.\n",
+    "  - If you try to read or write an element that does not exist, you get a `BoundsError`.\n",
+    "  - The keyword `end` points to the last index of the array.\n",
+    "\n",
+    "The `∈` (or `in`) operator also works on arrays:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "576fd3ae-aa20-4178-a52c-ac8e4b4fed29",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show \"Gouda\" ∈ cheeses\n",
+    "@show \"Brie\" in cheeses;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7cd2705c-2d8f-43b3-bb9b-204c1674da50",
+   "metadata": {},
+   "source": [
+    "### Traversing an array\n",
+    "\n",
+    "The most common way to traverse the elements of an array is with a for loop. The syntax is the same as for strings:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "51885e2d-3055-4433-b0f8-e561c88f888b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for cheese in cheeses\n",
+    "    println(cheese)\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3c83a447-2c3b-4cb2-8192-6f0f4249ae2e",
+   "metadata": {},
+   "source": [
+    "This works well if you only need to read the elements of the array, as you will get only copies of the elements and changing the copies does not change the array. But if you want to write or update the elements, you need the indices. A common way to do that is to use the built-in function `eachindex`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "00b187f3-7a0d-4741-82e3-7dc286dcf34f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for i in eachindex(numbers)\n",
+    "    numbers[i] = numbers[i] * 2\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c6f78ad1-4717-42e8-bb94-6fe33691f563",
+   "metadata": {},
+   "source": [
+    "A `for` loop over an empty array never runs the body:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "17ebecd7-63cc-46d8-b2c5-8721aa616e26",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "for x in []\n",
+    "    println(\"This can never happens.\")\n",
+    "end"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "adcdb6d4-35bc-4a1c-bbe0-f023a9ab2509",
+   "metadata": {},
+   "source": [
+    "`length` returns the number of elements in the array.\n",
+    "Although an array can contain another array, the nested array still counts as a single element. The length of this array is four:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4abdcef7-f424-4d52-91f2-00d1a0387c8c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "array = [\"spam\", 1, [\"Brie\", \"Roquefort\", \"Camembert\"], [1, 2, 3]]\n",
+    "@show length(array);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e64ec6e6-dfb7-4e05-ba9e-96cec72955bb",
+   "metadata": {},
+   "source": [
+    "### Array slices\n",
+    "The slice operator lets you select parts of an array."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7d31afed-81c8-42ef-bbb3-18d1c1d16548",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = ['a', 'b', 'c', 'd', 'e', 'f']\n",
+    "print(t[1:3])\n",
+    "print(t[3:end])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "beee009a-f707-4436-8144-8c7932f122e2",
+   "metadata": {},
+   "source": [
+    "The slice operator `[:]`, makes a copy of the whole array. Note that doing `t2 = t` does not create a copy. \n",
+    "Since arrays are mutable, it is often useful to make a copy before performing operations that modify arrays."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c73d80ff-ab60-418f-a0a4-0b2fae76f42e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t1 = t[:]\n",
+    "@show t1 === t\n",
+    "\n",
+    "t2 = t\n",
+    "@show t2 === t;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "42e8a620-d702-4f92-8d20-58608294fb38",
+   "metadata": {},
+   "source": [
+    "A slice operator on the left side of an assignment can update multiple elements:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c75b53ad-a98b-451c-be5a-9e0d939e23c3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t[2:3] = ['x', 'y']\n",
+    "print(t)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2ed7448b-9efc-42bf-a24e-22a6e060a1eb",
+   "metadata": {},
+   "source": [
+    "### Array library\n",
+    "Julia provides functions that operate on arrays. For example, `push!` adds a new element to the end of an array (note the exclamation mark, as it is impure). `pushfirst!` adds a new element at the beginning of the array."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "680e7967-fffb-4112-afb2-422e8e6b87ba",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = ['a', 'b', 'c']\n",
+    "push!(t, 'd')\n",
+    "pushfirst!(t, '0')\n",
+    "print(t)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1fb6e797-9d98-4264-a7ad-7fd0b6eb3b89",
+   "metadata": {},
+   "source": [
+    "`append!` add the elements of the second array to the end of the first:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b614b4ef-f34a-4aad-8b6d-973ffc8e16de",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t1 = ['a', 'b', 'c']\n",
+    "t2 = ['d', 'e']\n",
+    "append!(t1, t2)\n",
+    "print(t1)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b7a64ccf-077b-4f80-81a5-6c3ba0ac9ce3",
+   "metadata": {},
+   "source": [
+    "The function `insert!` inserts an element at a given index:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a1cb2569-4b2b-4e8c-a9bd-2bb0a14bfbca",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = ['a', 'b', 'c']\n",
+    "print(insert!(t, 2, 'x'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e5fd6caf-c66c-4c5a-8f95-f72a72caaf9d",
+   "metadata": {},
+   "source": [
+    "`sort!` arranges the elements of the array from low to high, while `sort` returns a copy of the elements of the array in order: "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "81fa98e2-4bad-4db9-a784-411069ab167b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t1 = ['d', 'c', 'e', 'b', 'a']\n",
+    "t2 = ['d', 'c', 'e', 'b', 'a']\n",
+    "\n",
+    "sort!(t1)\n",
+    "println(t1)\n",
+    "\n",
+    "t3 = sort(t2)\n",
+    "println(t3)\n",
+    "@show t3 === t2;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "130876d4-db72-44a9-beec-c39b5b307df9",
+   "metadata": {},
+   "source": [
+    "There are several ways to delete elements from an array. If you know the index of the element you want to delete, you can use `splice!`.  `splice!` modifies the array and returns the element that was removed. `deleteat!` does the same without returning the element."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f391ab7c-2890-4b70-904a-8e606ec424f2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = ['a', 'b', 'c']\n",
+    "element = splice!(t, 2)\n",
+    "println(t)\n",
+    "println(element)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "30b6b7fd-d07a-4792-8250-dd611653bbb1",
+   "metadata": {},
+   "source": [
+    "`pop!` deletes and returns the last element:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "47cee6d5-78c2-4f44-af74-054e05fb0d2c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = ['a', 'b', 'c']\n",
+    "element = pop!(t)\n",
+    "println(t)\n",
+    "println(element)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "65fc2a2a-8b06-4dca-910e-2a5d37974460",
+   "metadata": {},
+   "source": [
+    "### Exercise\n",
+    "  - The core functionality provides several other functions which act on an array. Given an array `x` try the following functions and try to guess what they do: `ndims(x)`, `eltype(x)`, `length(x)`, `size(x)`, `size(x, 1)`, `reshape(x, 3, 3)`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2c6e8e00-924b-4c88-8d8c-dfbcd18c6733",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n",
+    "# TODO implement your code here"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "393c8cf4-3c4d-400b-a1af-5af69f4dbf87",
+   "metadata": {},
+   "source": [
+    "### Dot syntax\n",
+    "For every binary operator like `^`, here is a corresponding dot operator .^ that is automatically defined to perform ^ element-by-element on arrays. For example, `[1, 2, 3]^3` is not defined, but `[1, 2, 3].^3` is defined as computing the elementwise result `[1^3, 2^3, 3^3]`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0aa2e922-aa84-455c-8737-dcaee139a1ba",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print([1, 2, 3] .^ 3)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8e88b86e-0284-4679-83c3-9b90e47b4e33",
+   "metadata": {},
+   "source": [
+    "Any Julia function `f` can be applied elementwise to any array with the dot syntax. For example to capitalize an array of strings, we don’t need an explicit loop:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ea7d08d7-1417-47e1-be71-abeb8ba80c1e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = uppercase.([\"abc\", \"def\", \"ghi\"])\n",
+    "print(t)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "93c1a351-0b39-4baf-86cc-66e1f9277db8",
+   "metadata": {},
+   "source": [
+    "### Arrays and strings\n",
+    "\n",
+    "A string is a sequence of characters and an array is a sequence of values, but an array of characters is not the same as a string. To convert from a string to an array of characters, you can use the function `collect`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a6e467ca-3130-449d-b876-f77720b515b5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(collect(\"spam\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c54747e6-1ebc-4b82-af65-77b982bc3835",
+   "metadata": {},
+   "source": [
+    "If you want to break a string into words, you can use the `split` function. The default delimiter is the space, but you can pass a custom delimiter as a second argument."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "96a09506-444f-4733-b047-f5de500fbfc2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show split(\"building an array of strings\")\n",
+    "@show split(\"02.11.2023\", \".\");"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9f9cf023-a1d5-43d5-8322-13e8319b6637",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "Finally, `join` is the inverse of split. It takes an array of strings and concatenates the elements:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "43e7272d-2a32-441f-86c0-2fa532411ff6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "t = [\"building\", \"a\", \"string\"]\n",
+    "@show join(t)\n",
+    "@show join(t, \" \")\n",
+    "@show join(t, \"-\");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c85d6757-a7ac-442c-879c-418137a2ebd0",
+   "metadata": {},
+   "source": [
+    "### Exercises\n",
+    "  - Write a function called `reverse_array(x)` which takes as input an array and returns a **new** array with the elements in reverse order."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "45c7bffe-c8df-440f-9db0-7923969c20fb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "45a34bfb-67c1-4fbc-ae18-c60403dc3dda",
+   "metadata": {},
+   "source": [
+    "  - Write a function called `reverse_array!(x)` which modifies in place the array by reversing its elements."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "19607e02-107e-4edd-90a8-abdca24fae6f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b5fc2426-b30b-4295-bf8e-f0c9b152267c",
+   "metadata": {},
+   "source": [
+    "  - Write a function `even_numbers(x)` which takes as input an array of ingegers and returns a new array containing only the elements which are even."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9babe5e0-2c11-4481-9938-42ec2f697e6b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "756cd401-77f2-4b90-9a87-10b4fcc6c7a7",
+   "metadata": {},
+   "source": [
+    "## Vectors and Matrices"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1c5d8a88-f00e-41a9-ac62-245a25d8c803",
+   "metadata": {},
+   "source": [
+    "In general arrays can have any number of dimensions, with this line we are instantiating a 2x3x4 array made of integer zeros."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "af571cb0-92be-4876-9a66-6217620c9671",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "A = zeros(Int, 2, 3, 4)\n",
+    "@show typeof(A);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "e2dafffd-9538-477b-9aec-7fb04d3f9d99",
+   "metadata": {},
+   "source": [
+    "However, if the dimension of the array is 1 or 2 the type is respectively `Vector` or `Matrix`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "46f3c7e3-e592-47df-abf4-3b9d650fb68d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v = [1, 2, 3, 4, 5]\n",
+    "@show typeof(v)\n",
+    "m = [1 2 3; 4 5 6; 7 8 9]\n",
+    "@show typeof(m);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "b1fc0ccd-1b16-4fec-9825-561198fa3763",
+   "metadata": {},
+   "source": [
+    " **Note:** instantiating a matrix does not require the commas between the elements. The following like will produce a 1x3 matrix and not a vector."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "490491f2-313b-41e2-9015-1ef7687beded",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = [1 2 3]\n",
+    "@show typeof(m);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9b91edb6-def2-4fa1-8b7a-b3db96e850be",
+   "metadata": {},
+   "source": [
+    "You can also explicitly check that a Vector is simply a 1d Array and that a Matrix is simply a 2d array."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "7c19aca9-a3ab-4ae7-9ecf-021ef89e73c8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show Vector{Any} === Array{Any, 1}\n",
+    "@show Matrix{Any} === Array{Any, 2}\n",
+    "@show Matrix{Any} === Array{Any, 3};"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "00c7fd47-c3aa-41f7-81a4-d08510126d85",
+   "metadata": {},
+   "source": [
+    "**Note:** `Array` and `Vector` are always concrete types, independently from the type of the elements. So the usual type relations don't give the expected results:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c94e645f-1e50-4999-be3b-e97de198b2d9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show Vector{Float32} <: Vector{Real}\n",
+    "@show Vector{Real} <: Vector{Any};"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1205c3f4-2e97-4952-86f2-bc8e44d638f7",
+   "metadata": {},
+   "source": [
+    "### Vectors"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a6a69a05-dfa3-42f9-a011-579478a8a9c1",
+   "metadata": {},
+   "source": [
+    "Vectors can be created using square brakets `[]` or by using the `Vector` construct. The data type can be explicitly stated."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e9049f28-4a9f-4416-9f80-664e7d786bc1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v1 = [1, 2, 3]\n",
+    "v2 = Float32[1, 2, 3]\n",
+    "@show typeof(v1)\n",
+    "@show typeof(v2);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1a73ec49-da56-4724-af51-883dd3c92da9",
+   "metadata": {},
+   "source": [
+    "There are other short ways to generate arrays with usual content, like zeros, ones or random numbers."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "0331ed6e-b857-4074-84bf-dc9eb9c95fc1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v1 = zeros(5)\n",
+    "v2 = ones(5)\n",
+    "v3 = rand(5)\n",
+    "@show v1 v2 v3;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "95b897c2-f58c-48fa-a845-e0a6a8934b9f",
+   "metadata": {},
+   "source": [
+    "It is also possible to enforce a specific data type."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a0b81571-1044-48ab-9cf6-639c32b4738f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v1 = zeros(Integer, 5)\n",
+    "v2 = ones(Float32, 5)\n",
+    "v3 = rand(Float32, 10)\n",
+    "@show v1 v2 v3;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d6315e57-2614-4caf-a50c-0e192ac19672",
+   "metadata": {},
+   "source": [
+    "Standard mathematical operation can be performed with the usual notation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "53a0f9b1-83ea-4914-9546-db5c248c7852",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v1 = [1, 2, 3]\n",
+    "v2 = [1, 1, 1]\n",
+    "\n",
+    "@show v1 - v2\n",
+    "@show v1 + 2*v2;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "09784b0e-9e8d-4271-a8d7-2c2ad409fad9",
+   "metadata": {},
+   "source": [
+    "The scalar product can be computed using the `dot` function, which is part of the `LinearAlgebra` package."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a98bac2e-9b09-4b6e-9666-f19eeddf2f67",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "using LinearAlgebra\n",
+    "v = [1, 2, 3]\n",
+    "@show dot(v, v);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4605b075-3754-46cc-9fbc-2c1bb22e95da",
+   "metadata": {},
+   "source": [
+    "Finally, the transpose can be accessed using the `'` operator."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "91541224-d1ff-4b07-850f-7fc168851b39",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show typeof(v) v\n",
+    "@show typeof(v') v';"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3aad0875-2339-47f6-ae1f-bd5ca455bec2",
+   "metadata": {},
+   "source": [
+    "### Matrices"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "243b7d80-11c6-4b2f-af93-0e4f5a56deee",
+   "metadata": {},
+   "source": [
+    "Matrices can be again initialized in various ways."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "cb41a853-5ce9-4c37-8cd4-62f54ff6312b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = zeros(Integer, 3, 3)\n",
+    "m2 = ones(Float32, 3, 3)\n",
+    "m3 = rand(Float32, 3, 3)\n",
+    "m4 = Float32[1 2 3; 4 5 6]\n",
+    "@show m1 m2 m3 m4;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "19df2da4-a6fe-44d5-aacc-070e27086c37",
+   "metadata": {},
+   "source": [
+    "If you have the diagonal (vector) and you want to construct a diagonal matrix, you can use the `diagm` function."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "906b5a91-3a29-4c9c-947a-d8a88a06dec2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@show diagm([1, 2, 3]);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "98f05747-23ba-46f1-afc1-31c5de94de8c",
+   "metadata": {},
+   "source": [
+    "The identity matrix is represented as a `I`. **Note**: this is not really a matrix, but rather a `UniformScaling` which brings the effect of the identity operation without needing allocated space. The uniform scalings automatically adapt to the size of the matrices they act on."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "73a7cd9e-1586-4215-8623-cccd465f6979",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = [1 2 3; 3 4 5; 6 7 8]\n",
+    "@show I*m1;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0f341138-d1e7-425a-a54b-d5278ae1a48b",
+   "metadata": {},
+   "source": [
+    "Again the `+` and `-` operations can be readily used on matrices, but in this case also the `*` operation is defined."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "1cbcd610-9cbd-4c73-9632-c4a6d47b374a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = [1 2 3; 3 4 5; 6 7 8]\n",
+    "m2 = [1 0 0; 0 0 0; 0 0 0]\n",
+    "@show m1 * m2;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "06b6d25f-92a1-4768-bdd8-760fa9212879",
+   "metadata": {},
+   "source": [
+    "To invert the matrices it is possible to use the function `inv` or a more subtle `/` operation."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "70de8ba6-6f63-4a43-9ff2-efff2b9a1368",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = [1 2; -1 2]\n",
+    "@show I/m1;\n",
+    "@show inv(m1);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eab561b9-b034-49e6-a100-09b201ff4caa",
+   "metadata": {},
+   "source": [
+    "The transpose works also on matrices, and note that for complex numbers it is an adjoint."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e3bbc37c-0bcd-4a54-891f-ec94ee437724",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = [0 -1im ; 2im 0];\n",
+    "@show m1\n",
+    "@show m1';"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d827b0b8-ead4-48c7-83bd-399ef7927414",
+   "metadata": {},
+   "source": [
+    "Finally, it is also possible to compute the determinant and the trace using respectively the `det` and `tr` functions."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9b52aabb-fc61-4006-99a8-73bd418e810a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m1 = [1 2 3; 3 4 5; 6 7 8]\n",
+    "@show det(m1);\n",
+    "@show tr(m1);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5c2d37bb-4417-4f9c-a894-352a2a5c741b",
+   "metadata": {},
+   "source": [
+    "## More advanced linear algebra\n",
+    "\n",
+    "The `LinearAlgebra` package implement various more advanced operations."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "787b534f-57c4-47da-9eb2-cf5c8f8a8344",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "m = [3 0.5 0.1; 0.1 5 0.2; 0.2 0.1 3]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ff83a09a-6ede-44df-8151-96e7ce961489",
+   "metadata": {},
+   "source": [
+    "  - Eigenvalue decomposition"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "d0ac2f75-02c4-482a-b668-baff9aac3128",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "λ, V = eigen(m)\n",
+    "m1 = V * diagm(λ) * inv(V)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "cba4ddfc-bcdf-4c3b-8fb4-1237ec15bf44",
+   "metadata": {},
+   "source": [
+    "  - Singular value decomposition"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4724ed84-0ac8-41e9-affd-d3ed0b44172b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "U, λ, V = svd(m)\n",
+    "m1 = U * diagm(λ) * V'"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3ae3a97a-aa0d-41a4-b957-1346860ad638",
+   "metadata": {},
+   "source": [
+    "  - LU decomposition"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4cfd0965-7f29-473c-86ab-24a50fb4e327",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "L, U = lu(m)\n",
+    "m1 = L * U\n",
+    "@show L\n",
+    "@show U\n",
+    "@show m1;"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d9116b9e-7650-4421-8497-40a440a842b8",
+   "metadata": {},
+   "source": [
+    "  - Cholesky (needs a Hermitian matrix)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3ad8a144-0829-4f65-b036-8bec7199e939",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "h = m + m'\n",
+    "L = cholesky(h)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "3f6ecf2e-0ca5-4fa4-ba39-541a7359564b",
+   "metadata": {},
+   "source": [
+    "For more information:\n",
+    "[https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "dae5385d-add4-47c2-9f7b-d8787942c22c",
+   "metadata": {},
+   "source": [
+    "## Exercises"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f5332b7c-c974-4949-a395-7219fcfc049f",
+   "metadata": {},
+   "source": [
+    "  - Write a function that computes the square root of a matrix."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5553cb6e-c0e4-49c6-b75b-586e14d89426",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "62c05d6b-6772-407b-a3cd-581ef829af4d",
+   "metadata": {},
+   "source": [
+    "  - Implement a solver for linear systems that uses the [Jacobi method](https://en.wikipedia.org/wiki/Jacobi_method). Then compare the result with the standard solution using matrix inversion."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "6dc61c1a-5583-4238-b017-7202f8595789",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# TODO: implement your code here"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Julia 1.9.3",
+   "language": "julia",
+   "name": "julia-1.9"
+  },
+  "language_info": {
+   "file_extension": ".jl",
+   "mimetype": "application/julia",
+   "name": "julia"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}