もう迷わない！X線天文学のためのkeV-Å変換と可視化の手引き

## はじめに

X線天文学では、エネルギー（keV）と波長（Ångström）の単位変換が欠かせません。論文中の図表を読み解いたり、観測データを解析したりする際に頻繁に必要となるためです。本記事では、PythonとMatplotlibを活用し、この変換を視覚化して直感的に理解できるツールを紹介します。

なお、本記事ではエネルギーを線形スケールで表示する実装に焦点を当てています。

## エネルギーと波長の変換  

エネルギーと波長は、次の式で表されます。

```math
\lambda (\text{Å}) = \frac{12.398}{E (\text{keV})}
```

この式を用いると、エネルギー（keV）または波長（Å）のいずれかが分かれば、もう一方を即座に計算できます。ここで、波長の単位「Å（オングストローム）」は、$1~\text{Å} = 10^{-10}~\textrm{m}$（メートル）を意味し、X線天文学では波長を表す一般的な単位です。

この変換式における定数「12.398」は、以下の関係式から導かれます。

```math
E = \frac{hc}{\lambda}
```

ここで、プランク定数$h$、光速$c$、およびkeVをジュールに変換する定数を用い、単位をオングストロームに合わせることで得られたものです。

## 実装コードの特徴

本記事の実装コードは、Google Colabからもご覧いただけます。

https://colab.research.google.com/drive/1exiRyeRz2OmD1UYAZW0GFU9Kfr8yaiID?usp=sharing

このツールは、以下の2つの主要な機能を備えています。

### 1. keVとÅの相互変換
エネルギー（keV）から波長（Å）への変換、およびその逆変換をPythonで簡単に実行できます。直感的な操作で変換を行うことが可能です。

### 2. 2軸のグラフ作成
Matplotlibを用いて、エネルギー（keV）と波長（Å）を同一グラフ内に同時表示します。これにより、異なる単位でのデータを視覚的に比較できます。

## 実装コードとグラフの出力例

以下に実装コードとグラフの出力例を示します。

### keVを主軸とするグラフ

<details><summary>keVを主軸とする実装コード</summary>

```python:keVを主軸とする実装コード
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FixedLocator

# 波長変換関数
def keV_to_angstrom(keV):
    return 12.398 / (keV + 1e-12)

def angstrom_to_keV(angstrom):
    return 12.398 / (angstrom + 1e-12)

# keV主体のグラフ作成関数
def plot_keV_angstrom_with_wave(keV_range=(1, 10), keV_major_step=1.0, show_wave=True):
    keV = np.linspace(keV_range[0], keV_range[1], 1000)  # keV範囲を設定
    angstrom = keV_to_angstrom(keV)  # 対応するÅを計算

    fig, ax1 = plt.subplots(figsize=(10, 3))

    # サインカーブ (Å基準、10山)
    if show_wave:
        wave_period = (angstrom[-1] - angstrom[0]) / 10
        wave = np.sin(2 * np.pi * (angstrom - angstrom[0]) / wave_period)
        ax1.plot(keV, wave, label="Wave (based on Å)")
        ax1.legend()

    # メインx軸 (keV)
    ax1.set_xlabel("Energy (keV)")
    ax1.set_ylabel("Amplitude" if show_wave else "")
    ax1.set_xlim(keV_range[0], keV_range[1])  # keV範囲

    # 主目盛りの位置を計算
    keV_major_ticks = np.arange(keV_range[0], keV_range[1] + keV_major_step, keV_major_step)
    ax1.xaxis.set_major_locator(FixedLocator(keV_major_ticks))
    ax1.grid(visible=True, linestyle="--", linewidth=0.5, alpha=0.7)  # グリッド設定

    # 副x軸 (Å)
    ax2 = ax1.secondary_xaxis("top", functions=(keV_to_angstrom, angstrom_to_keV))
    ax2.set_xlabel("Wavelength (Å)")
    angstrom_major_ticks = keV_to_angstrom(keV_major_ticks)
    ax2.set_xticks(angstrom_major_ticks)
    ax2.set_xticklabels([f"{x:.2f}" for x in angstrom_major_ticks])  # 目盛りラベル

    plt.title("Energy-Wavelength Dual Axis with Optional Wave")
    plt.tight_layout()
    plt.show()

# 実行例
plot_keV_angstrom_with_wave(
    keV_range=(1, 20),  # keVの範囲
    keV_major_step=1.0, # 主目盛りステップ
    show_wave=True      # 波を描画するか
)
```

</details>

エネルギーを主軸とし、その範囲に対応する波長を副軸として表示したグラフです。波長に基づいたサインカーブを描画することで、エネルギーと波動特性の関係を視覚化しています。

- keV範囲：1～20  
- 主目盛り間隔：1 keV 

