unyt: Pythonで物理単位を扱うための強力なライブラリ 💪

Python

科学技術計算の頼れる相棒、unytを徹底解説!

Pythonを使った科学技術計算やデータ分析を行っていると、物理単位の扱いに頭を悩ませることがありますよね 🤔。計算途中で単位を間違えたり、単位変換が面倒だったり…。そんな悩みを解決してくれるのが、今回紹介するPythonライブラリ unyt です!

unytは、物理単位を持つデータを簡単かつ正確に扱うための機能を提供します。もともとは、大規模な科学データ分析・可視化パッケージである yt Project の一部として開発されましたが、その便利さから独立したライブラリとして多くのプロジェクトで利用できるようになりました。

この記事では、unytの基本的な使い方から応用的な機能まで、具体的なコード例を交えながら詳しく解説していきます。unytを使いこなして、より効率的でミスのない科学技術計算を実現しましょう!🚀

unytのインストール

まずはunytを使えるようにしましょう!

unytのインストールは非常に簡単です。pipコマンドを使って、ターミナルやコマンドプロンプトで以下のコマンドを実行するだけです。

pip install unyt

unytは内部で NumPySymPy ライブラリに依存しています。通常、pipが依存関係を自動的に解決して一緒にインストールしてくれますが、もしこれらのライブラリがインストールされていない場合は、別途インストールが必要になることがあります。

また、パフォーマンス向上のために fastcache というライブラリのインストールも推奨されています。Cコンパイラが利用可能な環境であれば、以下のコマンドでインストールできます。

pip install fastcache

conda環境を使用している場合は、condaコマンドでもインストールできます。

conda install -c conda-forge unyt

condaを使用する場合、fastcache は通常、SymPyの依存関係として自動的にインストールされます。

インストールが完了したら、Pythonインタプリタやスクリプトで import unyt を実行し、エラーが出なければ成功です🎉。

基本的な使い方

unytのコア機能をマスターしよう!

単位付き数量 (unyt_quantity / unyt_array) の作成

unytの最も基本的な機能は、数値に単位を付与することです。unytでは、スカラー値に単位を付けたものを unyt_quantity、NumPy配列に単位を付けたものを unyt_array と呼びます。

単位付き数量を作成するには、数値やNumPy配列に、unytで定義されている単位オブジェクトを乗算します。

import unyt as u
import numpy as np

# スカラー値に単位を付与 (unyt_quantity)
length = 10.0 * u.m  # 10メートル
print(length)
print(type(length))

# NumPy配列に単位を付与 (unyt_array)
velocities = np.array([10, 20, 30]) * u.km / u.s  # [10, 20, 30] km/s
print(velocities)
print(type(velocities))

# リストやタプルからも作成可能
distances = [1.0, 2.5, 0.8] * u.AU # 天文単位 (Astronomical Unit)
print(distances)
print(type(distances))

出力を見ると、数値や配列に単位情報が付与されていることがわかります。また、unyt_array はNumPyの ndarray のサブクラスとして実装されているため、NumPyの多くの機能をそのまま利用できます。

単位の定義と操作

unytには、SI単位系をはじめ、CGS単位系、帝国単位、天文学で使われる単位など、非常に多くの物理単位が事前に定義されています。これらの単位は unyt モジュールから直接インポートして利用できます。

from unyt import m, s, kg, J, W, pc, Angstrom

# 事前定義された単位の使用例
energy = 100 * J         # 100ジュール
power = 50 * W           # 50ワット
distance_pc = 1.5 * pc   # 1.5パーセク
length_A = 5 * Angstrom  # 5オングストローム

print(energy)
print(power)
print(distance_pc)
print(length_A)

利用可能な単位の一覧は、公式ドキュメントの単位リストで確認できます。

また、既存の単位を組み合わせて新しい単位を定義することも簡単です。

import unyt as u

# 新しい単位の定義
acceleration_unit = u.m / u.s**2
density_unit = u.kg / u.m**3
pressure_unit = u.N / u.m**2 # N (ニュートン) も定義済み

accel = 9.8 * acceleration_unit
water_density = 1000 * density_unit
pressure = 101325 * pressure_unit

print(accel)
print(water_density)
print(pressure)

