# Getting started with Julia

Since JuMP is embedded in Julia, knowing some basic Julia is important for learning JuMP. This tutorial is designed to provide a minimalist crash course in the basics of Julia. You can find resources that provide a more comprehensive introduction to Julia here.

## Installing Julia

To install Julia, download the latest stable release, then follow the platform specific install instructions.

Unless you know otherwise, you probably want the 64-bit version.

Next, you need an IDE to develop in. VS Code is a popular choice, so follow these install instructions.

## Numbers and arithmetic

Since we want to solve optimization problems, we're going to be using a lot of math. Luckily, Julia is great for math, with all the usual operators:

```
@show 1 + 1
@show 1 - 2
@show 2 * 2
@show 4 / 5
@show 3^2
```

1 + 1 = 2 1 - 2 = -1 2 * 2 = 4 4 / 5 = 0.8 3 ^ 2 = 9

The `@`

in front of something indicates that it is a macro, which is just a special type of function. In this case, `@show`

prints the expression as typed (e.g., `1 - 2`

), as well as the evaluation of the expression (`-1`

).

Did you notice how Julia didn't print `.0`

after some of the numbers? Julia is a dynamic language, which means you never have to explicitly declare the type of a variable. However, in the background, Julia is giving each variable a type. Check the type of something using the `typeof`

function:

```
@show typeof(1)
@show typeof(1.0)
```

typeof(1) = Int64 typeof(1.0) = Float64

Here `1`

is an `Int64`

, which is an integer with 64 bits of precision, and `1.0`

is a `Float64`

, which is a floating point number with 64-bits of precision.

If you aren't familiar with floating point numbers, make sure to read the Floating point numbers section.

We create complex numbers using `im`

:

```
x = 2 + 1im
@show real(x)
@show imag(x)
@show typeof(x)
@show x * (1 - 2im)
```

real(x) = 2 imag(x) = 1 typeof(x) = Complex{Int64} x * (1 - 2im) = 4 - 3im

The curly brackets surround what we call the *parameters* of a type. You can read `Complex{Int64}`

as "a complex number, where the real and imaginary parts are represented by `Int64`

." If we call `typeof(1.0 + 2.0im)`

it will be `Complex{Float64}`

, which a complex number with the parts represented by `Float64`

.

There are also some cool things like an irrational representation of π.

`π`

π = 3.1415926535897...

To make π (and most other Greek letters), type `\pi`

and then press `[TAB]`

.

`typeof(π)`

Irrational{:π}

However, if we do math with irrational numbers, they get converted to `Float64`

:

`typeof(2π / 3)`

Float64

### Floating point numbers

If you aren't familiar with floating point numbers, make sure to read this section carefully.

A `Float64`

is a floating point approximation of a real number using 64-bits of information.

Because it is an approximation, things we know hold true in mathematics don't hold true in a computer! For example:

`0.1 * 3 == 0.3`

false

`sin(2π / 3) == √3 / 2`

false

Get `√`

by typing `\sqrt`

then press `[TAB]`

.

Let's see what the differences are:

`0.1 * 3 - 0.3`

5.551115123125783e-17

`sin(2π / 3) - √3 / 2`

1.1102230246251565e-16

They are small, but not zero!

One way of explaining this difference is to consider how we would write `1 / 3`

and `2 / 3`

using only four digits after the decimal point. We would write `1 / 3`

as `0.3333`

, and `2 / 3`

as `0.6667`

. So, despite the fact that `2 * (1 / 3) == 2 / 3`

, `2 * 0.3333 == 0.6666 != 0.6667`

.

Let's try that again using ≈ (`\approx + [TAB]`

) instead of `==`

:

`0.1 * 3 ≈ 0.3`

true

`sin(2π / 3) ≈ √3 / 2`

true

`≈`

is just a clever way of calling the `isapprox`

function:

`isapprox(sin(2π / 3), √3 / 2; atol = 1e-8)`

true

Floating point is the reason solvers use tolerances when they solve optimization models. A common mistake you're likely to make is checking whether a binary variable is 0 using `value(z) == 0`

. Always remember to use something like `isapprox`

when comparing floating point numbers.

Gurobi has a good series of articles on the implications of floating point in optimization if you want to read more.

If you aren't careful, floating point arithmetic can throw up all manner of issues. For example:

`1 + 1e-16 == 1`

true

It even turns out that floating point numbers aren't associative!

