Skip to content

Proposal: update semantics for nonisomorphic trees with order 0 or 1#8083

Merged
dschult merged 5 commits into
networkx:mainfrom
rossbar:non-isomorphic-tree-edge-case
Jul 8, 2025
Merged

Proposal: update semantics for nonisomorphic trees with order 0 or 1#8083
dschult merged 5 commits into
networkx:mainfrom
rossbar:non-isomorphic-tree-edge-case

Conversation

@rossbar
Copy link
Copy Markdown
Contributor

@rossbar rossbar commented May 31, 2025

While putting together an NX guide, I noticed a few (what I believe to be) inconsistencies in nonisomorphic_trees and number_of_nonisomorphic_trees, specifically for n = 0 and n = 1.

Currently, these functions simply raise a ValueError (with no exception message!) when n < 2. However, I think this is inconsistent with the definition of a tree, both generally and as used elsewhere in the library. For example, a graph with a single node and no edges is technically a tree:

>>> nx.is_tree(nx.empty_graph(1))
True

is_tree raises a PointlessConcept exception for the null graph (i.e. no nodes or edges), which makes sense. However, I don't think nx.number_of_nonisomorphic_trees should raise in that case (nor nx.nonisomorphic_trees, though this is subjective - see inline comment for details).

Proposed semantics

I propose to change the behavior of nx.nonisomorphic_trees and nx.number_of_nonisomorphic_trees for order=0 and order=1. I went about this in a test-driven manner, so the first commit 9de48f2 captures the proposed changes most clearly (the order < 0 case is consistent with current behavior, albeit with an added message to the ValueError)

@rossbar rossbar added type: Enhancements Discussion Issues for discussion and decision making labels May 31, 2025
Comment thread networkx/generators/nonisomorphic_trees.py
Copy link
Copy Markdown
Member

@Peiffap Peiffap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order==1 case is wrong I think.

Other than that: I see your nit_list and I raise you... my list of nits.

Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/nonisomorphic_trees.py
Comment thread networkx/generators/nonisomorphic_trees.py
Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/tests/test_nonisomorphic_trees.py Outdated
@pytest.mark.parametrize("n", range(5))
def test_nonisomorphic_tree_low_order_agreement(n):
"""Ensure all the order<2 'special cases' are consistent."""
assert len(list(nx.nonisomorphic_trees(n))) == nx.number_of_nonisomorphic_trees(n)
Copy link
Copy Markdown
Member

@Peiffap Peiffap May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, and it doesn't matter here, but since Ale just mentioned it it's fresh in my mind. We can avoid loading the whole list in memory.

Suggested change
assert len(list(nx.nonisomorphic_trees(n))) == nx.number_of_nonisomorphic_trees(n)
assert sum(1 for _ in nx.nonisomorphic_trees(n)) == nx.number_of_nonisomorphic_trees(n)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The memory footprint is negligible here, and I personally prefer the former pattern for readability when memory/performance is not a consideration. Just my 2c though!

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>
Copy link
Copy Markdown
Member

@dschult dschult left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me -- raise for negative order, yield nothing (raise StopIteration) for zero order. Use len(list(...)) instead of sum(1 for _ in ...)

Thanks for clearing up the corner cases!

Copy link
Copy Markdown
Member

@Peiffap Peiffap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All punctuation or style recs, take 'em or leave 'em. LGTM!

Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment on lines +52 to +55
>>> seen = []
>>> for G in nx.nonisomorphic_trees(n):
... assert not any(nx.is_isomorphic(G, H) for H in seen)
... seen.append(G)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This avoids having to use seen, especially since we already have nit_list. Not necessarily better, your call!

Suggested change
>>> seen = []
>>> for G in nx.nonisomorphic_trees(n):
... assert not any(nx.is_isomorphic(G, H) for H in seen)
... seen.append(G)
>>> for i, G in enumerate(nit_list):
... assert not any(nx.is_isomorphic(G, H) for H in nit_list[i + 1:])

Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Comment thread networkx/generators/nonisomorphic_trees.py Outdated
Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>
@rossbar rossbar removed the Discussion Issues for discussion and decision making label Jul 8, 2025
@dschult
Copy link
Copy Markdown
Member

dschult commented Jul 8, 2025

This has had many eyes and a fair amount of time -- discussion has all been in agreement and 2 approvals (and the author makes a 2nd core approval).
I'm merging it. Thanks everyone!

@dschult dschult merged commit f6a23d3 into networkx:main Jul 8, 2025
46 checks passed
amcandio pushed a commit to amcandio/networkx that referenced this pull request Jul 20, 2025
…etworkx#8083)

* TST: Add tests for proposed semantics.

* Implement proposed semantics.

* DOC: Update documentation for noniso tree fns.

* Apply suggestions from code review

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>

* Apply suggestions from code review

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>

---------

Co-authored-by: Gilles Peiffer <gilles.peiffer.yt@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants