Julia入門&導入

※一部、所属大学における講義資料として作成中のものを含みます。

Juliaとは

Juliaは、MITで開発された、数値計算を得意とするプログラミング言語で、 PythonやR、MATLABなどの言語の良いところを取り入れて開発されていて、高速に動作するという特徴を有する。

作者らの記事Why We Created Juliaから重要な部分を引用:

We want a language that's open source, with a liberal license. We want the speed of C with the dynamism of Ruby. We want a language that's homoiconic, with true macros like Lisp, but with obvious, familiar mathematical notation like Matlab. We want something as usable for general programming as Python, as easy for statistics as R, as natural for string processing as Perl, as powerful for linear algebra as Matlab, as good at gluing programs together as the shell. Something that is dirt simple to learn, yet keeps the most serious hackers happy. We want it interactive and we want it compiled.

ここにJulia言語の理念と特徴が集約されている。

この講義でJuliaを使用する理由は、限られた時間と少ない労力(コーディングや実行時間)でより多くのトピックを学ぶためである。

Pythonとの基本文法の違い

Pythonは0-based indexだが、Juliaは1-based indexで、配列arrの先頭の要素はarr[1]となる。
その意味では、PythonはC言語に近く、JuliaはMATLABやFortranに近いと言える。

Pythonの場合は、インデントを使ってブロックを表現するが、Juliaの場合は、beginendを使ってブロックを表現する。
また、for文やif文の後には、明示的にendを書く必要がある。
なお、Pythonで関数や条件式などを書く際に必要だったコロン:は不要になる。

個人的には、インデントを文法とするPythonの方が、コードの見た目がスッキリしやすいようにも思うが、 エディタの設定によっては、インデントがタブとスペースが混在してしまったり、 編集の過程でインデントが崩れてしまい、バグの温床になったりすることがあるので、 Juliaの方が、ブロックに対してambiguityが少ないというメリットがあるように思う。

またNumpyで言うところのブロードキャストをdefaultで有していて、vがFloat64の一次元配列とすると

u = a .* v

でスカラー倍をしたベクトルuを作ることができる。

また、

v .= a .* u

と書くと、v(が既に定義されていれば)の各要素にa .* uの各要素を代入してくれる。
単に

v = a .* u
A = a .* B

などと書くと、vAを新たに定義してしまい、メモリ確保が発生する。 ブロードキャストは、特に大次元のベクトルや行列を操作する際、メモリを節約したり更新操作を速く行うために有用である。

また、@.というマクロをつけることで、ブロードキャストを明示的に書く必要がなくなり、

@. A = a * B * C

などとしても\(A .= a .* (B .* C)\)と同じ意味になるが、 なれないうちは、ブロードキャストを明示的に書いた方が良い。

対応早見表

Julia

Python

文字列の出力

println("Hello, world!")

print("Hello, world!")

変数の定義

x = 10

x = 10

配列の定義

arr = [1, 2, 3]

arr = [1, 2, 3]

配列の先頭の要素の取得

arr[1]

arr[0]

配列の末尾の要素の取得

arr[end]

arr[-1]

配列の要素の変更

arr[1] = 5

arr[0] = 5

配列の長さ

length(arr)

len(arr)

行列積

A*B

np.dot(A,B)

関数の定義

function myfunc(引数1,引数2,...); ...; end

def myfunc(引数1,引数2,...):

値なし

nothing

None

高速化のtips

JuliaはPythonのように動的に使用することができるが、 Just-In-Time(JIT)コンパイルといって、実行時にソースコードを"幾つかの工程"を踏んで機械語に翻訳して実行することで (JITコンパイル自体の時間が生じるものの)非常に高速に実行することができる。

Juliaでコードを書いて高速に実行させるtipsとしては、コンパイラにちゃんとヒントを与えてやる事が挙げられる。 Juliaでは明示的に型を指定することは必須ではなく、きちんとコードを書けば勝手に速いコードを生成・実行してくれる。 ただし、演算の途中で型の不一致などがあると、勝手に速いコードを生成する妨げになり、コードが遅くなるため、 そうした想定外の型の不一致を避けるために、関数の引数や返り値の型を明示的に指定することが有用となる。

特に、行列やベクトルを定義するときは、その型(とサイズ)を明示的に指定して定義するのが良い。

A = zeros(Float64,n,m) # 倍精度floatを要素に持つn×m行列を0行列で定義
B = randn(Float32,n,m) # 単精度floatを要素に持つn×m行列を正規乱数をつかって定義

その他、高速化のtipsについては公式ドキュメントのPerformance Tipsが詳しい。

Juliaの実行環境

Juliaを動かす方法は幾つかあるが、この講義では、 Juliaをインストールしてターミナル(VS code経由など含む)ないし、JupyterNotebook環境で実行する方法を推奨とする。
一度インストールしてIJulia.jlパッケージをインストールすれば、.ipynb形式のファイルのJupyter用カーネルを用意することができる。

Juliaのインストール

