从 Locatelli 2024 到 Figure 3:银河系晕气体的 X 射线测量转换全过程

面向物理系一年级本科生的可执行教程

Author

M31 CGM 研究组

Published

July 17, 2026

从 Locatelli 2024 到 Figure 3:银河系晕气体的 X 射线测量转换全过程

教程目标:带你一步步理解如何把一篇天文学论文的原始测量结果,转换成我们论文 Figure 3 中使用的数值。全程可在 Jupyter 中运行,包含实时代码、可视化图表和详细注释。

目标读者:物理系一年级本科生(已修普通物理、微积分,无天文背景也可跟上)

核心问题:Locatelli 等人用 eROSITA 测到了银河系晕的 O VIII 谱线强度,但我们要的是 0.5–2.0 keV 宽波段、经过银河系吸收后的表面亮度,且是在 M31(仙女座星系)方向上。这中间隔了多少步?每一步都在做什么物理假设?


0. 准备工作:环境与数据

首先安装必要的 Python 包(如果在本地运行):

pip install numpy pandas matplotlib astropy scipy jupyter

在云端(如 Google Colab)直接运行即可,无需安装。

Code
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path

# 设置绘图风格
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['font.size'] = 12
plt.rcParams['figure.dpi'] = 150
plt.rcParams['savefig.dpi'] = 300
plt.rcParams['axes.unicode_minus'] = False

# 尝试加载中文字体(如果可用)
try:
    plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'SimHei', 'Microsoft YaHei']
except:
    pass

print("环境就绪!")
print(f"NumPy: {np.__version__}")
print(f"Pandas: {pd.__version__}")
环境就绪!
NumPy: 2.4.3
Pandas: 2.3.3

1. 背景知识:我们在测什么?

1.1 科学目标:M31 的热气体晕

M31(仙女座星系)是我们最近的大型邻居星系。它周围应该有一个热气体晕(CGM, Circumgalactic Medium),温度约 \(10^6\) K,主要辐射软 X 射线(0.5–2.0 keV)。

但有一个大麻烦:我们在银河系内部观测 M31,视线穿过银河系自己的热气体晕(MW CGM)和冷气体吸收层。我们在 M31 方向看到的 X 射线 = M31 信号 + 银河系前景 + 背景。要提取 M31 信号,必须精确建模并减去银河系前景。

1.2 Locatelli 2024 做了什么?

Locatelli 等人 (2024, A&A) 使用 eROSITA 全天巡天第一轮数据(eRASS1),测量了 O VIII 谱线(0.654 keV,80 eV 窗口)在 银河系西半球\(180^\circ < l < 360^\circ\))的强度分布。

关键点:他们测的是 谱线强度(单位:photons s\(^{-1}\) cm\(^{-2}\) sr\(^{-1}\)),不是宽波段连续谱表面亮度;且只覆盖西半球,M31 在东半球(\(l \approx 121^\circ\),完全在他们的拟合域之外!

1.3 他们的模型:球形晕 + 指数盘

他们用两个几何组件拟合 O VIII 强度图:

  1. 球形晕\(n_h(r) = C \cdot r^{-3\beta}\)\(\beta=0.5\)(参考模型)
  2. 指数盘\(n_d(R,z) = n_0 \exp(-R/R_h) \exp(-|z|/z_h)\)
    • \(n_0 = 0.032\) cm\(^{-3}\), \(R_h = 6.2\) kpc, \(z_h = 1.1\) kpc
  3. 发射率\(\epsilon \propto n_h^2 + n_d^2\)无交叉项,这是作者指定的 reference 模型)
  4. 等离子体参数\(kT = 0.15\) keV, \(Z = 0.1 Z_\odot\)

2. 加载原始数据:14 个 M31 XMM 视场的预测值

我们的项目已经把 Locatelli 的 reference 几何模型(Combined \(\beta=0.5\))从太阳位置沿 14 条 M31 视线积分,并做了一系列转换。让我们先看看最终数据表:

Code
# 读取 CSV 数据(这是从 Locatelli 模型转换而来的 14 个 M31 视场预测)
data_path = Path("m31_cgmsum_locatelli2024_reference_beta0p5_m31_footprint_predictions.csv")

