Julia crash course#

Author: Adam Wheeler (CCA, awheeler@simonsfoundation.org)

[1]:
using LinearAlgebra, Random

just-in-time compilation and performance#

[2]:
a = rand(1000000)
[2]:
1000000-element Vector{Float64}:
 0.8417023077161657
 0.7882766047652706
 0.06248966513930976
 0.7533522212090215
 0.3751857454329166
 0.3956741123772659
 0.12555534851830719
 0.703753774555457
 0.024855619108786198
 0.55113824302824
 0.18373921068967958
 0.38561630734961827
 0.34761495145267707
 ⋮
 0.6135477510887072
 0.21518380047112462
 0.07244318509651093
 0.4281735169893237
 0.3435090028649168
 0.09703858455122871
 0.09333621130341974
 0.13559936952585172
 0.8366169088592379
 0.811769370411643
 0.8925669150247477
 0.7550152382892712
[3]:
@time sum(a)
  0.026501 seconds (57.86 k allocations: 2.918 MiB, 98.78% compilation time)
[3]:
500034.00481887336
[4]:
function mysum(a)
    s = 0.0
    for x in a
        s += x
    end
    s
end
[4]:
mysum (generic function with 1 method)
[5]:
@time mysum(a)
  0.010825 seconds (3.80 k allocations: 194.516 KiB, 91.04% compilation time)
[5]:
500034.00481886975

multiple dispatch#

[6]:
# Define a single function with multiple methods

function process(x)
    # "$" is for string interpolation
    println("Default method: argument is of type $(typeof(x))")
end

# Add specialized methods for different types
function process(x::Int)
    println("Integer method: $x squared is $(x^2)")
end

# this is also a valid way to write a function
process(x::String) = println("String method: $(length(x)) characters, uppercase: $(uppercase(x))")

function process(x::Array)
    # TODO Print the length and sum.
end
[6]:
process (generic function with 4 methods)
[7]:
# try these out!

Custom type + multiple dispatch#

[8]:
# Define a simple complex number type
# don't actually do this! There's a built-in one.
struct MyComplex
    real_component::Float64
    imag_component::Float64
end

# Nice string representation
function Base.show(io::IO, z::MyComplex)
    if z.imag_component >= 0
        print(io, "$(z.real_component) + $(z.imag_component)i")
    else
        print(io, "$(z.real_component) - $(abs(z.imag_component))i")
    end
end
[9]:
# try creating a MyComplex
MyComplex(1, 2)
[9]:
1.0 + 2.0i
[10]:
# define a couple arithmetic operations

# we have to import these to override them
import Base: +, *

function +(a::MyComplex, b::MyComplex)
    MyComplex(a.real_component + b.real_component, a.imag_component + b.imag_component)
end
# "Real" is an abstract type that includes floats, rationals, etc.
+(a::MyComplex, b::Real) = MyComplex(a.real_component + b, a.imag_component)
+(a::Real, b::MyComplex) = b + a

function *(a::MyComplex, b::MyComplex)
    real_part = a.real_component * b.real_component - a.imag_component * b.imag_component
    imag_part = a.real_component * b.imag_component + a.imag_component * b.real_component
    MyComplex(real_part, imag_part)
end
*(a::MyComplex, b::Real) = MyComplex(a.real_component * b, a.imag_component * b)
*(a::Real, b::MyComplex) = b * a
;
[11]:
# try it out
z1 = MyComplex(3.0, 4.0)
z2 = MyComplex(1.0, 2.0)
r = 2.0

z1 + r
[11]:
5.0 + 4.0i
[12]:
# this is a silly example, but this is really powerful for, e.g. dual numbers, unitful types, etc.

basic linear algebra and broadcasting (“vectorization”)#

[13]:
# Define matrices and vectors with clean syntax
A = [1 2
     3 4]
B = [5 6; 7 8] # 2×2 matrix
v = [1, 2] # column vector
w = [3, 4]' # row vector
;
[14]:
A
[14]:
2×2 Matrix{Int64}:
 1  2
 3  4
[15]:
A' # adjoint (~transpose)
[15]:
2×2 adjoint(::Matrix{Int64}) with eltype Int64:
 1  3
 2  4
[16]:
# matrix addition
A + B
[16]:
2×2 Matrix{Int64}:
  6   8
 10  12
[17]:
# matrix multiplication
A * v
[17]:
2-element Vector{Int64}:
  5
 11
[18]:
# try w A v
[19]:
A^2 # matrix power
[19]:
2×2 Matrix{Int64}:
  7  10
 15  22
[20]:
# if we want to add v to each column, this doesn't work, use .+ instead
# "." is special broadcasting syntax

A + v
DimensionMismatch: a has size (2, 2), mismatch at dim 2

Stacktrace:
 [1] throw_promote_shape_mismatch(a::Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, b::Nothing, i::Int64)
   @ Base ./indices.jl:135
 [2] promote_shape
   @ ./indices.jl:199 [inlined]
 [3] promote_shape
   @ ./indices.jl:188 [inlined]
 [4] +(A::Matrix{Int64}, Bs::Vector{Int64})
   @ Base ./arraymath.jl:14
 [5] top-level scope
   @ In[20]:4
[21]:
# try adding w to each row
[22]:
# apply any function element-wise
sin.(A)
[22]:
2×2 Matrix{Float64}:
 0.841471   0.909297
 0.14112   -0.756802
[23]:
# the @. macro broadcasts every operation
@. sin(w) + A + v
[23]:
2×2 Matrix{Float64}:
 2.14112  2.2432
 5.14112  5.2432
[24]:
eigen(A)
[24]:
Eigen{Float64, Float64, Matrix{Float64}, Vector{Float64}}
values:
2-element Vector{Float64}:
 -0.3722813232690143
  5.372281323269014
vectors:
2×2 Matrix{Float64}:
 -0.824565  -0.415974
  0.565767  -0.909377
[25]:
# compute A⁻¹ v
x = A \ v
[25]:
2-element Vector{Float64}:
 0.0
 0.5
[26]:
A * x
[26]:
2-element Vector{Float64}:
 1.0
 2.0

PythonPlot#

[27]:
# I don't recommend doing this on Google colab

#using Pkg
#Pkg.add("PythonPlot")
using PythonPlot
ArgumentError: Package PythonPlot not found in current path.
- Run `import Pkg; Pkg.add("PythonPlot")` to install the PythonPlot package.

Stacktrace:
 [1] macro expansion
   @ ./loading.jl:2296 [inlined]
 [2] macro expansion
   @ ./lock.jl:273 [inlined]
 [3] __require(into::Module, mod::Symbol)
   @ Base ./loading.jl:2271
 [4] #invoke_in_world#3
   @ ./essentials.jl:1089 [inlined]
 [5] invoke_in_world
   @ ./essentials.jl:1086 [inlined]
 [6] require(into::Module, mod::Symbol)
   @ Base ./loading.jl:2260
[28]:
x = 1:0.01:10

figure(figsize=(3,3))
plot(x, sin.(x))
xlabel("x")
ylabel("sin(x)")
UndefVarError: `figure` not defined in `Main`
Suggestion: check for spelling errors or missing imports.

Stacktrace:
 [1] top-level scope
   @ In[28]:3