8888- Jean-Florent Raymond (2019-04): is_redundant, is_dominating,
8989 private_neighbors
9090
91+ - Cyril Bouvier (2024-11): is_module
92+
9193Graph Format
9294------------
9395
@@ -7181,20 +7183,131 @@ def cores(self, k=None, with_labels=False):
71817183 return core
71827184 return list (core .values ())
71837185
7184- @doc_index ("Leftovers" )
7185- def modular_decomposition (self , algorithm = None , style = 'tuple' ):
7186+ @doc_index ("Modules" )
7187+ def is_module (self , vertices ):
7188+ r"""
7189+ Return whether ``vertices`` is a module of ``self``.
7190+
7191+ A subset `M` of the vertices of a graph is a module if for every
7192+ vertex `v` outside of `M`, either all vertices of `M` are neighbors of
7193+ `v` or all vertices of `M` are not neighbors of `v`.
7194+
7195+ INPUT:
7196+
7197+ - ``vertices`` -- iterable; a subset of vertices of ``self``
7198+
7199+ EXAMPLES:
7200+
7201+ The whole graph, the empty set and singletons are trivial modules::
7202+
7203+ sage: G = graphs.PetersenGraph()
7204+ sage: G.is_module([])
7205+ True
7206+ sage: G.is_module([G.random_vertex()])
7207+ True
7208+ sage: G.is_module(G)
7209+ True
7210+
7211+ Prime graphs only have trivial modules::
7212+
7213+ sage: G = graphs.PathGraph(5)
7214+ sage: G.is_prime()
7215+ True
7216+ sage: all(not G.is_module(S) for S in subsets(G)
7217+ ....: if len(S) > 1 and len(S) < G.order())
7218+ True
7219+
7220+ For edgeless graphs and complete graphs, all subsets are modules::
7221+
7222+ sage: G = Graph(5)
7223+ sage: all(G.is_module(S) for S in subsets(G))
7224+ True
7225+ sage: G = graphs.CompleteGraph(5)
7226+ sage: all(G.is_module(S) for S in subsets(G))
7227+ True
7228+
7229+ The modules of a graph and of its complements are the same::
7230+
7231+ sage: G = graphs.TuranGraph(10, 3)
7232+ sage: G.is_module([0,1,2])
7233+ True
7234+ sage: G.complement().is_module([0,1,2])
7235+ True
7236+ sage: G.is_module([3,4,5])
7237+ True
7238+ sage: G.complement().is_module([3,4,5])
7239+ True
7240+ sage: G.is_module([2,3,4])
7241+ False
7242+ sage: G.complement().is_module([2,3,4])
7243+ False
7244+ sage: G.is_module([3,4,5,6,7,8,9])
7245+ True
7246+ sage: G.complement().is_module([3,4,5,6,7,8,9])
7247+ True
7248+
7249+ Elements of ``vertices`` must be in ``self``::
7250+
7251+ sage: G = graphs.PetersenGraph()
7252+ sage: G.is_module(['Terry'])
7253+ Traceback (most recent call last):
7254+ ...
7255+ LookupError: vertex (Terry) is not a vertex of the graph
7256+ sage: G.is_module([1, 'Graham'])
7257+ Traceback (most recent call last):
7258+ ...
7259+ LookupError: vertex (Graham) is not a vertex of the graph
7260+ """
7261+ M = set (vertices )
7262+
7263+ for v in M :
7264+ if v not in self :
7265+ raise LookupError (f"vertex ({ v } ) is not a vertex of the graph" )
7266+
7267+ if len (M ) == 0 or len (M ) == 1 or len (M ) == self .order ():
7268+ return True
7269+
7270+ N = None # will contains the neighborhood of M
7271+ for v in M :
7272+ if N is None :
7273+ # first iteration, the neighborhood N must be computed
7274+ N = { u for u in self .neighbor_iterator (v ) if u not in M }
7275+ else :
7276+ # check that the neighborhood of v is N
7277+ n = 0
7278+ for u in self .neighbor_iterator (v ):
7279+ if u not in M :
7280+ n += 1
7281+ if u not in N :
7282+ return False # u is a splitter
7283+ if n != len (N ):
7284+ return False
7285+ return True
7286+
7287+ @doc_index ("Modules" )
7288+ def modular_decomposition (self , algorithm = None , style = "tuple" ):
71867289 r"""
71877290 Return the modular decomposition of the current graph.
71887291
71897292 A module of an undirected graph is a subset of vertices such that every
71907293 vertex outside the module is either connected to all members of the
71917294 module or to none of them. Every graph that has a nontrivial module can
71927295 be partitioned into modules, and the increasingly fine partitions into
7193- modules form a tree. The ``modular_decomposition`` function returns
7194- that tree, using an `O(n^3)` algorithm of [HM1979]_ .
7296+ modules form a tree. The ``modular_decomposition`` method returns
7297+ that tree.
71957298
71967299 INPUT:
71977300
7301+ - ``algorithm`` -- string (default: ``None``); the algorithm to use
7302+ among:
7303+
7304+ - ``None`` or ``'corneil_habib_paul_tedder'`` -- will use the
7305+ Corneil-Habib-Paul-Tedder algorithm from [TCHP2008]_, its complexity
7306+ is linear in the number of vertices and edges.
7307+
7308+ - ``'habib_maurer'`` -- will use the Habib-Maurer algorithm from
7309+ [HM1979]_, its complexity is cubic in the number of vertices.
7310+
71987311 - ``style`` -- string (default: ``'tuple'``); specifies the output
71997312 format:
72007313
@@ -7204,16 +7317,10 @@ def modular_decomposition(self, algorithm=None, style='tuple'):
72047317
72057318 OUTPUT:
72067319
7207- A pair of two values (recursively encoding the decomposition) :
7208-
7209- * The type of the current module :
7210-
7211- * ``'PARALLEL'``
7212- * ``'PRIME'``
7213- * ``'SERIES'``
7214-
7215- * The list of submodules (as list of pairs ``(type, list)``,
7216- recursively...) or the vertex's name if the module is a singleton.
7320+ The modular decomposition tree, either as nested tuples (if
7321+ ``style='tuple'``) or as an object of
7322+ :class:`~sage.combinat.rooted_tree.LabelledRootedTree` (if
7323+ ``style='tree'``)
72177324
72187325 Crash course on modular decomposition:
72197326
@@ -7266,7 +7373,19 @@ def modular_decomposition(self, algorithm=None, style='tuple'):
72667373 The Petersen Graph too::
72677374
72687375 sage: graphs.PetersenGraph().modular_decomposition()
7269- (PRIME, [1, 4, 5, 0, 2, 6, 3, 7, 8, 9])
7376+ (PRIME, [1, 4, 5, 0, 6, 2, 3, 9, 7, 8])
7377+
7378+ Graph from the :wikipedia:`Modular_decomposition`::
7379+
7380+ sage: G = Graph('Jv\\zoKF@wN?', format='graph6')
7381+ sage: G.relabel([1..11])
7382+ sage: G.modular_decomposition()
7383+ (PRIME,
7384+ [(SERIES, [4, (PARALLEL, [2, 3])]),
7385+ 1,
7386+ 5,
7387+ (PARALLEL, [6, 7]),
7388+ (SERIES, [(PARALLEL, [10, 11]), 9, 8])])
72707389
72717390 This a clique on 5 vertices with 2 pendant edges, though, has a more
72727391 interesting decomposition::
@@ -7275,14 +7394,20 @@ def modular_decomposition(self, algorithm=None, style='tuple'):
72757394 sage: g.add_edge(0,5)
72767395 sage: g.add_edge(0,6)
72777396 sage: g.modular_decomposition()
7278- (SERIES, [(PARALLEL, [(SERIES, [1, 2, 3, 4]), 5, 6]), 0])
7397+ (SERIES, [(PARALLEL, [(SERIES, [3, 4, 2, 1]), 5, 6]), 0])
7398+
7399+ Turán graphs are co-graphs::
7400+
7401+ sage: graphs.TuranGraph(11, 3).modular_decomposition()
7402+ (SERIES,
7403+ [(PARALLEL, [7, 8, 9, 10]), (PARALLEL, [3, 4, 5, 6]), (PARALLEL, [0, 1, 2])])
72797404
72807405 We can choose output to be a
72817406 :class:`~sage.combinat.rooted_tree.LabelledRootedTree`::
72827407
72837408 sage: g.modular_decomposition(style='tree')
72847409 SERIES[0[], PARALLEL[5[], 6[], SERIES[1[], 2[], 3[], 4[]]]]
7285- sage: ascii_art(g.modular_decomposition(style='tree'))
7410+ sage: ascii_art(g.modular_decomposition(algorithm="habib_maurer", style='tree'))
72867411 __SERIES
72877412 / /
72887413 0 ___PARALLEL
@@ -7293,18 +7418,26 @@ def modular_decomposition(self, algorithm=None, style='tuple'):
72937418
72947419 ALGORITHM:
72957420
7296- This function uses the algorithm of M. Habib and M. Maurer [HM1979]_.
7421+ This function can use either the algorithm of D. Corneil, M. Habib, C.
7422+ Paul and M. Tedder [TCHP2008]_ or the algorithm of M. Habib and M.
7423+ Maurer [HM1979]_.
72977424
72987425 .. SEEALSO::
72997426
73007427 - :meth:`is_prime` -- tests whether a graph is prime
73017428
73027429 - :class:`~sage.combinat.rooted_tree.LabelledRootedTree`.
73037430
7431+ - :func:`~sage.graphs.graph_decompositions.modular_decomposition.corneil_habib_paul_tedder_algorithm`
7432+
7433+ - :func:`~sage.graphs.graph_decompositions.modular_decomposition.habib_maurer_algorithm`
7434+
73047435 .. NOTE::
73057436
7306- A buggy implementation of linear time algorithm from [TCHP2008]_ was
7307- removed in Sage 9.7, see :issue:`25872`.
7437+ A buggy implementation of the linear time algorithm from [TCHP2008]_
7438+ was removed in Sage 9.7, see :issue:`25872`. A new implementation
7439+ was reintroduced in Sage 10.6 after some corrections to the original
7440+ algorithm, see :issue:`xxxxx`.
73087441
73097442 TESTS:
73107443
@@ -7343,53 +7476,45 @@ def modular_decomposition(self, algorithm=None, style='tuple'):
73437476 sage: G2 = Graph('F@Nfg')
73447477 sage: G1.is_isomorphic(G2)
73457478 True
7346- sage: G1.modular_decomposition()
7479+ sage: G1.modular_decomposition(algorithm="habib_maurer" )
73477480 (PRIME, [1, 2, 5, 6, 0, (PARALLEL, [3, 4])])
7348- sage: G2.modular_decomposition()
7481+ sage: G2.modular_decomposition(algorithm="habib_maurer" )
73497482 (PRIME, [5, 6, 3, 4, 2, (PARALLEL, [0, 1])])
7483+ sage: G1.modular_decomposition(algorithm="corneil_habib_paul_tedder")
7484+ (PRIME, [6, 5, 1, 2, 0, (PARALLEL, [3, 4])])
7485+ sage: G2.modular_decomposition(algorithm="corneil_habib_paul_tedder")
7486+ (PRIME, [6, 5, (PARALLEL, [0, 1]), 2, 3, 4])
73507487
73517488 Check that :issue:`37631` is fixed::
73527489
73537490 sage: G = Graph('GxJEE?')
7354- sage: G.modular_decomposition(style='tree')
7491+ sage: G.modular_decomposition(algorithm="habib_maurer", style='tree')
73557492 PRIME[2[], SERIES[0[], 1[]], PARALLEL[3[], 4[]],
73567493 PARALLEL[5[], 6[], 7[]]]
73577494 """
7358- from sage .graphs .graph_decompositions .modular_decomposition import (NodeType ,
7359- habib_maurer_algorithm ,
7360- create_prime_node ,
7361- create_normal_node )
7362-
7363- if algorithm is not None :
7364- from sage .misc .superseded import deprecation
7365- deprecation (25872 , "algorithm=... parameter is obsolete and has no effect." )
7366- self ._scream_if_not_simple ()
7495+ from sage .graphs .graph_decompositions .modular_decomposition import \
7496+ modular_decomposition
73677497
7368- if not self .order ():
7369- D = None
7370- elif self .order () == 1 :
7371- D = create_normal_node (next (self .vertex_iterator ()))
7372- else :
7373- D = habib_maurer_algorithm (self )
7498+ D = modular_decomposition (self , algorithm = algorithm )
73747499
73757500 if style == 'tuple' :
7376- if D is None :
7501+ if D . is_empty () :
73777502 return tuple ()
73787503
73797504 def relabel (x ):
7380- if x .node_type == NodeType . NORMAL :
7505+ if x .is_leaf () :
73817506 return x .children [0 ]
73827507 return x .node_type , [relabel (y ) for y in x .children ]
73837508
73847509 return relabel (D )
73857510
73867511 elif style == 'tree' :
73877512 from sage .combinat .rooted_tree import LabelledRootedTree
7388- if D is None :
7513+ if D . is_empty () :
73897514 return LabelledRootedTree ([])
73907515
73917516 def to_tree (x ):
7392- if x .node_type == NodeType . NORMAL :
7517+ if x .is_leaf () :
73937518 return LabelledRootedTree ([], label = x .children [0 ])
73947519 return LabelledRootedTree ([to_tree (y ) for y in x .children ],
73957520 label = x .node_type )
@@ -7640,7 +7765,14 @@ def is_prime(self, algorithm=None):
76407765
76417766 A graph is prime if all its modules are trivial (i.e. empty, all of the
76427767 graph or singletons) -- see :meth:`modular_decomposition`.
7643- Use the `O(n^3)` algorithm of [HM1979]_.
7768+ This method computes the modular decomposition tree using
7769+ :meth:`~sage.graphs.graph.Graph.modular_decomposition`.
7770+
7771+ INPUT:
7772+
7773+ - ``algorithm`` -- string (default: ``None``); the algorithm used to
7774+ compute the modular decomposition tree; the value is forwarded
7775+ directly to :meth:`~sage.graphs.graph.Graph.modular_decomposition`.
76447776
76457777 EXAMPLES:
76467778
@@ -7661,17 +7793,15 @@ def is_prime(self, algorithm=None):
76617793 sage: graphs.EmptyGraph().is_prime()
76627794 True
76637795 """
7664- if algorithm is not None :
7665- from sage .misc .superseded import deprecation
7666- deprecation (25872 , "algorithm=... parameter is obsolete and has no effect." )
7667- from sage .graphs .graph_decompositions .modular_decomposition import NodeType
7796+ from sage .graphs .graph_decompositions .modular_decomposition import \
7797+ modular_decomposition
76687798
76697799 if self .order () <= 1 :
76707800 return True
76717801
7672- D = self . modular_decomposition ()
7802+ MD = modular_decomposition (self , algorithm = algorithm )
76737803
7674- return D [ 0 ] == NodeType . PRIME and len (D [ 1 ] ) == self .order ()
7804+ return MD . is_prime () and len (MD . children ) == self .order ()
76757805
76767806 def _gomory_hu_tree (self , vertices , algorithm = None ):
76777807 r"""
0 commit comments