# 単位の次元を確認
print(accel.units.dimensions)
print(water_density.units.dimensions)
print(pressure.units.dimensions) # Pressure は (mass)/(length*time**2)

units.dimensions 属性を使うと、その単位が基本次元(質量、長さ、時間など)でどのように構成されているかを確認できます。

単位付き演算

unytの大きな利点の一つは、単位付きの数値や配列同士で算術演算を行う際に、自動的に単位の整合性をチェックし、計算結果の単位を正しく処理してくれる点です。

import unyt as u

# 単位付き演算の例
distance = 100 * u.km
time = 2 * u.hr

# 速度 = 距離 / 時間
speed = distance / time
print(f"Speed: {speed}") # 結果の単位は km/hr

# 単位が異なるが次元が同じ演算 (自動変換)
length1 = 5 * u.m
length2 = 500 * u.cm
total_length = length1 + length2 # cm が m に自動変換される
print(f"Total length: {total_length}") # 結果はメートル単位

# 次元が異なる演算はエラーになる
mass = 10 * u.kg
try:
    result = length1 + mass
except u.exceptions.UnitOperationError as e:
    print(f"\nError occurred: {e}") # 次元が違うのでエラー!

上記の例のように、同じ次元を持つ単位同士(例: メートルとセンチメートル)であれば、unytが自動的に単位を揃えて計算してくれます。一方で、異なる次元を持つ単位同士(例: 長さと質量)を加算しようとすると、UnitOperationError が発生し、計算ミスを防いでくれます。これは非常に強力な機能で、コードの信頼性を大幅に向上させます ✨。

べき乗、対数、指数関数、三角関数なども unyt オブジェクトに対して適用できますが、これらは単位を無視して計算され、結果は単位なしの数値または配列になります。

import unyt as u
import numpy as np

x = 2.0 * u.m
y = 3.0 * u.m

# べき乗 (単位も計算される)
area = x * y
print(area) # 6.0 m**2

volume = x**3
print(volume) # 8.0 m**3

# 対数・指数・三角関数 (単位は無視され、結果は単位なし)
val_log = np.log(x)
print(val_log, type(val_log)) # log(2.0)、型は numpy.float64

val_exp = np.exp(x)
print(val_exp, type(val_exp)) # exp(2.0)、型は numpy.float64

angle = 0.5 * u.rad # ラジアン単位
val_sin = np.sin(angle)
print(val_sin, type(val_sin)) # sin(0.5)、型は numpy.float64

# 単位が付いている値に対してこれらの関数を適用するのは
# 物理的には意味をなさない場合が多いが、便宜上計算は可能になっている
# (ただし、べき乗 `**` は単位も考慮される)

単位変換

作成した単位付き数量は、to() メソッドや in_units() メソッドを使って簡単に別の単位に変換できます。

import unyt as u

speed_kmh = 60 * u.km / u.hr

# メートル毎秒 (m/s) に変換
speed_ms = speed_kmh.to("m/s")
print(f"{speed_kmh} = {speed_ms}")

# フィート毎秒 (ft/s) に変換 (文字列で指定)
speed_fts = speed_kmh.to("ft/s")
print(f"{speed_kmh} = {speed_fts}")

# 単位オブジェクトで指定することも可能
speed_mph = speed_kmh.to(u.mile / u.hr)
print(f"{speed_kmh} = {speed_mph}")

# in_units() は to() とほぼ同じ機能
energy_joule = 100 * u.J
energy_erg = energy_joule.in_units("erg")
print(f"{energy_joule} = {energy_erg}")

# in_base() で基本単位系 (MKS, CGS) に変換
force = 10 * u.kg * u.m / u.s**2 # 10 N
force_cgs = force.in_base("cgs") # CGS基本単位 (g, cm, s) に変換
print(f"{force} in CGS base units: {force_cgs}")
force_mks = force_cgs.in_base("mks") # MKS基本単位 (kg, m, s) に戻す
print(f"{force_cgs} in MKS base units: {force_mks}")

to()in_units() には、変換したい単位を文字列で指定するか、unytの単位オブジェクトで指定します。in_base() メソッドを使うと、指定した単位系の基本単位(例: MKS単位系のkg, m, s)に変換できます。

特定の単位変換、例えば温度(摂氏、華氏、ケルビン)や天文学における金属量(Zsun)などもサポートされています。