`(1 + 1e-16) - 1e-16 == 1 + (1e-16 - 1e-16)`

false

It's important to note that this issue isn't Julia-specific. It happens in every programming language (try it out in Python).

## Vectors, matrices and arrays

Similar to Matlab, Julia has native support for vectors, matrices and tensors; all of which are represented by arrays of different dimensions. Vectors are constructed by comma-separated elements surrounded by square brackets:

`b = [5, 6]`

2-element Array{Int64,1}: 5 6

`Array{Int64, 1}`

means that this is an `Array`

, with `Int64`

elements, and it has `1`

dimension.

Matrices can by constructed with spaces separating the columns, and semicolons separating the rows:

`A = [1.0 2.0; 3.0 4.0]`

2×2 Array{Float64,2}: 1.0 2.0 3.0 4.0

Note how this time the type is `Array{Float64, 2}`

; the elements are `Float64`

and there are `2`

dimensions.

We can do linear algebra:

`x = A \ b`

2-element Array{Float64,1}: -3.9999999999999987 4.499999999999999

Here is floating point at work again! `x`

is approximately `[-4, 4.5]`

.

`A * x`

2-element Array{Float64,1}: 5.0 6.0

`A * x ≈ b`

true

Note that when multiplying vectors and matrices, dimensions matter. For example, you can't multiply a vector by a vector:

`b * b`

