AlphaComplexDQPBuilder

org.appliedtopology.tda4j.alpha.AlphaComplexDQPBuilder
class AlphaComplexDQPBuilder(val space: PowerDistance, val maxPower: Double, val maxDimension: Int, val settings: AlphaDQPSettings = ...)

Algorithm 1: compute the d-skeleton of Alpha(S, p, a1).

Outer loop over dimension, inner loop over base vertex. B and U are built once per (dimension, vertex) rather than once per simplex, which is the only place ambient dimension enters the cost at all.

Attributes

Experimental
true
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Type members

Classlikes

final case class Found(cell: Simplex[Int], weight: Double, witness: Option[Array[Double]])

One record produced by a single successful QP solve.

One record produced by a single successful QP solve.

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class Object
trait Matchable
class Any
Show all

Value members

Concrete methods

def allFacetsPresent(sigma: Simplex[Int], present: HashSet[Simplex[Int]]): Boolean
def buildCandidates(k: Int, nbrs: IndexedSeq[IndexedSeq[Int]], alive: IndexedSeq[Boolean], byDim: IndexedSeq[IndexedBuffer[Simplex[Int]]], present: HashSet[Simplex[Int]]): Map[Int, IndexedBuffer[Simplex[Int]]]

Candidate k-simplices, bucketed by their minimal vertex.

Candidate k-simplices, bucketed by their minimal vertex.

k = 0: the vertices of G (line 6). k = 1: the edges of G (line 6). k >= 2: the k-simplices of Lazy_{k-1}(X) (line 8), i.e. every (k+1)-set all of whose facets are already known to be in the complex.

Each candidate is stored as its vertices other than the minimal one, because the base vertex is the bucket key. Considering only simplices whose minimum is x is the "x <= x_j1 <= ... <= x_jk" reduction of step 2(b): every simplex is then tested exactly once.

Attributes

def cechNeighbours(): IndexedSeq[IndexedSeq[Int]]

Line 1-2: the one-skeleton of the Cech complex of the weighted ball cover, Cech(S, p, a1).

Line 1-2: the one-skeleton of the Cech complex of the weighted ball cover, Cech(S, p, a1).

Only Cech neighbours can constrain the part of V_x that lies inside U_x: if U_x and U_z are disjoint then any y with pi_x(y) <= a1 already satisfies pi_x(y) <= pi_z(y). So restricting the QP's inequality constraints to N_G(x) is exact, not an approximation -- provided the optimum really is <= a1, which is precisely when we keep the simplex.

Uses the VP-tree spatial index already in FiniteMetricSpace.scala (JVPTree, via PowerDistance.toMetricSpace) rather than the naive O(N^2) all-pairs scan, when maxPower is finite -- see WORKLOG.md for why this only helps a genuinely-truncated complex: AlphaShapeDQP's default (maxRadius = Infinity, to match HelixDelaunay) makes every alive point trivially everyone's neighbour, where a spatial index buys nothing, so that case is still handled directly rather than via a VP-tree query with an infinite radius.

The per-point query radius radius(i) + maxRadiusOverAll is a conservative superset bound, not the exact pairwise one (weights make each point's true radius(i)+radius(j) threshold vary per pair) -- so every candidate the tree returns still goes through the same exact check as before. Correctness therefore doesn't depend on the spatial index at all, only its performance does; see AlphaComplexDQPSpatialIndexSpec for a brute-force cross-check across many random weighted and unweighted configurations.

Attributes

def clampMonotone(byDim: IndexedSeq[IndexedBuffer[Simplex[Int]]], weights: HashMap[Simplex[Int], Double]): Unit

w is monotone along faces by construction -- the QP for a face has strictly fewer equality constraints, hence a larger feasible set and a smaller optimum -- but floating point can violate it by an ulp or two, which some persistence algorithms will not forgive.

w is monotone along faces by construction -- the QP for a face has strictly fewer equality constraints, hence a larger feasible set and a smaller optimum -- but floating point can violate it by an ulp or two, which some persistence algorithms will not forgive.

Attributes

def coordsOf(x: Int): Option[Array[Double]]
def solveAtVertex(x: Int, candidates: IndexedBuffer[Simplex[Int]], nb: IndexedSeq[Int], wsCap: Int): IndexedBuffer[Found]

Steps 2(a)-(b): assemble the dual data for the cell V_x once, then run one QP per candidate simplex based at x.

Steps 2(a)-(b): assemble the dual data for the cell V_x once, then run one QP per candidate simplex based at x.

Attributes

def witnessOf(x: Int, nb: Array[Int], qp: DualQP): Option[Array[Double]]

KKT conditions (12): Phi(sigma) = y* = x - sum_i lambda_i (x_i - x). None when the input carried no coordinates, matching witness's own contract -- must stay None, not null: storing null under an otherwise-present map key makes .get return Some(null), not None.

KKT conditions (12): Phi(sigma) = y* = x - sum_i lambda_i (x_i - x). None when the input carried no coordinates, matching witness's own contract -- must stay None, not null: storing null under an otherwise-present map key makes .get return Some(null), not None.

Attributes

Concrete fields

val maxDimension: Int
val maxPower: Double
val n: Int