# Milky Way Globular Cluster Corpus

[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19907766.svg)](https://doi.org/10.5281/zenodo.19907766)
[![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/)
[![Version](https://img.shields.io/badge/version-v1.3.1-blue.svg)](https://github.com/eps-research/mw-globular-cluster-corpus/releases/tag/v1.3.1)
[![Clusters](https://img.shields.io/badge/clusters-174-green.svg)]()
[![Data Points](https://img.shields.io/badge/data%20points-17%2C438-green.svg)]()

**A multi-survey machine-readable database of fundamental parameters for 174 Milky Way globular clusters.**

Maintained by [David C. Flynn](https://www.eps-research.com) / EPS Research  
ORCID: [0000-0002-2768-6650](https://orcid.org/0000-0002-2768-6650)

---

## Overview

Each cluster record integrates four independent published surveys into a single consistently-typed JSON object:

| Block | Source | Clusters | Key parameters |
|-------|--------|----------|----------------|
| Harris (1996, 2010) | Harris catalog | 157/174 | Photometry, structure, metallicity, kinematics |
| `gaia_edr3` | Vasiliev & Baumgardt (2021) | 170/174 | Gaia EDR3 proper motions, parallax |
| `baumgardt2023` | Baumgardt et al. (2023) v4 | 154/174 | N-body mass, radii (pc), orbits, IMF slope |
| `apogee_dr17` | Schiavon et al. (2024) | 72/174 | APOGEE DR17 mean [Fe/H], RV, Jacobi radius |

**174 clusters · 17,438 non-null data points · CC BY 4.0**

---

## Files

| File | Size | Description |
|------|------|-------------|
| `harris_gc_corpus_v1.3.1.jsonl` | 622.6 KB | **Primary format** — one JSON object per line per cluster |
| `harris_gc_corpus_v1.3_flat.csv` | 64.2 KB | Flat table, 75 columns, all surveys |
| `harris_gc_corpus_v1.3.1.json` | 867.5 KB | Full nested JSON with metadata header |

---

## Quick Start

```python
import json

# Load all 174 clusters
clusters = [json.loads(line) for line in open("harris_gc_corpus_v1.3.1.jsonl")]

# Get a specific cluster
ngc104 = next(c for c in clusters if c["cluster_id"] == "NGC 104")

# All clusters with APOGEE chemistry
feh = {
    c["cluster_id"]: c["apogee_dr17"]["feh_apogee"]
    for c in clusters
    if c.get("apogee_dr17") and c["apogee_dr17"].get("feh_apogee") is not None
}

# All clusters with N-body dynamical masses
masses = {
    c["cluster_id"]: c["baumgardt2023"]["mass_msun"]
    for c in clusters
    if c.get("baumgardt2023") and c["baumgardt2023"].get("mass_msun") is not None
}

# Clusters on highly radial orbits (r_apo > 50 kpc)
outer_halo = [
    c["cluster_id"] for c in clusters
    if c.get("baumgardt2023") and
    c["baumgardt2023"].get("r_apo_kpc") is not None and
    c["baumgardt2023"]["r_apo_kpc"] > 50.0
]
```

---

## Schema

All numerics are native `float` or `int`. Missing values are JSON `null`. Errors are separate `_err` fields. Booleans are native JSON booleans.

```
cluster_id          str       Primary identifier (Harris 2010 convention)
alt_name            str|null  Common name (e.g. "47 Tuc", "ω Cen")
position            object    ra_hms, dec_dms, l_deg, b_deg
distances           object    r_sun_kpc, r_gc_kpc, x_kpc, y_kpc, z_kpc
metallicity         object    feh, feh_weight, ebv
photometry          object    v_hb, dist_mod, v_t, m_v_t, ellipticity, colors
kinematics          object    v_r_kms ± err, v_lsr_kms, sig_v_kms ± err
structure           object    king_concentration, core_collapsed (bool),
                              r_core_arcmin, r_half_arcmin, r_core_kpc, r_half_kpc
dynamics            object    log_t_rc_yr, log_t_rh_yr
flags               object    inner_galaxy (bool), sgr_stream (bool)
gaia_edr3           object    mu_alpha_mas_yr ± err, mu_delta_mas_yr ± err,
                              corr_mu, parallax_mas ± err, n_members_gaia
baumgardt2023       object    r_sun_kpc ± err, r_gc_kpc ± err,
                              rv_kms ± err, x/y/z_kpc ± err, u/v/w_kms ± err,
                              r_peri_kpc ± err, r_apo_kpc ± err,
                              mass_msun ± err, rc_pc, rhl_pc, rhm_pc, rt_pc,
                              sigma0_kms, v_esc_kms, mf_slope ± err,
                              eta_c, eta_h, a_rot_kms ± err, p_rot_pct
apogee_dr17         object    feh_apogee, rv_mean_kms ± err,
                              mass_1e4_msun, r_jacobi_deg, n_members
```

CSV column prefixes: `gaia_` = Vasiliev 2021 · `b_` = Baumgardt 2023 · `a_` = APOGEE DR17

---

## Example Record (abbreviated)

NGC 104 (47 Tuc) — full four-survey record:

```json
{
  "cluster_id": "NGC 104",
  "alt_name": "47 Tuc",
  "position": {"l_deg": 305.89, "b_deg": -44.89},
  "metallicity": {"feh": -0.72, "ebv": 0.04},
  "structure": {"king_concentration": 2.07, "core_collapsed": false,
                "r_core_kpc": 0.032, "r_half_kpc": 0.2821},
  "flags": {"inner_galaxy": false, "sgr_stream": false},
  "gaia_edr3": {"mu_alpha_mas_yr": 5.252, "mu_alpha_err": 0.021,
                "mu_delta_mas_yr": -2.551, "mu_delta_err": 0.021,
                "n_members_gaia": 39932},
  "baumgardt2023": {"mass_msun": 853000.0, "mass_err": 43000.0,
                    "rc_pc": 0.61, "sigma0_kms": 11.9,
                    "r_peri_kpc": 5.47, "r_apo_kpc": 7.51,
                    "mf_slope": -0.65, "p_rot_pct": 100.0},
  "apogee_dr17": {"feh_apogee": -0.74, "rv_mean_kms": -17.45,
                  "n_members": 297}
}
```

---

## Coverage Notes

- **4 Gaia-invisible clusters** (2MS-GC01, 2MS-GC02, GLIMPSE01, GLIMPSE02): E(B-V) = 5–34, `gaia_edr3` null throughout
- **17 post-Harris clusters** (discovered after 2010): Harris fields null, Gaia PM populated
- **12 Gaia-only clusters** (Bliss 1, ESO 93-8, Gran 1, Kim 3, Laevens 3, Mercer 5, Munoz 1, Pfleiderer 2, Ryu 059, Ryu 879, Segue 3, VVV CL002): Gaia PM only, insufficient members for N-body fitting
- **Known issue**: Harris `ra_hms`/`dec_dms`/`l_deg`/`b_deg` parser artifacts for NGC 104 and a small number of other clusters; use `baumgardt2023.ra_deg` / `baumgardt2023.l_deg` for positional queries. Fix in v1.4.

---

## Build Scripts

The `build/` directory contains the ingestion and merge scripts used to construct the corpus:

| Script | Purpose |
|--------|---------|
| `build/build_harris.py` | Parse Harris (1996, 2010) flat file |
| `build/merge_vasiliev.py` | Merge Vasiliev & Baumgardt (2021) Table A1 |
| `build/merge_baumgardt.py` | Merge Baumgardt et al. (2023) v4 orbits + structure tables |
| `build/merge_apogee.py` | Merge Schiavon et al. (2024) APOGEE DR17 GC VAC |

---

## Version History

| Version | Release | Clusters | Data points | Change |
|---------|---------|----------|-------------|--------|
| v1.0 | 2026 | 157 | — | Harris (1996, 2010 ed.) base |
| v1.1 | 2026 | 174 | — | + Vasiliev & Baumgardt (2021) Gaia EDR3 |
| v1.2 | 2026 | 174 | — | + Baumgardt et al. (2023) v4 N-body |
| **v1.3.1** | **2026** | **174** | **17,438** | **+ Schiavon et al. (2024) APOGEE DR17 · production release** |

---

## Citation

If you use this corpus, please cite:

```bibtex
@dataset{flynn2026gc,
  author    = {Flynn, David C.},
  title     = {Milky Way Globular Cluster Corpus v1.3.1},
  year      = {2026},
  publisher = {Zenodo},
  doi       = {10.5281/zenodo.19907766},
  url       = {https://doi.org/10.5281/zenodo.19907766}
}
```

And the four primary sources:

- **Harris (1996, 2010):** Harris, W.E. 1996, AJ, 112, 1487
- **Vasiliev & Baumgardt (2021):** MNRAS 505, 5978 — DOI: 10.1093/mnras/stab1475
- **Baumgardt et al. (2023):** Baumgardt & Hilker 2018, MNRAS 478, 1520; Baumgardt & Vasiliev 2021, MNRAS 505, 5957; arXiv:2303.01636
- **Schiavon et al. (2024):** MNRAS 528, 1393 — DOI: 10.1093/mnras/stad3419
- **APOGEE DR17:** Abdurro'uf et al. 2022, ApJS 259, 35

---

## Related

[Galaxy HI Rotation Curve Corpus](https://github.com/eps-research/galaxy-rotation-curve-corpus) — 438 galaxies (SPARC + THINGS + LITTLE THINGS + WALLABY), v7.0

[EPS Research](https://www.eps-research.com) · [Zenodo record](https://doi.org/10.5281/zenodo.19907766)
