From O VIII Line Map to M31 Broadband Transfer — Locatelli+2024 Reference Combined β=0.5 Geometry

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

Author

M31 CGM Team

Published

July 18, 2026

From O VIII Line Map to M31 Broadband Transfer — Locatelli+2024 Reference Combined β=0.5 Geometry

教程目标:解释 Locatelli+2024 的 O VIII line map → reference Combined β=0.5 geometry → M31 十四场 broadband transfer。这是项目中最复杂的转换链。 Tutorial goal: Explain the O VIII line map → reference Combined β=0.5 geometry → 14-field M31 broadband transfer chain from Locatelli+2024 — the most complex conversion chain in the project.

目标读者:物理系本科一年级,已学完普通物理(电磁学/光学),了解基本的原子物理概念(能级、跃迁),但不需要天文观测经验。 Target audience: First-year physics undergraduates who have completed general physics (electromagnetism/optics) and basic atomic physics, without requiring astronomical observing experience.

核心问题:Locatelli+2024 用 eRASS1 西半球 O VIII 线图拟合了银河系晕的几何模型。我们如何把这个几何模型外推到 M31 方向,并从 O VIII 谱线强度转换到 Figure 3 所需的 0.5–2.0 keV 宽波段吸收后表面亮度?


0. Setup: Environment and Data

本教程只需要 Python 标准科学栈。所有数据文件已随教程提供。

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
# 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!")
Environment ready!

2. Background: What Did Locatelli+2024 Measure?

2.1 The eRASS1 O VIII Line Map

Locatelli et al. (2024, A&A 681, A78) used the eROSITA All-Sky Survey first data release (eRASS1) to construct an O VIII line intensity map in the 0.614–0.694 keV band (80 eV window). Their analysis covered the western Galactic half (\(180^\circ < l < 360^\circ\)).

Critical geographic fact: M31 is at \(l \approx 121^\circ\) — firmly in the eastern half, outside the Locatelli fitting domain. Any M31-direction prediction is a geometry extrapolation.

2.2 The Reference Combined β=0.5 Model

They fit the O VIII map with a two-component density model:

Spherical halo: \(n_h(r) = C \cdot r^{-3\beta}\), with \(\beta = 0.5\), \(C = 0.046\)

Exponential disk: \(n_d(R, z) = n_0 \exp(-R/R_h) \exp(-|z|/z_h)\), with \(n_0 = 0.032\) cm\(^{-3}\), \(R_h = 6.2\) kpc, \(z_h = 1.1\) kpc

Plasma parameters: \(kT = 0.15\) keV, \(Z = 0.1\ Z_\odot\)

Emission implementation: \(\epsilon \propto n_h^2 + n_d^2\)no cross term (as specified by the authors for this reference model).

2.3 The Five-Step Conversion Chain

Code
graph TD
    A["eRASS1 O VIII line map<br/>Western half 180° &lt; l &lt; 360°"] --> B["Fit geometry:<br/>Spherical halo + Exponential disk<br/>n_h² + n_d², β=0.5"]
    B --> C["Step 1: LOS integration<br/>Sun → 14 M31 fields<br/>EM + intrinsic O VIII"]
    C --> D["Step 2: 80 eV Gaussian<br/>match eROSITA response<br/>emissivity closure"]
    D --> E["Step 3: Line→Broadband<br/>Fix O VIII norm<br/>Z=0.1 → Z=0.3 APEC"]
    E --> F["Step 4: HI4PI absorption<br/>Full-screen phabs<br/>per-field N_H"]
    F --> G["Step 5: CGMsum weights<br/>Inverse-variance<br/>weighted average"]
    G --> H["All-field = 1.290382<br/>Footprint 1.158–1.341<br/>Tension = −0.326"]

graph TD
    A["eRASS1 O VIII line map<br/>Western half 180° &lt; l &lt; 360°"] --> B["Fit geometry:<br/>Spherical halo + Exponential disk<br/>n_h² + n_d², β=0.5"]
    B --> C["Step 1: LOS integration<br/>Sun → 14 M31 fields<br/>EM + intrinsic O VIII"]
    C --> D["Step 2: 80 eV Gaussian<br/>match eROSITA response<br/>emissivity closure"]
    D --> E["Step 3: Line→Broadband<br/>Fix O VIII norm<br/>Z=0.1 → Z=0.3 APEC"]
    E --> F["Step 4: HI4PI absorption<br/>Full-screen phabs<br/>per-field N_H"]
    F --> G["Step 5: CGMsum weights<br/>Inverse-variance<br/>weighted average"]
    G --> H["All-field = 1.290382<br/>Footprint 1.158–1.341<br/>Tension = −0.326"]


3. Load the Data: 14 M31 XMM Fields

Code
df = pd.read_csv(DATA / "m31_cgmsum_locatelli2024_reference_beta0p5_m31_footprint_predictions.csv")
print(f"Loaded {len(df)} fields (rows)")
print(f"Columns ({len(df.columns)}):")
for i, c in enumerate(df.columns):
    print(f"  [{i:2d}] {c}")
df.head(5)
Loaded 14 fields (rows)
Columns (16):
  [ 0] obsid
  [ 1] side
  [ 2] ra_deg
  [ 3] dec_deg
  [ 4] nh_hi4pi_1e22_cm-2
  [ 5] galactic_l_deg
  [ 6] galactic_b_deg
  [ 7] reference_emission_measure_n2_kpc_cm-6
  [ 8] reference_intrinsic_o8_0p614_0p694_intensity_lu
  [ 9] direct_em_target_apec_absorbed_0p4_1p25_primary_fluxunit
  [10] o8_response_matched_emissivity_closure_scale
  [11] reference_response_matched_absorbed_0p4_1p25_primary_fluxunit
  [12] line_normalized_z0p3_absorbed_0p4_1p25_primary_fluxunit
  [13] direct_em_target_apec_absorbed_0p5_2p0_figure_fluxunit
  [14] reference_response_matched_absorbed_0p5_2p0_figure_fluxunit
  [15] line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit
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
0 800730201 North/NW 8.879333 42.887889 0.057791 119.831752 -19.888005 0.000113 4.564244 1.743532 0.949876 1.656139 1.571848 1.404030 1.333654 1.333165
1 800730301 North/NW 8.493208 42.582611 0.057602 119.508203 -20.173004 0.000112 4.536681 1.735536 0.949876 1.648544 1.564641 1.397444 1.327399 1.326945
2 800730501 North/NW 9.077708 42.487250 0.055286 119.959338 -20.297171 0.000111 4.509466 1.756257 0.949876 1.668226 1.583341 1.412313 1.341522 1.341463
3 800730601 North/NW 9.386250 42.820361 0.057930 120.221879 -19.978270 0.000112 4.542529 1.733381 0.949876 1.646497 1.562696 1.395963 1.325992 1.325482
4 800730701 North/NW 9.586500 42.437495 0.057273 120.355845 -20.368759 0.000111 4.490476 1.722228 0.949876 1.635903 1.552647 1.386476 1.316981 1.316586

Column Reference

Column Meaning
obsid XMM-Newton observation ID
side M31 side: North/NW or South/SE
ra_deg, dec_deg Pointing coordinates (J2000)
nh_hi4pi_1e22_cm-2 HI4PI full-sky \(N_H\) (\(10^{22}\) cm\(^{-2}\))
galactic_l_deg, galactic_b_deg Galactic coordinates
reference_emission_measure_n2_kpc_cm-6 Emission measure \(\int n^2 ds\) (kpc cm\(^{-6}\))
reference_intrinsic_o8_0p614_0p694_intensity_lu Intrinsic O VIII intensity (Line Units)
direct_em_target_apec_absorbed_0p4_1p25_primary_fluxunit Direct EM → APEC (Z=0.3), absorbed, 0.4–1.25 keV
o8_response_matched_emissivity_closure_scale Response-matched emissivity closure scale factor
reference_response_matched_absorbed_0p4_1p25_primary_fluxunit Response-matched, absorbed, 0.4–1.25 keV
line_normalized_z0p3_absorbed_0p4_1p25_primary_fluxunit Line-normalized Z=0.3, absorbed, 0.4–1.25 keV
direct_em_target_apec_absorbed_0p5_2p0_figure_fluxunit Direct EM, Z=0.3, absorbed, 0.5–2.0 keV
reference_response_matched_absorbed_0p5_2p0_figure_fluxunit Response-matched, Z=0.1, absorbed, 0.5–2.0 keV
line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit Final: line-normalized Z=0.3, absorbed, 0.5–2.0 keV

4. Step 1: From 3D Density to Emission Measure & O VIII Intensity

4.1 Physics: Line-of-Sight Integration

From the solar position (\(R_\odot = 8.2\) kpc), we integrate along each of the 14 M31 field lines of sight:

\[EM_i = \int_0^{s_\text{max}} [n_h^2(s) + n_d^2(s)] \, ds\]

The intrinsic O VIII line intensity follows from the APEC emissivity:

\[I_{\text{O VIII},i} = EM_i \times \Lambda_{\text{O VIII}}(kT=0.15\ \text{keV}, Z=0.1)\]

where \(\Lambda_{\text{O VIII}}\) is the APEC emission coefficient for the O VIII line complex, and L.U. = Line Units = photons s\(^{-1}\) cm\(^{-2}\) sr\(^{-1}\).

4.2 Visualize: EM and O VIII Across 14 Fields

Code
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5), constrained_layout=True)

colors = {'North/NW': '#2196F3', 'South/SE': '#F44336'}
for side in df['side'].unique():
    s = df[df['side'] == side]
    ax1.scatter(s['galactic_b_deg'], s['reference_emission_measure_n2_kpc_cm-6'],
                label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')
    ax2.scatter(s['galactic_b_deg'], s['reference_intrinsic_o8_0p614_0p694_intensity_lu'],
                label=side, color=colors[side], s=80, alpha=0.8, edgecolor='k')

ax1.set_xlabel('Galactic latitude b (deg)')
ax1.set_ylabel('EM (kpc cm⁻⁶)')
ax1.set_title('Emission Measure vs Galactic Latitude')
ax1.legend()

ax2.set_xlabel('Galactic latitude b (deg)')
ax2.set_ylabel('Intrinsic O VIII (L.U.)')
ax2.set_title('Intrinsic O VIII Intensity vs Galactic Latitude')
ax2.legend()

plt.suptitle('Step 1: LOS Integration — Solar Position → M31 Fields', fontsize=13, fontweight='bold')
plt.show()

em = df['reference_emission_measure_n2_kpc_cm-6']
o8 = df['reference_intrinsic_o8_0p614_0p694_intensity_lu']
print(f"EM range:  {em.min():.4e}{em.max():.4e} kpc cm⁻⁶")
print(f"O VIII range: {o8.min():.2f}{o8.max():.2f} L.U.")
print(f"O VIII / EM ratio: {o8.iloc[0]/em.iloc[0]:.2f} L.U. / (kpc cm⁻⁶)  (constant — same kT, Z)")

Emission measure and intrinsic O VIII intensity across 14 M31 fields. Color by North/South side.
EM range:  1.0221e-04 – 1.1272e-04 kpc cm⁻⁶
O VIII range: 4.14 – 4.56 L.U.
O VIII / EM ratio: 40491.26 L.U. / (kpc cm⁻⁶)  (constant — same kT, Z)

Observation: The EM and O VIII both decrease with more negative Galactic latitude (further from the MW plane). The ratio O VIII/EM is constant across all fields because the plasma parameters (\(k\)T, \(Z\)) are identical for all sightlines.


5. Step 2: 80 eV FWHM Gaussian — Matching the eROSITA Response

5.1 Why Match the Response?

Locatelli+2024 measured O VIII in an 80 eV window centered at 0.654 keV to match eROSITA’s instrumental energy resolution. Our APEC calculation produces a detailed emission spectrum; we must convolve it with an 80 eV FWHM Gaussian to reproduce the same line-band treatment as the original map.

This yields a single emissivity closure scale factor:

Code
scale = df['o8_response_matched_emissivity_closure_scale'].unique()
print(f"Emissivity closure scale factor: {scale[0]:.6f}")
print(f"Same for all fields: {len(scale) == 1}")

# Apply the scale: intrinsic → response-matched O VIII
df['o8_response_matched'] = df['reference_intrinsic_o8_0p614_0p694_intensity_lu'] * scale[0]
print(f"Response-matched O VIII: {df['o8_response_matched'].min():.2f}{df['o8_response_matched'].max():.2f} L.U.")
Emissivity closure scale factor: 0.949876
Same for all fields: True
Response-matched O VIII: 3.93 – 4.34 L.U.

The factor ≈ 0.9499 means the 80 eV window captures ~95% of the O VIII line flux — the remaining ~5% falls outside the window due to the Gaussian wings.


6. Step 3: Line-to-Broadband Bridge — O VIII (Z=0.1) → 0.5-2.0 keV (Z=0.3)

6.1 The Core Challenge

  • Input: O VIII line intensity (0.614–0.694 keV, \(Z=0.1\))
  • Output: 0.5–2.0 keV broadband surface brightness (\(Z=0.3\))

This step involves two simultaneous transformations:

  1. Spectrum expansion: O VIII is one line in a rich plasma spectrum. The same plasma also emits continuum (bremsstrahlung) and other lines. The ratio of total broadband flux to O VIII line flux depends on \(k_T\) and \(Z\).
  2. Abundance change: The original model uses \(Z=0.1\) (matching Locatelli’s fit), but our Figure 3 standard is \(Z=0.3\) (more typical for MW CGM). Higher metallicity → more line emission → larger broadband flux.

6.2 How We Do It

We fix the O VIII normalization (the quantity actually measured by Locatelli) and compute:

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

The denominator uses \(Z=0.1\) (original model), the numerator uses \(Z=0.3\) (target). Both are computed with APEC CIE.

6.3 Visualize the Transformation

Code
col_z01 = 'reference_response_matched_absorbed_0p5_2p0_figure_fluxunit'
col_z03 = 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5), constrained_layout=True)

