Skip to content

NNIDist(): make lower bounds valid; adopt Fack theorem 3 - #222

Open
ms609-agent wants to merge 2 commits into
mainfrom
fix/nni-lower-bound
Open

ms609-agent wants to merge 2 commits into
mainfrom
fix/nni-lower-bound

Conversation

@ms609-agent

Copy link
Copy Markdown
Collaborator

Fixes #220.

Why the bound was wrong

The error came from our own code in #28 (2020), not from the literature. Li et al. (1996) and Fack et al. (2002) bound the diameter of NNI tree space: the largest distance between any two trees with n leaves. #28 added these diameter values into a pairwise distance:

  • best_lower added exact_diameter for each region. A diameter bounds one pair's distance from above.
  • lower added min_diameter = max(k, ⌈lg(N!)/4⌉), a lower bound on the diameter. It equals k (the number of unmatched edges) only for k < 37.

The only pairwise lower bound in either paper is one NNI per unmatched edge (Li et al. 1996, §4). That was what lower computed before #28. Li et al.'s Lemma 3 also rules out building a lower bound from exact per-region distances, because optimal paths can touch matched edges.

Changes

  • lower and best_lower both count unmatched edges, so they are now equal. best_lower is kept for compatibility.
  • fack_upper / NNIDiameter()$fackMax use Fack et al. (2002) theorem 3. It is never looser than the theorem 1 bound used before; for example, an 8-leaf region gives 17 instead of 18, and a 20-leaf region gives 85 instead of 98. This tightens loose_upper and best_upper.
  • Document all seven NNIDist() fields and the reasoning behind the bounds. This also fixes the broken fackMax formula in the Rd.
  • Remove the now-unused min_diameter table. Correct the provenance of exact_diameter: the 12-leaf value comes from Fack et al. (2002), table 2.
  • Tests: the reprex from the issue, plus an exhaustive check that every bound brackets the exact (BFS) distance for all pairs of 6-leaf trees, and of 7-leaf trees with skip_on_cran() (about 70 s).

test-tree_distance_nni.R and test-tree_distance.R pass locally; spell_check_package() is clean.

🤖 Generated with Claude Code

PR #28 (2020) summed diameter quantities into pairwise bounds.
best_lower added the exact diameter of each region, which bounds a
pair's distance from above; lower added a lower bound on the diameter,
which exceeds the number of unmatched edges from 37 edges.  Li et al.
(1996) give only one pairwise lower bound: one NNI per unmatched edge.
Both fields now report that count.

fack_upper / NNIDiameter()'s fackMax now use Fack et al. (2002)
theorem 3, which is never looser than the theorem 1 bound used before.

Document all seven NNIDist() fields; drop the unused min_diameter table;
add tests that every bound brackets the exact distance on all 6- and
7-leaf tree pairs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ms609-agent
ms609-agent requested a review from ms609 October 19, 2026 14:22
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Performance benchmark results

Call Status Change Time (ms)
ClusteringInfoDistance(tr280) ⚪ NSD -3.23% 5.21, 5.03 →
5.38, 5.23
ClusteringInfoDistance(tr50) ⚪ NSD -1.43% 3.75, 3.81 →
3.88, 3.62
LAPJV(test2000) ⚪ NSD -0.68% 70.2, 70.9 →
70.9, 71.4
LAPJV(test40) ⚪ NSD -0.95% 0.0107, 0.0103 →
0.0106, 0.0106
LAPJV(test400) ⚪ NSD -0.76% 1.97, 1.92 →
1.98, 1.94
MutualClusteringInfo(tr280) 🟠 Slower 🙁 -58.93% 9.22, 9.01 →
14.6, 14.3
MutualClusteringInfo(tr50) ⚪ NSD -130.67% 14.3, 6.87 →
16.5, 17.1
NNIMaxStep(2000, normalize = TRUE) ⚪ NSD -2.57% 0.104, 0.104 →
0.106, 0.108
NNIMaxStep(300, normalize = TRUE) ⚪ NSD -2.19% 0.0169, 0.0168 →
0.0174, 0.017
NNIMaxStep(300) ⚪ NSD -2.32% 0.00511, 0.00525 →
0.00524, 0.00537
PathDist(postTrees) ⚪ NSD -1.98% 1.36, 1.35 →
1.39, 1.35
PhylogeneticInfoDistance(tr180) ⚪ NSD 1.95% 34.7, 33.2 →
33.8, 33.8
PhylogeneticInfoDistance(tr50) ⚪ NSD -2.77% 23.6, 22.6 →
23.8, 23.8
RobinsonFoulds(tr280) ⚪ NSD -1.67% 3.14, 3.13 →
3.2, 3.17
RobinsonFoulds(tr280) ⚪ NSD -1.74% 2.65, 2.62 →
2.67, 2.69
RobinsonFoulds(tr50) ⚪ NSD -3.65% 2.58, 2.6 →
2.74, 2.64

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NNIDist(): best_lower is not a valid lower bound

1 participant