import { VecN } from '@holotope/core'; import type { XpbdPointN, XpbdScalarConstraintEvaluationN, XpbdScalarConstraintN } from './xpbd-constraint.js'; /** Float64 value and point gradients of one unsigned simplex coordinate. */ export interface SimplexSquaredMeasureEvaluationN { readonly ambientDimension: number; readonly simplexDimension: number; /** `det(E^T E)` before division by `(k!)^2`. */ readonly gramDeterminant: number; /** Squared intrinsic k-measure of the simplex. */ readonly squaredMeasure: number; /** Intrinsic k-measure of the simplex. */ readonly measure: number; /** Gradients of `squaredMeasure` in point order. */ readonly gradients: readonly VecN[]; } export interface XpbdSimplexSquaredMeasureConstraintNOptions { readonly id: string; readonly points: readonly XpbdPointN[]; readonly restSquaredMeasure: number; /** Inverse stiffness for the squared-measure coordinate. Default zero. */ readonly compliance?: number; } export interface XpbdSimplexSquaredMeasureConstraintEvaluationN extends XpbdScalarConstraintEvaluationN, SimplexSquaredMeasureEvaluationN { readonly restSquaredMeasure: number; readonly restMeasure: number; readonly error: number; } /** Float64 value and point gradients of one signed full-dimensional simplex. */ export interface OrientedSimplexMeasureEvaluationN { readonly ambientDimension: number; /** Always equal to `ambientDimension`. */ readonly simplexDimension: number; /** `det([x1 - x0, ..., xN - x0])` before division by `N!`. */ readonly determinant: number; /** Signed N-measure in the ambient basis. */ readonly orientedMeasure: number; /** Absolute N-measure. */ readonly measure: number; /** Literal sign of the computed Float64 determinant. */ readonly orientation: -1 | 0 | 1; /** Gradients of `orientedMeasure` in point order. */ readonly gradients: readonly VecN[]; } export interface XpbdOrientedSimplexMeasureConstraintNOptions { readonly id: string; readonly points: readonly XpbdPointN[]; readonly restOrientedMeasure: number; /** Inverse stiffness for the oriented-measure coordinate. Default zero. */ readonly compliance?: number; } export interface XpbdOrientedSimplexMeasureConstraintEvaluationN extends XpbdScalarConstraintEvaluationN, OrientedSimplexMeasureEvaluationN { readonly restOrientedMeasure: number; readonly restMeasure: number; readonly error: number; } /** * Evaluates `det(E^T E) / (k!)^2` and its ambient point gradients, for a * `k`-simplex given as `k + 1` points in `R^n` with `k <= n`. * * Units: `squaredMeasure` is `length^(2k)`, `measure` is `length^k`, and * `gradients` are of the SQUARED measure — `length^(2k-1)` — in point order. * They are not gradients of `measure`; away from rank loss the two differ by * `2 * measure`. * * ## Rank semantics * * Exact rank deficiency and ill-conditioning are different statements and this * function keeps them apart. At exact rank deficiency — collinear points, a * repeated point, a collapsed simplex — `squaredMeasure` is exactly `0` and * every gradient component is exactly `0`. Callers may therefore use a zero * measure as a degeneracy test. * * That guarantee does **not** follow from the Float64 minor sum alone, and it is * worth being precise about why. Each minor's magnitude comes from a *pivoted * Gaussian elimination*, which divides, so an exactly singular minor can come * back as a small residue — for the collinear triangle `(0,0), (2,49), (4,98)` * the minor `[[2,4],[49,98]]` is `196 − 196`, exactly zero, but the elimination * pivots on 49 and forms the unrepresentable `fl(2/49)`. Because the Gram * determinant is a **sum of squares**, such a residue cannot cancel: it would * become a positive measure. The zero is therefore decided by an **exact dyadic * predicate** — entries scaled to a common power of two, hence exact integers, * then fraction-free Bareiss over `BigInt` — and that predicate, not the Float64 * structure, is what restores the contract. * * The predicate runs on **every** minor, unconditionally. An earlier revision * consulted it only when the elimination's magnitude fell inside a * `8k³·ε·Π‖row‖₂` bound; that bound is not valid, because partial pivoting's * error depends on the growth factor rather than on the product of row norms. A * 3×3 minor with row scales spanning `2^40` was measured returning `8.9e-3` for * an exactly singular matrix against a bound of `7.2e-13`, skipping the fallback. * No cheap bound decides rank here. * * No recovery direction is fabricated there, and none is available: the * unsigned measure has no unique gradient at exact collapse. Approaching one * flattened configuration along different paths gives different finite limits * for `d(measure)`, while `d(squaredMeasure)` tends to zero along all of them. * A caller that needs `d(measure)` must divide by `2 * measure` itself and * handle the vanishing denominator; this function will not choose a direction * on its behalf. * * Ill-conditioning is separate and is not reported. A thin simplex has a well * defined positive measure that this routine resolves to a relative error * proportional to the conditioning — not to its square, which is what forming * `E^T E` would cost. No conditioning threshold is applied, because no fitted * threshold on cell shape is a bound: one regressed on a fan of slivers * mispredicts a needle of slivers by a whole power. * * ## Cost * * That accuracy is bought, and the gradients pay for it. The determinant is a * sum over the `C(n, k)` axis subsets of `E`, and each gradient additionally * visits `k^2` cofactors per subset. Counted over `R^2..R^7` with `k <= 4`, the * FLOATING-POINT arithmetic alone stays competitive with forming the Gram matrix * — 1.28x at the widest — while value-with-gradients reaches 12.69x, at * `n = 7, k = 4`. **Both of those figures count floating-point operations only * and therefore exclude the exact predicate entirely**, so neither is this * function's cost; the measured wall clock below is. * * On top of them, every minor pays one exact `BigInt` Bareiss determinant, and * that is where the real cost now sits. It runs unconditionally — the invocation * rate is `1.0` on well-conditioned and degenerate cells alike, so there is no * data-dependent fallback rate to quote. What the data does change is the WIDTH: * the entries are scaled to a common power of two, so they carry * `exponent spread + 53` bits. Measured, that is 53-bit entries on an ordinary * cell, 80 on a `1e-8` sliver, 173 across a `2^60` exponent spread, and 1075 — * with a roughly 3200-bit determinant — for a cell containing a subnormal * coordinate. The worst case is a wide exponent range, NOT a bad aspect ratio. * * End to end against the released Gram evaluator, value-with-gradients: * * | shape | this | v0.0.16 | ratio | * | --- | --- | --- | --- | * | R2, k=2 | 1.95us | 1.26us | 1.54x | * | R3, k=3 | 3.63us | 2.80us | 1.30x | * | R4, k=4 | 6.64us | 5.19us | 1.28x | * | R7, k=4 | 44.42us | 5.55us | 8.00x | * * The `R7, k=4` row is the honest worst case and it is dominated by the exact * predicate: 35 minors, 35 `BigInt` determinants, and at least 1,260 `BigInt` * allocations per call, none of them reusable. Allocation shape in `Float64Array` * and `VecN` is unchanged from v0.0.16; the `BigInt` column is entirely new and is * the dominant allocation term at every size measured. * * There is a pivot and there is a branch: the elimination pivots, and it runs at * all only on the rare path where the exact magnitude has no Float64. What there * is *not* is a conditioning test — no fitted threshold on cell shape decides * anything, because no such threshold is a bound. * * There is no way to ask for the value without the gradients: they are always * computed, so a caller that reads only `measure` still pays for both. A * value-only entry point would not avoid the `BigInt` work either, because the * predicate is what produces the value. That is the honest cost of the current * signature rather than a property of the method, and it is the argument for such * an entry point if a high-`n`, high-`k` caller ever measures this as hot. */ export declare function evaluateSimplexSquaredMeasureN(positions: readonly VecN[]): SimplexSquaredMeasureEvaluationN; /** * Evaluates `det([x1 - x0, ..., xN - x0]) / N!` and its point gradients. * * The simplex must be full-dimensional in its ambient space. An embedded * lower-dimensional simplex has no ambient-rotation-invariant scalar * orientation without an additional authored normal-frame convention. */ export declare function evaluateOrientedSimplexMeasureN(positions: readonly VecN[]): OrientedSimplexMeasureEvaluationN; /** Unsigned simplex squared-measure equality consumed by the XPBD kernel. */ export declare class XpbdSimplexSquaredMeasureConstraintN implements XpbdScalarConstraintN { readonly id: string; readonly dimension: number; readonly simplexDimension: number; readonly points: readonly XpbdPointN[]; readonly restSquaredMeasure: number; readonly compliance: number; constructor(options: XpbdSimplexSquaredMeasureConstraintNOptions); evaluate(): XpbdSimplexSquaredMeasureConstraintEvaluationN; } /** Signed full-dimensional simplex-measure equality consumed by XPBD. */ export declare class XpbdOrientedSimplexMeasureConstraintN implements XpbdScalarConstraintN { readonly id: string; readonly dimension: number; readonly simplexDimension: number; readonly points: readonly XpbdPointN[]; readonly restOrientedMeasure: number; readonly compliance: number; constructor(options: XpbdOrientedSimplexMeasureConstraintNOptions); evaluate(): XpbdOrientedSimplexMeasureConstraintEvaluationN; } //# sourceMappingURL=xpbd-simplex-measure.d.ts.map