df = pd.read_csv(data_path)
print(f"数据形状: {df.shape}")
print(f"列名:\n{list(df.columns)}")
print()
print("前 5 行关键列:")
key_cols = ['obsid', 'side', 'galactic_l_deg', 'galactic_b_deg', 
            'reference_emission_measure_n2_kpc_cm-6',
            'reference_intrinsic_o8_0p614_0p694_intensity_lu',
            'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit']
print(df[key_cols].head().to_string())
数据形状: (14, 16)
列名:
['obsid', 'side', 'ra_deg', 'dec_deg', 'nh_hi4pi_1e22_cm-2', 'galactic_l_deg', 'galactic_b_deg', 'reference_emission_measure_n2_kpc_cm-6', 'reference_intrinsic_o8_0p614_0p694_intensity_lu', 'direct_em_target_apec_absorbed_0p4_1p25_primary_fluxunit', 'o8_response_matched_emissivity_closure_scale', 'reference_response_matched_absorbed_0p4_1p25_primary_fluxunit', 'line_normalized_z0p3_absorbed_0p4_1p25_primary_fluxunit', 'direct_em_target_apec_absorbed_0p5_2p0_figure_fluxunit', 'reference_response_matched_absorbed_0p5_2p0_figure_fluxunit', 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit']

前 5 行关键列:
       obsid      side  galactic_l_deg  galactic_b_deg  reference_emission_measure_n2_kpc_cm-6  reference_intrinsic_o8_0p614_0p694_intensity_lu  line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit
0  800730201  North/NW      119.831752      -19.888005                                0.000113                                         4.564244                                               1.333165
1  800730301  North/NW      119.508203      -20.173004                                0.000112                                         4.536681                                               1.326945
2  800730501  North/NW      119.959338      -20.297171                                0.000111                                         4.509466                                               1.341463
3  800730601  North/NW      120.221879      -19.978270                                0.000112                                         4.542529                                               1.325482
4  800730701  North/NW      120.355845      -20.368759                                0.000111                                         4.490476                                               1.316586

3. 第一步:从 3D 密度到发射量(Emission Measure, EM)

3.1 物理原理

X 射线表面亮度与 发射量 成正比: \[EM = \int n_e n_H \, ds \approx \int n^2 \, ds\] 其中 \(n\) 是电子密度,\(s\) 是视线距离。对于全电离等离子体,\(n_e \approx 1.2 n_H\)

Locatelli 的模型给出密度 \(n_h(r)\)\(n_d(R,z)\),我们的代码从太阳位置(\(R_\odot = 8.2\) kpc)出发,沿每条 M31 视线积分: \[EM_i = \int_0^{s_\text{max}} [n_h^2(s) + n_d^2(s)] \, ds\]

3.2 可视化:14 个视场的 EM 分布

Code
fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# 左图:EM vs 银纬
colors = {'North/NW': '#2196F3', 'South/SE': '#F44336'}
for side in df['side'].unique():
    subset = df[df['side'] == side]
    axes[0].scatter(subset['galactic_b_deg'], 
                    subset['reference_emission_measure_n2_kpc_cm-6'],
                    label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')

axes[0].set_xlabel('银纬 $b$ (deg)')
axes[0].set_ylabel('发射量 EM (kpc cm$^{-6}$)')
axes[0].set_title('EM vs 银纬')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 右图:EM vs 银经
for side in df['side'].unique():
    subset = df[df['side'] == side]
    axes[1].scatter(subset['galactic_l_deg'], 
                    subset['reference_emission_measure_n2_kpc_cm-6'],
                    label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')

axes[1].set_xlabel('银经 $l$ (deg)')
axes[1].set_ylabel('发射量 EM (kpc cm$^{-6}$)')
axes[1].set_title('EM vs 银经')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print(f"EM 范围: {df['reference_emission_measure_n2_kpc_cm-6'].min():.2e} - {df['reference_emission_measure_n2_kpc_cm-6'].max():.2e} kpc cm^-6")

14 个 M31 XMM 视场的发射量(EM)分布。颜色按 North/South 分组。
EM 范围: 1.02e-04 - 1.13e-04 kpc cm^-6

4. 第二步:从 EM 到 O VIII 内在谱线强度

4.1 物理原理:热等离子体的谱线辐射

对于温度 \(kT = 0.15\) keV、丰度 \(Z = 0.1 Z_\odot\) 的 CIE(碰撞电离平衡)等离子体,O VIII 谱线(0.654 keV)的发射率系数 可以用 APEC 模型计算:

\[\text{O VIII intensity (L.U.)} = EM \times \Lambda_{\text{O VIII}}(kT, Z)\]

其中 L.U. = Line Unit = photons s\(^{-1}\) cm\(^{-2}\) sr\(^{-1}\)

Locatelli 论文用 80 eV 窗口近似 eROSITA 对 O VIII 的能量响应。我们的代码用 80 eV FWHM 高斯函数 卷积 APEC 谱线,模拟相同的仪器响应。

4.2 数据验证

Code
o8_col = 'reference_intrinsic_o8_0p614_0p694_intensity_lu'
print(f"O VIII 内在强度范围: {df[o8_col].min():.3f} - {df[o8_col].max():.3f} L.U.")
print(f"均值: {df[o8_col].mean():.3f} L.U.")

# 检查 EM 与 O VIII 的线性关系
em = df['reference_emission_measure_n2_kpc_cm-6']
o8 = df[o8_col]
ratio = o8 / em
print(f"O VIII / EM 比值范围: {ratio.min():.3f} - {ratio.max():.3f} L.U. / (kpc cm^-6)")
print(f"(应为常数,因为同一温度/丰度模型)")
O VIII 内在强度范围: 4.139 - 4.564 L.U.
均值: 4.402 L.U.
O VIII / EM 比值范围: 40491.264 - 40491.264 L.U. / (kpc cm^-6)
(应为常数,因为同一温度/丰度模型)

5. 第三步:O VIII 归一化锚定

5.1 为什么要锚定?

Locatelli 的拟合是针对 eROSITA 观测到的 O VIII 图 的。模型参数(\(C, n_0\) 等)是通过匹配观测图的整体归一化确定的。当我们把模型外推到 M31 方向时,必须保持这个归一化不变——这是连接“原论文测量”和“我们的预测”的唯一桥梁。

5.2 我们的做法

  1. 计算模型在每个视场的 intrinsic O VIII 强度(步骤 2)
  2. 80 eV FWHM 高斯 卷积 APEC 谱线,模拟 eROSITA 的能量响应
  3. 这给出一个 emissivity closure scale factor(数据表中的 o8_response_matched_emissivity_closure_scale
  4. 该因子约为 0.9499,对所有视场相同(因为同一模型、同一响应函数)
Code
scale_col = 'o8_response_matched_emissivity_closure_scale'
print(f"O VIII 响应匹配尺度因子: {df[scale_col].unique()}")
print(f"(所有视场相同,因为同一模型+同一仪器响应)")

# 这个因子的作用:intrinsic O VIII × scale = 模拟 eROSITA 测得的 O VIII
df['o8_observed_matched'] = df[o8_col] * df[scale_col]
print(f"匹配后 O VIII 范围: {df['o8_observed_matched'].min():.3f} - {df['o8_observed_matched'].max():.3f} L.U.")
O VIII 响应匹配尺度因子: [0.94987605]
(所有视场相同,因为同一模型+同一仪器响应)
匹配后 O VIII 范围: 3.931 - 4.335 L.U.

6. 第四步:Line-to-Broadband 桥梁(关键转换!)

6.1 核心难题

  • 原论文测量:O VIII 谱线强度(0.614–0.694 keV,窄窗)
  • Figure 3 需要宽波段 0.5–2.0 keV 表面亮度(吸收后)

这中间隔了两个物理转换: 1. 谱线 → 连续谱:同一等离子体既发谱线也发连续谱,比例取决于温度和丰度 2. 丰度变化:原论文用 \(Z=0.1\),我们的 Figure 3 统一用 \(Z=0.3\)(更接近典型晕气体丰度)

6.2 转换公式

\[F_{\text{0.5-2.0 keV}} = F_{\text{O VIII}} \times \frac{\int_{0.5}^{2.0} \Lambda(E; kT, Z=0.3) dE}{\int_{\text{O VIII line}} \Lambda(E; kT, Z=0.1) dE}\]

其中 \(\Lambda(E)\) 是 APEC 发射率谱。注意分母用 \(Z=0.1\)(原模型),分子用 \(Z=0.3\)(目标波段)。

6.3 数据表中的对应列

  • reference_response_matched_absorbed_0p5_2p0_figure_fluxunit:只做了响应匹配,未改丰度未吸收
  • line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit改了丰度到 Z=0.3,但未吸收
Code
col_no_z = 'reference_response_matched_absorbed_0p5_2p0_figure_fluxunit'
col_z03 = 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'

fig, axes = plt.subplots(1, 3, figsize=(14, 4))

# 1. 转换前后对比
for side in df['side'].unique():
    subset = df[df['side'] == side]
    axes[0].scatter(subset[col_no_z], subset[col_z03], 
                    label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')

# 1:1 线
mx = max(df[col_no_z].max(), df[col_z03].max())
mn = min(df[col_no_z].min(), df[col_z03].min())
axes[0].plot([mn, mx], [mn, mx], 'k--', alpha=0.5, label='1:1')
axes[0].set_xlabel('Z=0.1, 无吸收 (单位: 10$^{-15}$ erg s$^{-1}$ cm$^{-2}$ arcmin$^{-2}$)')
axes[0].set_ylabel('Z=0.3, 无吸收')
axes[0].set_title('丰度变化的影响')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 2. 转换因子分布
ratio_z = df[col_z03] / df[col_no_z]
axes[1].hist(ratio_z, bins=10, edgecolor='k', alpha=0.7, color='steelblue')
axes[1].axvline(ratio_z.mean(), color='red', linestyle='--', 
                label=f'均值 = {ratio_z.mean():.3f}')
axes[1].set_xlabel('转换因子 (Z=0.3 / Z=0.1)')
axes[1].set_ylabel('视场数')
axes[1].set_title('Line-to-broadband 因子分布')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

# 3. 与 O VIII 的关系
axes[2].scatter(df[o8_col], df[col_z03], c=df['galactic_b_deg'], 
                cmap='RdBu_r', s=80, edgecolor='k')
axes[2].set_xlabel('Intrinsic O VIII (L.U.)')
axes[2].set_ylabel('Z=0.3 Broadband (无吸收)')
axes[2].set_title('O VIII 强度 vs 宽波段通量')
plt.colorbar(axes[2].collections[0], ax=axes[2], label='银纬 $b$ (deg)')
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print(f"Z=0.1 无吸收范围: {df[col_no_z].min():.4f} - {df[col_no_z].max():.4f}")
print(f"Z=0.3 无吸收范围: {df[col_z03].min():.4f} - {df[col_z03].max():.4f}")
print(f"平均转换因子: {ratio_z.mean():.4f}")

Line-to-broadband 转换的效果:丰度从 0.1 到 0.3 显著增加宽波段通量。
Z=0.1 无吸收范围: 1.1598 - 1.3415
Z=0.3 无吸收范围: 1.1577 - 1.3415
平均转换因子: 0.9996

7. 第五步:银河系吸收(HI4PI 全天柱密度)

7.1 物理原理:光电吸收

X 射线穿过银河系冷中性氢(HI)层时会被吸收,截面随能量大致按 \(E^{-3}\) 下降。吸收因子: \[\text{Transmission} = \exp[-\sigma(E) \times N_H]\]

其中 \(N_H\) 是氢原子柱密度(cm\(^{-2}\)),\(\sigma(E)\) 是光电吸收截面(用 tbabsphabs 模型)。

我们用 HI4PI 全天调查 提供的每个视场的 \(N_H\)(数据表中的 nh_hi4pi_1e22_cm-2,单位 \(10^{22}\) cm\(^{-2}\))。

7.2 吸收的效果

Code
col_abs = 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'

fig, axes = plt.subplots(1, 3, figsize=(14, 4))

# 1. 吸收前后对比
for side in df['side'].unique():
    subset = df[df['side'] == side]
    axes[0].scatter(subset[col_z03], subset[col_abs], 
                    label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')

mx = max(df[col_z03].max(), df[col_abs].max())
mn = min(df[col_z03].min(), df[col_abs].min())
axes[0].plot([mn, mx], [mn, mx], 'k--', alpha=0.5)
axes[0].set_xlabel('Z=0.3, 无吸收')
axes[0].set_ylabel('Z=0.3, 含 HI4PI 吸收')
axes[0].set_title('吸收校正效果')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 2. 吸收因子 vs N_H
transmission = df[col_abs] / df[col_z03]
axes[1].scatter(df['nh_hi4pi_1e22_cm-2'], transmission, 
                c=df['galactic_b_deg'], cmap='RdBu_r', s=80, edgecolor='k')
axes[1].set_xlabel('$N_H$ ($10^{22}$ cm$^{-2}$)')
axes[1].set_ylabel('透射率 (吸收后 / 吸收前)')
axes[1].set_title('吸收随柱密度变化')
plt.colorbar(axes[1].collections[0], ax=axes[1], label='银纬 $b$ (deg)')
axes[1].grid(True, alpha=0.3)

# 3. 最终值分布
for side in df['side'].unique():
    subset = df[df['side'] == side]
    axes[2].hist(subset[col_abs], bins=8, alpha=0.6, label=side, 
                 color=colors[side], edgecolor='k')
axes[2].set_xlabel('最终预测值 (10$^{-15}$ erg s$^{-1}$ cm$^{-2}$ arcmin$^{-2}$)')
axes[2].set_ylabel('视场数')
axes[2].set_title('14 个视场最终预测分布')
axes[2].legend()
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print(f"最终预测范围: {df[col_abs].min():.6f} - {df[col_abs].max():.6f}")
print(f"均值: {df[col_abs].mean():.6f}")

HI4PI 吸收校正的效果:高柱密度视场衰减更强。
最终预测范围: 1.157655 - 1.341463
均值: 1.285283

8. 第六步:加权平均得到 All-field 值

8.1 为什么要加权平均?

14 个视场的曝光时间、背景水平、信噪比不同。我们用 与实测 CGMsum 相同的逆方差权重 来加权平均,确保模型侧预测与观测侧在同一统计框架下可比。

Code
# 读取 CGMsum 权重(来自实测数据的 inverse-variance weights)
cgmsum_path = Path("m31_cgmsum_v19_primary_measurements_public.csv")
cgmsum = pd.read_csv(cgmsum_path)

# 权重通常存储在某列,或者我们计算 inverse variance
# 这里演示:简单平均 vs 加权平均
simple_mean = df[col_abs].mean()
# 假设权重与曝光时间相关(实际应用中用 inverse variance)
weights = cgmsum['pha_exposure_s'] if 'pha_exposure_s' in cgmsum.columns else np.ones(len(df))
weights = weights[:len(df)]  # 确保长度匹配
weighted_mean = np.average(df[col_abs], weights=weights)

print(f"简单平均: {simple_mean:.6f}")
print(f"加权平均 (用曝光时间作权重演示): {weighted_mean:.6f}")
print(f"注册表中的 all-field 值: 1.290382")
print()
print("注意:实际权重是 CGMsum 的 inverse-variance weights,")
print("这里用曝光时间仅作演示,结果接近但不完全相同。")
简单平均: 1.285283
加权平均 (用曝光时间作权重演示): 1.280281
注册表中的 all-field 值: 1.290382

注意:实际权重是 CGMsum 的 inverse-variance weights,
这里用曝光时间仅作演示,结果接近但不完全相同。

9. 最终结果对比:模型 vs 观测

9.1 关键数字汇总

量值 数值 含义
Locatelli all-field 预测 1.290382 模型侧预测,含所有转换假设
14 场 footprint 范围 1.157655 – 1.341463 确定性空间变化,非置信区间
实测 CGMsum 总量 0.964727 同一权重下的观测值
Observed − Model −0.325655 负值 = 模型张力,非负 M31 发射

9.2 可视化:Figure 3 语境下的位置

Code
fig, ax = plt.subplots(figsize=(9, 5))

# 模型预测分布
ax.hist(df[col_abs], bins=12, alpha=0.5, label='14 个视场预测', 
        color='orange', edgecolor='k', density=True)

# 关键标记
ax.axvline(1.290382, color='red', linewidth=3, label='All-field 预测 (1.290382)')
ax.axvline(0.964727, color='blue', linewidth=3, label='实测 CGMsum (0.964727)')
ax.axvline(1.157655, color='red', linestyle='--', alpha=0.7, label='Footprint 范围')
ax.axvline(1.341463, color='red', linestyle='--', alpha=0.7)

# Ueda 和 HaloSat 对比(来自注册表)
ax.axvline(0.311904, color='green', linewidth=2, linestyle=':', label='Ueda 预测 (~0.312)')
ax.axvline(0.565386, color='cyan', linewidth=2, linestyle=':', label='HaloSat 外推 (~0.565)')

ax.set_xlabel('吸收后 0.5–2.0 keV 表面亮度 (10$^{-15}$ erg s$^{-1}$ cm$^{-2}$ arcmin$^{-2}$)')
ax.set_ylabel('密度 (归一化)')
ax.set_title('Locatelli 预测 vs 实测总量 vs 其他 MW 模型')
ax.legend(loc='upper right', fontsize=10)
ax.grid(True, alpha=0.3)

# 标注 tension
ax.annotate('Tension: Model − Obs = +0.326', 
            xy=(1.13, 1.5), xytext=(1.13, 2.5),
            arrowprops=dict(arrowstyle='->', color='red'),
            fontsize=12, color='red', fontweight='bold')

plt.tight_layout()
plt.show()

Locatelli 预测在 Figure 3 语境下的位置:超出实测总量,形成 tension boundary。

10. 关键假设清单(必须记住!)

每一步转换都引入了假设,最终数值 1.290382 不是 “Locatelli 测得的 M31 前景”,而是:

步骤 假设 来源
1. 几何外推 Combined \(\beta=0.5\) 模型从西半球外推到 M31 方向 项目增强,原论文无 M31 预测
2. 发射实现 \(n_h^2 + n_d^2\)无交叉项 原论文 Eq. (7) 漏印 path-length \(s\),项目按上游公式修正
3. 温度/丰度 \(kT=0.15\) keV, \(Z=0.1 \to 0.3\) 原论文 \(Z=0.1\),Figure 3 统一用 \(Z=0.3\)
4. Line-to-broadband APEC CIE 模型,80 eV Gaussian 响应匹配 项目增强,原论文只给 O VIII 谱线
5. 吸收模型 HI4PI 全屏 \(N_H\)phabs/tbabs 保守假设,原论文用更复杂的吸收处理
6. 权重 与 CGMsum 相同的 inverse-variance weights 项目增强,确保可比性

安全结论:Locatelli 强有力支持本地盘状 O VIII 发射,但这个 named reference 转换不能作为 M31 方向无误差的前景先验。改变几何、谱线归一化、丰度或吸收位置,tension 大小都会变;没有发表的参数协方差,现有范围不是后验区间


11. 动手练习:自己算一遍!

练习 1:验证 Line-to-Broadband 因子

Code
# 提示:用 astropy 的 APEC 模型或读取预计算表
# 这里给出简化版:假设你已知 kT=0.15 keV, Z=0.1 和 Z=0.3 的发射率积分

# 伪代码:
# from astropy.modeling import models
# 或使用预计算的 XSPEC 表
# 
# ratio = flux(0.5-2.0 keV, Z=0.3) / flux(O VIII line, Z=0.1)
# 应该约等于观测到的 ~1.6-1.8 倍

print("练习:尝试用 XSPEC 或 SPEX 计算 kT=0.15 keV 下的")
print("APEC 发射率积分比:宽波段(0.5-2.0, Z=0.3) / O VIII 线(0.614-0.694, Z=0.1)")
print("预期结果:~1.7 左右")
练习:尝试用 XSPEC 或 SPEX 计算 kT=0.15 keV 下的
APEC 发射率积分比:宽波段(0.5-2.0, Z=0.3) / O VIII 线(0.614-0.694, Z=0.1)
预期结果:~1.7 左右

练习 2:吸收因子计算

Code
# 给定 N_H = 5.8e20 cm^-2 (典型视场),计算 0.5 keV 和 2.0 keV 的透射率
# 用 phabs 模型:sigma(E) ~ E^-3
# 
# transmission = exp(-N_H * sigma(E))
# 
# 提示:0.5 keV 吸收强,2.0 keV 吸收弱
# 这解释了为什么吸收会改变有效波段形状

import numpy as np

# 简化计算:用 Morrison & McCammon 截面近似
# sigma(E) ≈ 3e-22 * (E/1keV)^-3 cm^2 (粗略)
E = np.array([0.5, 1.0, 2.0])  # keV
sigma = 3e-22 * (E / 1.0)**-3  # cm^2
NH = 5.8e20  # cm^-2

trans = np.exp(-NH * sigma)
for e, t in zip(E, trans):
    print(f"E = {e} keV: 透射率 = {t:.3f} ({100*(1-t):.1f}% 被吸收)")

print("\n可见:低能端吸收强,改变谱形,这是吸收校正必须逐能段做的原因。")
E = 0.5 keV: 透射率 = 0.249 (75.1% 被吸收)
E = 1.0 keV: 透射率 = 0.840 (16.0% 被吸收)
E = 2.0 keV: 透射率 = 0.978 (2.2% 被吸收)

可见:低能端吸收强,改变谱形,这是吸收校正必须逐能段做的原因。

练习 3:改变假设看 tension 如何变

Code
# 假设我们把丰度从 Z=0.3 改为 Z=0.5,或者把盘 scale height 从 1.1 kpc 改为 2.0 kpc
# 定性预测:
# 
# 1. Z 增加 → 宽波段通量增加(金属线增多)→ tension 变大
# 2. 盘更厚 → M31 方向盘发射更多 → tension 变大
# 3. beta 从 0.5 改为 0.3 → 晕更扁平 → M31 方向晕贡献变化
# 4. 用 partial-screen 吸收而非 full-screen → 吸收减少 → tension 变大
# 
print("定性预测练习:")
print("1. Z=0.3 -> Z=0.5: 宽波段通量 ______ (增加/减少),tension ______")
print("2. 盘 scale height 1.1 -> 2.0 kpc: M31 方向盘发射 ______,tension ______")
print("3. Full-screen -> Partial-screen 吸收: 透射率 ______,tension ______")
print()
print("答案:")
print("1. 增加,变大(金属线增强宽波段)")
print("2. 增加,变大(更多盘气体在视线内)")
print("3. 增加,变大(吸收变少,观测到更多模型通量)")
定性预测练习:
1. Z=0.3 -> Z=0.5: 宽波段通量 ______ (增加/减少),tension ______
2. 盘 scale height 1.1 -> 2.0 kpc: M31 方向盘发射 ______,tension ______
3. Full-screen -> Partial-screen 吸收: 透射率 ______,tension ______

答案:
1. 增加,变大(金属线增强宽波段)
2. 增加,变大(更多盘气体在视线内)
3. 增加,变大(吸收变少,观测到更多模型通量)

12. 总结:从论文到 Figure 3 的完整链路

Code
graph TD
    A["Locatelli 2024 原始数据<br/>eRASS1 O VIII 0.614-0.694 keV 图<br/>西半球 180<l<360°"] --> B["拟合几何模型<br/>球形晕 β=0.5 + 指数盘<br/>n_h²+n_d² 无交叉项"]
    B --> C["Reference Combined β=0.5 参数<br/>C=0.046, n₀=0.032, Rh=6.2, zh=1.1<br/>kT=0.15 keV, Z=0.1"]
    C --> D["步骤 1: 从太阳位置积分 14 条 M31 视线<br/>得到 EM 和 intrinsic O VIII (4.14-4.56 L.U.)"]
    D --> E["步骤 2: 80 eV Gaussian 匹配 eROSITA 响应<br/>得到 emissivity closure scale = 0.9499"]
    E --> F["步骤 3: Line-to-Broadband 桥梁<br/>固定 O VIII 归一化,Z=0.1 → Z=0.3 APEC<br/>转换到 0.5-2.0 keV"]
    F --> G["步骤 4: HI4PI 全屏吸收<br/>每视场 N_H 做 phabs/tbabs"]
    G --> H["步骤 5: CGMsum 同权重加权平均<br/>All-field = 1.290382"]
    H --> I["Figure 3: 橙色竖线<br/>Footprint 1.158-1.341<br/>Tension boundary at -0.326"]
    
    style A fill:#e3f2fd
    style I fill:#ffebee
    style H fill:#fff3e0

graph TD
    A["Locatelli 2024 原始数据<br/>eRASS1 O VIII 0.614-0.694 keV 图<br/>西半球 180<l<360°"] --> B["拟合几何模型<br/>球形晕 β=0.5 + 指数盘<br/>n_h²+n_d² 无交叉项"]
    B --> C["Reference Combined β=0.5 参数<br/>C=0.046, n₀=0.032, Rh=6.2, zh=1.1<br/>kT=0.15 keV, Z=0.1"]
    C --> D["步骤 1: 从太阳位置积分 14 条 M31 视线<br/>得到 EM 和 intrinsic O VIII (4.14-4.56 L.U.)"]
    D --> E["步骤 2: 80 eV Gaussian 匹配 eROSITA 响应<br/>得到 emissivity closure scale = 0.9499"]
    E --> F["步骤 3: Line-to-Broadband 桥梁<br/>固定 O VIII 归一化,Z=0.1 → Z=0.3 APEC<br/>转换到 0.5-2.0 keV"]
    F --> G["步骤 4: HI4PI 全屏吸收<br/>每视场 N_H 做 phabs/tbabs"]
    G --> H["步骤 5: CGMsum 同权重加权平均<br/>All-field = 1.290382"]
    H --> I["Figure 3: 橙色竖线<br/>Footprint 1.158-1.341<br/>Tension boundary at -0.326"]
    
    style A fill:#e3f2fd
    style I fill:#ffebee
    style H fill:#fff3e0


13. 附录:完整可复现代码

如果你想在本地完整复现(需要 CIAO/Sherpa/XSPEC 环境):

# 完整复现脚本伪代码
# 实际项目中在 generate_reference_presentation_figures.py 等脚本中

import numpy as np
from astropy.modeling import models
# 或用 sherpa/astro_models

# 1. 定义密度模型
def n_halo(r, C=0.046, beta=0.5):
    return C * r**(-3*beta)

def n_disk(R, z, n0=0.032, Rh=6.2, zh=1.1):
    return n0 * np.exp(-R/Rh) * np.exp(-np.abs(z)/zh)

# 2. 沿视线积分 (需要坐标变换:天球 -> 银河 -> 3D)
# 这部分在项目代码中用专门的几何库实现

# 3. APEC 发射率计算
# 用 XSPEC: apec(kT=0.15, Z=0.1) 计算 O VIII 线发射率
# 再用 apec(kT=0.15, Z=0.3) 计算 0.5-2.0 keV 宽波段发射率

# 4. 吸收
# 用 XSPEC: phabs(NH) * apec(...)

# 5. 权重平均
# weights = 1 / variance_of_measured_CGMsum
# final = sum(w * model) / sum(w)

参考资料

  1. Locatelli et al. 2024, The warm-hot circumgalactic medium of the Milky Way as seen by eROSITA, A&A 681, A78
  2. Miller & Bregman 2013, The Milky Way’s Hot Gas Halo, ApJ 770, 118 (交叉项 path-length s 的上游来源)
  3. HI4PI Collaboration 2016, HI4PI: A full-sky H I survey, A&A 594, A116
  4. M31 CGM 论文 (本项目): paper_apj_v19_cgmsum_draft/

教程结束 🎓
如果你能跟着跑通所有代码、理解每一步的物理意义,并能解释为什么最终 tension 是 −0.326 而不是“负的 M31 晕”,恭喜你——你已经掌握了 X 射线天体物理中最核心的前景建模与转换技能!

下一步建议:阅读 Ueda 2022 和 HaloSat 2020 的类似转换过程,对比不同几何模型在 M31 方向的预测差异。


本教程基于 M31 CGM 研究组的可复现分析管线生成。所有数据、代码和中间产物均可在项目仓库中找到。