Fix mergetree bug - #106
Open
ishikaghosh2201 wants to merge 5 commits into
Open
ishikaghosh2201 wants to merge 5 commits into
ishikaghosh2201 wants to merge 5 commits into
Conversation
ishikaghosh2201
requested review from
lizliz
and
a lite review from Copilot
September 10, 2026 16:27
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical findings affect smoothing edge cases, label preservation, and interleaving bounds.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes smoothing failures for Reeb and MergeTree graphs containing infinite-valued vertices, with added regression tests.
Changes:
- Smooth finite subgraphs and reattach infinite vertices.
- Preserve
MergeTreeoutput handling. - Adjust interleaving search bounds.
- Add smoothing and labeling tests.
File summaries
| File | Summary |
|---|---|
tests/test_reeb_class.py |
Infinite-node smoothing regression tests |
tests/test_merge_tree.py |
MergeTree smoothing and label tests |
cereeberus/cereeberus/reeb/reebgraph.py |
Infinite-vertex smoothing and validation |
cereeberus/cereeberus/reeb/merge.py |
MergeTree smoothing wrapper |
cereeberus/cereeberus/distance/interleave.py |
Distance-search bound changes |
Review details
Suppressed comments (6)
cereeberus/cereeberus/reeb/reebgraph.py:973
- The
set(...)deduplicates attachment vertices and creates only onetv -> inf_vedge regardless of how many parallel original edges(u, inf_v)exist. SinceReebGraphis aMultiDiGraph, this collapses parallel edges and makes every correspondingmap_Eentry point to the same edge, changing the graph topology. Add one output edge per original edge key and record each newly created key.
for tv in set(v for verts in attach_up.values() for v in verts):
R_eps.add_edge(tv, inf_v, reset_pos=False)
for u in preds:
new_edges_u = [
(tv, inf_v, R_eps.number_of_edges(tv, inf_v) - 1)
cereeberus/cereeberus/reeb/reebgraph.py:985
- The same deduplication collapses parallel original edges
(inf_v, w)into one output edge, so the lower-side edge map loses multiplicity as well. Preserve one newly createdinf_v -> bvedge for each original edge key, rather than adding edges once per distinct endpoint.
for bv in set(v for verts in attach_down.values() for v in verts):
R_eps.add_edge(inf_v, bv, reset_pos=False)
for w in succs:
new_edges_w = [
(inf_v, bv, R_eps.number_of_edges(inf_v, bv) - 1)
cereeberus/cereeberus/reeb/reebgraph.py:968
- These neighbor lists can include another infinite vertex. For a valid
-inf -> +infedge, the other endpoint is not part of the finitecomp_top/comp_bottommaps (and may not yet be present inmap_V), so this lookup raisesKeyError. Defer infinite-to-infinite edges and reconnect/map them separately after processing finite attachments.
preds = list(self.predecessors(inf_v))
attach_up = {u: comp_top[map_V[u]] for u in preds}
cereeberus/cereeberus/reeb/reebgraph.py:963
componentsis recomputed after each infinite vertex is added, so a second+inf(or-inf) vertex sharing a finite component sees the earlier infinite vertex as that component's top (or bottom). It is then connected to the previous infinite vertex instead of the finite boundary, turning parallel root branches into a chain. Compute these boundaries from a finite-only snapshot before adding any infinite vertices.
components = list(nx.weakly_connected_components(R_eps))
comp_top = {}
comp_bottom = {}
for comp in components:
tops = [v for v in comp if R_eps.up_degree(v) == 0]
bottoms = [v for v in comp if R_eps.down_degree(v) == 0]
for v in comp:
comp_top[v] = tops
comp_bottom[v] = bottoms
cereeberus/cereeberus/reeb/reebgraph.py:949
- Because the finite graph is built without the infinite nodes,
_get_next_internal_vert_namecannot reserve their names. If an infinite vertex is named like an internal vertex, for example('reeb_auto', 0), smoothing can generate that name inR_epsand thenadd_node(inf_v, ...)raises a duplicate-vertexValueError. Reserve all original infinite names or otherwise choose output names disjoint from them.
inf_nodes = [v for v in self.nodes if np.isinf(self.f[v])]
if inf_nodes:
# smooth the finite part, then reattach inf vertices afterward
finite_nodes = [v for v in self.nodes if v not in inf_nodes]
f_finite = {v: self.f[v] for v in finite_nodes}
G_finite = ReebGraph(self.subgraph(finite_nodes), f_finite)
tests/test_reeb_class.py:283
- This regression test only asserts that
tophas no outgoing edge. An isolatedtopalso satisfies that assertion, so the test would pass even if the new reattachment logic dropped the connection to the finite component; assert thattophas an incoming edge as well.
self.assertEqual(R_eps.up_degree('top'), 0)
- Files reviewed: 5/5 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+247
to
+251
| @@ -243,9 +248,10 @@ def dist_fit(self, pulp_solver=None, verbose=False, max_n_for_error=100): | |||
| if bound < best_bound: | |||
| best_bound = bound # to tighten the upper bound on the search space. this tries to go higher | |||
| low = mid + 1 | |||
| except ValueError: # infeasible assignment | |||
| low = mid + 1 | |||
|
|
|||
| high = min(high, best_bound - 1) # to tighten the upper bound on the search space. this tries to go higher | |||
ishikaghosh2201
force-pushed
the
fix-mergetree-bug
branch
from
September 10, 2026 16:40
dd1c803 to
026ffc1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes an
IndexErrorinReebGraph.smoothing_and_mapsthat crashedwhenever a Reeb graph (or
MergeTree) contained an infinite-valuedvertex — most commonly a
MergeTree'sv_infroot.Root cause: the delta-shift step, used to find "the edge slightly below"
a critical value, assumed every critical value has a finite neighbour to
shift toward. That assumption breaks at +/- infinity, since
inf - epsis still
inf, so the shifted slice collapsed to the isolated infinitevertex itself with no overlap below it.
Fix:
ReebGraph.smoothing_and_mapsnow smooths only the finite part of thegraph, then reattaches infinite vertices afterwards, reconnected to
whichever finite component(s) end up adjacent to them post-smoothing.
Handles both
+infand-inf, and multiple branches attachingdirectly to an infinite vertex without a shared merge point below it.
MergeTree.smoothing_and_mapsis now a thin re-wrap of the (alreadycorrect) inherited method, rather than duplicating that logic.
lower_vert[0]/upper_vert[0]indexing replaced with explicitlength checks and a clear
ValueError, as a backstop for any otherunhandled degenerate case.
Motivation and Context
MergeTree.smoothing()/smoothing_and_maps()crashed on any tree withits (always-present)
v_infroot, making smoothing unusable forMergeTreeobjects entirely. This is linked to issues #102 and #100.How has this been tested?
(3-node path +
v_inf),randomMergeTree, multipleepsvalues, ashared-merge-point two-branch tree, and two branches attaching
directly to
v_infwith no shared merge point below them.ReebGraph(non-MergeTree) case with a manuallyadded
+infnode, plus-infand combined+inf/-infgraphs.MapperGraphandInterleaveare unaffected: bothstructurally exclude infinite function values, so the new code path
is unreachable through either.
tests/test_reeb_class.pyandtests/test_merge_tree.py.make tests: all passing.Types of changes
Checklist
pyproject.tomlfile if a new version needs to be pushed to pypi. Note that if the number isn't incremented, the package will not be pushed to pypi, which is useful if this PR is only for updating documentation.make formatto clean up the code withblack.make tests).