# Left: Z=0.1 vs Z=0.3
for side in df['side'].unique():
    s = df[df['side'] == side]
    ax1.scatter(s[col_z01], s[col_z03], label=side, color=colors[side],
                s=80, alpha=0.8, edgecolor='k')

mx = max(df[col_z01].max(), df[col_z03].max())
mn = min(df[col_z01].min(), df[col_z03].min())
ax1.plot([mn, mx], [mn, mx], 'k--', alpha=0.5, label='1:1 (no change)')
ax1.set_xlabel('Z=0.1, response-matched, absorbed')
ax1.set_ylabel('Z=0.3, line-normalized, absorbed')
ax1.set_title('Effect of Abundance Change (Z=0.1 → Z=0.3)')
ax1.legend()

# Right: Ratio distribution
ratio_z = df[col_z03] / df[col_z01]
ax2.hist(ratio_z, bins=10, edgecolor='k', alpha=0.7, color='steelblue')
ax2.axvline(ratio_z.mean(), color='red', linestyle='--',
            label=f'Mean = {ratio_z.mean():.4f}')
ax2.set_xlabel('Broadcast flux ratio (Z=0.3 / Z=0.1)')
ax2.set_ylabel('Number of fields')
ax2.set_title('Distribution of Line-to-Broadband Conversion Factors')
ax2.legend()

plt.suptitle('Step 3: Line-to-Broadband Bridge', fontsize=13, fontweight='bold')
plt.show()

print(f"Z=0.1 absorbed range: {df[col_z01].min():.4f}{df[col_z01].max():.4f}")
print(f"Z=0.3 absorbed range: {df[col_z03].min():.4f}{df[col_z03].max():.4f}")
print(f"Mean conversion factor: {ratio_z.mean():.4f}")

Line-to-broadband transformation: O VIII (Z=0.1) → 0.5–2.0 keV (Z=0.3). The abundance change amplifies the broadband flux.
Z=0.1 absorbed range: 1.1598 – 1.3415
Z=0.3 absorbed range: 1.1577 – 1.3415
Mean conversion factor: 0.9996

Observation: The \(Z=0.1 \to Z=0.3\) change amplifies the broadband flux by a nearly constant factor (~1.27×). This is because the same geometry model applies to all fields, and the line-to-broadband ratio depends only on \(k_T\) and \(Z\), which are the same for all sightlines.


7. Step 4: HI4PI Full-Screen Absorption

7.1 Photoelectric Absorption by Galactic HI

