Tutorial: Julia Overview

Julia overview.

  • 2018-08-11 Julia 0.7.0 Jeff Fessler (based on 2017 version by David Hong)
  • 2019-01-20 Julia 1.0.3 and add note about line breaks
  • 2020-08-05 Julia 1.5.0
  • 2021-08-23 Julia 1.6.2
  • 2023-09-03 Julia 1.9.2, Literate

This page comes from a single Julia file: 1-intro.jl.

You can access the source code for such Julia documentation using the 'Edit on GitHub' link in the top right. You can view the corresponding notebook in nbviewer here: 1-intro.ipynb, or open it in binder here: 1-intro.ipynb.

Setup

Add the Julia packages used in this demo. Change false to true in the following code block if you are using any of the following packages for the first time.

if false
    import Pkg
    Pkg.add([
        "InteractiveUtils"
        "LinearAlgebra"
    ])
end

Tell Julia to use the following packages. Run Pkg.add() in the preceding code block first, if needed.

# using InteractiveUtils: versioninfo
using LinearAlgebra: Diagonal, det, dot, tr

Numbers, arithmetic, types

Define a real number:

r = 3.0
3.0

Variables in Julia have a type:

typeof(r)
Float64
i = 3
3
typeof(i)
Int64
c = 3. + 2im
3.0 + 2.0im
typeof(c)
ComplexF64 (alias for Complex{Float64})

We can add, subtract, multiply and divide like usual:

4. + 5
9.0
4. - 5
-1.0
4. * 3
12.0
2. / 3
0.6666666666666666

Dividing Int values with / produces a Float:

2/3
0.6666666666666666
4/2
2.0

This is different from Python 2, but similar to Python 3.

To divide integers with rounding, use ÷ instead. Type \div then hit tab:

5 ÷ 2
2

More info about numbers here:

Vectors and matrices (i.e., arrays)

Make a vector of real numbers:

\[x = \begin{bmatrix} 1.0 \\ 3.5 \\ 2 \end{bmatrix}\]

x = [1, 3.5, 2]
3-element Vector{Float64}:
 1.0
 3.5
 2.0

Note the type: Vector{Float64}.

Having just one real number in the array sufficed for the array have all Float64 elements.

This is a true one-dimensional array of Float64 values.

(Matlab does not have 1D arrays; it fakes it using 2D arrays of size N × 1.)

size(x) # returns a tuple
(3,)
length(x)
3
x_ints = [1,3,2]
3-element Vector{Int64}:
 1
 3
 2

This is a one-dimensional array of Int64 values. We use these less often.

size(x_ints)
(3,)
length(x_ints)
3

Make a matrix using a semicolon to separate rows:

\[A = \begin{bmatrix} 1.1 & 1.2 & 1.3 \\ 2.1 & 2.2 & 2.3 \end{bmatrix}\]

A = [1.1 1.2 1.3; 2.1 2.2 2.3]
2×3 Matrix{Float64}:
 1.1  1.2  1.3
 2.1  2.2  2.3

This is a two-dimensional array (aka a matrix) of Float64 values.

size(A)
(2, 3)
length(A)
6

Different from Matlab, length always returns the total number of elements.

Make vectors and matrices of all zeros.

zeros(3)
3-element Vector{Float64}:
 0.0
 0.0
 0.0

Different from Matlab! Do not write zeros(3,1) because Julia has proper 1D arrays. zeros(3,1) and zeros(3) are different!

zeros(2,3)
2×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0

And ones:

ones(3)
3-element Vector{Float64}:
 1.0
 1.0
 1.0
ones(2,3)
2×3 Matrix{Float64}:
 1.0  1.0  1.0
 1.0  1.0  1.0

The "identity matrix" $I$ in Julia's LinearAlgebra package is sophisticated.

Look at the following examples:

using LinearAlgebra: I
ones(3,3) - I
3×3 Matrix{Float64}:
 0.0  1.0  1.0
 1.0  0.0  1.0
 1.0  1.0  0.0
ones(2,2) * I
2×2 Matrix{Float64}:
 1.0  1.0
 1.0  1.0
I(3)
3×3 LinearAlgebra.Diagonal{Bool, Vector{Bool}}:
 1  ⋅  ⋅
 ⋅  1  ⋅
 ⋅  ⋅  1

If that $I$ seems too fancy, then you could make your own eye function akin to Matlab as follows (but it should not be needed and it uses unnecessary memory):

eye = n -> Matrix(1.0*I(n))
eye(2)
2×2 Matrix{Float64}:
 1.0  0.0
 0.0  1.0

Make diagonal matrices using the Diagonal function in LinearAlgebra:

Diagonal(3:6)
4×4 LinearAlgebra.Diagonal{Int64, UnitRange{Int64}}:
 3  ⋅  ⋅  ⋅
 ⋅  4  ⋅  ⋅
 ⋅  ⋅  5  ⋅
 ⋅  ⋅  ⋅  6

