Greedy landmark-selection strategies over a FiniteMetricSpace[Int] -- the first step of building any witness complex (De Silva & Carlsson, "Topological estimation using witness complexes", 2004; see .claude/WORKLOG-witness-complex.md, checked against JavaPlex's own LandmarkSelector implementations). Landmarks are always a SUBSET of the ambient point set (their own ambient indices), matching JavaPlex's convention -- not arbitrary points outside it, which the original paper allows but no downstream tool actually uses.
Both selectors assume metricSpace's own elements are the contiguous range 0 until metricSpace.size -- the same assumption every other coface stream in this codebase already makes of its own FiniteMetricSpace[Int] (via SimplexIndexing's combinatorial-number-system enumeration; see IntMetricSpace's own doc), not a new one introduced here.
The covering radius of an ARBITRARY landmark set, not necessarily one maxmin/random chose -- R = max_x min_{l in landmarks} d(x,l). maxmin/random already compute this as part of their own selection loop and return it via LandmarkSelection; this standalone version is for a caller who already has a landmark set (hand-picked, or reused from an earlier selection) and wants R for it -- e.g. to apply the JavaPlex tutorial's own 2R threshold recipe to landmarks it didn't just pick. O(metricSpace.size * landmarks.size), the same cost random's own inline version (now just this call) always was.
The covering radius of an ARBITRARY landmark set, not necessarily one maxmin/random chose -- R = max_x min_{l in landmarks} d(x,l). maxmin/random already compute this as part of their own selection loop and return it via LandmarkSelection; this standalone version is for a caller who already has a landmark set (hand-picked, or reused from an earlier selection) and wants R for it -- e.g. to apply the JavaPlex tutorial's own 2R threshold recipe to landmarks it didn't just pick. O(metricSpace.size * landmarks.size), the same cost random's own inline version (now just this call) always was.
Sequential maxmin (furthest-point) sampling: start from firstLandmark, then repeatedly add the point currently furthest (in the ambient metric) from every landmark chosen so far. Deterministic given firstLandmark -- ties are broken by lowest ambient index (iterating sortedElements ascending and using maxBy, whose "first occurrence wins a tie" behavior does this for free), both for reproducibility and so tests can pin an exact landmark set.
Sequential maxmin (furthest-point) sampling: start from firstLandmark, then repeatedly add the point currently furthest (in the ambient metric) from every landmark chosen so far. Deterministic given firstLandmark -- ties are broken by lowest ambient index (iterating sortedElements ascending and using maxBy, whose "first occurrence wins a tie" behavior does this for free), both for reproducibility and so tests can pin an exact landmark set.
Also returns the resulting covering radius R = max_x min_{l in L} d(x,l) (JavaPlex's own getMaxDistanceFromPointsToLandmarks()) -- free, since the greedy loop already tracks minDistToLandmarks for every point; callers use it to pick a maxFiltrationValue (e.g. 2R, as the tutorial does).
Also returns each chosen landmark's OWN insertion radius (LandmarkSelection.insertionRadius), lambda in the greedy-permutation literature: lambda_p = d(p, {landmarks already chosen when p was added}), i.e. exactly minDistToLandmarks(next) read just before that iteration's update -- free for the same reason the covering radius is. firstLandmark's own lambda is Double.PositiveInfinity (there is no "distance to the empty set"); every later entry is a real, finite, non-increasing (in selection order) value. numLandmarks = metricSpace.size gives the FULL greedy permutation of the whole space, not just a landmark subset -- this is how streams.SheehyRipsSimplexStream (Cavanna-Jahanseir-Sheehy 2015's sparse-filtration construction) gets its own greedy permutation, reusing this loop rather than a second copy of it.
Uniform random selection of numLandmarks distinct ambient indices, seeded for reproducibility. Cheaper than maxmin (O(size) vs O(numLandmarks * size)) but gives no covering guarantee -- outliers can be missed entirely, unlike maxmin's worst-case coverage bound. The covering radius is still computed and returned (an honest O(size * numLandmarks) pass after the fact), for the same maxFiltrationValue-picking use as maxmin's.
Uniform random selection of numLandmarks distinct ambient indices, seeded for reproducibility. Cheaper than maxmin (O(size) vs O(numLandmarks * size)) but gives no covering guarantee -- outliers can be missed entirely, unlike maxmin's worst-case coverage bound. The covering radius is still computed and returned (an honest O(size * numLandmarks) pass after the fact), for the same maxFiltrationValue-picking use as maxmin's.