Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Anisotropy and rotation

San Diego State University

The default equilibrium velocity DFs in progenax — Plummer, King, EFF — are spherical, isotropic, and non-rotating. Real star clusters are often not: globular clusters show modest radial anisotropy in their outer regions King, 1966, young massive clusters frequently rotate, and post-merger or tidally-stressed systems can have substantial velocity anisotropy in either direction.

progenax extends each isotropic DF with two compositional layers:

  1. Osipkov-Merritt radial anisotropy — a one-parameter (rar_a, the anisotropy radius) generalisation that makes the velocity ellipsoid radially biased outside rar_a and stays isotropic within.

  2. Rotation — solid-body or differential, applied as an additive tangential velocity on top of the isotropic base.

The two layers attach differently: anisotropy is intrinsic to the DF (an anisotropy_radius constructor argument on PlummerVelocityDF / EFFVelocityDF), while the rotation overlays are plain functions applied to the already-sampled (velocities, positions) arrays (or declared via RotationParams in the pipeline). The composition is order-independent (anisotropy modifies the velocity ellipsoid; rotation adds a bulk flow), so the final IC has the right β(r)\beta(r) profile and the right rotation curve.

Osipkov-Merritt anisotropy

The Osipkov-Merritt construction replaces the energy-only DF f(E)f(\mathcal{E}) with a DF that depends on the augmented integral

Q    EL22ra2Q \;\equiv\; \mathcal{E} - \frac{L^2}{2\,r_a^2}

↗ model card

where L=r×vL = |\mathbf{r}\times\mathbf{v}| is the specific angular momentum and rar_a is the anisotropy radius. The DF f(Q)f(Q) — constructed by Eddington-style inversion using the augmented density ρQ(r)(1+r2/ra2)ρ(r)\rho_Q(r) \equiv (1 + r^2/r_a^2)\,\rho(r) — produces a velocity ellipsoid whose anisotropy is

β(r)    1σt2(r)σr2(r)  =  r2r2+ra2\beta(r) \;\equiv\; 1 - \frac{\sigma_t^2(r)}{\sigma_r^2(r)} \;=\; \frac{r^2}{r^2 + r_a^2}

↗ model card

Anisotropy machinery, measured (N = 2\times10^5 per model, seed 12).
OM-Plummer and OM-EFF realized \beta(r) sit on the exact identity
r^2/(r^2+r_a^2) (curves); the self-consistent Michie model (squares) rises
along, then saturates visibly below its OM ceiling (dashed) with a
truncation-edge dip — the lowering removes the most-radial high-energy
orbits, so a lowered model can never reach the pure OM limit. Regenerate:
python -m laboratory.icviz --only beta-anisotropy.

Figure 1:Anisotropy machinery, measured (N=2×105N = 2\times10^5 per model, seed 12). OM-Plummer and OM-EFF realized β(r)\beta(r) sit on the exact identity r2/(r2+ra2)r^2/(r^2+r_a^2) (curves); the self-consistent Michie model (squares) rises along, then saturates visibly below its OM ceiling (dashed) with a truncation-edge dip — the lowering removes the most-radial high-energy orbits, so a lowered model can never reach the pure OM limit. Regenerate: python -m laboratory.icviz --only beta-anisotropy.

Three regimes:

rr

β(r)\beta(r)

Velocity ellipsoid

ra\ll r_a

0\to 0

Isotropic (σr=σt\sigma_r = \sigma_t)

=ra= r_a

0.5

Moderately radial

ra\gg r_a

1\to 1

Fully radial (σt0\sigma_t \to 0)

The anisotropy radius rar_a is the only new parameter. Setting rar_a \to \infty recovers the isotropic base DF; setting ra=0r_a = 0 produces fully radial orbits at all radii (which is generally unphysical and unstable).

Implementation: the anisotropy_radius DF parameter

Osipkov-Merritt anisotropy is an intrinsic property of the velocity DF: pass anisotropy_radius (=ra=r_a) to a DF. None (default) gives the isotropic DF; a float gives the radially anisotropic OM DF for the same density.

from jaxstro.units import STELLAR
from progenax.kinematics import PlummerVelocityDF, EFFVelocityDF

# r_a in the same length units as r_h / a
om_plummer = PlummerVelocityDF(r_h=1.0, anisotropy_radius=2.0)
# gamma=5 reduces EFF to Plummer, so this OM-EFF starts in clean equilibrium
# (Q ~ 0.5). A steep gamma=3 EFF, strongly truncated, is a few percent
# sub-virial even before anisotropy — see the EFF chapter.
om_eff = EFFVelocityDF(a=1.0, gamma=5.0, r_t=10.0, anisotropy_radius=2.0)

