# T:3 Truncation

Source: https://pianistpy.com/docs/reference/technical-notes/truncation

The two truncation methods are defined in [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Section 3.1.

## T:3.1 Type II models beyond the anisotropy radius

*The model is not defined for* \(R > R_\mathrm{a}\)*.* In the Type II variable \(Q_+\), the tangential velocity enters with the factor \(1 - R^2/R_\mathrm{a}^2\). For \(R > R_\mathrm{a}\) this factor is negative, the velocity integral diverges, and no DF of this form yields a density. An untruncated profile that extends beyond \(R_\mathrm{a}\) therefore has no Type II DF, whereas a truncated model with \(R_\mathrm{t} < R_\mathrm{a}\) is well defined.

*Method I nevertheless needs* \(\varrho_\mathrm{aux}\) *there.* The inversion solves the Volterra equation [Eq. (T:2.3)](https://pianistpy.com/docs/reference/technical-notes/df#tn-eq-volterra), whose lower limit lies at \(R = \infty\). The values of \(\varrho_\mathrm{aux}\) on \(P > P(R_\mathrm{a})\) determine \(F_\mathcal{K}\) there only in combination with \(F_\mathcal{K}\) on \(P < P(R_\mathrm{a})\). Method I constructs the truncated DF from the untruncated one by the shift of [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Eq. (B27), and the contribution of the stretch \(0 < P < P(R_\mathrm{a})\) to \(F_\mathcal{K}\) depends on \(\mathcal{K}\), so it does not cancel in that difference. A value of \(\varrho_\mathrm{aux}\) must therefore be supplied on that stretch.

*The continuation.* For OM Type II, the factor \(1 - R^2/R_\mathrm{a}^2\) is a polynomial and is used as it stands. For COM Type II the factor \(w^{1-\beta_\mathrm{c}}\) is not real for \(w < 0\) where \(1 - \beta_\mathrm{c}\) is not an integer; PIANISTpy adopts, for every COM Type II model, the continuation

\begin{equation}
  w^{1-\beta_\mathrm{c}} \;\to\; \mathrm{sign}(w)\,|w|^{1-\beta_\mathrm{c}},
  \tag{T:3.1}
\end{equation}
which equals \(w^{1-\beta_\mathrm{c}}\) where \(1 - \beta_\mathrm{c}\) is an odd integer and differs from it in sign beyond \(R_\mathrm{a}\) where it is an even integer, is odd in \(w\), and reduces to the OM form at \(\beta_\mathrm{c} = 0\). The continuation is requested through an explicit argument in the symbolic form of the Method I inversion alone; every other reading of the factor at or beyond \(R_\mathrm{a}\) is refused with the reason stated. The adopted convention is recorded in the product file.

*The support.* An untruncated Type II model is defined only inside \(R_\mathrm{a}\), so its DF is returned over \(\mathcal{K} > P(R_\mathrm{a})\) only. The table keeps its fixed grid, the rows at and below \(P(R_\mathrm{a})\) hold no value, and the record states the first row of the support. The values that the inversion forms below \(P(R_\mathrm{a})\) belong to the continuation rather than to the model, whatever their sign; they are used by the inversion above \(P(R_\mathrm{a})\), which depends on them non-locally; the table does not carry them, and they are kept only among the inversion’s rows as made, which the truncation reads. The gates on the sign and on the last minimum of \(F_\mathcal{K}\) are evaluated on the support, and the lowest admissible truncation value is \(\max(\mathcal{K}_0, \mathcal{K}_{\min}, P(R_\mathrm{a}))\), below which the truncated DF would read the continuation; it is necessary but not sufficient for \(R_\mathrm{t} < R_\mathrm{a}\), which the truncated solve decides ([Section T:3.5](https://pianistpy.com/docs/reference/technical-notes/truncation#tn-sec-gate)). The anisotropy object declares the edge of its domain (\(R_\mathrm{a}\) for Type II, none otherwise); the support and the gates read that edge and not the Type. The same bound holds for a Method II model whose anisotropy is of Type II: a model whose \(R_\mathrm{a}\) does not lie beyond the Method I model’s edge is refused before the inversion.

*What the convention can and cannot change.* Every DF of the form \(\hat F(\hat{\mathcal{K}})\,L^{-2\beta_\mathrm{c}}\) in the Type II variable has the COM Type II anisotropy profile, and the truncated density is computed from \(\hat F\). The convention therefore affects neither the anisotropy profile nor the self-consistency of a Method I model. What it selects is which truncated density the model has, and under Method I that density is an output. For that density the DF is unique. The effect of the convention on the truncated density exceeds \(10^{-6}\) ([Section T:0.5](https://pianistpy.com/docs/reference/technical-notes/principles#tn-sec-tiers)).

Method II inverts the truncated pair, which lies within \(R_\mathrm{a}\), and never evaluates the continuation. A user who needs a Type II model with a prescribed density should therefore use Method II.

## T:3.2 The truncated DF and its density

From the table that the inversion leaves, the truncation energy, and the settings, the truncated DF \(F(\mathcal{K} + \mathcal{K}_\mathrm{t}) - F(\mathcal{K}_\mathrm{t})\) is formed on the source grid, together with its Taylor form at the edge and the edge integral that joins the form to the table. The auxiliary density

\begin{equation}
  \varrho_\mathrm{aux}(P) = C(\beta_\mathrm{c})\int_0^{P} \hat F(\mathcal{K})\,\left[2(P - \mathcal{K})\right]^{1/2-\beta_\mathrm{c}}\,\mathrm{d}\mathcal{K}
  \tag{T:3.2}
\end{equation}
with \(C(\beta_\mathrm{c}) = 2\pi^{3/2}\Gamma(1-\beta_\mathrm{c})/\Gamma(3/2-\beta_\mathrm{c})\) as in [Eq. (T:2.3)](https://pianistpy.com/docs/reference/technical-notes/df#tn-eq-volterra), is what the Poisson solve takes as its source (the table holds the integral, and the solve applies \(C\)); the density of a component is \(\varrho_\mathrm{aux}\) times the factor \(g(R)\) of its anisotropy, which the solve applies. The source grid runs from the floor to the gap below the truncated central potential, with logarithmic tails toward both ends over a stated fraction of the top. The integral at each potential is taken on two intervals: Gauss-Legendre nodes in \(\ln{\mathcal{K}}\) from the floor to a split at a stated fraction of \(P\), and above the split the map \(\mathcal{K} = P - v^{k}\): with \(k = 1/(3/2 - \beta_\mathrm{c})\) where \(1/2 - \beta_\mathrm{c}\) is negative, which removes the kernel’s singularity at \(\mathcal{K} = P\), and with a stated \(k\) (2) otherwise, which makes the kernel’s end-point factor \(v^{2 - 2\beta_\mathrm{c}}\), a polynomial for OM and isotropic models. The lookup at a grid node returns the node’s value, to the rounding of its logarithm; at the bottom no clip by an absolute epsilon enters (the clip is the table’s first node), and below the floor, where the solve ends, the source is set to zero.

### T:3.2.1 The low-energy tail of the DF in the density integral

The source quadrature runs for truncated models only: an untruncated model takes the closed forms of its source profile ([Section T:3.7](https://pianistpy.com/docs/reference/technical-notes/truncation#tn-sec-trust)), and a truncated DF vanishes at its lower end, so no tail term enters.

## T:3.3 Solving the truncated Poisson equation

The truncated Poisson equation, [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Eq. (B28), is integrated outward in \(s = \ln r\) as a first-order system in \([\Phi,\; W = r\,\mathrm{d}\Phi/\mathrm{d} r,\; u = P_0 - \Phi]\), under a relative tolerance of \(10^{-11}\) and no absolute tolerance (the edge continuation in \(\ln\Phi\), also relative-only), starting from the series of the cusp ([Section T:1.2.2](https://pianistpy.com/docs/reference/technical-notes/model#tn-sec-series)). The right-hand side is the density of [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Eq. (B29), read from the source table of [Section T:3.2](https://pianistpy.com/docs/reference/technical-notes/truncation#tn-sec-num-truncation). The solve ends at the event where \(\Phi\) reaches the floor, which is the edge of a spatially finite model, or else at the outer radius; the last rows of a truncated model are re-solved on a grid geometric in \(R_\mathrm{t} - r\), and the row at the floor is the crossing of the event. A request whose solve reaches no edge within its radius is refused with the reason stated. The solve is a serial integration, and it runs on the CPU on every device ([Section T:0.5](https://pianistpy.com/docs/reference/technical-notes/principles#tn-sec-tiers)).

## T:3.4 Completing the truncated profile toward the center and the truncation radius

The rows of the solve are sparse where the potential approaches the floor and where it approaches the center, so both ends are filled to the row density of the profile: at least a stated number of rows per decade of the potential and of the depth, in bins of a stated width, over the profile’s own span. The inner end is the inner series of [Section T:1.2.2](https://pianistpy.com/docs/reference/technical-notes/model#tn-sec-series), lowered by the central deficit where the model carries one, out to the depth to which the series is trusted (on a lowered model, moved inward to where the series and the rows of the solve agree); between that join and the threshold of the fill, the solve’s own interpolant is used. The outer end takes the radius by a log-log interpolation of the solve’s rows in the potential and the density likewise down to a fixed anchor, \(3.6\times10^{-7}P_0\), below which the density follows the power law of the two rows above the anchor and, below the Taylor switch, the closed form of the edge. The profile keeps every filled row that is finite, with positive density and the potential at or above the floor, the inner series rows inside the solve’s first radius included; a spatially finite model ends with one edge row at \(\Phi = 0\), at the radius where the Kepler exterior of its last row reaches zero; a profile that reaches no edge is read to its last row.

## T:3.5 The lowest admissible truncation value

The truncated DF of [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Eq. (B27) is non-negative and non-decreasing when the untruncated DF is non-decreasing above the truncation energy; the code asks in addition that the untruncated DF be non-negative there. From the computed untruncated DF on its support, the code takes the energy of its highest negative row and the end of its last decrease (by a relative amount above \(10^{-5}\)), and it accepts a truncation energy at or above the larger of the two and, for a Type II model, strictly above \(P(R_\mathrm{a})\) ([Section T:3.1](https://pianistpy.com/docs/reference/technical-notes/truncation#tn-sec-typeii)). The bound at \(P(R_\mathrm{a})\) is necessary but not sufficient: only the truncated solve places the edge, and a solve whose edge lies at or beyond \(R_\mathrm{a}\) is refused afterward with an error of its own, TYPE II EDGE. A truncation that no model can carry is refused before any stage runs: a cusp shallower than \(2\beta_\mathrm{c}(0)\) at the center, where the DF is negative there for every truncation ([An & Evans 2006b](https://doi.org/10.1086/501040)); a degenerate central series whose table the host floor alone would cut ([Section T:2.5.1](https://pianistpy.com/docs/reference/technical-notes/df#tn-sec-top-limit)); and a central DF that tends to a negative value, to minus infinity or to zero from below.

## T:3.6 Finding the truncation value for a target mass or truncation radius

Under Method I the untruncated DF does not depend on the truncation, so it is inverted once, and every evaluation of a target costs one truncated solve. The search runs on \(x = \ln{\mathcal{K}_\mathrm{t}}\) for a target \(v = M_\mathrm{t}\) or \(v = R_\mathrm{t}\), in four stages: deeper truncation from a probe until the model is admissible; a bracket by secant steps in \((\ln{\mathcal{K}_\mathrm{t}}, \ln v)\) toward the target, by at most a factor of 16 per step, until one admissible model lies on each side; where a step lands outside the admissible set, its boundary, namely the \(\mathcal{K}_\mathrm{t}\) at which \(R_\mathrm{t}\) reaches its limit, found by regula falsi on \(1/R_\mathrm{t}\) at the cap, and at \(R_\mathrm{a}\), where no model beyond it is finite, by secant steps on \(1/R_\mathrm{t}\) with bisection in \(\ln\mathcal{K}_\mathrm{t}\); and finally the Illinois form of regula falsi on \((\ln{\mathcal{K}_\mathrm{t}}, \ln(v/\text{target}))\) until \(|v/\text{target} - 1|\) is within \(10^{-6}\). A value of \(\mathcal{K}_\mathrm{t}\) is admissible when its solve succeeds, the model is spatially finite, \(R_\mathrm{t}\) is within the cap and, for Type II, inside \(R_\mathrm{a}\), and \(\mathcal{K}_\mathrm{t}\) lies at or above the gates and at or below \(0.9\) of the central potential; on the admissible set both targets decrease with \(\mathcal{K}_\mathrm{t}\). When no admissible model reaches the target, the request is refused with the reason stated and the value reached at the limit that binds; the solve takes no fallback.

## T:3.7 The closed-form profile of an untruncated model

An untruncated model solves no Poisson equation: its rows are the closed forms of the source profile’s density, potential and mass, geometric in \(R\) at the stated rows per decade from the innermost radius to where \(P\) falls below the floor, with rows added at the centers of the row-count bins of the potential and of the depth, and its depth near the center is the inner series. It carries no integration constant, so its radius of trust is infinite at every tier, recorded as such, and its table is not cut. Truncated products are not cut either.

## T:3.8 Checks of the self-consistency of a model

Each check is read on the rows of the profile through the profile object of [Section T:1.3](https://pianistpy.com/docs/reference/technical-notes/model#tn-sec-profile-object), so a model from a run and a model from a file are checked alike. The Poisson residual is Poisson’s equation in integral form on every interval between consecutive rows, including the edge row, \(\Phi(r_a) - \Phi(r_b) = \int M/r^2\,\mathrm{d} r\) and \(M(r_b) - M(r_a) = \int \varrho\,r^2\,\mathrm{d} r\), each relative to the larger of its two rows, widened on the floor event’s row by the root finder’s tolerance of the event over the solve’s relative tolerance. The round trip compares the density that the model’s own DF gives at a row, \(\varrho = g(R)\,C(\beta_\mathrm{c})\int_0^{\Phi} F(Q)\,[2(\Phi - Q)]^{1/2-\beta_\mathrm{c}}\,\mathrm{d} Q\), with \(g\) the anisotropy’s factor of [Chiang+(2026d)](https://arxiv.org/abs/2610.28758), Eq. (B44), with \(F\) read through the lookup, against the density of the row. It is evaluated at the rows nearest to each half-decade of radius and at every row from the last of those to the edge, so that the check reads what a dense read would read. The row density is the count of rows per bin that the fills keep; it is silent where every bin is full and refused at any shortfall.