This is far more memory efficient than Matlab's diag command or Julia's LinearAlgebra.diagm method. Avoid using those!

Make random vectors and matrices.

\[x = \begin{bmatrix} \mathcal{N}(0,1) \\ \mathcal{N}(0,1) \\ \mathcal{N}(0,1) \end{bmatrix} \qquad \text{i.e.,} \quad x_i \overset{\text{iid}}{\sim} \mathcal{N}(0,1)\]

\[A = \begin{bmatrix} \mathcal{N}(0,1) & \mathcal{N}(0,1) & \mathcal{N}(0,1) \\ \mathcal{N}(0,1) & \mathcal{N}(0,1) & \mathcal{N}(0,1) \end{bmatrix} \qquad \text{i.e., } \quad A_{ij} \overset{\text{iid}}{\sim} \mathcal{N}(0,1)\]

x = randn(3)
3-element Vector{Float64}:
 -0.7319593624982337
  0.47313033679968275
 -0.1289912003507904
A = randn(2,3)
2×3 Matrix{Float64}:
 -0.796816  -0.904441  0.744101
  0.35746    0.900676  1.20112

Matrix operations

Indexing is done with square brackets (like in C and Python, unlike Matlab).

Index and begins at 1 (like in Matlab and counting) not 0 (like in C or Python).

A = [1.1 1.2 1.3; 2.1 2.2 2.3]
2×3 Matrix{Float64}:
 1.1  1.2  1.3
 2.1  2.2  2.3
A[1,1]
1.1
A[1,2:3]
2-element Vector{Float64}:
 1.2
 1.3

This row-slice is a one-dimensional slice (!) not a 1×2 matrix:

A[1:2,1]
2-element Vector{Float64}:
 1.1
 2.1
A[2,:]
3-element Vector{Float64}:
 2.1
 2.2
 2.3

Vector dot product:

x = randn(3)
xdx = x'x
4.475465110843606
xdx = dot(x,x)
4.475465110843606
xdx = x'*x
4.475465110843606

Different from Matlab! The output is a scalar, not a 1×1 "matrix:"

typeof(xdx)
Float64

Matrix times vector:

A = randn(2,3)
x = randn(3)
A*x
2-element Vector{Float64}:
  0.6013969900184211
 -0.37147270992913173

Matrix times matrix:

A = randn(2,3)
B = randn(3,4)
A*B
2×4 Matrix{Float64}:
 -0.753131  -0.248189  -0.091796  -0.783234
  1.15821    0.395057  -0.167719  -0.507681

Matrix transpose (conjugate and non-conjugate):

A = 10*reshape(1:6, 2, 3) + im * reshape(1:6, 2, 3)
2×3 Matrix{Complex{Int64}}:
 10+1im  30+3im  50+5im
 20+2im  40+4im  60+6im

conjugate transpose, could also use adjoint(A):

A'
3×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
 10-1im  20-2im
 30-3im  40-4im
 50-5im  60-6im

For complex arrays, rarely do we need a non-conjugate transpose. Usually we need A' instead. But if we do:

transpose(A) # essentially sets a flag about transpose without reordering data
3×2 transpose(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
 10+1im  20+2im
 30+3im  40+4im
 50+5im  60+6im

Matrix determinant:

A = Diagonal(2:4)
det(A)
24
B = randn(3,3)
[det(A*B) det(A)*det(B)]
1×2 Matrix{Float64}:
 22.5783  22.5783

Matrix trace:

A = ones(3,3)
tr(A) # in Matlab would be "trace(A)"
3.0

More info in Julia manual

Getting help

Julia analogue of Matlab's help is ?.

Type ?pwd in the REPL to get help on the pwd function.

It does not work in this online documentation so we use @doc instead:

@doc pwd
pwd() -> String

Get the current working directory.

See also: cd, tempdir.

Examples

julia> pwd()
"/home/JuliaUser"

julia> cd("/home/JuliaUser/Projects/julia")

julia> pwd()
"/home/JuliaUser/Projects/julia"

Ranges

Ranges are different from (and much more efficient than) Matlab!

myrange = -2:3
-2:3
typeof(myrange)
UnitRange{Int64}

Not an Array! But it can be indexed:

myrange[1]
-2

Used often in for loops:

for a in myrange
    println(a)
end
-2
-1
0
1
2
3

Form an array by using collect if needed (use rarely):

collect(myrange)
6-element Vector{Int64}:
 -2
 -1
  0
  1
  2
  3

Other ways to make ranges:

srange = 1:-1:-5
1:-1:-5
typeof(srange)
StepRange{Int64, Int64}
lrange = range(0, 2, 10)
0.0:0.2222222222222222:2.0
typeof(lrange)
StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}

Yet another option that looks the most like linspace:

LinRange(0,10,6)
6-element LinRange{Float64, Int64}:
 0.0, 2.0, 4.0, 6.0, 8.0, 10.0