グラフ例：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/7dbdd751-bc78-07d9-7974-28de5fbc5cb2.png)

### Åを主軸とするグラフ

<details><summary>Åを主軸とする実装コード</summary>

```python:Åを主軸とする実装コード
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FixedLocator

# 波長変換関数
def keV_to_angstrom(keV):
    return 12.398 / (keV + 1e-12)

def angstrom_to_keV(angstrom):
    return 12.398 / (angstrom + 1e-12)

# Å主体のグラフ作成関数
def plot_angstrom_keV_with_wave(angstrom_range=(1.24, 12.4), angstrom_major_step=1.0, show_wave=True):
    angstrom = np.linspace(angstrom_range[0], angstrom_range[1], 1000)  # Å範囲を設定
    keV = angstrom_to_keV(angstrom)  # 対応するkeVを計算

    fig, ax1 = plt.subplots(figsize=(10, 3))

    # サインカーブ (keV基準、10山)
    if show_wave:
        wave_period = (keV[-1] - keV[0]) / 10
        wave = np.sin(2 * np.pi * (keV - keV[0]) / wave_period)
        ax1.plot(angstrom, wave, label="Wave (based on keV)")
        ax1.legend()

    # メインx軸 (Å)
    ax1.set_xlabel("Wavelength (Å)")
    ax1.set_ylabel("Amplitude" if show_wave else "")
    ax1.set_xlim(angstrom_range[0], angstrom_range[1])  # Å範囲

    # 主目盛りの位置を計算
    angstrom_major_ticks = np.arange(angstrom_range[0], angstrom_range[1] + angstrom_major_step, angstrom_major_step)
    ax1.xaxis.set_major_locator(FixedLocator(angstrom_major_ticks))
    ax1.grid(visible=True, linestyle="--", linewidth=0.5, alpha=0.7)  # グリッド設定

    # 副x軸 (keV)
    ax2 = ax1.secondary_xaxis("top", functions=(angstrom_to_keV, keV_to_angstrom))
    ax2.set_xlabel("Energy (keV)")
    keV_major_ticks = angstrom_to_keV(angstrom_major_ticks)
    ax2.set_xticks(keV_major_ticks)
    ax2.set_xticklabels([f"{x:.2f}" for x in keV_major_ticks])  # 目盛りラベル

    plt.title("Wavelength-Energy Dual Axis with Optional Wave")
    plt.tight_layout()
    plt.show()

# 実行例
plot_angstrom_keV_with_wave(
    angstrom_range=(1, 25),  # Åの範囲
    angstrom_major_step=1.0,      # 主目盛りステップ
    show_wave=True                # 波を描画するか
)
```

</details>

波長を主軸にし、エネルギーを副軸で表示したグラフです。

- Å範囲：1～25  
- 主目盛り間隔：1 Å  

グラフ例：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1599258/e7199e46-6ff5-69ca-e3d1-ae9d1495f0cb.png)

## 実装のポイントと補足
 
### 1. 2軸の実装  
Matplotlibの[`secondary_xaxis`](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_xaxis.html)を利用し、keVとÅを主軸と副軸で同時に表示しています。この機能により、エネルギーと波長の対応関係を一目で確認することができます。

:::note warn
なお、`secondary_xaxis`では内部的に変換式（例：`keV_to_angstrom`）を使用し、あらゆるスケール変換を解釈して目盛りを自動的に配置する仕組みがあります。このため、ゼロやゼロに近い値が分母に含まれる変換式では、ゼロ割りエラーが発生する可能性があります。これを防ぐために、変換式の分母に`1e-12`のような小さな値を加える工夫が重要です。こうした補正を行うことで、極端な値や境界値を含むデータでも正確なグラフを生成することができます。
:::


### 2. 目盛りの調整  
主要な目盛り位置を[`FixedLocator`](https://matplotlib.org/stable/api/ticker_api.html#matplotlib.ticker.FixedLocator)で手動設定することで、正確な数値ラベルを表示します。これにより、グラフが読み取りやすくなります。

### 3. サインカーブの描画  
波長やエネルギー範囲に基づくサインカーブを10周期分描画し、目盛りの歪みを視覚的に表現しています。解析結果の理解を助ける工夫です。

## まとめ
本記事では、エネルギー（keV）と波長（Å）の変換を行うPythonスクリプトを紹介しました。現在の実装ではエネルギーを線形スケールで表示していますが、Xspecの出力結果と比較する際には対数スケールで表示する方が便利な場合もあります。対数スケール表示にも挑戦しましたが、いくつかのバグが解消できなかったため、本記事では線形スケールの実装に絞りました。

このツールが、X線天文学におけるデータ解析やエネルギーと波長の関係を理解する一助となれば幸いです。