何かJuliaであまり知られていないっぽいけれど便利な機能をここに追記していこうと思います。
どんなトピックスがあるかは、右の目次を見てください。
確認環境
Julia 1.4.2
Mac OS 10.14.6
struct関連
structの値の初期化にkwdefを使う
Juliaのstructを使うときにあらかじめ値を入れておきたい場合があると思います。
一つの方法は
struct TestA
a ::Float64
b ::Array{Float64,1}
TestA() = new(0,rand(3))
end
とstructの中に定義する方法ですね。
testA = TestA()
println(testA)
TestA(0.0, [0.6482247142585289, 0.3035639028122792, 0.697402899461609])
同様のことをkwdefを使ってできます。
Base.@kwdef struct TestAkw
a ::Float64 = 0
b ::Array{Float64,1} = rand(3)
end
kwdefを使うと、フィールドにそのまま値を入れることができるので、初期値を入れるときに非常に便利です。
structで定義したArrayは実は値を変えられる
例えば、上で定義したstructを使って、
Base.@kwdef struct TestAkw
a ::Float64 = 0
b ::Array{Float64,1} = rand(3)
end
testAkw = TestAkw()
println(testAkw)
testAkw.b[:] = rand(3)
println(testAkw)
ができます。structは変更不可能なはずなのに、bの値を変えられます。ただし、
testAkw.b = rand(3)
testAkw.b = rand(4)
ERROR: LoadError: setfield! immutable struct of type TestAkw cannot be changed
Stacktrace:
とかはエラーが出ます。これは、定義した配列に代入することは可能でも、新しい配列をフィールドとして定義できないということだと思います。
また、定義ずみの配列にpushすることは可能です。
push!(testAkw.b,2)
println(testAkw)
は
TestAkw(0.0, [0.09444996612558998, 0.030326088326802836, 0.4934630346414506, 2.0])
とできます。
structを配列のように扱う
Base.@kwdef mutable struct TestB
a ::Float64 = 0
b ::Array{Float64,1} = rand(3)
end
というstructを定義したとします。このstructを配列のように扱いたければ、
function Base.setindex!(x::TestB,v,i)
x.b[i] = v
end
function Base.getindex(x::TestB,i)
return x.b[i]
end
という関数を定義しておくと、
testb = TestB()
println(testb)
testb[2] = 3
println(testb)
v = testb[2]
println(v)
TestB(0.0, [0.20274453089015654, 0.3365997667642584, 0.04122776256227012])
TestB(0.0, [0.20274453089015654, 3.0, 0.04122776256227012])
3.0
のようにtestb[2]のような形でフィールドのbに入れられます。
自分で作ったtypeをベクトルとみなす in Julia
や
Juliaの多重ディスパッチで遊んでみる:2次元1次元配列
も参照すると良いと思います。
structが持っているフィールド名を辞書型に格納する
あるstructの中身がわからないとして、その中身を取り出す方法があります。例えば、
function make_parametersdict(p::T) where T <: Any
pnames = fieldnames(T)
pdict = Dict()
for i=1:length(pnames)
pdict[String(pnames[i])] = getfield(p,pnames[i])
end
return pdict,pnames
end
という関数を定義しておくと、中身のわからないstruct pに対してmake_parametersdict(p)
すれば、フィールド名とその値がセットになったものを取り出すことができます。
例えば、
Base.@kwdef mutable struct TestB
a ::Float64 = 0
b ::Array{Float64,1} = rand(3)
end
function make_parametersdict(p::T) where T <: Any
pnames = fieldnames(T)
pdict = Dict()
for i=1:length(pnames)
pdict[String(pnames[i])] = getfield(p,pnames[i])
end
return pdict,pnames
end
testB = TestB()
pdict,pnames = make_parametersdict(testB)
display(pdict)
をすると、
Dict{Any,Any} with 2 entries:
"b" => [0.421301, 0.967549, 0.64976]
"a" => 0.0
が得られます。
structが持っているフィールド名と同じ名前の変数を用意する
上と組み合わせて、
Base.@kwdef mutable struct TestB
a ::Float64 = 0
b ::Array{Float64,1} = rand(3)
end
function make_parametersdict(p::T) where T <: Any
pnames = fieldnames(T)
pdict = Dict()
for i=1:length(pnames)
pdict[String(pnames[i])] = getfield(p,pnames[i])
end
return pdict,pnames
end
testB = TestB()
pdict,pnames = make_parametersdict(testB)
for i=1:length(pnames)
eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))
end
println("a = ", a)
println("b = ", b)
とします。出力結果は、
a = 0.0
b = [0.48592393007162893, 0.492182409060139, 0.609400886449827]
となります。aやbはあらわには定義していないはずなのに、ちゃんと定義されています。
これは、
eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))
がポイントで、pnames[i]
がaあるいはbを表しています。
aあるいはbにpdict[String(pnames[i])]
を代入するという意味の数式Exprを組み立て、それをevalで評価しています。
これを応用すると、
function loading()
p = TestB()
pdict,pnames = make_parametersdict(p)
for i=1:length(pnames)
eval(Expr(:(=), pnames[i], pdict[String(pnames[i])]))
end
if length(ARGS) ≥ 1
include(pwd()*"/"*ARGS[1])
end
for i=1:length(pnames)
pdict[String(pnames[i])] = eval(pnames[i])
setfield!(p, pnames[i], eval(pnames[i]));
end
display(p)
println("\n")
end
loading()
として、file.jlの中身を
a = 3.3
とすれば、
julia testj.jl file.jl
TestB(3.3, [0.44783084199216416, 0.7498487813060268, 0.361759560432239])
のように、structに対して、外部から与えられない値はそのまま、与えられた値は変更する、ということが可能です。
配列関連
collectで配列を取り出す
1:10
みたいなものから配列を作るには、
a = 1:10
b = collect(a)
とすればよいです。出力は
10-element Array{Int64,1}:
1
2
3
4
5
6
7
8
9
10
となります。なお、2飛ばしなどは
a = 1:2:10
b = collect(a)
で作れます。
5-element Array{Int64,1}:
1
3
5
7
9
値を取り出したいときは
a = 1:2:10
println(a[1])
println(a[2])
のような感じで簡単にできます。
StaticArraysは速い
例えばこのようなコードがあったとします。
mutable struct TestB
U::Array{Array{Float64,1},1}
TestB(n) = new(Array{Array{Float64,1},1}(undef,n))
end
n = 1000000
testB = TestB(n)
function loop1(testB)
for i=1:length(testB.U)
testB.U[i] = rand(4)
end
end
function loop2!(testB)
for i=1:length(testB.U)
testB.U[i] = rand(4)
end
end
println("loop1")
@time loop1(testB)
@time loop1(testB)
@time loop1(testB)
println("loop2!")
@time loop2!(testB)
@time loop2!(testB)
@time loop2!(testB)
これは、配列の配列であるUの中身を更新しています。
出力結果は例えば、
loop1
0.122696 seconds (1.03 M allocations: 108.415 MiB, 36.20% gc time)
0.097994 seconds (1000.00 k allocations: 106.812 MiB, 37.45% gc time)
0.150192 seconds (1000.00 k allocations: 106.812 MiB, 58.67% gc time)
loop2!
0.160977 seconds (1.03 M allocations: 108.294 MiB, 59.41% gc time)
0.106944 seconds (1000.00 k allocations: 106.812 MiB, 46.52% gc time)
0.108966 seconds (1000.00 k allocations: 106.812 MiB, 47.72% gc time)
となります。
これをStaticArraysを用いて、
mutable struct TestBst
U::Array{SVector{4,Float64},1}
TestBst(n) = new(Array{SVector{4,Float64},1}(undef,n))
end
testBst = TestBst(n)
println("loop1")
@time loop1(testBst)
@time loop1(testBst)
@time loop1(testBst)
println("loop2!")
@time loop2!(testBst)
@time loop2!(testBst)
@time loop2!(testBst)
とすると、
loop1
0.303754 seconds (651.65 k allocations: 44.886 MiB, 9.10% gc time)
0.000873 seconds (10.00 k allocations: 1.068 MiB)
0.000544 seconds (10.00 k allocations: 1.068 MiB)
loop2!
0.009977 seconds (39.01 k allocations: 2.593 MiB)
0.000544 seconds (10.00 k allocations: 1.068 MiB)
0.000540 seconds (10.00 k allocations: 1.068 MiB)
と速くなります。
StaticArraysとは
https://github.com/JuliaArrays/StaticArrays.jl
これで、
長さ一定の配列に制限することで速度が上がります。
二種類があって、Sがつくものは長さ一定かつ値も一定です。Mがつくものは値は変化しても良いものです。
Mの方は
mutable struct TestBstm
U::Array{MVector{4,Float64},1}
TestBstm(n) = new(Array{MVector{4,Float64},1}(undef,n))
end
testBstm = TestBstm(n)
println("loop1")
@time loop1(testBstm)
@time loop1(testBstm)
@time loop1(testBstm)
println("loop2!")
@time loop2!(testBstm)
@time loop2!(testBstm)
@time loop2!(testBstm)
ですが、これは
loop1
0.099530 seconds (2.04 M allocations: 154.782 MiB, 21.14% gc time)
0.124582 seconds (2.00 M allocations: 152.588 MiB, 37.75% gc time)
0.094865 seconds (2.00 M allocations: 152.588 MiB, 29.09% gc time)
loop2!
0.132371 seconds (2.03 M allocations: 154.112 MiB, 43.11% gc time)
0.093484 seconds (2.00 M allocations: 152.588 MiB, 31.07% gc time)
0.124293 seconds (2.00 M allocations: 152.588 MiB, 46.67% gc time)
速くなっていません。サイズが小さい配列だからかと思います。Sの方は物理では何らかの場の値を扱うときに使うと良さそうですね。
色々
発生する乱数を毎回同じにする
計算をデバッグするときとかに、毎回同じ乱数が出てくれると便利なことがあると思います。
using Random
Random.seed!(123)
testAkw = TestAkw()
println(testAkw)
testAkw = TestAkw()
println(testAkw)
testAkw = TestAkw()
println(testAkw)
seed!の中の値を変えると、出てくる乱数が変わります。ここを固定しておけば、何回実行しても同じ乱数が得られます。乱数列を固定している、ということですね。
エラーを起こしたところで止める
何かエラーが起きた時にメッセージを出して止めたいことがあると思います。
@assert
はまだ知られていますが、error
はあまり知られていないと思うので紹介します。
a = -1
if a < 0
println("error! a should be positive a = $a")
exit()
end
とすると、
error! a should be positive a = -1
となり、aが負だとエラーが出て止まります。これが一番素朴な感じですね。
これは、
a = -1
@assert a < 0 "error! a should be positive a = $a"
ともできて、この場合は
error! a should be positive a = -1
となって止まります。
また、errorという関数を使うと、
a = -1
if a < 0
error("error! a should be positive a = $a")
end
は
ERROR: LoadError: error! a should be positive a = -1
Stacktrace:
と止まります。errorを使う場合、どこの呼び出しで止まったかという情報が出てきてソースコードの番号が表示されるので、便利です。
Plotsの読み込みを高速化する。
PackageCompiler.jlでPlotsの呼び出しを高速化する2020年7月版
をやってみてください。
GoogleColabを使用
手元でJupyter NotebookでJuliaを実行して、ファイルを.ipynbで保存していたとします。
そのファイルをGoogle Colabでも実行してみたいことがありますよね?
そんなときは
Google ColabでJuliaを使う:元々あるノートブックを使う
こちらを参考にしてやってみてください。
データの入出力関係
出力あれこれ
printlnの使い方についてまとめます。
たとえば、
a = rand(3)
b = 4
println(a,"\t",b) #文字列を入れなくても変数でも書き出せるし、何個でも入れられる。\tはTab
println("$a $b") #このように$aなどとすると変数を文字列の中に入れられる
println("$a\t$b") #もちろんTabも入れられる
こんなことができます。
$を使えば、このように
println("$(2*a)\t$b")
println("$(2*a)\t$(sqrt(b))")
計算して出力することもできます。
もしたくさんある場合には、printlnではなく改行のないprintも使えます。このように、
c = rand(1:10,10)
for ci in c
print(ci,"\t")
end
とすると、
4 3 3 6 8 7 1 3 4 10
こんな感じに1行に並べることも可能です。
もう少しシンプルにも書けて、
c = rand(1:10,10)
[print("$ci ") for ci in c]
でも可能です。
print_matrixなど
https://twitter.com/LirimyDh/status/1295310732435705856?s=20
で知ったのですが、以下のようなやり方が可能です。
a = rand(4,4)
println("空白")
Base.print_matrix(stdout,a,""," ","\n")
println("タブ")
Base.print_matrix(stdout,a,"","\t","\n")
println(",")
Base.print_matrix(stdout,a,"",",","\n")
一つ目の値は出力先で、stdoutとすると画面に出力します。次が配列です。
残りの三つですが、一つ目は表示の頭に何か入れる場合です。ここでは空白にしています。
二つ目は値と値の間をどう区切るか、です。三つ目は最後の文字の後にどうするか、です。ここに\nを入れているのは、改行をしたいためです。
出力結果はこうなります。
空白
0.6768908174832444 0.3817345277763453 0.42646007837750055 0.17014559504201232
0.06283236014992255 0.8604594763575402 0.9500945643866037 0.7974495350151196
0.9238853594136449 0.6583229009292564 0.43140441711350563 0.8170287950442048
0.06778870328883024 0.4142144775490266 0.6645669648543755 0.334802102829435
タブ
0.6768908174832444 0.3817345277763453 0.42646007837750055 0.17014559504201232
0.06283236014992255 0.8604594763575402 0.9500945643866037 0.7974495350151196
0.9238853594136449 0.6583229009292564 0.43140441711350563 0.8170287950442048
0.06778870328883024 0.4142144775490266 0.6645669648543755 0.334802102829435
,
0.6768908174832444 ,0.3817345277763453,0.42646007837750055,0.17014559504201232
0.06283236014992255,0.8604594763575402,0.9500945643866037 ,0.7974495350151196
0.9238853594136449 ,0.6583229009292564,0.43140441711350563,0.8170287950442048
0.06778870328883024,0.4142144775490266,0.6645669648543755 ,0.334802102829435
Base.と毎回つけるのが面倒な場合には、
import Base:print_matrix
としておけば、print_matrix
だけでいけます。
REPLと同じように出力してみたい
Juliaで立ち上げるとREPLモードになり、いろいろできます。この時、
julia> a = rand(4,4)
4×4 Array{Float64,2}:
0.312639 0.317392 0.751933 0.651802
0.520705 0.0688579 0.0148247 0.57883
0.833153 0.542301 0.261739 0.933407
0.225191 0.725512 0.0661146 0.512802
のようないい感じな出力をしてくれます。これをファイルを読み込んで実行するときにもやりたい場合には、
a = rand(4,4)
display(a)
とします。型情報もついているので、デバッグで配列などを出力したいときに見やすいですね。
データの保存と読み込み
HDF5が便利です。
Juliaでのデータの保存と読み込み:HDF5を使ってみる
テキストデータ読み込みのテンプレート
テキストデータが
cat te.dat
space 3 4.2 3.1 3.4
tab 7 4.9 3.4 3.3
だったとします。これを読み込むには、
filename = "te.dat"
data = readlines(filename) #データ読み込み
num = countlines(filename) #データのラインの数の確認
for i=1:num
u = split(data[i]) #空白やtabを区切りとして読みこむ
st = u[1] #文字なら
println(st)
it = parse(Int64,u[2]) #整数なら
println(it)
dt = parse(Float64,u[3]) #倍精度実数なら
println(dt)
dts = parse.(Float64,u[4:end]) #まとめて配列として読み込むなら
println(dts)
end
などとします。