Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8e52936
feat(demos): Poisson number-density channel in _demo_inference (Batch…
drannarosen Jun 13, 2026
01300c9
fix(website): track science-demo figures (latent Batch B gap)
drannarosen Jun 13, 2026
8e29061
feat(demos): B9 binary energy budget -- the primordial-binary reservo…
drannarosen Jun 13, 2026
01f839e
fix(website): quote peuten-2017 description frontmatter (invalid YAML)
drannarosen Jun 13, 2026
eefdde4
feat(demos): B11 King concentration from star counts (Batch C)
drannarosen Jun 13, 2026
066259e
feat(demos): B10 differentiable structure diagnostics (Batch C)
drannarosen Jun 13, 2026
8bf8400
build(website): Makefile wrapping myst with --no-deprecation
drannarosen Jun 13, 2026
445ea22
feat(demos): B5 birth-environment archaeology (Batch C paper seed)
drannarosen Jun 13, 2026
854a6ba
feat(demos): B4 unresolved-binary mass function -- f_b + Moe coupling…
drannarosen Jun 13, 2026
050dd5e
feat(demos): B6 anisotropy -- OM measurability + OM-vs-Michie misspec…
drannarosen Jun 13, 2026
9325774
docs(batch-c): STATUS close-out — 6 core science demos complete
drannarosen Jun 13, 2026
e90cb6b
fix(demos): B5/B6 forecast-panel polish — detection-N marker in-range
drannarosen Jun 13, 2026
2090dcd
fix(demos): floor mu in poisson_fisher_information (empty-bin NaN-saf…
drannarosen Jun 13, 2026
4ff722e
feat(demos): B7 tidal radius from the count-limited outskirts (Batch …
drannarosen Jun 13, 2026
95ab467
feat(demos): B8 rotation + projection -- the omega-i degeneracy (Batc…
drannarosen Jun 13, 2026
15b6753
docs(batch-c): STATUS — all 8 demos done (6 core + 2 stretch B7/B8)
drannarosen Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,8 @@ docs/core-papers/
!docs/website/50-validation/figures/
!docs/website/50-validation/figures/*.png
!docs/website/50-validation/figures/*.pdf

# Science-demo figures embedded in the website (same committed exception): the
# rendered pages reference these PNGs, so a clean checkout / deploy build needs them.
!docs/website/60-science-demos/figures/
!docs/website/60-science-demos/figures/*.png
4 changes: 3 additions & 1 deletion STATUS.md

Large diffs are not rendered by default.

120 changes: 120 additions & 0 deletions docs/website/60-science-demos/anisotropy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Anisotropy & the OM-vs-Michie formalism (B6)
description: "A paper-seed demo on velocity anisotropy. For an Osipkov-Merritt cluster the anisotropy radius r_a is cleanly recovered from beta(r), with a detectability forecast. But the anisotropy FORMALISM matters: fitting the OM form to a Michie (anisotropic-King) cluster -- a genuinely different DF -- inflates the reduced chi^2 by 12.9x and mis-estimates r_a (8.9 vs 6.0). The anisotropy model you assume shapes what you infer."
---

# Anisotropy & the OM–vs–Michie formalism (B6)

Velocity anisotropy is encoded in the **Binney anisotropy**
$\beta(r) = 1 - \sigma_t^2/(2\sigma_r^2)$ — zero for isotropic orbits, rising toward
1 for radially-biased ones — and parametrized by an **anisotropy radius** $r_a$.
This demo asks two questions: how well can $r_a$ be measured, and does it matter
*which anisotropy formalism* you assume?

## (a) Well-specified: recovering Osipkov–Merritt $r_a$

The Osipkov–Merritt (OM) ansatz {cite:p}`Merritt1985` makes the DF a function of
$Q = E - L^2/2r_a^2$, giving the closed-form profile

```{math}
:label: b6-om
\beta_{\rm OM}(r) = \frac{r^2}{r^2 + r_a^2}.
```

A Plummer+OM cluster ($r_h=1$, true $r_a=1.5$ pc) is sampled, $\beta(r)$ binned, and
$r_a$ recovered by a $\chi^2$ fit of {eq}`b6-om`:

```{list-table}
:header-rows: 1

* - quantity
- truth
- recovered
* - $r_a$
- $1.5$ pc
- $1.55 \pm 0.035$ pc (pull $+1.49$)
```

The OM form fits an OM cluster well (reduced $\chi^2 = 0.43$; the conservative
$\beta$ error $\sigma_\beta = (1+|\beta|)/\sqrt n$ deflates it), and the Fisher
forecast gives $\sigma(r_a)\propto N^{-1/2}$. Anisotropy this strong
($r_a\sim r_h$) is detectable in a modest sample.

## (b) Misspecified: an OM fit to a Michie cluster

Anisotropy comes in **different formalisms**. The Michie (1963) anisotropic-King DF
{cite:p}`Michie1963`

```{math}
:label: b6-michie
f \;\propto\; \exp\!\left(-\frac{J^2}{2 r_a^2 \sigma^2}\right)
\left[\exp\!\left(-\frac{E}{\sigma^2}\right) - 1\right]
```

is **not** a function of a single $Q$, so its $\beta(r)$ shape differs from
{eq}`b6-om`. Sampling a Michie cluster ($W_0=7$, true $r_a=6$ pc) and fitting it with
the OM form:

```{list-table}
:header-rows: 1

* - quantity
- value
* - OM-fit $r_a$
- $8.90 \pm 0.27$ pc (vs true Michie $r_a = 6.0$)
* - OM-fit reduced $\chi^2$
- $5.58$ — a **12.9× inflation** over the well-specified fit
```

So assuming the wrong anisotropy formalism does two things: it **mis-estimates**
the anisotropy radius, and it leaves a **systematic $\beta(r)$ residual** (the
$12.9\times$ $\chi^2$ inflation) that *detects* the mismatch. The anisotropy model
you assume shapes what you infer — anisotropy is not a single number read off
$\beta(r)$ independent of the DF family.

## Figure

:::{figure} figures/demo_anisotropy.png
:label: sci-anisotropy
:width: 100%

**Anisotropy & the OM–vs–Michie formalism** (`scripts/demo_anisotropy.py`, ALL
PASS). **(a)** A Plummer+OM cluster: $\beta(r)$ (points, conservative errors) with
the recovered OM fit — a good match (reduced $\chi^2=0.43$). **(b)** A Michie
cluster fit with the OM form: the OM curve cannot follow the Michie $\beta(r)$
shape (reduced $\chi^2=5.58$, $12.9\times$ worse), and the best-fit $r_a=8.9$
mis-locates the true $6.0$. **(c)** Forecast: $\sigma(r_a)\propto N^{-1/2}$ for the
well-specified OM case.
:::

## Caveats

```{warning}
- **No "King+OM" model.** King's anisotropic form *is* Michie, so there is no
fixed-density OM-vs-Michie pair. The misspecification here is an OM fit to a
Michie *sample* on the $\beta(r)$ channel — not a self-consistent fixed-density
refit. It demonstrates the formalism-dependence of the inferred $r_a$, which is
the point.
- **$\beta(r)$ channel only.** The demo fits anisotropy alone; it does not co-fit
concentration or the density (the $r_a$–concentration degeneracy needs the
$\sigma(r)$ channel, already exercised in [B3](halo-core.md)).
- **Conservative $\beta$ errors.** $\sigma_\beta=(1+|\beta|)/\sqrt n$ deflates the
reduced $\chi^2$; the misspecification result is the *ratio* ($12.9\times$),
robust to the error scale.
- **The forecast $N\sim150$ is the asymptotic CRLB** for this strong anisotropy
($r_a\sim r_h$); the small-$N$ bound is optimistic, and weaker anisotropy needs
far more stars.
```

## How to run

```bash
env -u VIRTUAL_ENV uv run --no-sync python scripts/demo_anisotropy.py
```

## References

Osipkov–Merritt anisotropy is {cite:t}`Merritt1985`; the Michie anisotropic-King
DF is {cite:t}`Michie1963`. The OM and Michie velocity DFs are documented on the
[kinematics](../10-theory/populations/index.md) pages; B3's two-component OM
recovery is at [halo + core](halo-core.md).
229 changes: 229 additions & 0 deletions docs/website/60-science-demos/binary-energy-budget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
title: Binary energy budget (B9)
description: "The primordial-binary energy reservoir: build_binary_cluster virializes the system COMs to Q=0.5 treating binaries as point masses, leaving the internal binary binding energy as a separate reservoir. binary_energy_budget makes the two scales explicit -- Q_com recovers 0.5 cleanly while |E_internal| dwarfs |W_com|, and the same primordial population is a relatively larger energy store in a young EFF cluster than in a concentrated King GC."
---

# Binary energy budget (B9)

A cluster with primordial binaries carries **two energy scales**, and conflating
them is a classic mistake. `build_binary_cluster` virializes the *system
centres-of-mass* to $Q=0.5$ treating each binary as a single point mass — the
McLuster scale-separation convention {cite:p}`Kuepper2011_McLuster` — and leaves
the **internal binary binding energy** as a separate reservoir that the global
virial scaling never touches. This demo makes that separation explicit and shows
why the naive virial ratio of the *resolved* stars is not the cluster's.

The two scales, from `binary_energy_budget`:

- $W_{\rm com}$ — the cluster's bulk gravitational binding on the system COMs; the
scale the cluster is virialized on, $Q_{\rm com} = T_{\rm com}/|W_{\rm com}| \approx 0.5$.
- $E_{\rm internal} = \sum_b \left(-G\,m_{1}m_{2}/2a_b\right)$ — the internal binary
binding (vis-viva), set by the periods and masses and **independent of where the
COM sits** in the cluster.

```{math}
:label: b9-qresolved
Q_{\rm resolved} = \frac{T}{|W|}\Big|_{\rm resolved\ stars}
\ \neq\ Q_{\rm com},
```

because the resolved ratio mixes the two scales. It is **not** the cluster's virial
ratio (audit S10): a hard binary's *internal* virial is itself $\approx 0.5$
(time-averaged), so sampled at random orbital phases $Q_{\rm resolved}$ **scatters
around 0.5** rather than deflating monotonically. The robust, gated statement is
the energy *separation* — $Q_{\rm com}$ cleanly recovers $0.5$ while
$|E_{\rm internal}|$ dwarfs $|W_{\rm com}|$.

**Why a young cluster.** Primordial binaries are the population present *at birth*,
before dynamical processing ionizes the soft ones and hardens the rest. Their
natural home is therefore a **young** cluster, for which the
Elson–Fall–Freeman (1987) {cite:p}`ElsonFallFreeman1987` extended power-law profile
is the standard parametrization — not the King model of an old, relaxed globular.
So the primary cluster here is EFF ($a=1$ pc, $\gamma=2.5$, $r_t=15$ pc); a
concentrated King ($W_0=7$) {cite:p}`King1966` appears only in the environment
figure (below).

## What is built

A Plummer-masses-first EFF young cluster of $N_{\rm sys}=2000$ systems, virialized
to $Q=0.5$, with a smooth (differentiable) Maschberger $\alpha=2.3$ primary IMF over
$[0.08, 100]\,\Msun$ and companions from `IndependentCompanions` (or the full
Moe & Di Stefano (2017) {cite:p}`MoeDiStefano2017` model). Two controlled sweeps:

- **Hardness** — the companion period band `LogUniformPeriod` is slid from
$\log_{10}(P/{\rm day})=1.5$ (hard, $\sim$30 d) to $5.5$ (soft, $\sim$900 yr) at
fixed $f_b=0.5$. Shorter periods $\Rightarrow$ smaller $a$ $\Rightarrow$ deeper
binding (vis-viva).
- **Binary fraction** — $f_b$ swept $0.1\!\to\!1.0$ at a fixed broad period band.

## Result — freshly run, ALL PASS

Measured 2026-06-12 (CPU/float64, $N_{\rm sys}=2000$, seeds from `PRNGKey(0)`; wall
$\approx 32$ s; exit 0).

**Hardness sweep (EFF, $f_b=0.5$).** $Q_{\rm com}$ is pinned at $0.5$ to four
decimals at every point; $|E_{\rm internal}|$ spans **three orders of magnitude**;
$Q_{\rm resolved}$ scatters (the contamination):

```{list-table}
:header-rows: 1

* - $\log_{10} P$
- $Q_{\rm com}$
- $Q_{\rm resolved}$
- $|E_{\rm internal}|$
- $|W_{\rm com}|$
* - $1.5$ (hard)
- $0.5000$
- $0.464$
- $1.22\times10^{6}$
- $6.4\times10^{2}$
* - $2.5$
- $0.5000$
- $0.420$
- $8.4\times10^{5}$
- $1.0\times10^{3}$
* - $3.5$
- $0.5000$
- $0.512$
- $4.1\times10^{4}$
- $5.2\times10^{2}$
* - $4.5$
- $0.5000$
- $0.491$
- $1.4\times10^{4}$
- $6.4\times10^{2}$
* - $5.5$ (soft)
- $0.5000$
- $0.327$
- $7.1\times10^{3}$
- $6.1\times10^{2}$
```

Across both sweeps the reservoir ratio $|E_{\rm internal}|/|W_{\rm com}|$ stays in
$[11.6,\ 1.9\times10^{3}]$ — the internal binding **always dwarfs** the cluster
potential — while $\max|Q_{\rm resolved}-Q_{\rm com}| = 0.257$ confirms the resolved
ratio is a contaminated proxy. Gate summary:

```{list-table}
:header-rows: 1

* - Check
- Gate
- Status
* - $Q_{\rm com}\approx0.5$ (all 12 points)
- $|Q_{\rm com}-0.5|<0.01$
- **PASS**
* - $E_{\rm internal}<0$ (all bound)
- $<0$
- **PASS**
* - realized $f_b$ matches set $f_b$
- $3\sigma$ Poisson
- **PASS**
* - $|E_{\rm internal}|>|W_{\rm com}|$ (all points)
- reservoir dwarfs potential
- **PASS**
* - $E_{\rm internal}$ identical EFF vs King (same key)
- controlled comparison
- **PASS**
* - reservoir fraction EFF $>$ King
- environment effect
- **PASS**
```

## The environment comparison

The same realistic Moe & Di Stefano population is laid into a young EFF and a
concentrated King ($W_0=7$) potential **using the same random key**, so the masses
and orbital elements — and therefore $E_{\rm internal}$ — are *byte-identical*
between the two. Only the cluster structure differs:

```{list-table}
:header-rows: 1

* - Cluster
- $r_h$
- $|E_{\rm internal}|$
- $|W_{\rm com}|$
- $|E_{\rm internal}|/|W_{\rm com}|$
* - EFF (young)
- $5.32$ pc
- $2.965\times10^{5}$
- $6.7\times10^{2}$
- $440$
* - King $W_0=7$ (GC-like)
- $3.26$ pc
- $2.965\times10^{5}$
- $9.9\times10^{2}$
- $298$
```

The concentrated King is more tightly bound ($|W_{\rm com}|$ larger), so the *same*
primordial binary population is a relatively **larger energy store in the young,
puffy cluster** — the global importance of the binary reservoir depends on the
birth environment. This is the {cite:t}`Heggie1975` hard/soft intuition at the
level of the *total* energy budget; a per-binary hard/soft classification
($|E_{\rm bind}|$ vs the local $kT$) is a related but distinct quantity and is not
claimed here.

## Figures

:::{figure} figures/demo_binary_energy_budget.png
:label: sci-b9-sweeps
:width: 100%

**EFF young-cluster sweeps** (`scripts/demo_binary_energy_budget.py`). **(a)**
Hardness sweep: $Q_{\rm com}$ pinned at $0.5$ (blue) while $Q_{\rm resolved}$
(vermilion) scatters around it — the resolved ratio is not the cluster's. **(b)**
$|E_{\rm internal}|$ (green) towers over $|W_{\rm com}|$ (purple) and grows three
orders as binaries harden. **(c, d)** Binary-fraction sweep: same $Q_{\rm com}$
pinning, and the reservoir fraction rising with $f_b$, with the realistic
Moe & Di Stefano population marked (★).
:::

:::{figure} figures/demo_binary_energy_budget_environment.png
:label: sci-b9-environment
:width: 85%

**Birth-environment comparison** (same Moe population, same key). **(a)**
$|E_{\rm internal}|$ is identical between EFF and King (the controlled comparison);
$|W_{\rm com}|$ is larger for the concentrated King. **(b)** The reservoir fraction
$|E_{\rm internal}|/|W_{\rm com}|$ is therefore larger in the young EFF cluster.
$r_h$ for each cluster is annotated.
:::

## Caveats

```{warning}
- **Initial conditions only.** This is the binary energy budget *at birth*. The
dynamical consequences — soft-binary ionization, hard-binary hardening, the
binary-burning energy source that halts core collapse — require N-body evolution
(the deferred gravax forward chain), not these ICs.
- **$Q_{\rm resolved}$ is a diagnostic, not a gate.** Its exact value is set by the
instantaneous orbital phases of the binary sample and scatters around $0.5$; the
demo gates the robust separation ($Q_{\rm com}$, the reservoir magnitude), and
*shows* the $Q_{\rm resolved}$ scatter as the cautionary point.
- **Environment comparison is at characteristic $r_h$, not fixed $r_h$.** EFF and
King are two representative fiducial clusters with their own half-mass radii
(both annotated). The reservoir-fraction difference is driven by $|W_{\rm com}|$
(the more bound King), disclosed in the table; a controlled-at-fixed-$r_h$ study
is a refinement, not done here.
- **Point-mass binaries.** The hardest band ($\log P = 1.5$, $\sim$30 d) keeps $a$
well above stellar radii for these masses; the point-mass orbital treatment is
valid across the sampled range.
```

## How to run

```bash
env -u VIRTUAL_ENV uv run --no-sync python scripts/demo_binary_energy_budget.py
```

## References

The EFF young-cluster profile is {cite:t}`ElsonFallFreeman1987`; the King model
{cite:t}`King1966`; the binary statistics follow {cite:t}`MoeDiStefano2017`; the
COM/internal scale separation is the McLuster convention
{cite:t}`Kuepper2011_McLuster`; the hard/soft intuition is {cite:t}`Heggie1975`.
The budget API and its vis-viva / scale-separation tests live in
`progenax.binaries.diagnostics`.
Loading
Loading