Juliaから外部プログラムを実行する
ccall
で呼び出せます. しかし, 共有ライブラリとしてコンパイルしないとけないので, 標準入力と標準出力でデータをやり取りするメインプログラムを呼び出すことは難しいように思われます. 元のプログラムには手を加えないで, Julaiからプログラムを呼び出せるようにすることがこのノートのテーマです. CやFortranに限らず, 外部プログラムを呼び出す方法は
に概ね書いてありますが, サンプルが少なく, Windows環境だといろいろ引っかかるポイントがあるので補足説明していきます. 環境は以下の通りです.
versioninfo()
Julia Version 1.6.2
Commit 1b93d53fc4 (2021-07-14 15:36 UTC)
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: Intel(R) Core(TM) i7-4650U CPU @ 1.70GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-11.0.1 (ORCJIT, haswell)
Hello World!
さっそくですがHello World!していきましょう. 戻り値が不要な(標準出力で良い)場合はrun()
, 結果を受け取りたい場合はread()
かreadchomp()
です. なお, コマンドを囲むのはバッククォートです.
run(`echo hello`)
read(`echo hello`, String)
readchomp(`echo hello`)
run(`cmd /C echo hello`)
hello
read(`cmd /C echo hello`, String)
"hello\r\n"
readchomp(`cmd /C echo hello`)
"hello"
run(`powershell /C echo hello`)
hello
read(`powershell /C echo hello`, String)
"hello\r\n"
readchomp(`powershell /C echo hello`)
"hello"
型はそれぞれ以下のようになります. 出力にhello
が挟まりますが気にしないでください.
println("1. ", typeof(`cmd /C echo hello`))
println("2. ", typeof(run(`cmd /C echo hello`)))
println("3. ", typeof(read(`cmd /C echo hello`, String)))
println("4. ", typeof(readchomp(`cmd /C echo hello`)))
1. Cmd
hello
2. Base.Process
3. String
4. SubString{String}
メモ帳notepad.exe
などのプログラムを呼び出すこともできます.
run(`notepad`)
run(`notepad.exe`)
run(`notepad.exe program1.f90`)
標準出力のみ
Hello World!と同じですが, 一応確認しましょう. 次のprogram1.f90
は4という数値を標準出力に返すだけのプログラムです. コンパイルして生成されたprogram1.exe
を呼び出して標準出力の動作を確認していきます. (ファイルはリポジトリにあります. gfortran
がインストールされてパスが通っていればcompile.bat
をクリックすると勝手にコンパイルされます.)
program main
write(6,*) 4
end program main
run(`program1`);
4
run(`program1.exe`);
4
run(`cmd /C program1.exe`)
4
read(`program1.exe`, String)
" 4\r\n"
readchomp(`program1.exe`)
" 4"
標準入力から変数を1つ渡す
次のprogram2.f90
は標準入力をread文で読み取り, 数値を2乗して標準出力に返すプログラムです. コンパイルして生成されたprogram2.exe
を呼び出して動作を確認していきます. (ファイルはリポジトリにあります. gfortran
がインストールされてパスが通っていればcompile.bat
をクリックすると勝手にコンパイルされます.)
program main
implicit none
integer x
read(5,*) x
write(6,*) x**2
end program main
5
まず, コマンドラインから<
によって標準入力を渡せますが, <
は'<'
のようにシングルクォーテーションで囲います.
run(`cmd /C program2.exe '<' input2.txt`)
25
コマンドプロンプトを経由せずに実行するにはpipeline()
を使います. 引数stdin
にファイル名やコマンドを与えることができます. ファイル名の時はダブルクォーテーション, コマンドはバッククォートなので気を付けてください.
run(pipeline(`program2.exe`, stdin="input2.txt"))
25
run(pipeline(`program2.exe`, stdin=`cmd /C echo 6`))
36
以下のようにopen
文で標準入力を渡すこともできます. 恐らく, 結果を受け取れないようなのでrun
でよいと思います. 強いて言うなら, open
とend
の間でfor
文を回したり, write
, print
,println
などが使い分けられるなどのメリットがあります.
open(`program2.exe`, "w", stdout) do io
println(io, 7)
end
49
io = open(`program2.exe`, "w", stdout)
println(io, 7)
close(io)
49
標準入力から変数を2つ渡す
次のprogram3.f90
は標準入力をread文で読み取り, 2つの数値の和を取って標準出力に返すプログラムです. 基本的には先ほどの例と同じですが, Fortran側のプログラムが2つの数値を読み取れるようになっています. コンパイルして生成されたprogram3.exe
を呼び出して動作を確認していきます. (ファイルはリポジトリにあります. gfortran
がインストールされてパスが通っていればcompile.bat
をクリックすると勝手にコンパイルされます.)
program main
implicit none
double precision x, y
read(5,*) x, y
write(6,*) x + y
end program main
5
6
run(pipeline(`program3.exe`, stdin="input3.txt"))
11.000000000000000
run(pipeline(`program3.exe`, stdin=`cmd /C echo 5 7`))
12.000000000000000
open(`program3.exe`, "w", stdout) do io
println(io, "5 8")
end
13.000000000000000
input = "5
9"
open(`program3.exe`, "w", stdout) do io
println(io, input)
end
14.000000000000000
open(`program3.exe`, "w", stdout) do io
println(io, 5)
println(io, 10)
end
15.000000000000000
配列として渡したい場合には以下のように対処します.
input = [5,11]
run(pipeline(`program3.exe`, stdin=`cmd /C echo $input`))
16.000000000000000
input = [5,12]
open(`program3.exe`, "w", stdout) do io
for x in input
println(io, x)
end
end
17.000000000000000
外部プログラムを関数として扱う
先ほどのprogram3.exe
を例に解説します. このプログラムを関数として扱うには, 戻り値が必要なのでread()
かreadchomp()
を利用します. これらの結果は文字列なので, さらにparse()
を使って数値に型変換します.
f(X) = parse(Float64,readchomp(pipeline(`program3.exe`, stdin=`cmd /C echo $X`)))
y = f([5,13])
18.0
外部プログラムのパラメータ最適化
ここではOptim.jlを利用します. 事前にパッケージモードでadd Optim
を実行してインストールし, ノート上ではusing Optim
を宣言しておく必要があります. まず, Rosenbrock関数を最小化する例を見てみましょう.
# using Pkg
# Pkg.add("Optim")
using Optim
rosenbrock_julia(x) = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
x0 = [0.0, 0.0]
opt = optimize(rosenbrock_julia, x0)
println(Optim.minimizer(opt))
println(Optim.minimum(opt))
[0.9999634355313174, 0.9999315506115275]
3.5255270584829996e-9
次のprogram4.f90
は標準入力から変数program4.exe
をJuliaの関数としての扱い,
program main
implicit none
double precision x, y
read(5,*) x, y
write(6,*) 100.*(x*x-y)**2+(1.-x)**2
end program main
rosenbrock_fortran(x) = parse(Float64,readchomp(pipeline(`program4.exe`, stdin=`cmd /C echo $x`)))
x0 = [0.0, 0.0]
opt = optimize(rosenbrock_fortran, x0)
println(Optim.minimizer(opt))
println(Optim.minimum(opt))
[0.9999634355313174, 0.9999315506115275]
3.5255270584829996e-9
Juliaだけの例と, Fortranと連携した例で全く同じ結果が得られました.
Discussion