import unyt as u

# 温度変換
temp_c = 25 * u.degC
temp_k = temp_c.to(u.K)
temp_f = temp_c.to(u.degF)
print(f"{temp_c} = {temp_k} = {temp_f}")

# ケルビンから摂氏へ
temp_k2 = 300 * u.K
temp_c2 = temp_k2.to(u.degC)
print(f"{temp_k2} = {temp_c2}")

# 金属量 (Solar Metallicity) の変換
metallicity = 0.02 * u.dimensionless # 質量分率として
# Asplund et al. (2009) の太陽金属量単位に変換
met_zsun_aspl = metallicity.to("Zsun_aspl")
print(f"{metallicity} = {met_zsun_aspl}")

# 逆変換
met_frac = met_zsun_aspl.to("dimensionless")
print(f"{met_zsun_aspl} = {met_frac}")

# 他の太陽金属量の定義も利用可能 (Anders & Grevesse 1989)
met_zsun_angr = metallicity.to("Zsun_angr")
print(f"{metallicity} = {met_zsun_angr}")

ただし、電磁気単位(MKSとCGS間など)の変換は限定的にサポートされており、単純な単位(例:テスラからガウス)は変換できますが、複雑な複合単位の変換はエラーになる場合があります。

import unyt as u

# テスラからガウスへの変換 (サポートされている)
magnetic_field_T = 1.0 * u.T
magnetic_field_G = magnetic_field_T.to(u.G)
print(f"{magnetic_field_T} = {magnetic_field_G}")

# 複雑な複合単位のCGS変換 (エラーになる可能性がある)
try:
    complex_unit_mks = 1.0 * u.C * u.T * u.V # クーロン * テスラ * ボルト
    complex_unit_cgs = complex_unit_mks.in_cgs()
    print(complex_unit_cgs)
except u.exceptions.UnitConversionError as e:
    print(f"\nError converting complex EM unit: {e}")

NumPyとの連携

NumPy配列のようにunyt_arrayを扱う

前述の通り、unyt_array はNumPyの ndarray のサブクラスです。これにより、NumPyのユニバーサル関数(ufunc)や配列操作メソッドの多くを、単位情報を保ったまま利用できます。

import unyt as u
import numpy as np

# unyt_arrayの作成
data = np.array([1.0, 2.0, 3.0, 4.0]) * u.m / u.s

# NumPy ufunc の適用
print("Original data:", data)
print("Squared data:", np.square(data)) # 単位も2乗される (m**2/s**2)
print("Square root:", np.sqrt(data))    # 単位も平方根される (m**0.5/s**0.5)
print("Sine of data (unit ignored):", np.sin(data)) # 単位は無視される

# 配列操作
print("Sum:", np.sum(data))         # 合計値 (単位はそのまま)
print("Mean:", np.mean(data))        # 平均値 (単位はそのまま)
print("Sliced data:", data[1:3])     # スライス (単位はそのまま)

# 単位を取り除いてNumPy配列を取得
# .value または .v 属性 (ビューを返す)
data_value_view = data.value
print("\nData value (view):", data_value_view, type(data_value_view))
# ビューなので、元のデータを変更するとこちらも変わる
data[0] = 10 * u.m / u.s
print("Data value (view) after modification:", data_value_view)

# np.array() または .to_value() (コピーを返す)
data_value_copy = np.array(data)
print("Data value (copy with np.array):", data_value_copy, type(data_value_copy))

# 単位を指定して変換してから値を取得することも可能
data_value_kmh = data.to_value("km/hr")
print("Data value in km/hr:", data_value_kmh)

# 逆にNumPy配列に単位を付与する
plain_array = np.arange(5)
array_with_units = u.unyt_array(plain_array, units=u.g)
print("\nArray with units added:", array_with_units)

# u.umath モジュールで単位対応の数学関数も提供
# (np.sqrt などは u.sqrt でも同じように動作する)
print("Sqrt using u.sqrt:", u.sqrt(data))

NumPy関数が単位をどのように扱うかは関数によります。算術演算や累乗などは単位も計算されますが、三角関数や対数関数などは単位を無視して計算結果を単位なしの floatndarray として返します。

