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