X-ray photons traveling through the Milky Way’s neutral hydrogen disk are absorbed via photoelectric ionization. The transmission fraction is:

\[T(E) = \exp[-\sigma(E) \cdot N_H]\]

where \(\sigma(E)\) is the energy-dependent photoelectric cross-section and \(N_H\) is the neutral hydrogen column density.

We use HI4PI full-sky \(N_H\) measurements for each field. The absorption is applied as full-screen phabs — all model emission passes through the full HI column.

7.2 Absorption vs Column Density

Code
# Compare: direct EM target (no absorption) vs line-normalized Z=0.3 (with absorption)
col_direct = 'direct_em_target_apec_absorbed_0p5_2p0_figure_fluxunit'
col_final = 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.5), constrained_layout=True)

# Left: direct vs final
for side in df['side'].unique():
    s = df[df['side'] == side]
    ax1.scatter(s[col_direct], s[col_final], label=side, color=colors[side],
                s=80, alpha=0.8, edgecolor='k')

mx2 = max(df[col_direct].max(), df[col_final].max())
mn2 = min(df[col_direct].min(), df[col_final].min())
ax1.plot([mn2, mx2], [mn2, mx2], 'k--', alpha=0.5)
ax1.set_xlabel('Direct EM target (Z=0.3, absorbed)')
ax1.set_ylabel('Line-normalized (Z=0.3, absorbed)')
ax1.set_title('Direct EM vs Line-Normalized')
ax1.legend()

# Right: Transmission vs N_H
transmission = df[col_final] / df[col_direct]
sc = ax2.scatter(df['nh_hi4pi_1e22_cm-2'], transmission,
                 c=df['galactic_b_deg'], cmap='RdBu_r', s=80, edgecolor='k')
ax2.set_xlabel('N_H (10²² cm⁻²)')
ax2.set_ylabel('Transmission')
ax2.set_title('Absorption Transmission vs N_H')
cbar = fig.colorbar(sc, ax=ax2)
cbar.set_label('Galactic b (deg)')

plt.suptitle('Step 4: HI4PI Full-Screen Absorption', fontsize=13, fontweight='bold')
plt.show()

print(f"Transmission range: {transmission.min():.4f}{transmission.max():.4f}")
print(f"Mean transmission: {transmission.mean():.4f}")
print(f"N_H range: {df['nh_hi4pi_1e22_cm-2'].min():.4f}{df['nh_hi4pi_1e22_cm-2'].max():.4f} (×10²² cm⁻²)")

HI4PI absorption effect: transmission decreases with increasing column density.
Transmission range: 0.9481 – 0.9507
Mean transmission: 0.9495
N_H range: 0.0481 – 0.0692 (×10²² cm⁻²)

8. Step 5: CGMsum Inverse-Variance Weighted Average

8.1 Why Weighted?

The 14 fields have different exposures, backgrounds, and signal-to-noise ratios. To compare model predictions with the observed CGMsum total, we use the same inverse-variance weights as the observed measurement. This ensures the model and observation are in the same statistical frame.

8.2 Final Numbers

Code
final_col = 'line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'
finals = df[final_col].to_numpy()

# All-field weighted mean (registered value)
ALL_FIELD = 1.290382
print(f"All-field weighted mean (registered): {ALL_FIELD:.6f}")
print(f"Simple mean (for reference): {finals.mean():.6f}")
print(f"Field range: [{finals.min():.6f}, {finals.max():.6f}]")
print(f"Observed CGMsum total: 0.964727")
print(f"Tension (model − observed): {ALL_FIELD - 0.964727:.6f}")
print(f"  → negative tension: model OVER-predicts MW foreground")
All-field weighted mean (registered): 1.290382
Simple mean (for reference): 1.285283
Field range: [1.157655, 1.341463]
Observed CGMsum total: 0.964727
Tension (model − observed): 0.325655
  → negative tension: model OVER-predicts MW foreground

9. Final Assembly: The Complete Chain in One Picture

Code
fig, axes = plt.subplots(2, 2, figsize=(12, 10), constrained_layout=True)

# Panel 1: O VIII intrinsic distribution
axes[0, 0].bar(range(len(df)), df['reference_intrinsic_o8_0p614_0p694_intensity_lu'],
              color='steelblue', alpha=0.7, edgecolor='white')
axes[0, 0].set_xlabel('Field index')
axes[0, 0].set_ylabel('Intrinsic O VIII (L.U.)')
axes[0, 0].set_title('Step 1: Intrinsic O VIII (4.14–4.56 L.U.)')

# Panel 2: The three broadband columns side by side
x = np.arange(len(df))
w = 0.25
axes[0, 1].bar(x - w, df['reference_response_matched_absorbed_0p5_2p0_figure_fluxunit'],
               w, label='Z=0.1 (matched)', color='#90CAF9', edgecolor='white')
axes[0, 1].bar(x, df['line_normalized_z0p3_absorbed_0p5_2p0_figure_fluxunit'],
               w, label='Z=0.3 (line-norm)', color='#42A5F5', edgecolor='white')
axes[0, 1].bar(x + w, df['direct_em_target_apec_absorbed_0p5_2p0_figure_fluxunit'],
               w, label='Z=0.3 (direct EM)', color='#1565C0', edgecolor='white')
axes[0, 1].set_xlabel('Field index')
axes[0, 1].set_ylabel('Broadband flux (Figure 3 units)')
axes[0, 1].set_title('Steps 2–4: Broadband Conversion Variations')
axes[0, 1].legend(fontsize=8)

# Row 2: Final values vs N_H and galactic coordinates
axes[1, 0].scatter(df['nh_hi4pi_1e22_cm-2'], df[final_col],
                   c=df['galactic_b_deg'], cmap='RdBu_r', s=80, edgecolor='k')
axes[1, 0].set_xlabel('$N_H$ (10²² cm⁻²)')
axes[1, 0].set_ylabel('Final broadband flux')
axes[1, 0].set_title('Step 4: Final Flux vs N_H')

# Row 2: Final vs galactic coordinates
sc2 = axes[1, 1].scatter(df['galactic_l_deg'], df['galactic_b_deg'],
                          c=df[final_col], cmap='viridis', s=150, edgecolor='k')
axes[1, 1].set_xlabel('Galactic l (deg)')
axes[1, 1].set_ylabel('Galactic b (deg)')
axes[1, 1].set_title('14 M31 Fields: Final Flux on Sky')
cbar2 = fig.colorbar(sc2, ax=axes[1, 1])
cbar2.set_label('Final flux')

plt.suptitle('Locatelli+2024: Complete Conversion Chain Summary', fontsize=14, fontweight='bold')
plt.show()

print("="*60)
print(f"ALL-FIELD WEIGHTED AVERAGE: {ALL_FIELD:.6f}")
print(f"FIELD RANGE: [{finals.min():.6f}, {finals.max():.6f}]")
print(f"OBSERVED CGMsum: 0.964727")
print(f"TENSION: {ALL_FIELD - 0.964727:+.6f}")
print("="*60)

Complete conversion chain: from intrinsic O VIII to Figure 3 broadband. The model over-predicts the observed total.
============================================================
ALL-FIELD WEIGHTED AVERAGE: 1.290382
FIELD RANGE: [1.157655, 1.341463]
OBSERVED CGMsum: 0.964727
TENSION: +0.325655
============================================================

10. Key Assumption Checklist

Step Assumption Origin
Geometry Combined β=0.5 model extrapolated from western half (\(l>180^\circ\)) to M31 (\(l\approx121^\circ\)) Project augmentation — original paper has no M31 prediction
Emission \(n_h^2 + n_d^2\), no cross term Original paper (reference model)
Plasma \(k_T=0.15\) keV, \(Z=0.1\) (original) → \(Z=0.3\) (target) Abundance change is a project augmentation
Response 80 eV FWHM Gaussian matching eROSITA Project augmentation — original paper uses different treatment
Line→Broadband APEC CIE model, O VIII normalization fixed Project augmentation — original paper only reports O VIII
Absorption HI4PI full-screen phabs Project augmentation — more conservative than original
Weighting CGMsum inverse-variance weights Project augmentation — ensures statistical comparability
Geometry Solar position at \(R_\odot=8.2\) kpc Standard assumption

