# Linear algebra in Julia: Introduction

2021-01-29

Julia is a relatively new general-purpose programming language (almost 9 years old at the time I write this) that is gaining ground in scientific computation and data science.

This post is the start of an exploration of using Julia to illustrate computations and concepts in a first linear algebra course. The plan is basically to just dive into the linear algebra content and pick up Julia bits (syntax, concepts, etc.) as needed along the way.

## Setup

To follow along, you can
download and install it on your own machine
or
get access to it on something like repl.it
(account required, the free Starter account should be more than enough for
what we'll be doing with Julia; however, note that their version of Julia
seems to lag behind, and the free account doesn't give you any privacy). I
prefer to run things on my own metal, so it's just a matter of
`pacman -S julia`

.

## Hello Julia

```
❯ julia
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.5.3 (2020-11-09)
_/ |\__'_|_|_|\__'_| |
|__/ |
julia>
```

Sanity check:

```
julia> 2+2
4
julia> x = 3
3
julia> x
3
```

Let's get a 2x2 matrix going:

```
julia> A = [1 x
8 -7]
2×2 Array{Int64,2}:
1 3
8 -7
```

Observe that this is called an array and that Julia decided that its entries are integers (fair enough).

The same result can be achieved via

```
julia> A = [1 x; 8 -7]
2×2 Array{Int64,2}:
1 3
8 -7
```

or

```
julia> A = [[1, 8] [x, -7]]
2×2 Array{Int64,2}:
1 3
8 -7
```

Note the conspicuous absence of a comma between
`[1, 8]`

and
`[x, -7]`

.

I want a column vector with floating point entries:

```
julia> v = [1, -6, 9.3, 4]
4-element Array{Float64,1}:
1.0
-6.0
9.3
4.0
```

How about complex floats? TODO

## Arithmetic

Let's spice things up a bit.

```
julia> A = [[1, -2, 1] [4, 0, 3]];
julia> B = [[-8, 1, 0] [1, 9, 3]];
julia> A + B
3×2 Array{Int64,2}:
-7 5
-1 9
1 6
julia> C = [[0, 1, -1] [-1, 3, -6] [3, -4, 3]];
julia> 3*C
3×3 Array{Int64,2}:
0 -3 9
3 9 -12
-3 -18 9
julia> 3C
3×3 Array{Int64,2}:
0 -3 9
3 9 -12
-3 -18 9
```

The latter is kind of nice. However

```
julia> D = [[8, 0, 1] [-1, 1, 8] [2, -5, 1]];
julia> CD
ERROR: UndefVarError: CD not defined
Stacktrace:
[1] top-level scope
[2] run_repl(::REPL.AbstractREPL, ::Any) at /build/julia/src/julia-1.5.3/usr/share/julia/stdlib/v1.5/REPL/src/REPL.jl:288
```

because Julia takes `CD`

to be the name of a
variable, and it has not yet been defined. So we have to do

```
julia> C*D
3×3 Array{Int64,2}:
3 23 8
4 -30 -17
-5 19 31
```

Beware the order of multiplication

```
julia> A*C
ERROR: DimensionMismatch("matrix A has dimensions (3,2), matrix B has dimensions (3,3)")
Stacktrace: (omitted)
julia> C*A
3×2 Array{Int64,2}:
5 9
-9 -8
14 5
```

Also useful:

```
julia> transpose(A)
2×3 LinearAlgebra.Transpose{Int64,Array{Int64,2}}:
1 -2 1
4 0 3
```

Up for a stupid trick?

```
julia> A + 2
ERROR: MethodError: no method matching +(::Array{Int64,2}, ::Int64)
For element-wise addition, use broadcasting with dot syntax: array .+ scalar
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...) at operators.jl:538
+(::Complex{Bool}, ::Real) at complex.jl:301
+(::Missing, ::Number) at missing.jl:115
...
Stacktrace: (omitted)
```

Thank you, Julia, for not following MATLAB's example in miserably failing freshman mathematics. And do I detect a hint of annoyance at MATLAB fandom asking why the operation didn't work and how oh how could one recover this appalling bit of syntactic sugar? Anyway, let's listen to the instruction in the error message:

```
julia> A .+ 2
3×2 Array{Int64,2}:
3 6
0 2
3 5
```

## Access to entries

Single entries can be accessed for both reading and writing as follows:

```
julia> A
3×2 Array{Int64,2}:
1 4
-2 0
1 3
julia> A[1, 2]
4
julia> A[1, 2] = 5
5
julia> A
3×2 Array{Int64,2}:
1 5
-2 0
1 3
```

So indexing is 1-based rather than 0-based.

We can get a whole column or row at a time:

```
julia> A[:,2]
3-element Array{Int64,1}:
5
0
3
julia> A[1,:]
2-element Array{Int64,1}:
1
5
```

Or maybe you're after a submatrix:

```
julia> A[2:3,1:2]
2×2 Array{Int64,2}:
-2 0
1 3
```

This is just scratching the surface of Julia's indexing system.

It does bring up the notion of range, so let's play with that:

```
julia> 1:5
1:5
```

You don't say! Let's try a little harder:

```
julia> r = 1:5;
julia> length(r)
5
julia> r[1]
1
julia> r[2]
2
julia> r[3]
3
julia> r[4]
4
julia> r[5]
5
julia> r[6]
ERROR: BoundsError: attempt to access 5-element UnitRange{Int64} at index [6]
Stacktrace: (omitted)
julia> r[end]
5
```

If you're used to Python, note that in Julia the range contains the endpoint.

Another example:

```
julia> r = 1:.5:3
1.0:0.5:3.0
julia> length(r)
5
julia> Array(r)
5-element Array{Float64,1}:
1.0
1.5
2.0
2.5
3.0
julia> s = 1:.5:3.2
1.0:0.5:3.0
julia> r == s
true
```

Here the `==`

operator is comparison.

## Some matrix creation functions

```
julia> zeros(3, 2)
3×2 Array{Float64,2}:
0.0 0.0
0.0 0.0
0.0 0.0
```

Bummer, I wanted integers:

```
julia> zeros(Int, 3, 2)
3×2 Array{Int64,2}:
0 0
0 0
0 0
```

Better. But let's observe that these are not integers as mathematicians know
and love them: they're Int64's, in other words they go from
`-2^63`

to
`2^63-1`

. What comes after that? Let's see:

```
julia> a = 2^63-1
9223372036854775807
julia> a+1
-9223372036854775808
```

If you grew up on C or somesuch, this is normal behaviour. If you're coming
from Python or the like, you can have actual integers using
`BigInt`

```
julia> a = BigInt(2^63-1)
9223372036854775807
julia> a+1
9223372036854775808
```

Let's resume the matrix creation show:

```
julia> ones(BigInt, 2, 3)
2×3 Array{BigInt,2}:
1 1 1
1 1 1
```

In case you're wondering if there's a function called
`twelves`

: no, of course not. But you can do

```
julia> fill(12, 3, 2)
3×2 Array{Int64,2}:
12 12
12 12
12 12
```

Identity matrices require pulling in the
`LinearAlgebra`

module

```
julia> Matrix(I, 3, 3)
ERROR: UndefVarError: I not defined
Stacktrace: (omitted)
julia> using LinearAlgebra
julia> Matrix(I, 3, 3)
3×3 Array{Bool,2}:
1 0 0
0 1 0
0 0 1
```

Seriously, the type is
`Bool`

? We can fix this:

```
julia> Matrix{Float64}(I, 3, 3)
3×3 Array{Float64,2}:
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
```

Another useful construct (also needs
`LinearAlgebra`

, but we've already imported
that)

```
julia> D = diagm([1, 3, 2])
3×3 Array{Int64,2}:
1 0 0
0 3 0
0 0 2
```

You can also put smaller matrices together to get larger ones:

```
julia> [zeros(3, 2) ones(3, 5)]
3×7 Array{Float64,2}:
0.0 0.0 1.0 1.0 1.0 1.0 1.0
0.0 0.0 1.0 1.0 1.0 1.0 1.0
0.0 0.0 1.0 1.0 1.0 1.0 1.0
julia> [zeros(2, 3); ones(5, 3)]
7×3 Array{Float64,2}:
0.0 0.0 0.0
0.0 0.0 0.0
1.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
1.0 1.0 1.0
```