{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Arrays\n", "\n", "Arrays are a good example to get some idea of the Julian way of defining interfaces." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining arrays\n", "\n", "Arrays can be defined by explicitly providing their content:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i = [1, 2, 3 , 4] # An integer array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f = [1.2, 3.4, 2.3, π] # A float array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s = [\"abc\", \"def\", \"ghi\"] # A string array" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice, that the type of arrays is `Array{T, N}`, where `T` is a type and `N` is the number of dimensions. This type is an example of a **parametric type**, i.e. a type, which itself is parametrised by other values or types.\n", "\n", "Arrays do not need to be of one type only, for example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = [1, 3.4, \"abc\"] # A mixed array" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In some cases, Julia tries to be smart, however, and converts the types of the elements. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = [1, 3.4, 4] # As soon as there is one float element, it's a float array" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One can influence this by explictly denoting the type:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = Number[1, 3.4, 4]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# or\n", "m = Float32[1, 3.4, 4]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Even though all above arrays present as `Array`, their data layout in memory differs. Arrays with pointer-free element types such as `Array{Float64, 1}` or `Array{Int64, 1}` are stored contiguously in memory, but `Array`s with mutable types (such as `Array{String, 1}` and arrays containing abstract types are stored *boxed*, which means that only a pointer to a different memory location is stored inside the array.\n", "\n", "On the interface level Julia does not distinguish between both kind of arrays.\n", "Since operations on boxed elements are a lot slower, arrays with abstract types should be avoided in critical parts of a program." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Multidimensional arrays can be defined similarly:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = [[1 2 3]; # Define row-wise\n", " [5 6 7]]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = [[1, 5] [2, 6] [3, 7]] # Define column-wise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the subtle difference to an Array of Arrays" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = [[1, 5], [2, 6], [3, 7]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yet another option is to explicitly filled arrays:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = zeros(3, 4, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@show randn(1, 2) # Float64\n", "A = randn(Float32, 3, 4, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vector operations and vectorised operations\n", "\n", "Array addition (`+`, `-`) and scalar multiplication are directly available on arrays (of any dimension):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = [1,2,3]\n", "y = [4,5,6]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x + 2.0y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For element-wise operations the vectorisation syntax is used:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x .* y # elementwise mulitplication" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x .^ y # Elementwise exponentiation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note, that the `.`-syntax continues to functions of the standard library ..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sqrt.(cos.(2π * x) .+ sin.(2π * x))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@. sqrt(cos(2π * x) + sin(2π * x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... custom functions ..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "myrand(x) = rand() * x\n", "myrand.(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... and may be easily chained " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@. exp(cos(x^2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise\n", "Create the following arrays using Julia code:\n", "$$\\left(\\begin{array}{ccccc}\n", " 2&2&2&2&2 \\\\\n", " 2&2&2&2&2 \\\\\n", " 2&2&2&2&2 \\\\\n", " \\end{array}\\right) \\qquad\n", " \\left(\\begin{array}{cccc}\n", " 0.1&0.5&0.9&1.3\\\\\n", " 0.2&0.6&1.0&1.4\\\\\n", " 0.3&0.7&1.1&1.5\\\\\n", " 0.4&0.8&1.2&1.6\\\\\n", " \\end{array}\\right)\n", " $$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing array elements\n", "\n", "Array elements can be indexed individually ..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "A[1, 2, 1] = 15\n", "@show A[1, 2, 1]\n", "@show A[2, 2, 2];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... or using ranges" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "A[2:end, 1, :] = ones(2, 2) # : is full range, 2:end means ignore 1st\n", "@show A[2:end, 1, :]\n", "@show A[1, 1, :];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It should be noted that Arrays in Julia are stored in column-major order (like in MATLAB, FORTRAN and R) and that indices start with *1*:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "vec = collect(1:6) # Collect all numbers 1 to 6 in a Vector\n", "@show vec\n", "A = reshape(vec, 2, 3) # Reshape the result" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "for i in 1:length(A)\n", " # Iterate through the storage in memory order\n", " println(i, \" \", A[i])\n", "end" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basic functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = randn(Float32, 4, 5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ndims(A) # Get the number of dimensions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eltype(A) # Get the type of the array elements" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "length(A) # Return the number of elements" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(A) # Get the size of the array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(A, 1) # Get the size along an axis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "reshape(A, 2, 5, 2) # Return an array with the shape changed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exercise: Propagating a particle in 2D\n", "We want improve our capabilities to perform simulations to two dimensions using the 2D harmonic potential\n", "$$ V\\left( \\begin{array}{c} x_1 \\\\ x_2 \\end{array}\\right) = x_1^2 + x_2^2 $$\n", "and the acceleration map\n", "$$ \\vec{A}_V = - \\nabla V. $$\n", "The adapted forward-Euler scheme is\n", "$$\\left\\{\\begin{array}{l}\n", "\\vec{v}^{(n+1)} = \\vec{v}^{(n)} + \\vec{A}_V(\\vec{x}^{(n)}) \\Delta t\\\\\n", "\\vec{x}^{(n+1)} = \\vec{x}^{(n)} + \\vec{v}^{(n)} \\Delta t\\\\\n", "\\end{array}\\right. .$$\n", "\n", "Change the `euler` function we introduced in [02_Functions_Types_Dispatch.ipynb](02_Functions_Types_Dispatch.ipynb)\n", "accordingly (Hint: Suprisingly few changes are needed) and run the dynamics for five steps using\n", "$$x_n = \\left( \\begin{array}{c} 0 \\\\ 0 \\end{array}\\right) \\qquad v_n = \\left( \\begin{array}{c} 1 \\\\ 0 \\end{array}\\right) .$$\n", "Check against your previous implementation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# You're code here" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding elements to arrays\n", "\n", "Julia provides the `push!` and `append!` functions to add additional elements to an existing array.\n", "For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = Vector{Float64}() # Create an empty Float64 array" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "push!(A, 4.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "append!(A, [5, 6, 7])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice, that the `!` is part of the name of the function. In Julia the `!` is a convention to indicate that the respective function *mutates* the content of at least one of the passed arrays." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Automatically deducing element types" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Very helpful functions for type-generic code in Julia are:\n", "\n", "- `zero`, which allocates an array of zeros of the same element type" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = randn(Float32, 3, 4)\n", "zero(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- `similar`, which returns an uninitialised array, which is similar to the passed array. This means that by default array type, element type and size are all kept." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "similar(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- One may also change these parameters easily:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "similar(A, (3, 2)) # Keep element type and array type" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "similar(A, Float64) # Change element type" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "similar(A, Float64, (1, 2)) # Change element type and shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comprehensions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A syntax Julia borrowed from Python are comprehensions. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "arr = randn(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[e for e in arr if e > 0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### More details on Arrays\n", "- https://docs.julialang.org/en/v1/manual/arrays/\n", "- https://docs.julialang.org/en/v1/base/arrays/\n", "- https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array-1" ] } ], "metadata": { "kernelspec": { "display_name": "Julia 1.3.0", "language": "julia", "name": "julia-1.3" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", "version": "1.3.0" } }, "nbformat": 4, "nbformat_minor": 2 }