では、実際にJuliaをインストールする方法をOSごとに紹介しよう。
(著者の意見では)Juliaの環境構築はPythonの環境構築よりも遥かに簡単で安全である。 以下に示すOSごとの手順のほか、最近(v1.10くらいの頃から?)は公式ページでも、後述のjuliaupを用いる方法が推奨されている。

Macの場合

  1. Julia言語の公式ページから、macOS用の.dmgファイル(長期サポート(LTS)のv1.6か最新版)をダウンロードする。
    ※Apple Siliconチップの場合は、macOS(Apple Silicon)を選択するほうが性能上良いが、どちらでも動かすことは可能。

  2. ダウンロードしたファイルを開き、Juliaアプリケーションをアプリケーションフォルダに移動する。

  3. PATHを通すために、ターミナルを開き下記を実行する。
    ※Julia-1.6の部分やbashrcの部分は、適宜ダウンロードしたJuliaのバージョンに置き換えたり使用しているシェルに合わせて変更すること。

    echo 'export PATH="/Applications/Julia-1.6.app/Contents/Resources/julia/bin:$PATH"' >> ~/.bashrc
    

    Mac OSの場合、(いつからだったか)標準のシェルがbashからzshに変更されているので、zshを使う場合は、~/.bashrcではなく~/.zshrcに追記すること。

  4. ターミナルでsource ~/.bashrcなどを行い変更を反映したのち、juliaと入力して、JuliaのREPL(対話環境)が起動することを確認する。

Dockerを使う方法もあるが、その場合は(とくにApple Siliconの場合は)適切なLinuxイメージを用いれば、以降のLinuxの場合の手順に従えばよい。

Windowsの場合

  1. WSL2をインストールする。

  2. 下記のLinuxの場合の手順に従う。

Linux(WSL)の場合

  1. Julia言語の公式ページから、Linux用のtar.gzファイル(長期サポート(LTS)のv1.6か最新版)をダウンロードする。
    ※ アーキテクチャに応じたものを選択すること。

  2. tar.gzを解凍するとjulia-1.9.0というフォルダができる。この中のjulia-1.9.0/bin/juliaが実行ファイルなので、 この実行ファイルにPATHを通すために、ターミナルを開き下記のように実行する。

    echo 'export PATH="/path/to/julia-1.9.0/bin:$PATH"' >> ~/.bashrc
    
    • 1.9.0の部分はダウンロードしたバージョンに依る。

    • /path/to/は適宜ファイルの場所に置き換えること。とくにWSLの場合は、ダウンロードしたものもLinuxのホーム以下に置いておこう。
      例えば、/home/username/julia-1.9.0/bin/juliaというパスにjuliaの実行ファイルjuliaがある場合、export PATH=/home/username/julia-1.9.0/binをホーム直下の隠しファイルである~/.bashrc等に追記する。bashrcとは、bashシェルが起動する際に読み込まれる設定ファイルであり、zchなど他のシェルを用いる場合は、それぞれのシェルに合わせた設定ファイルに追記すること。Ubuntuは(少なくともこれを執筆した当時は)デフォルトでbashを使っているので、bashrcに追記する。

  3. ターミナルでsource ~/.bashrcなどを行い変更を反映したのち、juliaと入力して、JuliaのREPL(対話環境)が起動することを確認する。

Juliaパッケージ

Juliaには、独自のパッケージマネージャが組み込まれており、それ自体がPkgというパッケージになっている。 Juliaでパッケージを使用する際は、using パッケージ名としてやればよい。これが、Pythonでいうimportに相当する。

Juliaパッケージのインストールは、Pkg.add("パッケージ名")とする。 相当複雑な依存関係を含むものでない限り、これで問題なくインストールできる。 PyCallPyPlotなど、Pythonとの連携を行うためのパッケージを導入する場合は、環境によっては、少し手間がかかることもある。

また、Juliaの対話環境REPLで]キーを打鍵すると、パッケージマネージャの対話環境に入ることができる。 そこでは単にadd package_nameとするだけで、パッケージをインストールできる。 package_nameの部分はもちろん、自分の使いたいパッケージ名に置き換えること。

インストールされたパッケージは、とくに指定しなければホームディレクトリに~/.juliaという隠しフォルダが 生成され、~/.julia/packages以下にインストールされる。 またJuliaの優れた特徴として、自分が今いるディレクトリ(環境やプロジェクト)ごとに自身が使用するパッケージやそのバージョン&依存関係を 指定することも簡単にできる。

例として、線形代数演算を用いるLinearAlgebraを使ってみよう。 ランダム行列を作って、行列のdeterminant(行列式)を求めてみる。

using LinearAlgebra
A = randn(Float64,5,5)
det(A)
0.942391333164314

細かいことを言うと、usingでパッケージを読み込むと、そのパッケージ内でexportされている関数や型を、そのまま使うことができる。 import パッケージ名とすると、パッケージ内の関数や型を、パッケージ名.関数名のようにして呼び出す必要がある。