MethodError: no method matching *(::Array{Int64,1}, ::Array{Int64,1}) Closest candidates are: *(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502 *(!Matched::MutableArithmetics.Zero, ::Any) at /home/runner/.julia/packages/MutableArithmetics/6pKCK/src/rewrite.jl:49 *(!Matched::LinearAlgebra.Adjoint{#s49,#s48} where #s48<:SparseArrays.SparseMatrixCSC where #s49<:MutableArithmetics.AbstractMutable, ::Union{DenseArray{T,1}, ReinterpretArray{T,1,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, ReshapedArray{T,1,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray}, SubArray{T,1,A,I,L} where L where I<:Tuple{Vararg{Union{Int64, AbstractRange{Int64}, AbstractCartesianIndex},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, ReshapedArray{T,N,A,MI} where MI<:Tuple{Vararg{SignedMultiplicativeInverse{Int64},N} where N} where A<:Union{ReinterpretArray{T,N,S,A} where S where A<:Union{SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, SubArray{T,N,A,I,true} where I<:Tuple{AbstractUnitRange,Vararg{Any,N} where N} where A<:DenseArray where N where T, DenseArray} where N where T, DenseArray}} where T) at /home/runner/.julia/packages/MutableArithmetics/6pKCK/src/dispatch.jl:195 ...

But multiplying transposes works:

`b' * b`

61

`b * b'`

2×2 Array{Int64,2}: 25 30 30 36

## Other common types

### Strings

Double quotes are used for strings:

`typeof("This is Julia")`

String

Unicode is fine in strings:

`typeof("π is about 3.1415")`

String

Use `println`

to print a string:

`println("Hello, World!")`

Hello, World!

We can use `$()`

to interpolate values into a string:

```
x = 123
println("The value of x is: $(x)")
```

The value of x is: 123

### Symbols

Julia `Symbol`

s provide a way to make human readable unique identifiers:

`:my_id`

:my_id

`typeof(:my_id)`

Symbol

You can think of a `Symbol`

as a `String`

that takes up less memory, and that can't be modified.

### Tuples

Julia makes extensive use of a simple data structure called Tuples. Tuples are immutable collections of values. For example:

`t = ("hello", 1.2, :foo)`

("hello", 1.2, :foo)

`typeof(t)`

Tuple{String,Float64,Symbol}

Tuples can be accessed by index, similar to arrays:

`t[2]`

1.2

And they be "unpacked" like so:

```
a, b, c = t
b
```

1.2

The values can also be given names, which is a convenient way of making light-weight data structures.

`t = (word = "hello", num = 1.2, sym = :foo)`

(word = "hello", num = 1.2, sym = :foo)

Values can be accessed using dot syntax:

`t.word`

"hello"

## Dictionaries

Similar to Python, Julia has native support for dictionaries. Dictionaries provide a very generic way of mapping keys to values. For example, a map of integers to strings:

`d1 = Dict(1 => "A", 2 => "B", 4 => "D")`

Dict{Int64,String} with 3 entries: 4 => "D" 2 => "B" 1 => "A"

Type-stuff again: `Dict{Int64,String}`

is a dictionary with `Int64`

keys and `String`

values.

Looking up a values uses the bracket syntax:

`d1[2]`

"B"

Dictionaries support non-integer keys and can mix data types:

`Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)`

Dict{String,Number} with 3 entries: "B" => 2.5 "A" => 1 "D" => 2-3im

Julia types form a hierarchy. Here the value type of the dictionary is `Number`

, which is a generalization of `Int64`

, `Float64`

, and `Complex{Int}`

. In general, having variables with "Abstract" types like `Number`

can lead to slower code, so you should try to make sure every element in a dictionary or vector is the same type. For example, in this case we could represent every element as a `Complex{Float64}`

:

`Dict("A" => 1.0 + 0.0im, "B" => 2.5 + 0.0im, "D" => 2.0 - 3.0im)`

Dict{String,Complex{Float64}} with 3 entries: "B" => 2.5+0.0im "A" => 1.0+0.0im "D" => 2.0-3.0im

Dictionaries can be nested:

`d2 = Dict("A" => 1, "B" => 2, "D" => Dict(:foo => 3, :bar => 4))`

Dict{String,Any} with 3 entries: "B" => 2 "A" => 1 "D" => Dict(:bar=>4,:foo=>3)

`d2["B"]`

2

`d2["D"][:foo]`

3

## Loops

Julia has native support for for-each style loops with the syntax `for <value> in <collection> end`

:

```
for i in 1:5
println(i)
end
```

1 2 3 4 5

Ranges are constructed as `start:stop`

, or `start:step:stop`

.

```
for i in [1.2, 2.3, 3.4, 4.5, 5.6]
println(i)
end
```

1.2 2.3 3.4 4.5 5.6

This for-each loop also works with dictionaries:

```
for (key, value) in Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)
println("$(key): $(value)")
end
```

B: 2.5 A: 1 D: 2 - 3im

Note that in contrast to vector languages like Matlab and R, loops do not result in a significant performance degradation in Julia.

## Control Flow

Julia control flow is similar to Matlab, using the keywords `if-elseif-else-end`

, and the logical operators `||`

and `&&`

for **or** and **and** respectively:

```
for i in 0:3:15
if i < 5
println("$(i) is less than 5")
elseif i < 10
println("$(i) is less than 10")
else
if i == 10
println("the value is 10")
else
println("$(i) is bigger than 10")
end
end
end
```

0 is less than 5 3 is less than 5 6 is less than 10 9 is less than 10 12 is bigger than 10 15 is bigger than 10

## Comprehensions

Similar to languages like Haskell and Python, Julia supports the use of simple loops in the construction of arrays and dictionaries, called comprehensions.

A list of increasing integers:

`[i for i in 1:5]`

5-element Array{Int64,1}: 1 2 3 4 5

Matrices can be built by including multiple indices:

`[i * j for i in 1:5, j in 5:10]`

5×6 Array{Int64,2}: 5 6 7 8 9 10 10 12 14 16 18 20 15 18 21 24 27 30 20 24 28 32 36 40 25 30 35 40 45 50

Conditional statements can be used to filter out some values:

`[i for i in 1:10 if i % 2 == 1]`

5-element Array{Int64,1}: 1 3 5 7 9

A similar syntax can be used for building dictionaries:

`Dict("$(i)" => i for i in 1:10 if i % 2 == 1)`

Dict{String,Int64} with 5 entries: "1" => 1 "5" => 5 "7" => 7 "9" => 9 "3" => 3

## Functions

A simple function is defined as follows:

```
function print_hello()
println("hello")
end
print_hello()
```

hello

Arguments can be added to a function:

```
function print_it(x)
println(x)
end
print_it("hello")
print_it(1.234)
print_it(:my_id)
```

hello 1.234 my_id

Optional keyword arguments are also possible:

```
function print_it(x; prefix = "value:")
println("$(prefix) $(x)")
end
print_it(1.234)
print_it(1.234, prefix = "val:")
```

value: 1.234 val: 1.234

The keyword `return`

is used to specify the return values of a function:

```
function mult(x; y = 2.0)
return x * y
end
mult(4.0)
```

8.0

`mult(4.0, y = 5.0)`

20.0

### Anonymous functions

The syntax `input -> output`

creates an anonymous function. These are most useful when passed to other functions. For example:

```
f = x -> x^2
f(2)
```

4

`map(x -> x^2, 1:4)`

4-element Array{Int64,1}: 1 4 9 16

### Type parameters

We can constrain the inputs to a function using type parameters, which are `::`

followed by the type of the input we want. For example:

```
function foo(x::Int)
return x^2
end
function foo(x::Float64)
return exp(x)
end
function foo(x::Number)
return x + 1
end
@show foo(2)
@show foo(2.0)
@show foo(1 + 1im)
```

foo(2) = 4 foo(2.0) = 7.38905609893065 foo(1 + 1im) = 2 + 1im

But what happens if we call `foo`

with something we haven't defined it for?

`foo([1, 2, 3])`

MethodError: no method matching foo(::Array{Int64,1}) Closest candidates are: foo(!Matched::Float64) at none:3 foo(!Matched::Int64) at none:2 foo(!Matched::Number) at none:3

We get a dreaded `MethodError`

! A `MethodError`

means that you passed a function something that didn't match the type that it was expecting. In this case, the error message says that it doesn't know how to handle an `Array{Int64, 1}`

, but it does know how to handle `Float64`

, `Int64`

, and `Number`

.

Read the "Closest candidates" part of the error message carefully to get a hint as to what was expected.

### Broadcasting

In the example above, we didn't define what to do if `f`

was passed an `Array`

. Luckily, Julia provides a convenient syntax for mapping `f`

element-wise over arrays! Just add a `.`

between the name of the function and the opening `(`

. This works for *any* function, including functions with multiple arguments. For example:

`f.([1, 2, 3])`

3-element Array{Int64,1}: 1 4 9

Get a `MethodError`

when calling a function that takes an `Array`

? Try broadcasting it!

## Mutable vs immutable objects

Some types in Julia are *mutable*, which means you can change the values inside them. A good example is an array. You can modify the contents of an array without having to make a new array.

In contrast, types like `Float64`

are *immutable*. You can't modify the contents of a `Float64`

.

This is something to be aware of when passing types into functions. For example:

```
function mutability_example(mutable_type::Vector{Int}, immutable_type::Int)
mutable_type[1] += 1
immutable_type += 1
return
end
mutable_type = [1, 2, 3]
immutable_type = 1
mutability_example(mutable_type, immutable_type)
println("mutable_type: $(mutable_type)")
println("immutable_type: $(immutable_type)")
```

mutable_type: [2, 2, 3] immutable_type: 1

Because `Vector{Int}`

is a mutable type, modifying the variable inside the function changed the value outside of the function. In contrast, the change to `immutable_type`

didn't modify the value outside the function.

You can check mutability with the `isimmutable`

function:

`isimmutable([1, 2, 3])`

false

`isimmutable(1)`

true

## The package manager

### Installing packages

No matter how wonderful Julia's base language is, at some point you will want to use an extension package. Some of these are built-in, for example random number generation is available in the `Random`

package in the standard library. These packages are loaded with the commands `using`

and `import`

.

```
using Random # The equivalent of Python's `from Random import *`
import Random # The equivalent of Python's `import Random`
Random.seed!(33)
[rand() for i in 1:10]
```

10-element Array{Float64,1}: 0.8245577112736127 0.2928364052074266 0.8765793121770682 0.41615145984974955 0.7113242552761618 0.7762718106176869 0.407423649552187 0.15761624576044575 0.8889767003637221 0.017829104289712516

The Package Manager is used to install packages that are not part of Julia's standard library.

For example the following can be used to install JuMP,

```
using Pkg
Pkg.add("JuMP")
```

For a complete list of registered Julia packages see the package listing at JuliaHub.

From time to you may wish to use a Julia package that is not registered. In this case a git repository URL can be used to install the package.

```
using Pkg
Pkg.add("https://github.com/user-name/MyPackage.jl.git")
```

### Package environments

By default, `Pkg.add`

will add packages to Julia's global environment. However, Julia also has built-in support for virtual environments.

Activate a virtual environment with:

`import Pkg; Pkg.activate("/path/to/environment")`

You can see what packages are installed in the current environment with `Pkg.status()`

.

We *strongly* recommend you create a Pkg environment for each project that you create in Julia, and add only the packages that you need, instead of adding lots of packages to the global environment. The Pkg manager documentation has more information on this topic.

*This page was generated using Literate.jl.*