Superposition#
Module pianistpy.superposition
Linear superposition of models that share one truncated density.
superpose combines models built on one truncated potential-density pair into one model, moments returns the velocity moments of the combined model, and mixture combines any per-component quantity by the weights.
- pianistpy.superposition.mixture(weights, values, xp=np)#
Return the weighted sum of per-component values, with the weights normalized.
A superposed model combines a density, a velocity moment or any other quantity computed per component by the weighted sum mixture returns. The sum is linear in the values and, through the normalization, a smooth function of the weights. With
xp=jax.numpythe sum is differentiable in the weights by JAX.- Parameters:
weights (array-like, shape (n_components,)) – The weights \(w_k\), positive. The call divides the weights by the weights’ sum and does not check the weights.
values (array-like, shape (n_components, ...)) – The value \(v_k\) of each component, along the first axis.
xp (module, optional) – The array module,
numpyorjax.numpy. Default:np, which means NumPy arrays in and out.
- Returns:
value – The sum \(\sum_k w_k v_k / \sum_k w_k\), with the shape of one component’s value, as an array of xp.
- Return type:
array, shape (…)
See also
Examples
>>> from pianistpy.superposition import mixture >>> mixture([1.0, 3.0], [[2.0, 4.0], [6.0, 8.0]]) array([5., 7.])
- pianistpy.superposition.moments(model, r, n_nodes=None)#
Return the second velocity moments of a superposed model at 3D radii.
- Parameters:
model (Model) – A superposed model (superpose) whose components share one spatially finite density.
r (array-like, shape (n,)) – User-specified 3D radii, in internal units whatever the model’s unit system.
n_nodes (int, optional) – The number of Gauss-Legendre nodes of each component’s Jeans integral. Default:
None, which means the value of the model’s settingobservables.n_jeans_nodes.
- Returns:
moments – Arrays in internal units, whatever the model’s unit system:
sigma_r2andsigma_t2, the squared radial and tangential dispersions of the superposed model, andbeta_sigma, the superposed model’s velocity anisotropy, each with the shape of r;sigma_r2_componentsandbeta_components, the squared radial dispersion and the anisotropy of each component, one row per component along the first axis, each row with the shape of r.beta_sigmais NaN at a radius wheresigma_r2is not positive.- Return type:
- Raises:
InputError – If model is not a Model.
InputError – If the density of model is not spatially finite.
Notes
The squared radial dispersion \(\sigma_{r,k}^2\) of component \(k\) is the Jeans integral of the component’s augmented density on the shared profile, evaluated by a Gauss-Legendre rule in the variable \(\ln(1 - \mathcal{K}/P_0)\), with \(\mathcal{K}\) the binding energy and \(P_0\) the central potential. Because the components share one density, the moments of the superposed model are the weighted sums
\[\sigma_r^2 = \sum_k w_k \sigma_{r,k}^2, \qquad \sigma_t^2 = 2 \sum_k w_k (1 - \beta_k) \sigma_{r,k}^2, \qquad \beta_\sigma = 1 - \frac{\sigma_t^2}{2 \sigma_r^2},\]with \(w_k\) the normalized weight and \(\beta_k\) the velocity anisotropy of component \(k\).
References
Chiang+(2026d), Eq. (B43), Chiang+(2026d), Eq. (B44) and Chiang+(2026d), Eq. (B74).
Examples
>>> import pianistpy as pp >>> settings = pp.Settings(quick=True) >>> profile = pp.DoublePowerLaw.nfw() >>> parent = pp.solve(pp.Component(profile), pp.Truncation(k_t=0.05), settings) >>> children = [ ... pp.solve( ... pp.Component(parent.profile, pp.Anisotropy(beta_c=b)), None, settings ... ) ... for b in (0.0, 0.2) ... ] >>> model = pp.superpose({children[0]: 0.5, children[1]: 0.5}) >>> result = pp.superposition.moments(model, [0.5, 1.0]) >>> result["sigma_r2"].shape, result["sigma_r2_components"].shape ((2,), (2, 2))
- pianistpy.superposition.superpose(members)#
Return the superposition of models built on one truncated profile.
The returned model is an equilibrium of the shared pair, because every member’s distribution function was inverted from the shared density.
- Parameters:
members (mapping of Model to float) – Two or more models, each of one component and each built on the same truncated profile (for example, Method II children of one truncated parent), mapped to the models’ weights. Each weight is positive and finite. The weights are normalized to sum to 1, with a logged warning where the weights’ sum differs from 1 by more than \(10^{-10}\).
- Returns:
model – A model with one component per member, in the order of members. Each component is the member’s component, with a mass equal to the member’s weight divided by the first member’s weight, and with the member’s DF table on the member’s own energy grid. The model’s profile is the shared truncated profile with the density split among the components by weight. Each check of the model’s ValidationReport holds the largest error of the check over the members.
- Return type:
- Raises:
InputError – If members is not a mapping.
InputError – If fewer than two members are given.
InputError – If a member is not a Model, or has more than one component.
InputError – If a member’s profile is not a truncated profile with \(\mathcal{K}_{\rm t} > 0\).
InputError – If a weight is not positive and finite.
InputError – If the members are built on different truncated profiles.
InputError – If the members were computed with different settings, or are in different units.
InputError – If a member’s DF is signed.
InputError – If the members’ central values of the potential differ.
InputError – If some members’ distribution functions come from closed forms (directly or through a closed-form parent) and the other members’ distribution functions were computed by inversion. Build every member the same way, from closed forms or by inversion, to superpose the members.
InputError – If the members’ validation reports hold different sets of checks.
See also
References
Chiang+(2026d), Appendix B5; Notes Section T:4.
Examples
>>> import pianistpy as pp >>> settings = pp.Settings(quick=True) >>> profile = pp.DoublePowerLaw.nfw() >>> parent = pp.solve(pp.Component(profile), pp.Truncation(k_t=0.05), settings) >>> children = [ ... pp.solve( ... pp.Component(parent.profile, pp.Anisotropy(beta_c=b)), None, settings ... ) ... for b in (0.0, 0.2) ... ] >>> model = pp.superpose({children[0]: 0.5, children[1]: 0.5}) >>> len(model.components), [c.mass for c in model.components] (2, [1.0, 1.0])
