Line-of-sight table#
pianistpy.LosTable, pianistpy.losvd(), pianistpy.sigma_los(),
pianistpy.sigma_los_aperture(), pianistpy.sigma_r(), pianistpy.sigma_t(), pianistpy.pdf_v()
LosTable(model) computes, once, everything the observables of a model read:
the intrinsic dispersion profile (Radial velocity dispersion);
the surface density \(\Sigma(R_{\rm 2D})\) and the line-of-sight dispersion \(\sigma_{\rm los}(R_{\rm 2D})\) over projected radius;
the line-of-sight velocity distributions \(D(R_{\rm 2D}, V_{\rm los})\) (Line-of-sight velocity distribution), one row per projected radius, from the center outward.
Every observable then takes the table in place of the model and reads its values from the table. Build a table when a model is asked for more than one observable, or for one observable many times (a fit, a grid of apertures).
As in Chiang+(2026d), the dispersion inside an aperture is the surface-density weighted average of \(\sigma_{\rm los}^2\). A centered circle of radius \(R_{\rm ape}\) gives
and an offset circle or a rectangle gives the same average over its own area.
sigma_los_aperture() computes the dispersion inside an aperture in two ways:
Given the model,
sigma_los_aperturecomputes the surface-density weighted average of \(\sigma_{\rm los}^2\) and reads no velocity distribution.Given the table,
sigma_los_aperturetakes the second moment of the aperture’s velocity distribution, which is the table’s rows averaged over the aperture with the surface density as the weight.
The results on the model and on the table agree to a relative error of \(10^{-6}\). The call on the table takes longer than the call on the model.
Arguments and units#
LosTable(model) takes a Model of one component or a superposition
(superpose()). For a superposition it holds one table per component in parts, and reads each
with its component’s weight.
The table is computed in internal units, at the model’s own settings. table.to("physical", ...) recomputes
nothing. table.to returns the same tables presented in the units of model.to("physical", ...), and the
observables read from the returned table take and return quantities in those units.
The rows are computed in parallel processes. In a script, build the table under if __name__ == "__main__":, as
Python’s multiprocessing requires. A call of losvd() or aperture_losvd() on a
model, and a ProjectedSampler given a model, also build a table and need the same
if __name__ == "__main__": guard. A notebook or an interactive session needs no if __name__ == "__main__":
guard. If a parallel process ends during the computation, the call raises pianistpy.exceptions.WorkerError at
once and states the cause. The usual cause is that the machine ran out of memory. Fewer cores
(set_cores()) lower the memory that the call uses.
The table holds:
model, the model the table was built from;profile(the intrinsic dispersion profile),projected(the surface density and the line-of-sight dispersion over projected radius),kept(the ranges of projected radius of the velocity quadrature) andtable(the line-of-sight velocity distributions), each of the first component for a superposition;validation, the table’s accuracy report;parts, one table per component.
Where the rows run#
The rows stop at the first row beyond which less than losvd.outer_mass_share of the model’s total projected
mass lies. The default is \(10^{-6}\). Settings(losvd={"outer_mass_share": 0}) keeps the
rows to the model’s edge.
By default the rows start at the innermost radius of the dispersion profile. The setting
losvd.innermost_read_radius sets the smallest projected radius the table will be read at. The rows then start
at the last row inside the projected radius whose disk holds \(10^{-6}\) times the projected mass inside
innermost_read_radius. An aperture’s integral then starts at the table’s first row. Each row kept is identical
to the row at the same projected radius of a table built with innermost_read_radius and outer_mass_share
set to 0.
A read outside the rows raises InputError. For a read beyond the last row or inside the first
row, at a point or over an aperture, the error message gives the radius of the end row and the setting that moves
the end row (losvd.outer_mass_share or losvd.innermost_read_radius). An aperture whose part inside the
first row carries more than \(10^{-6}\) of the aperture’s weight also raises InputError.
The error message gives the radius of the smallest centered circle that the table computes to a relative error of
\(10^{-6}\).
Accuracy report#
The table adds rows until the interpolation between rows meets the tolerance of the model’s settings. Every row is
checked against the surface density and the line-of-sight dispersion, both computed without the row. The report table.validation holds six checks, each with its relative error
and the radius where it is worst:
norm: each row’s integral against the surface density;second_moment: each row’s second moment against the projected dispersion;unjudged: infinite if a row where the surface density is positive could not be checked, and 0 otherwise;t_converged: infinite if the integral of a row along the line of sight did not converge, and 0 otherwise;certificate: the table’s estimate of its error between rows;layer_floor: the error floor of the integration toward zero angular momentum. The floor is nonzero only for a central anisotropy \(\beta_{\rm c}\) between 1/2 and 1, and a floor above \(10^{-6}\) raisesResultError.
The report’s tier is the band of the worst relative error among the six checks and the model’s own report
(Accuracy). Building a table whose relative error is above \(10^{-4}\) issues an
AccuracyWarning. A table whose relative error is above \(10^{-2}\) (tier "refused")
raises AccuracyError, unless the model’s settings set write_anyway=True. An aperture call
on a table (aperture_losvd, sigma_los_aperture) warns above \(10^{-4}\) and raises above
\(10^{-2}\) in the same way for the aperture’s distribution. table.table.r_uncertified records the
intervals of projected radius where the table could not meet the table’s tolerance. A read inside one of the
intervals in table.table.r_uncertified raises ResultError, whose message gives the
interval.
Example#
import numpy as np
import pianistpy as pp
model = pp.solve(
pp.Component(pp.DoublePowerLaw.hernquist(), pp.Anisotropy(beta_c=0.25)),
pp.Truncation(k_t=0.05),
settings=pp.Settings(quick=True),
)
table = pp.LosTable(model) # built once
table.validation.tier # "silent", "recorded", "warned" or "refused"
{key: check.error for key, check in table.validation.checks.items()}
circle = pp.Aperture.circle(1.0)
pp.sigma_los_aperture(model, circle), pp.sigma_los_aperture(table, circle) # on the model and on the table
pp.sigma_los(table, [0.1, 1.0, 10.0])
pp.losvd(table, 1.0, [0.0, 0.5, 1.0])
physical = table.to("physical", concentration=10.0, mass_0=1e9, r_0=500.0)
pp.sigma_los_aperture(physical, pp.Aperture.circle(500.0)) # km/s, 500 pc circle
The Representative computing time page gives the time of a table’s first build and of the calls that read the table, measured on a workstation CPU, with a GPU, and on a laptop.
