Skip to content

bottleneck and wasserstein return finite distances between diagrams that are infinitely far apart #105

Description

@sushovan4

Summary

bottleneck and wasserstein drop bars with non-finite death times and return a finite distance where the true distance is inf. The accompanying warning does not distinguish the case where the result is still correct from the case where it is not — and the warning count is inverted with respect to correctness.

Reproduced on persim 0.3.8.

Reproduction

import numpy as np, persim

A = np.array([[0.0, np.inf], [0.1, 0.5]])   # has an essential class
B = np.array([[0.0, 1.0],    [0.1, 0.5]])   # does not

persim.bottleneck(A, B)    # -> 0.5
persim.wasserstein(A, B)   # -> 0.7071067811865476

There is no finite-cost matching between A and B: the essential bar cannot be matched to the diagonal, and B has no essential bar to match it to. The distance is inf. The returned 0.5 is the distance between the finite remainders.

The warning does not track correctness

Inputs bottleneck Correct? Warnings
A vs A 0.0 yes 2
A vs B 0.5 no — should be inf 1

The warning is emitted once per argument containing a non-finite death, so the correct call raises two and the incorrect call raises one. Neither its presence nor its absence certifies a result. The first row is correct only incidentally — dropping matching essential bars from both sides happens to preserve a distance of zero.

The text itself,

UserWarning: dgm1 has points with non-finite death times;ignoring those points

states the mechanism accurately but not the consequence, so it reads as a routine preprocessing note rather than "this return value is not the bottleneck distance". It is also removed entirely by the warnings.filterwarnings("ignore") that sits near the top of a great many analysis scripts.

wasserstein behaves the same way.

Suggested fix

Partition both diagrams by finiteness, per homological degree:

  • if the essential-bar counts differ, return inf without calling the matching;
  • if they agree, the distance is max(bottleneck(finite parts), max |bᵢ − b′ᵢ|) over the sorted essential births, since essential bars can only be matched to one another and matching (b, ∞) to (b′, ∞) costs |b − b′|.

Happy to open a PR if that shape looks right to you.


Found while building a backend-agnostic persistence-diagram interchange layer (akritihq/akriti); we depend on persim for distances. Fuller write-up in our RFC-0001 §9.1 and Appendix A.4.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions