はじめに
PyXspec で Plot.addComp() を用いて
モデルの各成分ごとのスペクトルをプロットしようとした際に、model.componentNames と対応が取れず混乱するケースに遭遇しました。
特に、
model = xs.Model("tbabs*(vpshock+vpshock)")
のような 乗算成分(multiplicative)と加算成分(additive)が混在するモデルでは、直感的に書いたコードが破綻しやすく、原因が分かりにくいと感じました。
同じところで詰まる人の助けになればと思い、本記事では 挙動の整理と、安全に自動化するための実装例をログとしてまとめます。
問題の概要
上記のモデルに対して
model.componentNames
を確認すると、例えば次のようになります。
['TBabs', 'vpshock', 'vpshock_3']
一方で、
xs.Plot.addComp(i)
で取得できるのは 加算成分(additive component)のみです。
内部的な対応は次のようになります。
tbabs * (vpshock + vpshock)
↑ ↑
addComp(1) addComp(2)
整理すると、
-
componentNames:乗算成分+加算成分 すべて -
Plot.addComp(i):加算成分のみ
という仕様になっており、この 対象集合のズレが混乱の原因になります。
よくある間違い(条件付きで動く実装)
componentNames と Plot.addComp(i) の index をそのまま対応付ける実装は、
モデルが加算成分(additive)のみで構成されている場合には問題なく動きます。
一方で、吸収成分などの 乗算成分(multiplicative)がモデルに含まれると破綻します。
# ⚠ 条件付きで動く例(additive-only ならOK / multiplicative が入るとズレる)
num_components = xs.Plot.nAddComps()
component_names = model.componentNames
for i in range(1, num_components + 1):
y = np.array(xs.Plot.addComp(i))
name = component_names[i - 1]
ax.plot(x, y, label=name)
例えば tbabs*(vpshock+vpshock) の場合、componentNames には乗算成分である TBabs が含まれる一方で、Plot.addComp(i) は 加算成分のみを返します。
その結果、
-
addComp(1)→vpshock -
componentNames[0]→TBabs
となり、名前と中身が一致しません。
この書き方は「additive 成分のみの単純なモデル」であれば有効ですが、汎用的なコードとしては安全ではありません。
対処方針
XSPEC では基本的に次が成り立ちます。
- 加算成分(additive component)は
normパラメータを持つ - 乗算成分(multiplicative component)は
normを持たない
これを利用し、
-
componentNamesを順番通りに走査 -
normを持つものだけを抽出(=加算成分) - その順序で
Plot.addComp(i)と対応付ける
という方針を取ります。
安全でシンプルな実装例
# additive component 名だけを抽出
additive_names = []
for name in model.componentNames:
comp = getattr(model, name)
try:
_ = comp.norm # additive component の判定
additive_names.append(name)
except Exception:
pass
# Plot.addComp(i) と対応付けてプロット
for i, name in enumerate(additive_names, start=1):
y_component = np.array(xs.Plot.addComp(i))
ax.plot(x_data, y_component, ':', label=name)
この方法で、例えば次のように対応します。
addComp(1) -> vpshock
addComp(2) -> vpshock_3
- モデル式の順序を保持
- 名前は XSPEC の内部仕様に追従
- モデルが変わってもコードを書き換え不要
という状態になります。
補足
-
Plot.addComp(i)を使う前に、xs.Plot("ldata")などで一度プロットを生成しておく必要があります - 同名成分が複数あっても、
componentNames側ですでに一意化されています -
tbabs*(apec+powerlaw+apec)など、より複雑なモデルでも同様に動作します
おわりに
Plot.addComp と componentNames が噛み合わない原因は、component 名そのものではなく、乗算成分が含まれていることによる「対象集合の違い」 にあります。
XSPEC / PyXspec は内部仕様が暗黙的な部分も多く、この挙動はドキュメントだけを読んでも把握しにくいと感じました。そのため、モデルが少し複雑になると「なぜ合わないのか」が分からず、混乱しやすいポイントだと思います。
同じように「addComp と componentNames が合わない」と悩んだ方の参考になれば幸いです。