unyt_array から単位情報を取り除き、純粋なNumPy配列として値を取得したい場合は、.value 属性(または .v)や np.array() コンストラクタ、.to_value() メソッドを使用します。.value / .v は元のデータのビュー(参照)を返すのに対し、np.array().to_value() はデータのコピーを返します。 .to_value() では、値を取得する前に単位を指定して変換することも可能です。

逆に、単位のないNumPy配列に単位を付けたい場合は、unyt.unyt_array() コンストラクタを使用します。

物理定数

主要な物理定数も簡単に利用可能

unytには、多くの基本的な物理定数が unyt_quantity オブジェクトとして事前に定義されており、単位付きで簡単に利用できます。これらの定数は unyt モジュールから直接インポートできます。

import unyt as u

# 物理定数の利用例
# 万有引力定数 G
print("Gravitational constant (G):", u.G)

# 光速 c
print("Speed of light (c):", u.c)
print("Speed of light in cm/s:", u.c.to('cm/s'))

# プランク定数 h
print("Planck constant (h):", u.h)

# ボルツマン定数 k_B
print("Boltzmann constant (k_B):", u.kb)

# アボガドロ定数 N_A
print("Avogadro constant (N_A):", u.Na)

# 電子の質量 me
print("Electron mass (me):", u.me)

# 太陽質量 Msun
print("Solar mass (Msun):", u.Msun)
print("Solar mass in grams:", u.Msun.to(u.g))

# 地球半径 Rearth (Rearth のような定数は直接は提供されていないようだが、質量などはある)
# Mearth (地球質量) を使う例
print("Earth mass (Mearth):", u.Mearth)
# Rearth は別途定義するか、他のライブラリから取得する必要がある場合がある
# 例として、おおよその値で定義
Rearth = 6371 * u.km
print("Approx. Earth radius (Rearth):", Rearth)

# 例: 地球の脱出速度の計算
escape_velocity = np.sqrt(2 * u.G * u.Mearth / Rearth)
print("\nEarth escape velocity:", escape_velocity.to("km/s")) # 結果を km/s に変換

物理定数も単位付きの unyt_quantity なので、他の単位付き数量と組み合わせて計算する際に、自動的に単位整合性チェックや単位計算が行われます。これにより、物理計算における単位関連のミスを減らすことができます 👍。

利用可能な物理定数の一覧は、公式ドキュメントの物理定数リストで確認できます。

注意点として、一部の名前(例: “me”, “mp”)は単位記号と物理定数の両方で使われている場合があります。この場合、トップレベルの unyt 名前空間では、デフォルトで物理定数がエクスポートされます。特定の単位記号を使いたい場合は、unyt.unit_symbols から明示的にインポートする必要があるかもしれません。

単位レジストリ (Unit Registry)

独自の単位系を定義・管理する

unytでは、デフォルトで提供されている単位系(SI、CGSなど)以外に、ユーザーが独自の単位や単位系を定義し、管理するための「単位レジストリ」機能を提供しています。これは、特定のシミュレーションコードや分野独自の単位系を扱う際に非常に便利です。

import unyt as u
from unyt import UnitRegistry, dimensions

# 新しい単位レジストリを作成
my_registry = UnitRegistry()

# 新しい基本単位を定義 (例: シミュレーション内のコード単位)
# 長さ: code_length (1単位 = 10 kpc)
# 質量: code_mass (1単位 = 1e10 Msun)
# 時間: code_time (1単位 = 1 Myr)
my_registry.add("code_length", 10.0 * u.kpc, dimensions.length)
my_registry.add("code_mass", 1e10 * u.Msun, dimensions.mass, tex_repr="M_{code}") # TeX表現も指定可能
my_registry.add("code_time", 1.0 * u.Myr, dimensions.time)

# 新しい単位を使って数量を作成 (レジストリを指定)
sim_distance = 5.0 * my_registry["code_length"]
sim_mass = 10.0 * my_registry["code_mass"]
sim_time = 2.0 * my_registry["code_time"]

print("Simulation distance:", sim_distance)
print("Simulation mass:", sim_mass)
print("Simulation time:", sim_time)

# 物理単位 (例: km/s) に変換
sim_velocity = sim_distance / sim_time
print("\nSimulation velocity:", sim_velocity)
velocity_kms = sim_velocity.to("km/s", equivalence='physical', registry=my_registry) # レジストリを指定して変換
print("Simulation velocity in km/s:", velocity_kms)

