ワーシャルフロイド法をJuliaで(強引に)アニメーション
はじめに
競プロとかやってると、たまに聞くワーシャルフロイド法
コードは短くて悩むところないけど、これで「抜け」ないんだっけ?
とか思ったりするので、一度動画にしてみる。
Julia言語にはGraphPlotなるものがあるのでそれの練習
ワーシャルフロイド法
WikipediaとかもあるけどJuliaでは
for k ∈ 1:n , i ∈ 1:n , j ∈ 1:n
Dist[i,j]=min(Dist[i,j],Dist[i,k]+Dist[k,j])
end
Dist #Distは有向グラフをマトリクスにしたもの
短いです
解説
ググれば素晴らしい解説があると思いますが簡単に
負の閉路のない(クルクル回るとマイナス無限になったりしない)有向(無向)グラフで、最短の経路を求めるには
ある中継点を通った方が直通より短ければ、直通の距離をそれに書き換える。
それを各中継点で全通り調べると、それぞれ直通が最短距離の値になっている
動画
一本橋モデル
適当なランダム
動画で見たかった人は以上です
(ゆっくりみたい人は下のコード自分でいじるか、gif保存して動画プレーヤーとかで見てください)
Juliaコード
パッケージについて
あとで
using Graphs,GraphPlot,Compose,Plots
import Cairo
します。
グラフ描画にGraphs
GraphPlot
を用いますが、上にテキストを重ねたかったりしたので
融通を効かせる為、Compose
(GraphPlotは元々この形式出力みたい)を使います。
さらに今回アニメーションにする上でPlot
(ffmpeg使うだけ)、一旦png形式出力の為Cairo
を使います。
Julia Programming Language Compose GraphPlot LightGraphs Positionの投稿参照
Graphs,GraphPlot について
たきろぐさんの記事を参考にしました
基本的な流れは
G1=DiGraph(5) #有効グラフnode5点
add_edge!(G1,2,3) #edge追加1
add_edge!(G1,1,4) #edge追加2
GP=gplot(G1,layout=random_layout,nodelabel=1:5) #描画(Jupyterとかで)
となります。
edgeの個別追加が面倒なら、マトリクスをDiGraph
に食わせると0
でない成分をedgeとして拾ってくれます。
Compose について
yuifuさんの記事を参考にしました
さきほどのグラフプロットに追記していきます
compose(GP1
,(context(), Compose.text(-0.5, -0.5, "テキストの書き込み"))
)
Plots,Cairo について
I_pppの記事(私ですが)の動画の項目でしたいのと同じなのですが、今回Composeの形式をCairo
でPNGファイルにして、それをPlots
のAnimation()
を使って変換します。
使ったコード
using Graphs,GraphPlot,Compose,Plots
import Cairo
"グラフマトリクスの通常描画"
function plotGraph(B;INF=10^3)
A=copy(B)
A[A.==INF].=0
circleGraphPlot(A,edgelabel=filter(≠(0),A'))
end
"経路の上書き描画用"
function plotGraphVia(B,ijk=(1,1,1))
(i,j,k)=ijk
n=size(B)[1]
A=zeros(n,n)
A[i,k]=1
A[k,j]=1
nodefillc=[colorant"turquoise" for _ in 1:n]
nodefillc[[i,j,k]].=colorant"orange"
# nodefillc[k]=colorant"green"
circleGraphPlot(A
,edgestrokec=[colorant"orange" for _ in 1:2]
,nodefillc=nodefillc
)
end
"更新があった描画用"
function plotGraphUp(B,ijk=(1,1,1))
(i,j,k)=ijk
A=zeros(size(B)...)
A[i,j]=1
nodefillc=[colorant"turquoise" for _ in 1:n]
nodefillc[[i,j]].=colorant"red"
nodefillc[k]=colorant"orange"
circleGraphPlot(A
,edgestrokec=[colorant"red"]
,nodefillc=nodefillc
)
end
"グラフ固定描写"
function circleGraphPlot(A;kws...)
n=size(A)[1]
gplot(DiGraph(A)
,layout=circular_layout
,nodelabel=1:size(A)[1]
;kws...
)
end
"アニメのフレーム追加"
function addAnim!(anim,draw)
f=anim.frames
tmpfilename=joinpath(anim.dir,string(length(f),pad=6)*".png")
Compose.draw(PNG(tmpfilename,4inch,4inch,dpi=128),draw)
push!(f,tmpfilename)
end
"グラフ描写(Compse形式)に文字追加"
function drawGraph(dist,ijk;Up=false,INF=10^3)
(i,j,k)=ijk
(ij,ik,kj)=(dist[i,j],dist[i,k],dist[k,j])
genINF(x)= (x ≥ INF ? "INF" : x)
output = compose(
plotGraphVia(dist,ijk)
,plotGraph(dist,INF=INF)
,(context(), Compose.text(-1.1, -1.02, "経路$i→$k→$j: $(genINF(ik+kj)) = $(genINF(ik)) + $(genINF(kj))\n経路$i→$j : $(genINF(ij))"))
,(context(), rectangle(), fill("white"))
)
Up && (output=compose(
plotGraphUp(dist,ijk)
,(context(),Compose.text(-0.1, 0, "$(genINF(ij))→$(genINF(ik+kj)) "))
,output)
)
return output
end
"ワーシャルフロイドメインから呼ばれる関数"
function addGraphAnim!(anim,dist,ijk;Up=false,INF=10^3)
draw=drawGraph(dist,ijk,Up=Up,INF=INF)
for _ in 1:(Up ? 20 : 1)
addAnim!(anim,draw)
end
end
"ワーシャルフロイドメイン関数"
function FWAlgoPlot(A)
Dist=copy(A)
n=size(A)[1]
# 繋がってない部分は大きな値として計算させる
INF=10^3
Dist[Dist.==0].=INF
for i in 1:n
Dist[i,i]=0
end
anim=Animation()
n=size(A)[1]
for k in 1:n , i in 1:n , j in 1:n
addGraphAnim!(anim,Dist,(i,j,k),INF=INF)
if Dist[i,j]>Dist[i,k]+Dist[k,j]
addGraphAnim!(anim,Dist,(i,j,k),Up=true,INF=INF)
Dist[i,j]=Dist[i,k]+Dist[k,j]
end
end
Dist[Dist.==INF].=0
Dist,anim
end
#一本道
A=[
0 5 0 0 0 0 0 0
0 0 11 0 0 0 0 0
0 0 0 3 0 0 0 0
0 0 0 0 21 0 0 0
0 0 0 0 0 4 0 0
0 0 0 0 0 0 8 0
0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0
]
Dist,anim1=FWAlgoPlot(A)
mp4(anim1,"anim_test1.mp4",fps=60) #上の動画はgif出力
#適当なランダム
n=8
A=zeros(Int,n,n)
for _ in 1:n*2 ; A[rand(1:n),rand(1:n)]=rand(0:5n);end
for i in 1:n ; A[i,i]=0;end
Dist,anim2=FWAlgoPlot(A)
mp4(anim2,"anim_test2.mp4",fps=60) #上の動画はgif出力2
注意点とかハマり点として
・gplot
はいい感じでnode配置してくれるけど、デフォがランダムなのでlayoutで指定か第2,3引数に直接X,Y値(のベクトル)を指定しないと動画に向かない
(layoutはXYを返す関数を指定するものの様)
・そんで、描画の融通が効かないので、更新のedgeとかもう一個グラフ作って重ねたりした
・細かい指定(線幅とか)もしてみたけど、PNG出力時にいろいろ無効になってしまう
・edgeの無(0)とグラフマトリクスの断絶(INF)がかち合うので、変換が必要
・mp4出力したいときはPNGファイルの縦幅2の倍数でないと駄目なので、inch✕dpiで指定
・最短距離更新の止め画は無駄に同じPNGファイルを吐き出した
最後に
GraphPlot
なるパッケージ見つけて、なんか動画いけるんじゃねと思ったけど
細かいとこは面倒だった
これでワーシャルフロイド法が分かりやすくなったかと言えば・・・
(そう言う人がいればいいな)
Discussion