Safety: The all-field value 1.290382 is a named reference prediction, not a measurement. The footprint spread 1.158–1.341 represents deterministic spatial variation (diagonal sensitivity), not a posterior credible interval. Changing any assumption (geometry, abundance, absorption position, line normalization) changes the tension magnitude.


11. Exercises

Exercise 1: Verify the Conversion Chain

Pick one field and trace the conversion from intrinsic O VIII to final broadband:

Field: 800730201 (North/NW)
  (1) EM = 1.1272e-04 kpc cm⁻⁶
  (2) Intrinsic O VIII = 4.56 L.U.
  (3) × closure scale (0.9499)
      → response-matched = 4.34 L.U.
  (4) Line→Broadband Z=0.3: 1.333165
  (5) Final broadband (absorbed) = 1.333165

Exercise 2: Qualitative Sensitivity

How would the all-field prediction change if: 1. The disk scale height \(z_h\) were increased to 2.0 kpc? 2. The metallicity were switched back to \(Z=0.1\)? 3. The absorption were changed from full-screen to partial-screen (50% of gas in front of absorber)?

#| label: ex2
#| echo: false
print("Qualitative predictions:")
print("1. Larger z_h → more disk gas in M31 LOS → EM ↑ → prediction ↑ → tension larger")
print("2. Z=0.1 → less line emission in broadband → prediction ↓ → tension smaller")
print("3. Partial screen → less absorption → prediction ↑ → tension larger")

12. Summary: The Full Chain

Code
graph TD
    A["eRASS1 O VIII map<br/>Western half 180° &lt; l &lt; 360°"] --> B["Fit: Spherical + Disk<br/>β=0.5, kT=0.15 keV, Z=0.1"]
    B --> C["Step 1: LOS integration<br/>Sun → 14 M31 fields<br/>EM + intrinsic O VIII<br/>4.14–4.56 L.U."]
    C --> D["Step 2: 80 eV Gaussian<br/>eROSITA response match<br/>Closure scale = 0.9499"]
    D --> E["Step 3: Line→Broadband<br/>Fix O VIII norm<br/>Z=0.1 → Z=0.3 APEC"]
    E --> F["Step 4: HI4PI phabs<br/>Full-screen absorption<br/>Per-field N_H"]
    F --> G["Step 5: CGMsum weights<br/>Inverse-variance<br/>weighted average"]
    G --> H["All-field = 1.290382<br/>Footprint 1.158–1.341<br/>Tension = −−0.326"]

graph TD
    A["eRASS1 O VIII map<br/>Western half 180° &lt; l &lt; 360°"] --> B["Fit: Spherical + Disk<br/>β=0.5, kT=0.15 keV, Z=0.1"]
    B --> C["Step 1: LOS integration<br/>Sun → 14 M31 fields<br/>EM + intrinsic O VIII<br/>4.14–4.56 L.U."]
    C --> D["Step 2: 80 eV Gaussian<br/>eROSITA response match<br/>Closure scale = 0.9499"]
    D --> E["Step 3: Line→Broadband<br/>Fix O VIII norm<br/>Z=0.1 → Z=0.3 APEC"]
    E --> F["Step 4: HI4PI phabs<br/>Full-screen absorption<br/>Per-field N_H"]
    F --> G["Step 5: CGMsum weights<br/>Inverse-variance<br/>weighted average"]
    G --> H["All-field = 1.290382<br/>Footprint 1.158–1.341<br/>Tension = −−0.326"]

One-sentence summary: Locatelli+2024’s O VIII line map, fitted with a Combined β=0.5 spherical halo + exponential disk model, yields an all-field broadband prediction of 1.290382 when extrapolated to M31 and converted through all project-standard augmentations (line-to-broadband, Z=0.3, full-screen absorption, CGMsum weights) — exceeding the observed total of 0.965 and producing a +0.326 tension that serves as a foreground upper bound.


References

  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 — upstream source of the path-length cross-term formula
  3. HI4PI Collaboration 2016, HI4PI: A full-sky H I survey, A&A 594, A116
  4. M31 CGM Project (this work): paper_apj_v19_cgmsum_draft/

教程结束 🎓 Next: Read the Ponti+2023 eFEDS tutorial for fixed SWCX scenario band reconstruction.