Julia is a language for scientific computing that has similar features as Matlab and Python, but can usually (via automatic just-in-time compilation) achieve performance close to C/C++.
Similarly to Python, it can be used interactively on the terminal, for executing script files, or via notebooks. The basic language and the accompanying tools are free software.
This is a brief overview of syntax and capabilities. A complete documentation can be found here.
Built-in capabilities for handling matrices and vectors similar to Matlab
A = [4. -1.; 1. 2.]
2×2 Array{Float64,2}: 4.0 -1.0 1.0 2.0
b = [1., 2.]
2-element Array{Float64,1}: 1.0 2.0
x = A\b # solve A*x = b
2-element Array{Float64,1}: 0.4444444444444444 0.7777777777777778
A*x
2-element Array{Float64,1}: 0.9999999999999999 2.0
Unlike Matlab, Julia differentiates between various basic types:
Float16
, Float32
, Float64
(default)Int16
, Int32
, Int64
(default, alias Int
), Int128
UInt16
, UInt32
, UInt64
(default, alias UInt
), UInt128
Bool
BigInt
, BigFloat
Char
, String
i = 103491;
typeof(i)
Int64
a = 1. + 3im
b = complex(2., 1.)
a/b
1.0 + 1.0im
n = 17
c = 's'
s = "tip "*string(n)*": string"*string(c)*" are concatenated with *"
"tip 17: strings are concatenated with *"
Argument types are strictly enforced:
log(-1.)
DomainError with -1.0: log will only return a complex result if called with a complex argument. Try log(Complex(x)). Stacktrace: [1] throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:31 [2] log(::Float64) at ./special/log.jl:285 [3] top-level scope at In[8]:1
log(complex(-1))
0.0 + 3.141592653589793im
Arbitrary unicode characters are allowed as identifiers (entered, e.g., via \lambda
[TAB], A^-
[TAB] ^1
[TAB]) - see also the character list
using LinearAlgebra
λ, Ψ = eigen(A) # eigenvalue decomposition, with two separate return values
Eigen{Float64,Float64,Array{Float64,2},Array{Float64,1}} eigenvalues: 2-element Array{Float64,1}: 3.0 3.0 eigenvectors: 2×2 Array{Float64,2}: 0.707107 0.707107 0.707107 0.707107
A⁻¹= inv(A)
2×2 Array{Float64,2}: 0.222222 0.111111 -0.111111 0.444444
General note: In the interactive terminal, to display the help for a specific function, type "?" and then its name (e.g., "eig")
c = rand();
if c <= 0.49999
println("heads");
elseif c >= 0.50001
println("tails");
else
println("side");
end
tails
(rand() <= 0.5 ? "heads" : "tails")
"heads"
for i = 1:10
print(i, " ");
end
1 2 3 4 5 6 7 8 9 10
p = 0;
while p < 3 && p > -5
p += rand([-1,1]);
println(p, " ", abs(p), " ", sign(p));
end
1 1 1 2 2 1 1 1 1 2 2 1 1 1 1 2 2 1 3 3 1
Matrices and vectors are of Array
type. Note that indexing is 1-based.
y = zeros(size(A,2));
for i = 1:size(A,1)
for j = 1:size(A,2)
y[i] += A[i,j] * x[j];
end
end
y
2-element Array{Float64,1}: 0.9999999999999999 2.0
Support for Array-based operations is similar to Matlab:
x = range(0., stop=1., length=20);
x.^4 .* exp.(-x.^2/2)
20-element Array{Float64,1}: 0.0 7.662739828393933e-6 0.0001220954599444615 0.0006138425254290904 0.0019213270938265815 0.00463263107424585 0.009460977839160578 0.017214859280621424 0.0287639500805727 0.045002106780113724 0.06680885149456278 0.0950107863621051 0.13034437391683382 0.17342145188748168 0.22469873214322042 0.28445236848224176 0.3527584743644178 0.42948023864709434 0.5142620350122585 0.6065306597126334
A = [1. 2. ; 3. 4.]
2×2 Array{Float64,2}: 1.0 2.0 3.0 4.0
A[1,:]
2-element Array{Float64,1}: 1.0 2.0
A[:,1] = [5, 6];
A
2×2 Array{Float64,2}: 5.0 2.0 6.0 4.0
Arrays can also hold other types (e.g., other Arrays). Note the syntax Type{T}
for parameterized types (similar to templates in C++, generics in Java and recent versions of Python)
arr1 = Array{Array{Float64,1},2}(undef, 2, 2)
2×2 Array{Array{Float64,1},2}: #undef #undef #undef #undef
arr1[1,1] = Array{Float64,1}();
arr1[1,2] = Float64[];
arr1[2,1] = Array{Float64,1}([1., 2.]);
arr1[2,2] = Float64[2., 3.];
arr1
2×2 Array{Array{Float64,1},2}: [] [] [1.0, 2.0] [2.0, 3.0]
Even mixed Arrays are possible (but are usually not best for performance):
arr2 = [1, 1., "1", [1.]]
4-element Array{Any,1}: 1 1.0 "1" [1.0]
Arrays are always handled by reference, copies need to be requested specifically:
a = [1., 1.];
b = a; # b references the same array as a
c = copy(a); # c is a copy of a
b[1] = 2.;
println("a = ", a, "\nb = ", b, "\nc = ", c)
a = [2.0, 1.0] b = [2.0, 1.0] c = [1.0, 1.0]
Tuple
is an array type that is indexed similarly as a 1D Array
. They are initialized with round brackets or simply by a comma-separated list. Tuples are, however, immutable: they cannot be modified after creation.
t = (1, 2)
(1, 2)
t = 1, 2
(1, 2)
x, y = 1, 2; # same as (x,y) = (1,2)
println("x = ", x, ", y = ", y);
x = 1, y = 2
y, x = x, y; # swap x and y
println("x = ", x, ", y = ", y);
x = 2, y = 1
MixedSet = Set();
push!(MixedSet,"abc");
push!(MixedSet,π);
push!(MixedSet,9);
MixedSet
Set(Any["abc", 9, π = 3.1415926535897...])
π in MixedSet
true
SparseIntSet = Set{Int}([-2411, 1022981,9]);
push!(SparseIntSet, 3);
for i in SparseIntSet
if i ∈ MixedSet
println(i);
end
end
9
DenseIntSet = BitSet([1,3,5,7,11]) # implemented by bit vectors, for non-sparse sets
BitSet([1, 3, 5, 7, 11])
union(SparseIntSet,DenseIntSet) # analogously: intersect, symdiff, ...
Set([7, 9, 1022981, 3, -2411, 5, 11, 1])
D = Dict{Tuple{Int,Int},Float64}((1,2)=>π, (1,0)=>ℯ);
D[(9,9)] = 0.;
D
Dict{Tuple{Int64,Int64},Float64} with 3 entries: (1, 2) => 3.14159 (1, 0) => 2.71828 (9, 9) => 0.0
(1,1) ∈ keys(D)
false
D[(1,0)]
2.718281828459045
Using the syntax for i = I
(or equivalently for i in I
or for i ∈ I
) one can iterate over any object for which the functions
iterate(I) → (firstitem, initialstate)
, iterate(I, state) → (nextitem,newstate)
,
both returning nothing
if when no elements remain,
are available, see the documentation. In particular, this applies to the built-in containers:
A = [1., 2., 3.];
for a ∈ A
print(a, " ");
end
println("");
B = BitSet([13, 5, 2, 9]);
for b ∈ B
print(b, " ");
end
1.0 2.0 3.0 2 5 9 13
The range notation 1:n
does not create a vector as in Matlab, but a UnitRange
object that can be iterated over (but needs only constant memory!)
typeof(1:10)
UnitRange{Int64}
typeof(0:0.1:1)
StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}
To obtain an actual vector from any iterable, one can use collect
collect(0:0.1:1)
11-element Array{Float64,1}: 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
There are some useful built-in functions for generating new iterable objects from collections.
for (i, x) in enumerate(B)
println(i, ": ", x);
end
1: 2 2: 5 3: 9 4: 13
V = [1, 2, 3]; W = [4, 5, 6];
for (x,y) in zip(V, W)
println(x, ", ", y);
end
1, 4 2, 5 3, 6
A similar syntax can be used in array comprehensions (similar to Python) and in generator expressions:
[n^2 for n=1:5]
5-element Array{Int64,1}: 1 4 9 16 25
[1.0/(i+j) for i = 1:3, j = 1:6]
3×6 Array{Float64,2}: 0.5 0.333333 0.25 0.2 0.166667 0.142857 0.333333 0.25 0.2 0.166667 0.142857 0.125 0.25 0.2 0.166667 0.142857 0.125 0.111111
sum(i for i=1:10)
55
Dict(x => sin(π*x) for x in 0:.5:2.)
Dict{Float64,Float64} with 5 entries: 0.0 => 0.0 0.5 => 1.0 2.0 => -2.44929e-16 1.5 => -1.0 1.0 => 1.22465e-16
function f(x,y)
y*cos(x), y*sin(x)
end
f(π/3, 2)
(1.0000000000000002, 1.7320508075688772)
function newtonstep(f::Array, Df::Array, x::Array)
println("using vector definition");
return x - Df\f
end
function newtonstep(f::Number, Df::Number, x::Number)
println("using scalar definition");
return x - f/Df
end
newtonstep (generic function with 2 methods)
newtonstep([1,1], [2. -1.; -1. 2.], [0.,0.])
using vector definition
2-element Array{Float64,1}: -1.0 -1.0
newtonstep(1, 2., 0)
using scalar definition
-0.5
Inline declaration and anonymous functions:
f(x) = x^2;
g = x->x^3;
f(2), g(2)
(4, 8)
h = x->(cos(x),sin(x));
h.([π, π/3, π/8])
3-element Array{Tuple{Float64,Float64},1}: (-1.0, 1.2246467991473532e-16) (0.5000000000000001, 0.8660254037844386) (0.9238795325112867, 0.3826834323650898)
map(t->t[1], [h(i*π/10) for i = 0:10])
11-element Array{Float64,1}: 1.0 0.9510565162951535 0.8090169943749475 0.5877852522924731 0.30901699437494745 6.123233995736766e-17 -0.30901699437494734 -0.587785252292473 -0.8090169943749473 -0.9510565162951535 -1.0
Names of functions that modify their arguments by convention end in !
. For instance, push!
adds an element to a data structure, whereas empty!
removes all content.
SomeSet = Set([1, 2]);
empty!(SomeSet)
Set(Int64[])
Note the following pitfall, which arises because array indexing with :
creates copies:
function setone!(v)
fill!(v, 1.);
end
A = [2. 2.; 2. 2.];
setone!(A[:,1]);
A
2×2 Array{Float64,2}: 2.0 2.0 2.0 2.0
To instead pass a reference to the memory of the underlying array, use view
:
setone!(view(A,:,1));
A
2×2 Array{Float64,2}: 1.0 2.0 1.0 2.0
abstract type RoundShape end
mutable struct Ellipse <: RoundShape
center::Tuple{Float64,Float64}
A::Real
B::Real
end
mutable struct Circle <: RoundShape
center::Tuple{Float64,Float64}
r::Real
end
getcenter(S::RoundShape) = S.center;
area(S::Ellipse) = π * S.A * S.B;
area(S::Circle) = π * S.r^2;
Ell = Ellipse((1., 0.), 2, 1);
getcenter(Ell), area(Ell)
((1.0, 0.0), 6.283185307179586)
Note that concrete types (such as Ellipse, Circle) can only be subtypes of abstract types (here: RoundShape). Built-in abstract types are, e.g., Number
, Real
, Integer
@time s = 0; for i=1:1e6 s += i^3; end; print("result: ", s);
0.000001 seconds (4 allocations: 160 bytes) result: 2.5000050000024622e23
@time s = sum(map(x->x^3, 1:1e6)); print("result: ", s);
0.100751 seconds (286.55 k allocations: 21.845 MiB, 4.34% gc time) result: 2.5000050000025e23
@code_native sin(π/2)^2
.section __TEXT,__text,regular,pure_instructions ; Function ^ { ; Location: math.jl:793 ; Function Type; { ; Location: math.jl:793 vcvtsi2sdl %edi, %xmm1, %xmm1 decl %eax movl $3298697720, %eax ## imm = 0xC49E21F8 .byte 0xff .byte 0x7f .byte 0x00 addb %bh, %bh loopne 0x68 nopw %cs:(%eax,%eax) ;}}
using Printf
println(@sprintf "iteration %d: error %e, step size %e" 139 2.34232131e-5 1.290137e-6)
iteration 139: error 2.342321e-05, step size 1.290137e-06
Modules are used to encapsulate functions and types. Additional modules can be installed by the built-in package manager.
using Pkg
Pkg.add("StaticArrays");
Updating registry at `~/.julia/registries/General` Updating git-repo `https://github.com/JuliaRegistries/General.git` [1mFetching: [========================================>] 100.0 %.0 % Resolving package versions... Updating `~/.julia/environments/v1.0/Project.toml` [no changes] Updating `~/.julia/environments/v1.0/Manifest.toml` [no changes]
They are loaded by using
or import
(where the latter puts identifiers into a separate directory)
using StaticArrays
For using
and import
, julia searches for modules in the directories listed in LOAD_PATH. To also search for module files you have created in /some/directory
, add the following line to the file ~/.juliarc.jl
,
push!(LOAD_PATH,"/some/directory")
See also workflow tips for working with modules, and the Revise
package:
Pkg.add("Revise");
using Revise
Resolving package versions... Updating `~/.julia/environments/v1.0/Project.toml` [no changes] Updating `~/.julia/environments/v1.0/Manifest.toml` [no changes]
After Revise
has been loaded, every change in
Julia offers interfaces to FORTRAN, C, C++, and Python. It is especially easy to use existing Python packages:
Pkg.add("PyPlot");
using PyPlot
x = range(1e-2,stop=1,length=10000);
plot(x, sin.(1.0./x));
Resolving package versions... Updating `~/.julia/environments/v1.0/Project.toml` [no changes] Updating
`~/.julia/environments/v1.0/Manifest.toml`
[no changes]
Pkg.add("SymPy");
using SymPy
x, y = symbols("x, y", real=true);
diff(x^y*exp(y*exp(x*y)) - x*y + 1, y)
Resolving package versions... Updating `~/.julia/environments/v1.0/Project.toml` [no changes] Updating `~/.julia/environments/v1.0/Manifest.toml` [no changes]
rand([1, 4, 16])
4
rand("abc")
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
rand(3,3,3)
3×3×3 Array{Float64,3}: [:, :, 1] = 0.137612 0.219521 0.258723 0.846962 0.605823 0.961066 0.125312 0.598955 0.400506 [:, :, 2] = 0.368323 0.776075 0.625162 0.274516 0.688724 0.984733 0.163143 0.878335 0.715124 [:, :, 3] = 0.436599 0.221008 0.0171596 0.0239657 0.421176 0.613731 0.791671 0.0843151 0.150949
rand(Int, 2, 2, 2)
2×2×2 Array{Int64,3}: [:, :, 1] = -5020191725107600484 -5847271543186582121 -6008095998924860804 -3567908730296754207 [:, :, 2] = 9164288876322877779 4615745002041857521 -8504649829447353854 2004894841892499977
using Random
A = zeros(5)
rand!(view(A,1:3))
A
5-element Array{Float64,1}: 0.04579687676883348 0.42363037620611 0.17266345126070703 0.0 0.0
rng = MersenneTwister(5312)
rand!(rng, A) # similar effect as calling seed!(5312), but this does not change global random number generator
5-element Array{Float64,1}: 0.1824382704421581 0.4456902956386928 0.17818792893243773 0.5461182756070553 0.12142121921163795
bitrand(3, 3, 3)
3×3×3 BitArray{3}: [:, :, 1] = false true false false true true true true false [:, :, 2] = true false false true true true false false false [:, :, 3] = false false true false false true true true true
G = randn(1000000);
using Plots; gr()
histogram(G, nbins=200)
WARNING: using Plots.plot in module Main conflicts with an existing identifier.