# 物理単位からコード単位への変換
physical_density = 1e-24 * u.g / u.cm**3
code_density_unit = my_registry["code_mass"] / my_registry["code_length"]**3
sim_density = physical_density.to(code_density_unit, registry=my_registry)
print(f"\n{physical_density} in code units: {sim_density}")

# デフォルトのレジストリとは独立
print("\nAccessing 'kpc' from default registry:", u.kpc)
# print("Accessing 'code_length' from default registry:", u.code_length) # エラーになる

# レジストリは辞書のように振る舞う
print("\nUnits in my_registry:", list(my_registry.keys()))

UnitRegistry オブジェクトを作成し、add() メソッドで新しい単位を定義します。定義時には、単位名、物理単位での値、次元(unyt.dimensions で定義されているもの)、オプションでTeX表現などを指定します。

作成したレジストリ内の単位を使うには、辞書のようにアクセスします(例: my_registry["code_length"])。単位変換を行う際にも、registry 引数に使用するレジストリを指定する必要があります。

これにより、プロジェクト固有の単位系をカプセル化し、グローバルな単位名前空間を汚染することなく、安全に単位を管理できます。

他の単位ライブラリとの連携

Astropy Units や Pint との相互運用

Pythonの科学技術計算エコシステムには、unyt以外にも有名な単位ライブラリが存在します。代表的なものに Astropy UnitsPint があります。

既存のコードがこれらのライブラリを使用している場合でも、unytは相互に変換するための機能を提供しているため、スムーズな連携が可能です。

Astropy Units との連携

Astropyの Quantity オブジェクトをunytの unyt_quantityunyt_array に変換したり、その逆を行ったりできます。

import unyt as u
import numpy as np
# Astropy Units がインストールされている必要がある
try:
    import astropy.units as astropy_u

    # Astropy Quantity を作成
    astropy_length = 10 * astropy_u.parsec
    astropy_velocity = np.array([100, 200]) * astropy_u.km / astropy_u.s
    print("Astropy Quantity (length):", astropy_length)
    print("Astropy Quantity (velocity):", astropy_velocity)

    # Astropy Quantity から unyt_quantity/unyt_array へ変換
    unyt_length = u.unyt_quantity.from_astropy(astropy_length)
    unyt_velocity = u.unyt_array.from_astropy(astropy_velocity)
    print("\nConverted to unyt (length):", unyt_length, type(unyt_length))
    print("Converted to unyt (velocity):", unyt_velocity, type(unyt_velocity))

    # unyt_quantity/unyt_array から Astropy Quantity へ変換
    astropy_length_back = unyt_length.to_astropy()
    astropy_velocity_back = unyt_velocity.to_astropy()
    print("\nConverted back to Astropy (length):", astropy_length_back, type(astropy_length_back))
    print("Converted back to Astropy (velocity):", astropy_velocity_back, type(astropy_velocity_back))

except ImportError:
    print("Astropy is not installed. Skipping Astropy interoperability examples.")

unyt_quantity.from_astropy()unyt_array.from_astropy() クラスメソッドでAstropyオブジェクトからunytオブジェクトを作成し、.to_astropy() メソッドでunytオブジェクトをAstropyオブジェクトに変換します。

Pint との連携

同様に、Pintの Quantity オブジェクトとの相互変換もサポートされています。

import unyt as u
import numpy as np
# Pint がインストールされている必要がある
try:
    import pint
    ureg = pint.UnitRegistry() # Pint の単位レジストリ

    # Pint Quantity を作成
    pint_pressure = 5.0 * ureg.atm # atm = atmosphere
    pint_density = np.array([1.0, 1.2]) * ureg.g / ureg.cm**3
    print("Pint Quantity (pressure):", pint_pressure)
    print("Pint Quantity (density):", pint_density)

    # Pint Quantity から unyt_quantity/unyt_array へ変換
    unyt_pressure = u.unyt_quantity.from_pint(pint_pressure)
    unyt_density = u.unyt_array.from_pint(pint_density)
    print("\nConverted to unyt (pressure):", unyt_pressure, type(unyt_pressure))
    print("Converted to unyt (density):", unyt_density, type(unyt_density))

    # unyt_quantity/unyt_array から Pint Quantity へ変換
    # Pint のレジストリを渡す必要がある場合がある
    pint_pressure_back = unyt_pressure.to_pint() # デフォルトレジストリが使われる
    pint_density_back = unyt_density.to_pint()
    print("\nConverted back to Pint (pressure):", pint_pressure_back, type(pint_pressure_back))
    print("Converted back to Pint (density):", pint_density_back, type(pint_density_back))

