Julia makes it easy to write your own types, which are similar to structs in C-family languages. Multiple dispatch adds a powerful way to handle tasks like default values or different behaviours really easily.
Here we'll look at how you could construct a simple N-body code. We'll assume everything has mass 1 to keep it simple.
Tasks:
Extras:
In [1]:
abstract Body
This code block demonstrates how to define "concrete types" which actually contain values. We declare two types, MovingBody and FixedBody, and make them subtypes of Body (by writing <: Body
). We also write a function MovingBody(x,y)
which makes a MovingBody that defaults to zero movement.
In [2]:
type MovingBody <: Body
x::Float64
y::Float64
dx::Float64
dy::Float64
end
MovingBody(x, y) = MovingBody(x, y, 0, 0)
type FixedBody <: Body
x::Float64
y::Float64
end
In [3]:
type System
bodies::Vector{Body}
end
In [4]:
function setup()
p1 = FixedBody(0,0)
p2 = MovingBody(1,0) # defaults to no motion ... like writing MovingBody(1,0,0,0)
p3 = MovingBody(1,1,1,1)
p4 = MovingBody(0,1,1,-1)
bodies = [p1, p2, p3, p4]
System(bodies)
end
sys = setup()
Out[4]:
In [5]:
const dt = 0.1 # declare it as const because it's not in a function
function move!(b::MovingBody)
b.x += b.dx * dt
b.y += b.dy * dt
b # we return the body here - its coordinates have now been altered
end
move!(b::FixedBody) = b
Out[5]:
See how we just wrote a second version of move!
but defined it to only be applicable to a FixedBody? Now we can call move!
on either a MovingBody or a FixedBody and know that it will behave appropriately.
This code block shows how you can use Unicode characters like ² (type \^2
and press Tab) in variable names to make them more compehensible.
In [6]:
function force(b1::Body, b2::Body)
dx = b2.x - b1.x
dy = b2.y - b1.y
d² = dx^2 + dy^2 + 1e-9 # we add a 1e-9 softening term to avoid dividing by zero
inv_d² = 1 / d²
Fx = dx * inv_d²
Fy = dy * inv_d²
Fx, Fy
end
Out[6]:
This function needs to include the system (group of bodies) as an argument, because each body is affected by the gravity of all other bodies in the system. Another option would be to make sys
, the System
, a global variable. But this option is nicer style and lets us make multiple System
s if we want.
In [7]:
function accelerate!(sys::System, b::MovingBody)
d2x = 0.0
d2y = 0.0
for other in sys.bodies
if b !== other
Fx, Fy = force(b, other)
d2x += Fx
d2y += Fy
end
end
b.dx += d2x * dt
b.dy += d2y * dt
b
end
accelerate!(sys::System, b::FixedBody) = b
Out[7]:
In [8]:
function step!(sys::System, b::Body)
accelerate!(sys, b)
move!(b)
end
function step!(sys::System)
for b in sys.bodies
step!(sys, b)
end
end
Out[8]:
Since the plot
and scatter
(etc) commands from Matplotlib overlay plots by default, we can take advantage of this by writing different plot
commands for different points. We make fixed things red, moving things black with direction arrows, and then write a third plot
command to plot everything in the system by just calling plot
on each individual point.
In [9]:
using PyPlot
function plot(b::FixedBody)
scatter(b.x, b.y, s=50, color="Red")
end
function plot(b::MovingBody)
scatter(b.x, b.y, s=50, color="Black")
arrow(b.x, b.y, b.dx * dt/2, b.dy * dt/2, facecolor="Black")
end
function plot(s::System)
for b in s.bodies
plot(b)
end
axis("equal")
end
Out[9]:
In [11]:
sys = setup()
plot(sys)
Out[11]:
In [12]:
for t = 0:dt:5
plot(sys)
step!(sys)
end