velocities = om_plummer.sample_velocities(positions, masses, key, G=STELLAR.G)

Internally (Merritt 1985):

  1. Build f(Q)f(Q) from the augmented density ρQ=(1+r2/ra2)ρ\rho_Q = (1+r^2/r_a^2)\rho — the closed-form Eq. 45 for Plummer, the numerical Eddington inversion of ρQ\rho_Q for EFF. The potential Ψ(r)\Psi(r) is unchanged (set by the true mass density).

  2. At each radius sample a speed ss from s2f(Ψs2/2)s^2 f(\Psi - s^2/2) by inverse-CDF — the same machinery as the isotropic DF.

  3. Split ss isotropically in the stretched frame wt=vt1+r2/ra2w_t = v_t\sqrt{1+r^2/r_a^2}, then un-stretch the tangential component, which realises β(r)=r2/(r2+ra2)\beta(r)=r^2/(r^2+r_a^2) exactly.

The DF is JIT-compatible and differentiable in rar_a (and rhr_h/aa).

Check yourself

1. Read rar_a off a curve

The OM identity gives β(ra)=12\beta(r_a) = \tfrac12 exactly. Read the two OM models’ rar_a off Figure 1 by finding where each curve crosses β=0.5\beta = 0.5 — you should recover ra=2r_a = 2 and 3.

2. Why does Michie sit below its OM ceiling?

Both constructions use the same eJ2/2ra2σ2e^{-J^2/2 r_a^2 \sigma^2} weight — so why do the squares saturate near β0.5\beta \approx 0.5 instead of climbing to the dashed curve? (The King-style lowering removes the highest-energy orbits, and at large radii those are precisely the most radial ones an OM model relies on; near rtr_t the DF empties entirely and β\beta dips. The validation suite pins this ordering: test_beta_below_osipkov_merritt_ceiling.)

Solid-body rotation

The simplest rotation prescription adds a tangential velocity vϕ(r)=Ω×r\mathbf{v}_\phi(\mathbf{r}) = \mathbf{\Omega} \times \mathbf{r} to the isotropic base velocities, where Ω\mathbf{\Omega} is a constant angular-velocity vector (typically aligned with the cluster’s zz-axis). The resulting per-particle velocity is

vi  =  viiso+Ω×ri.\mathbf{v}_i \;=\; \mathbf{v}_i^{\mathrm{iso}} + \mathbf{\Omega}\times\mathbf{r}_i.

This adds a bulk-rotation kinetic-energy contribution

Trot  =  12imiΩ×ri2  =  12Ω2IT_{\mathrm{rot}} \;=\; \tfrac{1}{2}\sum_i m_i\,|\mathbf{\Omega}\times\mathbf{r}_i|^2 \;=\; \tfrac{1}{2}\,\Omega^2 I_\perp

with II_\perp the moment of inertia perpendicular to Ω\mathbf{\Omega}. Because solid-body rotation adds only tangential velocity, the random-motion velocity dispersion is preserved — but the total kinetic energy increases, which means the resulting cluster is supervirial by Trot/VT_{\mathrm{rot}}/|V|. You can rescale the speeds to a target QvirQ_{\mathrm{vir}} via virial_scale after rotation is applied, but note the source module’s own warning: a uniform speed rescale does not restore stationarity — it cannot remove the injected angular momentum LzL_z, so a rotation-overlaid IC is a deliberately rotating, non-stationary initial condition.

from progenax import PlummerProfile, PlummerVelocityDF, virial_scale
from progenax.kinematics import apply_solid_body_rotation

profile = PlummerProfile(r_h=1.0)
df = PlummerVelocityDF(r_h=1.0)
positions = profile.sample_positions(masses, key_pos)
velocities = df.sample_velocities(positions, masses, key_vel, G=STELLAR.G)

# Overlay: add the streaming component v_rot = omega x r. The overlays are
# plain FUNCTIONS on (velocities, positions) arrays — not DF wrappers.
velocities = apply_solid_body_rotation(
    velocities, positions, omega=0.5, axis=jnp.array([0.0, 0.0, 1.0])
)

# Optional: rescale speeds to a target Q_vir (returns the scaled velocities).
# CAVEAT: the uniform rescale cannot remove the injected L_z — the rotating
# IC is deliberately NON-STATIONARY (see kinematics/rotation.py's docstring).
velocities = virial_scale(positions, velocities, masses, Q_target=0.5, G=STELLAR.G)

