Juliaで、単位付き数値の算術 - Physical.jl パッケージ

__[2018/01/19 コメント]__ 3年前の記事に「いいね」を頂きましたので最新事情を手短かに補足します。 
`Physical.jl`パッケージはJulia 0.6以降動作しません。同様の機能を持つものとして `Unitful.jl` パッケージが公開されています。http://ajkeller34.github.io/Unitful.jl/stable/
`Physical.jl`と似た感じで利用できます。

[2018/12/12] Unitfulパッケージの紹介記事を書きました。→ [Julia 1.0で単位付き数値の算術 - Unitful パッケージ](https://qiita.com/tenfu2tea/items/882f539a9cf3cde1fa57)

# はじめに

単位付きの数値を (物理)量 ((Physical) Quantity)といいます。
私たち技術者は日々、たくさんの量と付き合っていて、量の単位を変換したり、異なる単位で表された量同士を計算したりしています。しかし、算法言語 programming language の「数」は単位を持たない数値のみなので、算譜 program は数同士の計算の記述に留め、単位はコメントやノートに書いておくことが多いのではないでしょうか？ 量をそのまま扱う手法があれば、見通しがよくなりなるはずです。

ここでは、Julia言語で、量の算術を支援する Physical パッケージを紹介します。

# インストール

Physical.jl パッケージの本家は https://github.com/ggggggggg/Physical.jl です。
Julia言語の公式パッケージではないので、直接 `github` からインストールします。

```jlcon
Pkg.clone("https://github.com/ggggggggg/Physical.jl.git")
```

パッケージを使うには `using` で読み込みます。

```jlcon
julia> using Physical
```

# 簡単な例から

本パッケージでも、単位付き数値(量)は、数値と単位を掛け算したものです。接頭辞 (`Micro`, `Milli`,  `Kilo` など) をつける場合は、接頭辞と単位を括弧 `()` で囲みます。

```jlcon
julia> 1.0 * Meter
1.0 m 

julia> 1000.0 * (Milli*Meter)
1000.0 mm 
```

比較演算子 `==` や近似比較関数 `isapprox` を用いて、量同士を比較できます。
単位は異なっても同じ量であれば真と判定されます。次元が異なる量を比較すると例外が発生します。

```jlcon
julia> 1.0 * Meter == 1000.0 * (Milli*Meter)
true

julia> 0.9 * Meter == 1000.0 * (Milli*Meter)
false

julia> (1.0 + 1e-8) * Meter == 1000.0 * (Milli*Meter)
false

julia> isapprox( (1.0 + 1e-8) * Meter , 1000.0 * (Milli*Meter) )
true

julia> 1.0 * Second == 1000.0 * (Milli*Meter)
ERROR: AssertionError:
 in == at C:\Users\ ___ \.julia\v0.4\Physical\src\Quantities.jl:122
```

量の大きさ(数字)と単位を取り出すには `.value` と `.unit` をそれぞれ用います。
`.unit` の結果は、文字列型ではなく `PUnits.Unit` 型です。

```jlcon
julia> (1.0 * Meter).value
1.0

julia> (1.0 * Meter).unit
m 

julia> typeof(ans)
PUnits.Unit

julia> (1000.0 * (Milli*Meter)).value
1000.0

julia> (1000.0 * (Milli*Meter)).unit
mm 

julia> (1.0 * Meter).unit == (1000.0 * (Milli*Meter)).unit
false
```

同じ次元の量は加減算できます。次元が異なる量を加減算すると、例外が発生します。

```jlcon
julia> 1000.0*(Milli*Meter) + 0.1*Meter
1100.0 mm 

julia> 1000.0*(Milli*Meter) + 0.1*Gram
ERROR: incompatible base units kg  and m 
 in error at error.jl:21
```

基本単位として、質量(重さ)、長さ、時間、電流、温度、物質量を知っておけばよいでしょう。単位はフルスペルで書きます。Julia の流儀に従い、定数の頭文字は大文字にします。

```jlcon
julia> Kilogram
1 kg  

julia> Gram
1 g  

julia> Meter
1 m  

julia> Second
1 s  

julia> Ampere
1 A  

julia> Kelvin
1 K  

julia> Mole
1 mol  
```

# 組立単位

単位同士を乗除すると組立単位を作れます。

```jlcon
julia> 1.0 * Meter / Second
1.0 s⁻¹m  
```

量同士は乗除できます。単位も含めて計算できます。結果が無次元になると、数字型 `Float` や `Complex` になります。

```jlcon
julia> (1.0*Meter) / (1.0*Second)
1.0 s⁻¹m 


julia> v=(1.0*Meter)^3
1.0 m³

julia> v^(1/3)
1.0 m  

julia> v^(2/3)
1.0 m²

julia> (1.0*Meter)/(1.0*Meter)
1.0

julia> typeof(ans)
Float64

julia> Complex(1.0,1.0)*Meter / (1.0*Meter)
1.0 + 1.0im

julia> typeof(ans)
Complex{Float64}
```

# 単位の変換

単位の変換には `as` を用います。第二引数が変換先の単位です。変換先は組立単位でも構いません。
変換できない単位を指定すると例外が発生します。

```jlcon
julia> as( 1000.0 * (Milli*Meter), Meter)
1.0 m 

julia> as( 1000.0 * (Milli*Meter), Second)
ERROR: incompatible base units m  and s 
 in error at error.jl:21
```

## エネルギーの単位

$\textrm{J}$ (joule, ジュール)はエネルギーの単位です。joule に等価な組立単位が複数あります。

```jlcon
julia> (1.0 * KiloGram)* (1.0*Meter/Second)^2
1000.0 g s⁻²m²

julia> as( ans, Joule )
1.0 J 

julia> (1.0*Volt)*(1.0*Coulomb)
1.0 V C 

julia> as( ans, Joule )
1.0 J 
```


# 定義済の単位や型

本パッケージには、多数の単位や定数が定義されています。定義ファイルは、`data / default` ディレクトリ (`github` のソースでは https://github.com/ggggggggg/Physical.jl/tree/master/data/default ) にあります。
以下の、4つのファイルがインクルードされているので、中身を見てみるとよいでしょう　(なお `b_english.jl` の定義は、現在 exportされていないので使えません)。

```jlcon
julia> Physical.loaded_files
4-element Array{ByteString,1}:
 "a_prefix.jl"
 "a_si_plus.jl"
 "b_english.jl"
 "c_physics.jl"
```

## エネルギーの単位: 電子ボルト
電磁気学や電気電子工学では、エネルギーを電子ボルト(electron volt) で表示する場合があります。

```jlcon
Julia> 1.0 * ElectronVolt
1.0 eV 

Julia> as( ans, Joule)
1.602176565e-19 J 
```

# 新しい派生単位の作成

新しく派生単位を定義するには、`DerivedUnit` を用います。第一変数は、新しい派生単位の文字列表現です。例で説明しましょう。

## 日本の長さの単位
日本の尺貫法の長さ・面積の単位を作ってみましょう。

```jlcon
julia> const Shaku = DerivedUnit( "尺", 10 // 33 * Meter)
1 尺

julia> const KenJp = DerivedUnit( "閒", 6. * Shaku)
1 閒

julia> const Tsubo = DerivedUnit( "坪", KenJp ^2 )
1 坪 

julia> as( 30.* Tsubo, Meter^2 )
99.17355371900828 m²
```

## 体積の単位 リットル

体積の単位 リットル (liter) を定義しましょう。
1リットルは 一辺 $10~\textrm{cm}$ の立方体(サイコロ)の体積です。 $1~\textrm{dm} = 10^{-1}~\textrm{m} = 10~\textrm{cm}$ 。
$1~\textrm{L} = 1~\textrm{dm}^3 = (1~\textrm{dm})^3$ 。

```jlcon
julia> const Liter = DerivedUnit( "L", (Deci*Meter)^3 )
1 L 

julia> (10.*(Centi*Meter))^3
1000.0 cm³

julia> as( ans, Liter)
1.0 L 
```

## 圧力の単位

$\textrm{Pa}$ (pascal, パスカル)は圧力の単位です。
圧力とは、単位面積当たりに加わる力です。$\textrm{Pa}=\frac{\textrm{N}}{\textrm{m}^2}$

1気圧を定義してみましょう。1気圧は 1013.25 hPa (ヘクトパスカル)です。 

```jlcon
julia> const Atmosphere = DerivedUnit("atm", 1013.25 (Hecto*Pascal) )

julia> 1.0 * Atomsphere
1.0 atm

julia> as( ans, Pascal)
101325.0 Pa  

julia> as(ans, Mega*Pascal)
0.101325 MPa  
```

psi という圧力の単位を知っていますか？ pound-force per square inch の頭文字を取ったもので、「一辺 1 inch の正方形に 1 pound の荷重が印加された」圧力に対応します。米国はヤードポンド法を使っているので、米国発祥の科学技術分野 (特に航空宇宙分野)に関わると慣れざるをえません。
1 inch = 25.4 mm、1 pound = 453 g　(ハンバーガーなどの、クォーターパウンダーは $\frac{1}{4}$ pound = 113 g)。 `g_earth_gravity`は、地上における重力加速度です ( [c_physics.jl](https://github.com/ggggggggg/Physical.jl/blob/master/data/default/c_physics.jl) に定義されています)。

```jlcon
julia> const Inch = DerivedUnit("in", 254//10000*Meter)
1 in 

julia> const Pound = DerivedUnit("lb", 453.59237*Gram)
1 lb 

julia> as(0.25*Pound, Gram)
113.3980925 g 

julia> const Psi = DerivedUnit("psi", Pound * g_earth_gravity / Inch^2)
1 psi 

julia> as( 1.0*Psi, Pascal )
6894.757293168362 Pa 
```

1気圧は何psi かな？

```jlcon
julia> 1.0 * Atomsphere
1.0 atm

julia> as( ans, Psi)
14.695948775513449 psi 
```

# 単位付き数値ベクトルの利用

数値のコレクションに単位を付与できます。数値ベクトルや `linspace` 型に使えます。

```jlcon
julia> [ 0., 1., 2. ] * Meter
[0.0,1.0,2.0] m 
```

## 単位付き数値ベクトルの計算

一次元運動の数値解を求めてみましょう。

球を鉛直上向きに放り投げたとき、その球が鉛直下向きの重力のみを感じて運動するとします。
球の速度 $v$ と高さ $y$ は、以下の式で表されます ($g$ は重力加速度)。
$$ v = v_0 - g t ，$$ 
$$ y = v_0 t - \frac{1}{2}g t^2  ．$$
ただし、時刻 $t=0~\textrm{s}$における速度を $v_0$、高さを $y = 0~\textrm{m}$ としました。

$v$ と $y$ を算出する時刻 $t$ のベクトルを作ります。

```jlcon
julia> ts = linspace(0,2)  * Second
linspace(0.0,2.0,50) s 
```

初速度を、次のようにしました。

```jlcons
julia> v0=13.* Meter/Second
13.0 s⁻¹m 
```

時刻 $t$ における速度 $v$ と高さ $y$ を計算します。上の式をそのまま書けばよいですね。$.*$ は、ベクトルの要素同士の乗算です。

```jlcon
julia> vs = v0 - g_earth_gravity .* ts
linspace(13.0,-6.613299999999999,50) s⁻¹m 

julia> ys = v0 .* ts - g_earth_gravity * ts .* ts / 2.
[0.0,0.522443,1.02855,1.51832,1.99175,2.44884,2.8896,3.31401,3.72209,4.11384  ……
  8.1544,8.02334,7.87594,7.71221,7.53213,7.33572,7.12297,6.89389,6.64846,6.3867]
 m 
```

[PyPlot](https://github.com/stevengj/PyPlot.jl) を用いて $v(t)$, $y(t)$ をグラフに描きましょう。
PyPlot は、Python の 2次元プロットライブラリ [matplotlib](http://matplotlib.org)を Juliaから使うためのパッケージです。
matplotlibでダブルYグラフ (x軸が共通で、y軸が左右にあるグラフ)を描くサンプルプログラムが [api example code: two_scales.py](http://matplotlib.org/examples/api/two_scales.html?highlight=twiny#api-example-code-two-scales-py) にあります。
これを Juliaで実装しましょう。量の数字部分を取り出すには `.value` を使うのでした。

```julia
using PyPlot
plot( ts.value ,vs.value, "b.")
ax1 = gca()
ax1[:set_xlabel]("time (s)")
ax1[:set_ylabel]("velocity (m/s)", color="b")
for t1 in ax1[:get_yticklabels]()
    t1[:set_color]("b")
end
ax2 = ax1[:twinx]()
ax2[:plot](ts.value, ys.value, "r-")
ax2[:set_ylabel]("height (m)", color="r")
for tl in ax2[:get_yticklabels]()
    tl[:set_color]("r")
end
```
![output_73_0.png](https://qiita-image-store.s3.amazonaws.com/0/103217/5e746315-3f9c-fff8-2abc-db6667a25382.png)

なお、PyPlotで matplotlib の任意の関数を呼ぶ方法は、拙文で紹介しました。
- Qiita: 決定版？「Juliaでジュリア集合を描く」http://qiita.com/tenfu2tea/items/6bcbdd7586ea070cc25d
- Drawing Julia sets in Julia language matplotllib, APIの呼出し http://nbviewer.ipython.org/gist/tenfu2tea/fb8741db797aa42b6585#matplotllib-API%E3%81%AE%E5%91%BC%E5%87%BA%E3%81%97

# まとめ

以上、Physical パッケージの機能を紹介しました。
本パッケージは、Julia のコマンドライン (REPL)を、量の電卓として使うのに十分な機能を提供します。ぜひ試してみてください。