Linking Number
Module: aether_core::linking, in crates/aether-core/src/linking.rs. It computes the Gauss linking number, the writhe and the knot determinant of closed polygons in \(\mathbb{R}^3\). It also states the rule under which a floating-point linking number may be reported as an integer. Evidence: 18 tests in tests/linking.rs.
Object
A closed polygon is a vertex list \(a_0, \ldots, a_{n-1} \in \mathbb{R}^3\) with \(n \ge 3\). The list is read cyclically: segment \(i\) runs from \(a_i\) to \(a_{(i+1) \bmod n}\). For two disjoint closed curves \(A\) and \(B\), with \(r_A\) a point on \(A\) and \(r_B\) a point on \(B\), the Gauss double integral
is an integer, and it is invariant under isotopies that keep the curves disjoint.
Closed form over segment pairs
Take segment \(p_1 \to p_2\) of \(A\) and segment \(p_3 \to p_4\) of \(B\). Write \(r_{ij} = p_j - p_i\) and \(\hat r_{ij} = r_{ij} / \lVert r_{ij} \rVert\). For unit vectors \(a, b, c\), the Van Oosterom–Strackee formula gives the signed solid angle of their spherical triangle:
The Gauss map sweeps a spherical quadrilateral with vertices \(\hat r_{13}, \hat r_{14}, \hat r_{24}, \hat r_{23}\). Fanning it from \(\hat r_{13}\) gives
The sum has no quadrature term and no length constant. The leading minus sign orients it to the integral. nerve fixed that sign against an independent midpoint quadrature, and linking_number_matches_midpoint_quadrature pins it again here.
The writhe applies the same sum to one curve against itself, over non-adjacent segment pairs:
Adjacent segments are coplanar with their shared vertex and contribute zero. The writhe depends on the embedding, is not an invariant, and is never rounded.
Error bound
\(u = 2^{-53}\) is the binary64 unit roundoff. The bound assumes IEEE-754 round-to-nearest, a correctly rounded sqrt and an atan2 accurate to 2 ulp. It treats the vertices as exact, so it covers the arithmetic and not the process that produced the coordinates. Terms of order \(u^2\) are dropped.
- Each difference \(p_j - p_i\) is rounded componentwise, and normalising it costs at most \(5u\) per component. Each unit direction therefore lies within \(7u\) of the exact direction.
- With directions perturbed by \(7u\), the computed numerator \(N\) and denominator \(D\) of each triangle lie within \(47u\) and \(60u\) of their exact values. Both are bounded by \(K = 128u\).
- Suppose the box of half-width \(K\) about \((D, N)\) misses the branch cut \(\{N = 0,\ D \le 0\}\), and \(\rho = \operatorname{hypot}(N, D) > 2K\). Then
atan2is smooth on the box, and the triangle's error is [ e = 2\left(\frac{\sqrt{2}\,K}{\rho - \sqrt{2}\,K} + 8u\right). ] - Recursive summation of \(m\) terms adds at most \(\gamma_m \sum \lvert \omega_{ij} \rvert\), with \(\gamma_m = mu/(1 - mu)\) (Higham, Accuracy and Stability of Numerical Algorithms, ch. 4). The final division by \(4\pi\) adds \(2u\,\lvert\widehat{\operatorname{Lk}}\rvert\).
where \(e_{ij}\) sums the two triangle errors of pair \(ij\) and the rounding \(u\,\lvert\Omega_1 + \Omega_2\rvert\) of their sum. nerve measured the deviation from the integer on \((2, 2n)\) torus links, at most \(2.16 \times 10^{-13}\) at 1024 segments and growing with the segment count, and rounded on that evidence. This port carries \(B\) instead.
What is certified
Let \(n = \operatorname{round}(\widehat{\operatorname{Lk}})\). GaussLinking::certify rounds only when
The interval \([\widehat{\operatorname{Lk}} - B,\ \widehat{\operatorname{Lk}} + B]\) then lies inside \((n - \tfrac12,\ n + \tfrac12)\), so \(n\) is the only integer it can contain. Half a unit is the threshold that nerve-periodic names as the only defensible one for a closed polygon.
| Verdict | Condition | Meaning |
|---|---|---|
Linked { lk } |
rule holds, \(n \ne 0\) | \(\operatorname{Lk} = n\). A split link has \(\operatorname{Lk} = 0\), so no isotopy that keeps the curves disjoint can separate them (tangle's theorem T3, read contrapositively). |
ZeroLinking |
rule holds, \(n = 0\) | \(\operatorname{Lk} = 0\) is proven, and it certifies nothing. The Whitehead link has \(\operatorname{Lk} = 0\) and is not split. |
Undetermined { lk_estimate, error_bound } |
rule fails, including NaN | The estimate is returned. No claim is made. |
No verdict reads "unlinked", because the linking number cannot supply the converse of the certificate. The certificate is conditional on the first-order bound above. It is not interval arithmetic.
| Quantity | Status |
|---|---|
| Integer \(\operatorname{Lk}\) | Certified, conditional on the first-order bound |
| \(\widehat{\operatorname{Lk}}\), \(B\) | Computed value and a first-order bound on its error |
| Writhe | Estimate, no certificate |
| Knot determinant | Exact i128 arithmetic on the diagram. Extracting the diagram uses an absolute tolerance of \(10^{-9}\) and carries no error bound. |
Knot determinant
Consider a knot diagram in which crossing \(c\) has over-arc \(o\), incoming under-arc \(a\) and outgoing under-arc \(b\). The Alexander matrix rows are
At \(t = -1\) these become \((-1, -1, 2)\) and \((1, 1, -2)\), which are exact negatives. Negating a row leaves \(\lvert\det\rvert\) unchanged, so for a diagram with \(c\) crossings
where \(M'\) is any \((c-1)\times(c-1)\) minor of the \(c \times c\) matrix at \(t = -1\). No crossing sign is computed. The minor is evaluated exactly in i128 by Bareiss elimination. The diagram comes from a projection along \(z\), retried over eight fixed reorientations when that projection is not generic. The projection tolerance \(10^{-9}\) is the source's and is not scale-free. The determinant is an invariant but not a complete one: \(4_1\) and \(5_1\) both give 5, and a value of 1 does not certify the unknot.
Refusals
Every refusal is a LinkingError, never a number.
| Variant | Condition |
|---|---|
TooFewVertices { curve, len } |
A curve has fewer than three vertices. nerve returned 0.0, or Some(1) for the determinant. |
NonFinite { curve, vertex } |
A coordinate is NaN or infinite. |
Intersecting { segment_a, segment_b } |
Two segments meet, or come within rounding of meeting. This is the branch-cut condition of step 3, and it involves no length scale. |
OutOfRange { segment_a, segment_b } |
A difference vector's squared length is not a normal binary64 number, that is, a length outside roughly \([1.5 \times 10^{-154},\ 1.3 \times 10^{154}]\). The analysis does not hold there. |
NoGenericProjection |
None of the eight reorientations gives a generic projection with an i128-representable determinant. |
Rust API
pub fn linking_number(a: &[[f64; 3]], b: &[[f64; 3]]) -> Result<GaussLinking, LinkingError>;
pub fn writhe(a: &[[f64; 3]]) -> Result<f64, LinkingError>;
#[cfg(feature = "alloc")]
pub fn knot_determinant(curve: &[[f64; 3]]) -> Result<u128, LinkingError>;
pub struct GaussLinking { pub value: f64, pub error_bound: f64 }
impl GaussLinking { pub fn certify(&self) -> LinkVerdict; }
pub enum LinkVerdict {
Linked { lk: i64 },
ZeroLinking,
Undetermined { lk_estimate: f64, error_bound: f64 },
}
pub enum LinkingError {
TooFewVertices { curve: usize, len: usize },
NonFinite { curve: usize, vertex: usize },
Intersecting { segment_a: usize, segment_b: usize },
OutOfRange { segment_a: usize, segment_b: usize },
NoGenericProjection,
}
linking_number visits \(O(\lvert a\rvert\,\lvert b\rvert)\) segment pairs and does not allocate. writhe visits \(O(\lvert a\rvert^2)\) pairs and does not allocate. Reversing one curve negates the linking number, and swapping the curves leaves it unchanged.
Test evidence
tests/linking.rs holds 18 #[test] functions. The generators twisted_band, rotate, torus_knot and figure_eight are nerve's. The braid words are tangle's.
cargo test -p aether-core --test linking
| Test | Pins |
|---|---|
hopf_link_certifies_linked_with_lk_of_magnitude_one |
The Hopf link (one twist, 200 vertices) certifies \(\lvert lk \rvert = 1\) |
torus_link_2_2k_certifies_lk_k_with_one_sign_across_the_family |
Coplanar rings sum to exactly 0.0 and give ZeroLinking. The \((2,2k)\) links certify \(\lvert lk\rvert = k\) for \(k = 1..4\), all with one sign |
whitehead_link_has_lk_zero_and_is_never_certified_as_separable |
Braid controls give \(\lvert lk\rvert\) of 1 and 2. The Whitehead braid [1, -2, 1, -2, -2] gives ZeroLinking, with its value inside \(B\) |
linking_number_matches_midpoint_quadrature |
The closed form agrees with a midpoint rule within \(2\times10^{-2}\) at 500 vertices, for 0 to 2 twists. This pins the sign and the \(1/(4\pi)\) |
error_bound_covers_the_measured_distance_to_the_integer |
For 1 to 3 twists at \(m \in \{64, 256, 512\}\), the deviation from the integer is at most \(B\), and \(B < 10^{-6}\) |
certification_rounds_only_when_the_bound_proves_the_rounding |
\((0.9, 0.2)\) certifies 1. \((0.7, 0.2)\), \((0.5, 10^{-12})\), \((3.0, 0.5)\), NaN and \(\infty\) give Undetermined |
linking_number_is_invariant_under_rigid_rotation |
Value agrees within \(10^{-10}\), with the same verdict |
linking_number_is_invariant_under_translation |
Translation by \((123.5, -7.25, 4096)\) keeps the value within \(10^{-10}\) |
linking_number_and_bound_are_bitwise_invariant_under_power_of_two_scaling |
Value and bound are bitwise identical for \(c \in \{0.25, 0.5, 2, 1024, 1/1024, 2^{400}\}\) |
linking_number_is_invariant_under_cyclic_shift_of_the_start_vertex |
Value agrees within \(10^{-12}\) |
reversing_one_orientation_or_reflecting_negates_lk |
Reversing \(A\), reversing \(B\) or reflecting negates \(lk\). Reversing both preserves it |
swapping_the_curves_keeps_lk |
Value agrees within \(10^{-12}\) |
a_strand_through_the_other_curve_is_refused_and_either_side_is_certified |
Offsets of \(\pm 1/1024\) certify \(\lvert lk\rvert = 1\) or ZeroLinking. Offset 0 and a shared vertex are Intersecting |
too_few_vertices_and_non_finite_coordinates_are_refused_everywhere |
All three functions refuse, and the refusal names the curve and vertex |
out_of_range_differences_are_refused_rather_than_summed_as_zero |
Scales \(2^{600}\) and \(2^{-600}\) give OutOfRange, not a false zero |
writhe_is_real_valued_odd_under_reflection_and_refuses_self_intersection |
A planar circle's writhe is below \(10^{-12}\). A twisted curve's writhe is not integral. Reflection negates it. A lemniscate of Gerono is refused |
knot_determinant_matches_the_classical_table |
Unknot 1, \(3_1\) 3, \(4_1\) 5, \(5_1\) 5, \(7_1\) 7, and braid closures give 3 and 5 |
knot_determinant_is_projection_invariant_mirror_blind_and_incomplete |
Four rotations of the trefoil give 3. The mirror trefoil gives 3. \(4_1\) and \(5_1\) agree |
Deviations from the sources
The error bound \(B\) and the rounding rule built on it appear in neither source. They are derived for this port. nerve returned 0.0 from the linking number for fewer than three vertices and for degenerate segment pairs, and Some(1) from the determinant for fewer than three vertices. Here each of these is a typed refusal. The module documentation names no source component that was left unported.
Provenance
- nerve:
nerve/crates/nerve-topo/src/lib.rs(linking_number_closed,omega,solid_angle,writhe_closed),nerve/crates/nerve-melt/src/lib.rs(alexander_det_minus_one,det_for_orientation,det_bareiss) andnerve/crates/nerve-periodic/src/lib.rs(the half-unit threshold). - tangle:
tangle/tangle/certify.py, the one-directionalLINKEDcertificate (CERTIFIED LINKEDwhen \(lk \ne 0\),NOT CERTIFIED LK_ZEROwhen \(lk = 0\)), its theorem T3, and the braid words oftests/test_certify.pyandtests/test_alexander.py.