Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix the behavior for immutable graphs in methods related to isomorphisms in sage/graphs/generic_graph.py #39296

Merged
merged 9 commits into from
Mar 22, 2025
27 changes: 24 additions & 3 deletions src/sage/graphs/bipartite_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -2549,7 +2549,8 @@ def _subgraph_by_deleting(self, vertices=None, edges=None, inplace=False,
return B

def canonical_label(self, partition=None, certificate=False,
edge_labels=False, algorithm=None, return_graph=True):
edge_labels=False, algorithm=None, return_graph=True,
immutable=None):
r"""
Return the canonical graph.

Expand Down Expand Up @@ -2590,6 +2591,10 @@ class by some canonization function `c`. If `G` and `H` are graphs,
instead of the canonical graph. Only available when ``'bliss'``
is explicitly set as algorithm.

- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable (di)graph. ``immutable=None`` (default) means that
the (di)graph and its canonical (di)graph will behave the same way.

EXAMPLES::

sage: B = BipartiteGraph( [(0, 4), (0, 5), (0, 6), (0, 8), (1, 5),
Expand Down Expand Up @@ -2648,6 +2653,19 @@ class by some canonization function `c`. If `G` and `H` are graphs,
sage: B.canonical_label()
Bipartite multi-graph on 4 vertices

Check the behavior for immutable graphs::

sage: G = BipartiteGraph(graphs.CycleGraph(4))
sage: G.canonical_label().is_immutable()
False
sage: G.canonical_label(immutable=True).is_immutable()
True
sage: G = BipartiteGraph(graphs.CycleGraph(4), immutable=True)
sage: G.canonical_label().is_immutable()
True
sage: G.canonical_label(immutable=False).is_immutable()
False

.. SEEALSO::

:meth:`~sage.graphs.generic_graph.GenericGraph.canonical_label()`
Expand All @@ -2657,7 +2675,8 @@ class by some canonization function `c`. If `G` and `H` are graphs,
certificate=certificate,
edge_labels=edge_labels,
algorithm=algorithm,
return_graph=return_graph)
return_graph=return_graph,
immutable=immutable)

else:
from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
Expand Down Expand Up @@ -2696,7 +2715,9 @@ class by some canonization function `c`. If `G` and `H` are graphs,
a, b, c = search_tree(GC, partition, certificate=True, dig=False)
cert = {v: c[G_to[v]] for v in G_to}

C = self.relabel(perm=cert, inplace=False)
if immutable is None:
immutable = self.is_immutable()
C = self.relabel(perm=cert, inplace=False, immutable=immutable)

C.left = {cert[v] for v in self.left}
C.right = {cert[v] for v in self.right}
Expand Down
36 changes: 30 additions & 6 deletions src/sage/graphs/bliss.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,9 @@ cdef canonical_form_from_edge_list(int Vnr, list Vout, list Vin, int Lnr=1, list
return new_edges


cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True, certificate=False) noexcept:
cpdef canonical_form(G, partition=None, return_graph=False,
use_edge_labels=True, certificate=False,
immutable=None) noexcept:
r"""
Return a canonical label for the given (di)graph.

Expand All @@ -404,6 +406,12 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
- ``certificate`` -- boolean (default: ``False``); when set to ``True``,
returns the labeling of G into a canonical graph

- ``immutable`` -- boolean (default: ``None``); whether to create a
mutable/immutable (di)graph. ``immutable=None`` (default) means that
the (di)graph and its canonical (di)graph will behave the same way.

This parameter is ignored when ``return_graph`` is ``False``.

TESTS::

sage: from sage.graphs.bliss import canonical_form # optional - bliss
Expand Down Expand Up @@ -503,6 +511,19 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
Traceback (most recent call last):
...
ValueError: some vertices of the graph are not in the partition

Check the behavior of parameter ``immutable``::

sage: g = Graph({1: {2: 'a'}})
sage: canonical_form(g, return_graph=True).is_immutable() # optional - bliss
False
sage: canonical_form(g, return_graph=True, immutable=True).is_immutable() # optional - bliss
True
sage: g = Graph({1: {2: 'a'}}, immutable=True)
sage: canonical_form(g, return_graph=True).is_immutable() # optional - bliss
True
sage: canonical_form(g, return_graph=True, immutable=False).is_immutable() # optional - bliss
False
"""
# We need this to convert the numbers from <unsigned int> to <long>.
# This assertion should be true simply for memory reasons.
Expand Down Expand Up @@ -579,14 +600,17 @@ cpdef canonical_form(G, partition=None, return_graph=False, use_edge_labels=True
relabel = {int2vert[i]: j for i, j in relabel.items()}

if return_graph:
if immutable is None:
immutable = G.is_immutable()
if directed:
from sage.graphs.digraph import DiGraph
H = DiGraph(new_edges, loops=G.allows_loops(), multiedges=G.allows_multiple_edges())
from sage.graphs.digraph import DiGraph as GT
else:
from sage.graphs.graph import Graph
H = Graph(new_edges, loops=G.allows_loops(), multiedges=G.allows_multiple_edges())
from sage.graphs.graph import Graph as GT

H = GT([range(G.order()), new_edges], format='vertices_and_edges',
loops=G.allows_loops(), multiedges=G.allows_multiple_edges(),
immutable=immutable)

H.add_vertices(range(G.order()))
return (H, relabel) if certificate else H

# Warning: this may break badly in Python 3 if the graph is not simple
Expand Down
Loading
Loading