except ImportError:
    print("Pint is not installed. Skipping Pint interoperability examples.")

.from_pint() クラスメソッドと .to_pint() メソッドを使用します。これにより、異なるライブラリ間で単位付きデータを柔軟にやり取りできます🤝。

ライブラリ間の比較と選択

unyt, Astropy Units, Pint はそれぞれ特徴があります。どのライブラリを選択するかは、プロジェクトの要件や個人の好みによります。

特徴unytAstropy UnitsPint
主な依存関係NumPy, SymPyNumPy (Astropyパッケージの一部)(依存関係なし)
開発元/背景yt Project (天体物理学) から独立Astropy Project (天文学)独立プロジェクト
APIスタイルytに類似、NumPyライクAstropyエコシステムと統合、NumPyライク独自のUnitRegistryを使用、NumPyライク
パフォーマンス一般に高速 (特に配列操作、SymPyによる最適化)unyt/Pintと比較してやや遅い場合がある一般に高速 (文字列解析ベース)
主な用途科学技術計算全般、ytエコシステム天文学、Astropyエコシステム科学技術計算全般
相互運用性Astropy Units, Pint との変換機能ありunyt, Pint との変換機能あり (部分的に)unyt, Astropy Units との変換機能あり

unyt は、yt Project 由来の経緯から、特に大規模データやシミュレーションとの連携に強みがあり、SymPy を利用した厳密な単位代数処理とパフォーマンスが特徴です。Astropy Units は Astropy という巨大な天文学ライブラリ群の一部であり、天文学分野での利用実績が豊富です。Pint は依存関係がなく軽量であり、文字列ベースの単位定義・解析が特徴的です。

2018年の unyt の論文では、一般的な算術演算のベンチマークにおいて、unyt が Astropy Units や Pint と同等か、より高速であるケースが多いと報告されています。ただし、ライブラリは常に進化しているため、最新の状況は異なる可能性があります。

どのライブラリも NumPy との親和性が高く、基本的な機能は似ていますが、APIの細かな違い、依存関係、コミュニティ、特定の機能(カスタム単位レジストリ、特定の物理分野への対応など)を考慮して選択すると良いでしょう。

まとめ

unytで快適な単位計算ライフを!

unyt は、Python で物理単位を扱うための非常に強力で便利なライブラリです。主な特徴をまとめると以下のようになります。

  • 簡単な単位付与: NumPy配列やスカラー値に簡単に単位を付けられる (unyt_array, unyt_quantity)。
  • 単位整合性チェック: 演算時に単位の次元を自動チェックし、ミスを防ぐ。
  • 自動単位計算・変換: 演算結果の単位を正しく計算し、to() メソッドなどで簡単に単位変換が可能。
  • NumPyとの高い親和性: NumPyのサブクラスであり、多くのNumPy関数をそのまま利用できる。
  • 豊富な定義済み単位・定数: SI、CGS、天文単位など多くの単位と物理定数がすぐに使える。
  • カスタマイズ性: 単位レジストリ機能で独自の単位系を定義・管理できる。
  • 相互運用性: Astropy Units や Pint といった他の単位ライブラリとの連携が可能。
  • パフォーマンス: SymPyを活用し、比較的高速な動作が期待できる。

科学技術計算、データ分析、シミュレーションなど、物理単位が関わるあらゆる場面で unyt は活躍します。単位の取り扱いに関する煩雑さやエラーから解放され、より本質的な計算や分析に集中できるようになるでしょう。

まだ unyt を使ったことがない方は、ぜひこの機会に導入してみてはいかがでしょうか?きっとあなたのPythonコーディング体験をより快適で信頼性の高いものにしてくれるはずです!🥳

さらに詳しい情報やAPIリファレンスについては、unyt公式ドキュメントを参照してください。

コメント

タイトルとURLをコピーしました