Anchor Triangle and Chart¶
The chart is fixed by three anchor trees. This module builds the anchor triangle from pairwise clade-branch-score distances, validates its conditioning, and places a query tree in barycentric coordinates. See The Three-Anchor 2D Chart.
AnchorTriangle
dataclass
¶
Geometry of the three-anchor 2D chart.
The plain constructor stores already-computed geometry. To build a chart
from three anchor trees (deriving the metric, measuring distances, running
the validation checks, and solving the embedding), use
:meth:from_anchors.
Attributes:
| Name | Type | Description |
|---|---|---|
P1, P2, P3 |
(ndarray, shape(2))
|
Anchor positions in the local Euclidean chart. |
r1, r2, r3 |
HifukuTree
|
The three anchor trees. |
dist |
(ndarray, shape(3, 3))
|
Pairwise metric distances. |
qual |
(ndarray, shape(3, 3))
|
Pairwise B_frac quality scores. |
metric |
TreeMetric
|
The chart's metric instance, built from an anchor set (this triangle's own anchors, or a global set when stitching several triplets into one frame). Required: a chart without a metric is broken. |
Source code in src/hifuku/anchor_triangle.py
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | |
tree_metric
property
¶
The chart's metric instance. Raises if the chart is missing its metric or its anchors, both of which a usable chart must have.
from_anchors
classmethod
¶
from_anchors(r1: HifukuTree, r2: HifukuTree, r3: HifukuTree, *, w: float = METRIC_W, stitch_anchors=None, b_frac_threshold: float = B_FRAC_THRESHOLD, anchor_q_min: float = ANCHOR_Q_MIN) -> 'AnchorTriangle'
Build a validated chart from three anchor trees.
This is the entry point that turns three anchor trees into a chart. It
does the work the plain constructor does not: it derives the chart's
metric from the anchor set, measures the three pairwise distances,
checks that the anchors form a usable chart (any failure raises), solves
the triangle embedding, and returns a fully populated instance. The
plain constructor is the low-level counterpart: it takes the already
computed fields (P1, P2, P3, dist, qual,
metric, ...) and only stores them. Use it when the geometry is
already known (loading a saved chart, or a test with fixed geometry);
use from_anchors when starting from trees.
Three checks run in order (any failure raises ValueError and stops):
-
Metric saturation - the B_frac quality score for each of the three anchor pairs must be >= b_frac_threshold. Near-zero B_frac means the branch-score metric has lost resolution for that pair (metric saturation; distinct from substitution saturation, which is a property of the sequence model).
-
Chartability (triangle inequality / Euclidean-embeddability) - the three distances must form a valid planar triangle. Equivalently, the double-centered squared-distance matrix must be positive semidefinite (Schoenberg's condition for 3 points). Checked before the sqrt in
embed_anchor_triangle. -
Triangle quality Q >= anchor_q_min - the anchor-position covariance must be non-degenerate (the three anchors are not near-collinear). The second axis carries too little information to form a useful 2D CV when Q is below this floor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
r1
|
HifukuTree
|
Anchor trees restricted to the shared global namespace, with
|
required |
r2
|
HifukuTree
|
Anchor trees restricted to the shared global namespace, with
|
required |
r3
|
HifukuTree
|
Anchor trees restricted to the shared global namespace, with
|
required |
w
|
float
|
Topology weight in [0, 1] for the chart's metric (default METRIC_W). |
METRIC_W
|
stitch_anchors
|
sequence of HifukuTree
|
Anchor set the metric normalizers are derived from. Default is the triangle's own three anchors. Pass the full global anchor set when stitching several triplet charts into one common frame, so every chart shares one metric. |
None
|
b_frac_threshold
|
float
|
Minimum B_frac quality score per anchor pair (default B_FRAC_THRESHOLD). |
B_FRAC_THRESHOLD
|
anchor_q_min
|
float
|
Minimum triangle quality Q (default ANCHOR_Q_MIN). |
ANCHOR_Q_MIN
|
Returns:
| Type | Description |
|---|---|
AnchorTriangle
|
The fully-built chart: embedding positions, distance/quality matrices, the three anchors, and the metric instance the chart owns. |
Raises:
| Type | Description |
|---|---|
ValueError
|
On metric saturation, chartability failure, or insufficient triangle quality. The error message names the failing pair/triple and the relevant diagnostic values. |
Source code in src/hifuku/anchor_triangle.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | |
TreeMetric
¶
Bases: Protocol
Protocol for tree distance metrics used by the 2D CV.
A callable that takes two HifukuTree objects and returns a (distance, quality) pair. The quality score is a reliability / confidence value interpretable as a likelihood-like weight: high quality = the metric distinguishes the two trees well; low quality = metric saturation (the metric has lost resolution; distinct from substitution saturation).
Only the CBS concrete implementation (CBSMetric below) is provided. Code outside it must not assume CBS internals.
Source code in src/hifuku/metric/branch_score.py
CBSMetric
dataclass
¶
Normalized branch-score implementation of the TreeMetric protocol.
distance = d = sqrt((1 - w) * CBS^2 / c_ref + w * RF / r_ref): the
Kuhner-Felsenstein (1994) clade branch score combined with the Robinson-Foulds
topological distance, weighted by w in [0, 1] (0 = pure branch score,
1 = pure topology). c_ref and r_ref normalize the two terms; the
defaults 1.0 give the raw branch score at w = 0. Use :meth:for_anchors
to derive the normalizers from an anchor set.
quality = B_frac in [0, 1]; the length-overlap fraction. Near 0 = branch-
score saturation; near 1 = similar trees.
kuhner1994branch, robinson1981rf.
Source code in src/hifuku/metric/branch_score.py
|
for_anchors
classmethod
¶
Build a metric whose normalizers are the mean CBS^2 and mean RF over the anchor set.
w then means the same across datasets, and every chart built on these
anchors shares one metric, so the charts stitch into one common frame.
The anchors must have global_leaf_indices set.
Source code in src/hifuku/metric/branch_score.py
compute_anchor_distances
¶
compute_anchor_distances(r1: HifukuTree, r2: HifukuTree, r3: HifukuTree, metric: TreeMetric) -> tuple[np.ndarray, np.ndarray]
Compute pairwise distances and quality scores for three anchor trees.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
r1
|
HifukuTree
|
Anchor trees, each restricted to the shared global taxon namespace
with |
required |
r2
|
HifukuTree
|
Anchor trees, each restricted to the shared global taxon namespace
with |
required |
r3
|
HifukuTree
|
Anchor trees, each restricted to the shared global taxon namespace
with |
required |
metric
|
TreeMetric
|
The metric to use for the pairwise distances (see |
required |
Returns:
| Type | Description |
|---|---|
(dist, qual) : two (3, 3) float64 arrays
|
|
Source code in src/hifuku/anchor_triangle.py
embed_anchor_triangle
¶
embed_anchor_triangle(d12: float, d13: float, d23: float) -> tuple[np.ndarray, np.ndarray, np.ndarray]
Embed three anchor trees in a 2D plane from their pairwise tree distances.
This is the law-of-cosines (classical MDS for three points) embedding:
P1 = (0, 0)
P2 = (d12, 0)
x3 = (d12^2 + d13^2 - d23^2) / (2 * d12)
y3 = sqrt(d13^2 - x3^2)
P3 = (x3, y3)
The resulting positions satisfy |P1-P2| = d12, |P1-P3| = d13, |P2-P3| = d23, providing the local Euclidean chart of tree space.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d12
|
float
|
Pairwise CBS distances between anchors 1-2, 1-3, and 2-3. |
required |
d13
|
float
|
Pairwise CBS distances between anchors 1-2, 1-3, and 2-3. |
required |
d23
|
float
|
Pairwise CBS distances between anchors 1-2, 1-3, and 2-3. |
required |
Returns:
| Type | Description |
|---|---|
P1, P2, P3 : np.ndarray, shape (2,)
|
Anchor positions in the 2D chart. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If d12 == 0 (degenerate anchors 1 and 2), or if the three distances violate the triangle inequality (the square inside the sqrt is negative, indicating the distances are not Euclidean-embeddable). The latter is the Euclidean-embeddability (Schoenberg) condition for 3 points. |
Notes
The 2D plane is a local Euclidean chart of tree space, which is CAT(0) / non-Euclidean. This chart is valid only where the sampled region is approximately flat. The out-of-plane residual z (Section 4.3/4.11) quantifies the a-posteriori deviation from flatness per sample.
kuhner1994branch : CBS split branch score; distances used here.
Source code in src/hifuku/anchor_triangle.py
|
triangle_quality
¶
Compute the triangle quality Q = lambda_min / lambda_max.
Q is the ratio of the minimum to the maximum eigenvalue of the 2x2 population covariance of the three anchor positions:
centroid = (P1 + P2 + P3) / 3
Cov = (1/3) * sum_i (Pi - centroid)(Pi - centroid)^T
Q = lambda_min(Cov) / lambda_max(Cov)
Q in (0, 1]: 1 at the equilateral triangle, 0 at collinear anchors. Q is scale-invariant and pivot-independent. It is the a-priori proxy for geometric dilution of precision (GDOP): trilateration is most accurate when the query sees the three anchors ~120 degrees apart, which occurs at the centroid of an equilateral triangle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P1
|
(ndarray, shape(2))
|
Anchor positions in the 2D Euclidean chart. |
required |
P2
|
(ndarray, shape(2))
|
Anchor positions in the 2D Euclidean chart. |
required |
P3
|
(ndarray, shape(2))
|
Anchor positions in the 2D Euclidean chart. |
required |
Returns:
| Type | Description |
|---|---|
(Q, lambda_min, lambda_max) : (float, float, float)
|
Q is the quality ratio; lambda_min and lambda_max are the covariance eigenvalues (for diagnostic reporting). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If lambda_max is zero (all three anchors are identical points). |
Notes
parkinson1996gdop : GDOP, geometric dilution of precision in navigation; Q is the anchor-level GDOP proxy prior to any sample evaluation.
Source code in src/hifuku/anchor_triangle.py
barycentric
¶
Map a query tree's anchor distances to barycentric coordinates plus the out-of-plane residual z.
Given distances s1, s2, s3 from the query to the three anchors (obtained via the CBS TreeMetric), solve for the planar position X and convert to barycentric coordinates (lambda1, lambda2, lambda3).
Solve method: linear trilateration. Write:
f_i(X) = |X - Pi|^2 - si^2
Subtract f_1 from f_2 and f_3 to cancel |X|^2, giving a 2x2 linear system:
2 (P2 - P1) . X = (|P2|^2 - |P1|^2) - (s2^2 - s1^2)
2 (P3 - P1) . X = (|P3|^2 - |P1|^2) - (s3^2 - s1^2)
Solve for X (the in-plane position), then convert to barycentric via:
[lambda1, lambda2]^T = [P1-P3, P2-P3]^{-1} (X - P3)
lambda3 = 1 - lambda1 - lambda2
Out-of-plane residual (measure-only). After solving for X, the residual c = f_1(X) = f_2(X) = f_3(X) satisfies c = -z^2, where z is the distance of the true tree position from the anchor plane. When the three distances si are planar-consistent, z ~ 0. When the query lies off the plane (the 2D CV is lossy there), z > 0. No code in the loop may branch on z; it is stored as a diagnostic only.
Geometric dilution of precision (GDOP): the reliability of the in-plane position depends on the angles at which the query sees the three anchors. With unit line-of-sight vectors u_i = (X - Pi) / |X - Pi| and H = [u1^T; u2^T; u3^T], GDOP = sqrt(trace((H^T H)^{-1})). GDOP is smallest at the triangle centroid (equilateral, ~120 deg between anchors) and grows as the query moves outside the triangle. GDOP is the in-plane companion to z; both are measure-only diagnostics. parkinson1996gdop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
s1
|
float
|
CBS distances from the query tree to r1, r2, r3. |
required |
s2
|
float
|
CBS distances from the query tree to r1, r2, r3. |
required |
s3
|
float
|
CBS distances from the query tree to r1, r2, r3. |
required |
triangle
|
AnchorTriangle
|
Precomputed anchor geometry. |
required |
Returns:
| Type | Description |
|---|---|
(lambdas, z) : (np.ndarray shape (3,), float)
|
|
Source code in src/hifuku/anchor_triangle.py
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 | |
gdop
¶
Geometric dilution of precision at in-plane query position X.
GDOP = sqrt(trace((H^T H)^{-1})) where H = [u1^T; u2^T; u3^T] and u_i = (X - Pi) / |X - Pi| are unit line-of-sight vectors to the three anchors. GDOP is a principled measure of in-plane trilateration reliability: it is smallest (2/sqrt(3) ~= 1.155) at the centroid of an equilateral triangle and grows without bound as the query moves far outside the triangle (lines of sight become near-parallel).
GDOP is the in-plane companion to the out-of-plane residual z: - z measures departure from the chart's plane (how non-Euclidean the local tree space is), - GDOP measures how weakly the in-plane position is constrained by the three anchor distances.
No code may branch on GDOP in the loop. It is a measure-only diagnostic. parkinson1996gdop : GDOP definition and navigation context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
(ndarray, shape(2))
|
In-plane position (e.g., from the Cartesian coordinates derived during the barycentric solve, or from lambda @ [P1, P2, P3]). |
required |
triangle
|
AnchorTriangle
|
|
required |
Returns:
| Type | Description |
|---|---|
float
|
GDOP >= 0. Very large values indicate poor in-plane conditioning. |