たとえ異なるパッケージで同一の関数名が定義されていても、Juliaには多重ディスパッチで関数を呼び出す仕組みがあるので、 問題が起こる(複数のライブラリ間で関数の定義が完全に衝突する)事例は稀なので、基本的にusingを使えばよい。

プロジェクト

Juliaでは、複数のパッケージを含むプロジェクトを作成することができる。
自作パッケージなどのプロジェクトフォルダ内にProject.tomlManifest.tomlというファイルを作成することで、 複雑な依存関係を含むパッケージの管理を行いながら、開発・実行することができる。
(Pythonでもrequirements.txtやpyproject.tomlなどを用いたパッケージ管理だが、私は使ったことがないので詳しくない)

とくに自作パッケージを作成する場合は必須事項となるので、Pkg.jlのドキュメント
日本語の書籍であればJuliaプログラミング大全実践Julia入門を参照してほしい。

bashrc(など)に、

export JULIA_PROJECT=@.

と書いておけば、juliaを実行した際に、そのディレクトリがJuliaプロジェクトであれば、tomlファイルを介してそのプロジェクトの環境を読み込んでくれる。 GitHubにあるJuliaパッケージのレポジトリをクローンないしダウンロードしてきてサンプルコードをサクッと使いたい場合は、こちらの方法も有用である。

Juliaのバージョン管理

juliaupというJuliaのバージョンを管理するためのツールが便利で、Juliaのバージョンを切り替えることができる。juliaupのレポジトリに従ってインストールしておこう。

Jupyter Notebookの実行

この講義の資料のように、ipynb形式のファイルを実行する場合、JuliaのREPLで

]add IJulia 

などとして、IJuliaパッケージをインストールする。
その後、コードの実行時などにJuliaカーネルを選択することで、Juliaのコードを実行することができる。

小話

筆者の環境(VS Code)でJupyter環境でJuliaコードを実行すると、たまに停止やカーネルの再起動といった操作を受け付けず、プロセスが残り続けることがある。 講義では、資料の配布のしやすさからipynb形式を採用しているが、実際のコード開発や実行において筆者がJupyter環境を使うことはまず無い。

関連する?issue

サンプルコード: Python vs Julia

ランダム行列の特異値分解のコードを書いてみよう。どちらも同じくらいシンプルになる。

#ランダム行列を生成し特異値分解するPythonコード
import numpy as np

def mat_svd(dim):
    A = np.random.randn(dim,dim)
    U, s, V = np.linalg.svd(A)
    return U, s, V

if __name__ == '__main__':
    dim = 500
    itnum = 1000
    for it in range(itnum):
        mat_svd(dim)    
#ランダム行列を生成し特異値分解するJuliaコード
using LinearAlgebra

function mat_svd(dim)
    A = randn(dim,dim)
    U, s, V = svd(A)
    return U, s, V
end

function main()
    dim = 500
    itnum = 1000
    for it = 1:itnum
        mat_svd(dim)    
    end
end
main()

NuclearToolkit.jlの導入

この講義では、筆者が2022年に公開したJuliaパッケージNuclearToolkit.jlの実行結果の紹介を行う。

このパッケージの概要と、導入方法などをまとめておく。 また、パッケージには一応ドキュメントがあるので、 そちらも必要に応じて参照してほしい。

NuclearToolkit.jlはいくつかのbuilding blockからなるパッケージで、整理されていないsrcフォルダを見ると分かるように 5つほどのサブディレクトリがあり、それぞれが独立したコード群となっている。

  • ChiEFTint: 主に相互作用ファイルの作成を行う

  • hartreeforck: 主にHF/HF-MBPT計算や、IMSRG法のインターフェースを与える

  • IMSRG.jl: IMSRG法

  • ShellModel: 殻模型計算やEigenvector Continuation法

  • NuclData.jl: AME(atomic mass evaluation)2020から限られたデータを切り出したもの。

だいたい原子核分野のコードというとこれら一つ一つに対してFortranやC++で書かれた5万-10万行のコードが普通なように思うが、 NuclearToolkit.jlではそれぞれ数千行、合計2万行程度なので、比較的小規模である。 性能はともかくとして、この規模で、核力から核構造計算までをカバーするコードは、筆者の知る限りでは他にない。

パッケージのインストールは簡単で、

using Pkg
Pkg.add("NuclearToolkit")

とするだけ。

パッケージはGitHub Actionsを用いて、Linux, macOS, Windowsの3つのOSでCIを回しているので、少なくともv1.9以降のJuliaであれば、どのOSでも動くはずであるが、本資料の執筆時点で筆者が使用している、Julia v1.10.0及びNuclearToolkit.jlのv0.4.1(2024/03/04)以降での実行を推奨とする。

注釈

ぜひ、Juliaの導入, NuclearToolkit.jlの導入ののち、JuliaのREPLでusing NuclearToolkitとして、パッケージを読み込むところまでを事前に試してください。もし、エラーが出たり、環境構築でわからないことがあれば、Slack or 講義の前など、気軽に声をかけてください。