Differential rotation

Differential rotation generalises (3) by allowing Ω\mathbf{\Omega} to depend on position:

vi  =  viiso+Ω(ri)×ri.\mathbf{v}_i \;=\; \mathbf{v}_i^{\mathrm{iso}} + \mathbf{\Omega}(r_i) \times \mathbf{r}_i.

progenax implements a peaked rotation curve in cylindrical radius R=x2+y2R = \sqrt{x^2 + y^2}:

vϕ(R)  =  vpeakRRpeakexp ⁣(1RRpeak)v_\phi(R) \;=\; v_{\mathrm{peak}}\,\frac{R}{R_{\mathrm{peak}}}\, \exp\!\Bigl(1 - \frac{R}{R_{\mathrm{peak}}}\Bigr)

which is solid-body (vϕRv_\phi \propto R, Ω\Omega constant) in the core, reaches vpeakv_{\mathrm{peak}} exactly at R=RpeakR = R_{\mathrm{peak}}, and decays exponentially outward — no rotation at the centre or in the far outskirts, the shape observed in rotating globular clusters. (An earlier revision of this page described a user-supplied power-law Ω(R)\Omega(R) callable; the shipped API takes the two scalars below.)

from progenax.kinematics import apply_differential_rotation

velocities = apply_differential_rotation(
    velocities, positions, v_peak=0.5, R_peak=1.0,
    axis=jnp.array([0.0, 0.0, 1.0]),
)

The overlay is a plain function on the sampled (velocities, positions) arrays, fully vmap/JIT-compatible and differentiable in vpeakv_{\mathrm{peak}} and RpeakR_{\mathrm{peak}} (both are grad-audited registry cases).

Composability

Anisotropy is intrinsic to the DF; rotation and an optional virial rescale are layered by the velocity pipeline:

from progenax.kinematics import (
    PlummerVelocityDF, VelocityModel, RotationParams, sample_velocities_pipeline,
)

model = VelocityModel(
    df=PlummerVelocityDF(r_h=1.0, anisotropy_radius=2.0),   # radial anisotropy
    rotation=RotationParams(solid_body=True, pattern_speed=0.3),
)
velocities = sample_velocities_pipeline(key, positions, masses, model, G=STELLAR.G)

The DF sets the velocity ellipsoid (σr\sigma_r, σt\sigma_t); rotation adds a bulk flow on top without changing the random-motion dispersions.

Domain of validity

  1. Osipkov-Merritt non-negativity. progenax refuses an rar_a below the Plummer bound ra0.75ar_a \ge 0.75\,a (Merritt 1985, Eq. 46) at construction (raises ValueError), and EFF rejects an rar_a that drives f(Q)f(Q) negative. Even above the bound, very small rar_a is prone to the radial-orbit instability within a relaxation time under evolution.

  2. Solid-body rotation up to Ω/Ωbreakup0.5\Omega/\Omega_{\mathrm{break-up}} \sim 0.5 is physically reasonable. Above that, the cluster flattens significantly and a spherical IC is a poor approximation.

  3. Differential-rotation profiles must be smooth — discontinuities in Ω(R)\Omega(R) produce delta-function torques on the cluster and non-equilibrium starting states. If your science requires a sharp transition (e.g. a rotating core embedded in a non-rotating envelope), use a smooth-blend Ω(R)\Omega(R) rather than a step function.

Implementation, validation & references

References
  1. King, I. R. (1966). The structure of star clusters. III. Some simple dynamical models. The Astronomical Journal, 71, 64–75. 10.1086/109857
  2. Merritt, D. (1985). Spherical stellar systems with spheroidal velocity distributions. The Astronomical Journal, 90, 1027–1037. 10.1086/113810
  3. Aarseth, S. J., Henon, M., & Wielen, R. (1974). A comparison of numerical methods for the study of star cluster dynamics. Astronomy and Astrophysics, 37, 183–187.
  4. Küpper, A. H. W., Maschberger, T., Kroupa, P., & Baumgardt, H. (2011). Mass segregation and fractal substructure in young massive clusters. Monthly Notices of the Royal Astronomical Society, 417, 2300–2317. 10.1111/j.1365-2966.2011.19412.x
  5. Gieles, M., & Zocchi, A. (2015). A family of lowered isothermal models. Monthly Notices of the Royal Astronomical Society, 454, 576–592. 10.1093/mnras/stv1848