---
title: "从HaloSat南天拟合到M31方向域外外推 — From HaloSat southern-sky fit to the M31 out-of-domain extrapolation"
subtitle: "面向物理系本科一年级 · 可执行教程"
author: "M31 CGM Team"
date: "2026-07-18"
format:
html:
theme:
light: [cosmo, custom.scss]
dark: [cosmo, custom-dark.scss]
toc: true
toc-depth: 3
code-fold: true
code-tools: true
fig-cap-location: bottom
fig-width: 7
fig-height: 4
fig-align: center
embed-resources: true
css: styles.css
execute:
echo: true
eval: true
warning: false
message: false
jupyter: python3
---
# 从HaloSat南天拟合到M31方向域外外推
> **教程目标**:理解 Kaaret et al. (2020) HaloSat 的 empirical disk + adiabatic halo 模型如何从73个南天高银纬场拟合,再域外外推到M31方向,变成Figure 3上的一个 supplemental extrapolation 点。
> **Tutorial goal**: Understand how Kaaret et al. (2020) HaloSat's empirical disk plus adiabatic halo model, fitted to 73 southern high-latitude fields, is extrapolated outside its fit domain toward M31 to become a supplemental extrapolation point on Figure 3.
> **目标读者**:物理系本科一年级,已学完普通物理(电磁学/光学),了解基本的原子物理概念(能级、跃迁),但不需要天文观测经验。
> **Target audience**: First-year physics undergraduates who have completed general physics (electromagnetism/optics) and basic atomic physics, without requiring astronomical observing experience.
> **核心问题**:HaloSat 是一颗 CubeSat,它用73个 b<-30° 的南天场拟合出银河系热气体的三维形态模型。M31 在 b≈-21.6°,全部十四场都在拟合域外。这个模型如何"外推"到M31方向?为什么它只是一个 supplemental extrapolation,而不是直接预测?
---
## 0. 准备工作:环境与数据
本教程只需要 Python 标准科学栈。所有数据文件已随教程提供。
```{python}
#| label: setup
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
# NOTE: do NOT add a CJK font fallback here — see SKILL.md
# All matplotlib text labels stay English-only.
DATA = Path("assets/data")
print("Environment ready!")
```
---
## 1. 背景知识:HaloSat 是什么?
### 1.1 HaloSat:一颗看X射线的 CubeSat
HaloSat 是一颗专门观测软X射线(约0.2–2 keV)的 **CubeSat**——一种体积很小、成本较低的卫星。它不是成像望远镜,而是用 **silicon-drift detectors**(硅漂移探测器)和宽视场来测量大天区的X射线背景。它的科学目标是理解银河系周围的热气体——**circumgalactic medium, CGM**——是什么形状的。
### 1.2 银河系热气体的两种"形状"
银河系周围 $10^6$–$10^7$ K 的热气体发出软X射线。但它的空间分布是**盘状(disk-dominated)**还是**球状(spherical halo)**?这关系到银河系如何吸积和释放气体。Kaaret et al. (2020) 用 HaloSat 的73个南天高银纬场($b<-30^\circ$)的 **emission measure**(发射量度,EM)数据来回答这个问题。
**Emission measure** 的定义是沿视线积分 $\int n_e n_H \, ds$(单位 cm$^{-6}$ pc),它正比于X射线发射强度。EM 越大,说明该方向上的热气体越多或路径越长。
### 1.3 论文结论:disk-dominated 且 patchy
Kaaret et al. (2020) 比较了 disk 和 halo 两种形态模型后,结论是:**soft X-ray emitting CGM 在该南天样本中是 disk-dominated 的,而且有 patchiness(不均匀性)**。最终采用的 composite model(empirical disk + adiabatic halo)的拟合优度是 $\chi^2 \approx 71.7/70$。
---
## 2. HaloSat 的三维模型:empirical disk + adiabatic halo
### 2.1 Empirical disk(经验盘)
盘成分的密度模型是:
$$n_{\mathrm{disk}}(R, z) = n_0 \cdot \Sigma(R) \cdot \exp\left(-\frac{|z|}{z_0}\right)$$
其中:
- $n_0 = 0.0081$ cm$^{-3}$ 是盘的密度归一化;
- $z_0 = 1.60$ kpc 是垂直标高;
- $\Sigma(R)$ 是一个 piecewise(分段)的径向函数,描述盘密度随银心距 $R$ 的变化。
### 2.2 Adiabatic halo(绝热晕)
晕成分采用 **Fang et al. (2013) 的 polytropic(多方)模型**,描述在 **NFW 势阱**中处于流体静力学平衡的气体:
- $\rho_V = 4.8 \times 10^{-5}$ cm$^{-3}$(virial 密度归一化);
- $R_s = 21.7$ kpc(NFW scale radius);
- $C_V = 12$(concentration 参数)。
这是一个"绝热"气体晕——温度随密度按绝热关系变化,而不是等温的。
### 2.3 关键:发射量度的计算方式
模型预测的 EM 通过沿视线积分 **$(n_{\mathrm{disk}} + n_{\mathrm{halo}})^2$** 得到:
$$\mathrm{EM}_{\mathrm{model}} = \int (n_d + n_h)^2 \, ds$$
注意这里**保留了 disk-halo cross term(交叉项)** $2 n_d n_h$。这个 convention 是通过对73场 catalog 的 $\chi^2$ closure 验证确定的——只有保留交叉项,才能复现论文的拟合统计量 $\chi^2 = 71.665$。
> **关键假设**:论文没有说明拟合得到的 $n$ 是 $n_e$、$n_H$、total-ion 还是 total-particle 密度。因此本项目**不额外乘** $n_e/n_H = 1.2$ 的 composition factor。这与 Ueda+2022 的处理不同。
```{mermaid}
graph TD
A["73 southern fields<br/>b < -30 deg"] --> B["Empirical disk<br/>n0=0.0081, z0=1.60 kpc"]
A --> C["Adiabatic halo<br/>Fang+2013 polytrope in NFW"]
B --> D["EM = ∫(n_d+n_h)² ds<br/>cross term retained"]
C --> D
D --> E["chi2 = 71.7/70<br/>catalog closure verified"]
E --> F["Extrapolate to M31<br/>b ≈ -21.6 deg (outside domain)"]
```
---
## 3. 加载M31方向的外推数据
```{python}
#| label: load-data
halosat = pd.read_csv(DATA / "m31_cgmsum_halosat_kaaret2020_disk_adiabatic_halo_m31_extrapolation.csv")
print(f"Loaded {len(halosat)} M31 sightlines")
print(f"Columns: {list(halosat.columns)}")
halosat.head(8)
```
数据包含14条M31方向的XMM视场。每条视场有:
| 列名 | 含义 |
|------|------|
| `obsid` | XMM-Newton 观测ID |
| `galactic_l_deg`, `galactic_b_deg` | 场中心的银道坐标 |
| `nh_hi4pi_1e22_cm-2` | HI4PI 中性氢柱密度($10^{22}$ cm$^{-2}$) |
| `outside_halosat_fit_domain` | 是否在HaloSat拟合域外(全部为True) |
| `nominal_em_cm-6_pc` | nominal(含交叉项)EM(cm$^{-6}$ pc) |
| `no_cross_term_em_cm-6_pc` | 去交叉项的 EM(structural check) |
| `absorbed_0p5_2p0_flux_per_em_fluxunit` | 单位 EM 对应的 absorbed 0.5–2.0 keV flux |
| `nominal_absorbed_0p5_2p0_fluxunit` | nominal absorbed 0.5–2.0 keV brightness(Figure 3 单位) |
| `no_cross_term_absorbed_0p5_2p0_fluxunit` | 去交叉项的 absorbed brightness |
| `patchiness_sigma_absorbed_0p5_2p0_fluxunit` | patchiness scatter 转换后的 1σ |
| `measurement_staterr_absorbed_0p5_2p0_fluxunit` | 观测统计误差(用于权重计算) |
---
## 4. 为什么这是"域外外推"?
### 4.1 HaloSat 的拟合域 vs M31 的位置
HaloSat 的73个拟合场全部在 $b < -30^\circ$ 的南天高银纬区域。M31 在 $b \approx -21.6^\circ$,十四场全部位于拟合域外。最近的一个拟合场中心距 M31 约 **17.70°**,超过了 HaloSat 约 7° 的零响应半径。
```{python}
#| label: domain-check
# Verify all 14 M31 fields are outside the fit domain
all_outside = halosat["outside_halosat_fit_domain"].all()
print(f"All 14 fields outside HaloSat fit domain: {all_outside}")
print(f"Galactic latitude range: [{halosat['galactic_b_deg'].min():.2f}, {halosat['galactic_b_deg'].max():.2f}] deg")
print(f"M31 is at b ≈ -21.6 deg, fit domain is b < -30 deg")
print(f"Nearest fit field center to M31: 17.70 deg (beyond ~7 deg zero-response radius)")
```
### 4.2 可视化:HaloSat 拟合域与 M31 视场
```{python}
#| label: sky-coverage
#| fig-cap: "HaloSat 73-field fit domain (b < -30 deg, cyan) vs 14 M31 XMM fields (orange). M31 (red star) lies outside the fit domain. The nearest fit center is 17.70 deg away."
#| fig-width: 8
#| fig-height: 5
# Load the 73 HaloSat southern fields
halosat_fields = pd.read_csv(DATA / "m31_cgmsum_halosat_kaaret2020_southern_fields.csv")
fig, ax = plt.subplots(figsize=(8, 5), subplot_kw={"projection": None})
# Plot HaloSat 73 field centers
ax.scatter(
halosat_fields["galactic_l_deg"],
halosat_fields["galactic_b_deg"],
s=18, c="#2ca6c4", alpha=0.7, edgecolor="none",
label=f"HaloSat 73 fit fields (b < -30 deg)"
)
# Plot selection envelope boundary at b = -30
ax.axhline(-30, color="#2ca6c4", linestyle="--", linewidth=1.0, alpha=0.5)
# Plot 14 M31 fields
ax.scatter(
halosat["galactic_l_deg"],
halosat["galactic_b_deg"],
s=60, c="#e07b3a", alpha=0.9, edgecolor="white", linewidth=0.5,
label=f"14 M31 XMM fields (b ≈ -21.6 deg)",
zorder=5
)
# M31 center
ax.scatter(121.17, -21.57, marker="*", s=200, c="#b74842", edgecolor="white",
linewidth=0.8, zorder=6, label="M31 center")
ax.set_xlabel("Galactic longitude l (deg)")
ax.set_ylabel("Galactic latitude b (deg)")
ax.set_title("HaloSat fit domain vs M31 footprint")
ax.legend(fontsize=9, loc="lower left")
ax.set_xlim(60, 130)
ax.set_ylim(-85, 5)
plt.show()
```
**关键观察**:
- 73个青色点是 HaloSat **实际拟合**的场中心,不是连续的天空覆盖。
- 橙色点是14个M31 XMM视场,全部在 $b=-30^\circ$ 边界(青色虚线)之上——即拟合域外。
- 红色五角星是M31中心,最近的一个青色点离它约17.7°。
---
## 5. 第一步:模型投影到M31视线
### 5.1 物理原理
要把 HaloSat 的三维密度模型投影到 M31 方向,需要沿每条M31视线积分 $(n_d + n_h)^2$:
1. **积分路径**:从太阳位置($R_\odot = 8.0$ kpc)沿视线积分到 260 kpc;
2. **|z| 对称**:盘密度使用 $|z|$ 实现银道面对称;
3. **catalog-closed convention**:保留 disk-halo 交叉项,即 $\int(n_d+n_h)^2 ds$,不是 $\int n_d^2 ds + \int n_h^2 ds$。
### 5.2 验证:nominal vs no-cross-term
```{python}
#| label: cross-term-check
# The nominal branch retains the cross term; the no-cross-term is a structural check
print("Emission measure comparison (cm^-6 pc):")
print(f" Nominal (with cross term): mean = {halosat['nominal_em_cm-6_pc'].mean():.6f}")
print(f" No cross term (structural chk): mean = {halosat['no_cross_term_em_cm-6_pc'].mean():.6f}")
print(f" Ratio (nominal / no-cross): mean = {(halosat['nominal_em_cm-6_pc'] / halosat['no_cross_term_em_cm-6_pc']).mean():.4f}")
print()
print("The cross term adds ~29% to the EM, confirming it is retained in the nominal branch.")
```
---
## 6. 第二步:补充温度——为什么需要 kT=0.225 keV?
### 6.1 模型不预测温度
HaloSat 的密度模型只告诉我们 $n(r)$,即气体在空间各处的密度。但要把 EM 转换成X射线 flux,还需要知道气体的**温度** $kT$——因为温度决定了X射线谱的形状(哪些能量发射多少光子)。
问题在于:**HaloSat 的 disk+halo 模型不预测M31方向的温度**。它只在73个南天场各自拟合了温度,但M31在拟合域外,没有对应的拟合温度。
### 6.2 补充 median kT = 0.225 keV
解决方案:用 HaloSat 73场的 **sample median** $kT = 0.225$ keV 作为M31方向的补充温度。这是一个合理的"代表性"温度,但必须记住它是**额外假设**,不是模型自带的预测。
```{python}
#| label: median-kt
# Verify the median kT from the 73-field catalog
kt_values = halosat_fields["kt_keV"].dropna()
median_kt = kt_values.median()
print(f"HaloSat 73-field median kT = {median_kt:.3f} keV")
print(f"Adopted M31-direction kT = 0.225 keV (sample median)")
print(f"Note: This is an explicit augmentation — the density model has no M31-direction temperature prediction.")
```
---
## 7. 第三步:APEC + TBabs + HI4PI → absorbed 0.5–2.0 keV
### 7.1 光谱转换链
有了 EM 和温度,还需要把 intrinsic(吸收前)的发射转换成 absorbed(吸收后)的观测值。转换链是:
1. **APEC**(Astrophysical Plasma Emission Code):给定 $kT=0.225$ keV 和金属丰度 $Z=0.3$,计算等离子体的发射谱;
2. **TBabs**(Tübingen-Boulder absorption):用 Wilms 丰度和 Verner 截面计算光致电离吸收;
3. **HI4PI**:每条M31视线有 field-specific 的中性氢柱密度 $N_\mathrm{H}$。
### 7.2 验证:flux 与 EM 的关系
```{python}
#| label: flux-em-relation
# Each field has a per-EM conversion factor
fig, ax = plt.subplots(figsize=(8, 4.5))
sc = ax.scatter(
halosat["nh_hi4pi_1e22_cm-2"],
halosat["absorbed_0p5_2p0_flux_per_em_fluxunit"],
c=halosat["galactic_b_deg"],
cmap="viridis",
s=50, edgecolor="white", linewidth=0.5,
)
cbar = fig.colorbar(sc, ax=ax)
cbar.set_label("Galactic latitude b (deg)")
ax.set_xlabel("N_H (10^22 cm^-2, HI4PI)")
ax.set_ylabel("Absorbed flux per EM (flux unit / cm^-6 pc)")
ax.set_title("Per-field APEC+TBabs conversion factor")
plt.show()
print(f" N_H range: [{halosat['nh_hi4pi_1e22_cm-2'].min():.4f}, {halosat['nh_hi4pi_1e22_cm-2'].max():.4f}] x 10^22 cm^-2")
print(f" Flux/EM range: [{halosat['absorbed_0p5_2p0_flux_per_em_fluxunit'].min():.2f}, {halosat['absorbed_0p5_2p0_flux_per_em_fluxunit'].max():.2f}]")
print(" Higher N_H → more absorption → lower flux per EM (softer spectrum suppressed).")
```
### 7.3 重建 nominal absorbed brightness
```{python}
#| label: reconstruct-flux
# Manual reconstruction: EM × (flux per EM) = absorbed flux
manual_nominal = halosat["nominal_em_cm-6_pc"] * halosat["absorbed_0p5_2p0_flux_per_em_fluxunit"]
csv_nominal = halosat["nominal_absorbed_0p5_2p0_fluxunit"]
print("Field-by-field reconstruction (first 5):")
for i in range(5):
print(f" Field {i+1}: manual={manual_nominal.iloc[i]:.6f}, CSV={csv_nominal.iloc[i]:.6f}, diff={abs(manual_nominal.iloc[i]-csv_nominal.iloc[i]):.2e}")
np.testing.assert_allclose(manual_nominal, csv_nominal, rtol=1e-6)
print("\n✓ Manual reconstruction matches CSV to < 1e-6 relative tolerance")
```
---
## 8. 第四步:14场汇总到Figure 3的一个点
### 8.1 inverse-variance 加权
Figure 3 需要把14个视场汇成一个 all-field estimator。采用与实测 CGMsum 相同的 **inverse-variance weighting**(以观测统计误差为权重):
$$S_{\mathrm{all}} = \frac{\sum_i w_i S_i}{\sum_i w_i}, \quad w_i = \frac{1}{\sigma_{\mathrm{stat},i}^2}$$
### 8.2 计算与验证
```{python}
#| label: aggregate
# Inverse-variance weighted average using measurement statistical errors
flux = halosat["nominal_absorbed_0p5_2p0_fluxunit"].to_numpy()
sigma = halosat["measurement_staterr_absorbed_0p5_2p0_fluxunit"].to_numpy()
weights = 1.0 / sigma**2
all_field = np.average(flux, weights=weights)
# Footprint range (min/max across 14 fields)
footprint_lo = flux.min()
footprint_hi = flux.max()
# Patchiness predictor (inverse-variance weighted mean, same weights as all-field)
patchiness = np.average(halosat["patchiness_sigma_absorbed_0p5_2p0_fluxunit"].to_numpy(), weights=weights)
# No-cross-term structural check
flux_nc = halosat["no_cross_term_absorbed_0p5_2p0_fluxunit"].to_numpy()
all_field_nc = np.average(flux_nc, weights=weights)
print(f"All-field inverse-variance prediction: {all_field:.6f}")
print(f"14-field footprint range: [{footprint_lo:.6f}, {footprint_hi:.6f}]")
print(f"Patchiness predictor (mean sigma): {patchiness:.6f}")
print(f"No-cross-term structural check: {all_field_nc:.6f}")
```
### 8.3 与冻结的 Figure 3 ledger 对账
```{python}
#| label: ledger-check
# Frozen Figure 3 ledger values from I023 / B014
LEDGER_ALL_FIELD = 0.565386
LEDGER_FOOTPRINT_LO = 0.526442
LEDGER_FOOTPRINT_HI = 0.577710
LEDGER_PATCHINESS = 0.165165
LEDGER_NO_CROSS = 0.438683
np.testing.assert_allclose(all_field, LEDGER_ALL_FIELD, rtol=1e-4)
np.testing.assert_allclose(footprint_lo, LEDGER_FOOTPRINT_LO, rtol=1e-4)
np.testing.assert_allclose(footprint_hi, LEDGER_FOOTPRINT_HI, rtol=1e-4)
np.testing.assert_allclose(patchiness, LEDGER_PATCHINESS, rtol=1e-4)
np.testing.assert_allclose(all_field_nc, LEDGER_NO_CROSS, rtol=1e-4)
print("✓ Matches frozen Figure 3 ledger to < 1e-4")
```
---
## 9. 可视化:14场分布与Figure 3位置
```{python}
#| label: field-distribution
#| fig-cap: "14 M31 fields' nominal absorbed brightness. Orange line = all-field inverse-variance prediction (0.565). Grey band = footprint range. Red dashed = observed CGMsum total (0.965). Patchiness sigma shown as error bars."
#| fig-width: 9
#| fig-height: 5
fig, ax = plt.subplots(figsize=(9, 5))
# Plot each field with patchiness error bars
field_idx = np.arange(len(halosat))
ax.errorbar(
field_idx, flux,
yerr=halosat["patchiness_sigma_absorbed_0p5_2p0_fluxunit"],
fmt="o", color="#2676b8", markersize=6, capsize=3, linewidth=0.8,
label="14 M31 fields (patchiness sigma)"
)
# All-field prediction
ax.axhline(all_field, color="#d47a2c", linewidth=2, label=f"All-field prediction = {all_field:.3f}")
ax.axhspan(footprint_lo, footprint_hi, color="#d47a2c", alpha=0.12, label=f"Footprint [{footprint_lo:.3f}, {footprint_hi:.3f}]")
# Observed CGMsum total for reference
ax.axhline(0.964727, color="#b74842", linestyle="--", linewidth=1.5, label="Observed CGMsum = 0.965 (M31)")
ax.set_xlabel("Field index (0-13)")
ax.set_ylabel("Absorbed 0.5-2.0 keV brightness (10^-15 erg s^-1 cm^-2 arcmin^-2)")
ax.set_title("HaloSat extrapolation: 14 M31 fields")
ax.set_xticks(field_idx)
ax.legend(fontsize=9, loc="upper right")
plt.show()
```
**关键观察**:
- 14场的 nominal 预测集中在约0.53–0.58,变化不大——因为14场在天空上很接近,模型给出的EM很相似。
- All-field 预测 0.565 明显**低于**实测 CGMsum 总量 0.965——这说明 HaloSat 域外外推只解释了M31方向总量的一部分,剩余部分来自M31自身的CGM。
- Patchiness sigma(蓝色误差棒)约0.16,反映模型承认的南天场间不均匀性。
---
## 10. 关键假设清单(必须记住!)
| 步骤 | 假设 | 来源 |
|------|------|------|
| 拟合域 | 73个 $b<-30^\circ$ 南天场 | Kaaret+2020 catalog |
| EM convention | $\int(n_d+n_h)^2 ds$,保留交叉项 | catalog $\chi^2$ closure |
| $n$ 的粒子身份 | 未定义(不乘 $n_e/n_H=1.2$) | 论文未说明 |
| 温度 | $kT=0.225$ keV(sample median) | 额外补充,模型不预测M31方向温度 |
| 丰度 | $Z=0.3$ | 项目统一选择 |
| 吸收 | TBabs + Wilms + Verner + field-specific HI4PI | 项目实现 |
| 积分路径 | $R_\odot=8.0$ kpc,0–260 kpc 内部LOS | 项目实现 |
| M31位置 | $b\approx-21.6^\circ$,全部14场在拟合域外 | 域外外推 |
| 证据等级 | supplemental extrapolation,非直接预测 | 本项目定位 |
---
## 11. 动手练习
### 练习 1:验证交叉项的贡献
计算 nominal EM 与 no-cross-term EM 的比值,量化交叉项对发射量度的贡献比例。
```{python}
#| label: ex1
#| echo: false
# Solution (hidden by default — expand to see)
ratio = halosat["nominal_em_cm-6_pc"] / halosat["no_cross_term_em_cm-6_pc"]
print(f"Cross-term ratio (nominal / no-cross):")
print(f" mean = {ratio.mean():.4f}")
print(f" std = {ratio.std():.4f}")
print(f"\nThe cross term contributes ~{(ratio.mean()-1)*100:.1f}% of the EM.")
print("Removing it would drop the absorbed flux to ~0.439 (structural check).")
```
### 练习 2:N_H 与吸收的关系
计算14场的 $N_\mathrm{H}$ 与 flux-per-EM 之间的 Pearson 相关系数。这个相关性是正还是负?为什么?
```{python}
#| label: ex2
#| echo: false
r = np.corrcoef(halosat["nh_hi4pi_1e22_cm-2"], halosat["absorbed_0p5_2p0_flux_per_em_fluxunit"])[0, 1]
print(f"Pearson r = {r:.4f}")
print("Negative — higher N_H means more absorption, so each unit of EM produces less absorbed flux.")
```
### 练习 3:patchiness 的物理含义
Patchiness sigma 是什么?它和 measurement statistical error 有什么区别?
```{python}
#| label: ex3
#| echo: false
print("Patchiness sigma (mean):", halosat["patchiness_sigma_absorbed_0p5_2p0_fluxunit"].mean())
print("Measurement staterr (mean):", halosat["measurement_staterr_absorbed_0p5_2p0_fluxunit"].mean())
print()
print("Patchiness sigma reflects real spatial scatter in the southern HaloSat fields")
print("(the model explicitly requires sigma_p ≈ 3.4e-3 cm^-6 pc of EM scatter).")
print("Measurement staterr is the observational uncertainty of each M31 field.")
print("They are independent sources of spread: one is model-intrinsic, one is instrumental.")
```
---
## 12. 总结:从HaloSat拟合到Figure 3的完整链路
```{mermaid}
graph TD
A["HaloSat 73 southern fields<br/>b < -30 deg, EM + kT fitted"] --> B["Empirical disk<br/>n0=0.0081, z0=1.60 kpc"]
A --> C["Adiabatic halo<br/>Fang+2013 in NFW potential"]
B --> D["EM = ∫(n_d+n_h)² ds<br/>cross term retained, no n_e/n_H factor"]
C --> D
D --> E["Project to 14 M31 sightlines<br/>R_sun=8.0 kpc, 0-260 kpc, |z|"]
E --> F["Augment kT=0.225 keV<br/>(model has no M31-direction T)"]
F --> G["APEC(Z=0.3) × TBabs × HI4PI<br/>→ absorbed 0.5-2.0 keV"]
G --> H["14-field inverse-variance average<br/>= 0.565 (Figure 3 point)"]
H --> I["Supplemental extrapolation<br/>NOT a direct HaloSat prediction"]
```
**一句话总结**:Kaaret et al. (2020) 用 HaloSat 的73个南天场拟合出银河系热气体的 empirical disk + adiabatic halo 三维模型。这个模型沿M31方向14条视线外推积分,补充 sample-median 温度后转换为 absorbed 0.5–2.0 keV brightness,得到 all-field 预测 0.565——这是一个 **supplemental domain extrapolation**,不是 HaloSat 对M31方向的直接预测,因为M31完全在拟合域外。
---
## 参考资料
1. Kaaret, P. et al. (2020). "A disk-dominated and clumpy circumgalactic medium of the Milky Way seen in X-ray emission." *Nature Astronomy*, 4, 1072–1077. [doi:10.1038/s41550-020-01215-w](https://doi.org/10.1038/s41550-020-01215-w)
2. CDS catalog J/other/NatAs/4.1072 — [VizieR link](https://vizier.cds.unistra.fr/viz-bin/VizieR?-source=J/other/NatAs/4.1072)
3. Fang, T. et al. (2013). Adiabatic polytropic CGM model in NFW potential.
4. HI4PI Collaboration (2016). "HI4PI: A full-sky H I line survey." *A&A*, 594, A116.
5. Bluem, J. et al. (2022). "HaloSat's 156-field two-temperature catalog." *ApJ*.
---
> **教程结束** 🎓
> 下一步:继续阅读 Ueda+2022 tutorial,了解 in-domain disk model 如何在M31 footprint内直接投影。