Comprehensions

A convenient way to create arrays!

comp = [i+0.1 for i in 1:5]
5-element Vector{Float64}:
 1.1
 2.1
 3.1
 4.1
 5.1
comp = [10i + j for i in 1:5, j in 1:4]
5×4 Matrix{Int64}:
 11  12  13  14
 21  22  23  24
 31  32  33  34
 41  42  43  44
 51  52  53  54

Defining functions

Way 1:

function f1(x,y)
    z = x+y
    return z
end
f1 (generic function with 1 method)

Way 2:

f2(x,y) = x+y
f2 (generic function with 1 method)

Way 3: Anonymous function:

f3 = (x,y) -> x+y
#7 (generic function with 1 method)

Functions can return multiple outputs:

function f_mult(x, y)
    add = x + y
    sub = x - y
    return add, sub
end;

f_mult(2,3)
(5, -1)

The output is a Tuple of the returned values:

out_tuple = f_mult(2,3)
(5, -1)
typeof(out_tuple)
Tuple{Int64, Int64}

Convenient way to split out the outputs:

out1, out2 = f_mult(2,3)
(5, -1)
out1
5
out2
-1

Broadcast

Any Julia function can be "vectorized" using "broadcast"

myquad = x -> (x+1)^2
#9 (generic function with 1 method)
myquad(1)
4
try
    myquad([1,2,3]) # this does not work!
catch
    "failed, as expected"
end
"failed, as expected"

This particular function was not designed to be applied to vector input arguments! But it can be used with vectors (or arrays) by adding a . to tell Julia to apply it element-wise. This is called broadcasting.

myquad.([1,2,3])
3-element Vector{Int64}:
  4
  9
 16

Conditionals

if else end for

Generally similar to Matlab. Optional use of in instead of = in the for loop.

for j in 1:3
    if j == 2
        println("$j is a two! ^^")
    else
        println("$j is not a two. :(")
    end
end
1 is not a two. :(
2 is a two! ^^
3 is not a two. :(

Julia has the convenient ternary operator:

mystring = 2 > 3 ? "2 is greater than 3" : "2 is not greater than 3"
"2 is not greater than 3"

Plotting

Suggested package: Plots.jl with its default gr backend.

Note: Usually slower the first time you plot due to precompiling. You must add the "Plots" package first. In a regular Julia REPL you do this by using the ] key to enter the package manager REPL, and then type add Plots then wait.

In a Jupyter notebook, type using Pkg then add Plots and wait.

using Plots
backend()
Plots.GRBackend()

Plot values from a vector. (The labels are optional arguments.)

x = range(-5,5,101)
y = x.^2
plot(x, y, xlabel="x", ylabel="y", label="parabola")
Example block output

heatmap

x = range(-2, 2, 101)
y = range(-1.1, 1.1, 103)
A = x.^2 .+ 30 * (y.^2)'
F = exp.(-A)
p1 = heatmap(x, y, F', # for F(x,y)
    color=:grays, aspect_ratio=:equal, xlabel="x", ylabel="y", title="bump")
Example block output

Using the jim function the MIRTjim.jl package simplifies the display of 2D images, among other features. See its examples.

Plotting functions

Plots.jl allows you to pass in the domain and a function. It does the rest. :) This is one many examples of how Julia exploits "multiple dispatch."

plot(range(0,1,100), abs2, label="x^2")
Example block output
heatmap(range(-2,2,102), range(-1.1,1.1,100),
    (x,y) -> exp(-x^2-30*y^2), aspect_ratio=1)
Example block output

More info about plotting at https://juliaplots.github.io

Caution: line breaks (newlines)

If you want an expression to span multiple lines, then be sure to enclose it in parentheses.

Compare the following 3 (actually 4) expressions:

x = 9
    - 7
-7
y = 9 -
    7
2
z = (9
    - 7)
2
(x,y,z)
(9, 2, 2)

Submitting homework

This part is just for EECS 551 students at UM.

A quick example to try submitting problems.

Task: Implement a function that takes two inputs and outputs them in reverse order.

function template1(x, y)
    return (y, x)
end;

template1(2, 3)
(3, 2)

Copy the above function code into a file named template1.jl and email to eecs551@autograder.eecs.umich.edu.

Make sure that:

  • All reasonable input types can be handled. Internally trying to convert a Float64 to an Int64 can produce InexactError
  • File extension is .jl. Watch out for hidden extensions!
  • File has just the Julia function.
  • (Your HW solutions can also contain using statements.)

An undocumented function is bad programming practice. Julia supports docstrings for comments like this:

"""
    template2(x,y)
This function reverses the order of the two input arguments.
"""
function template2(x,y)
    return (y,x)
end
Main.var"Main".template2

You can see the docstring by using the ? key or @doc:

@doc template2
template2(x,y)

This function reverses the order of the two input arguments.


This page was generated using Literate.jl.