passagemath-graphs 10.5.43__cp312-cp312-macosx_14_0_arm64.whl → 10.6.1rc1__cp312-cp312-macosx_14_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/METADATA +5 -6
  2. {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/RECORD +132 -130
  3. sage/combinat/abstract_tree.py +188 -17
  4. sage/combinat/cluster_algebra_quiver/interact.py +1 -2
  5. sage/combinat/cluster_algebra_quiver/mutation_type.py +518 -519
  6. sage/combinat/cluster_algebra_quiver/quiver.py +233 -205
  7. sage/combinat/designs/covering_design.py +2 -6
  8. sage/combinat/designs/database.py +11 -10
  9. sage/combinat/designs/designs_pyx.cpython-312-darwin.so +0 -0
  10. sage/combinat/designs/designs_pyx.pyx +2 -2
  11. sage/combinat/designs/evenly_distributed_sets.cpython-312-darwin.so +0 -0
  12. sage/combinat/designs/evenly_distributed_sets.pyx +4 -4
  13. sage/combinat/designs/gen_quadrangles_with_spread.cpython-312-darwin.so +0 -0
  14. sage/combinat/designs/latin_squares.py +53 -20
  15. sage/combinat/designs/orthogonal_arrays.py +2 -1
  16. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-312-darwin.so +0 -0
  17. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +22 -21
  18. sage/combinat/designs/resolvable_bibd.py +191 -157
  19. sage/combinat/designs/subhypergraph_search.cpython-312-darwin.so +0 -0
  20. sage/combinat/designs/subhypergraph_search.pyx +4 -4
  21. sage/combinat/designs/twographs.py +2 -2
  22. sage/combinat/finite_state_machine.py +6 -6
  23. sage/combinat/posets/bubble_shuffle.py +247 -0
  24. sage/combinat/posets/d_complete.py +3 -3
  25. sage/combinat/posets/elements.py +3 -3
  26. sage/combinat/posets/hasse_cython.cpython-312-darwin.so +0 -0
  27. sage/combinat/posets/hasse_cython.pyx +1 -1
  28. sage/combinat/posets/hasse_diagram.py +16 -22
  29. sage/combinat/posets/hochschild_lattice.py +158 -0
  30. sage/combinat/posets/incidence_algebras.py +14 -16
  31. sage/combinat/posets/lattices.py +51 -53
  32. sage/combinat/posets/linear_extension_iterator.cpython-312-darwin.so +0 -0
  33. sage/combinat/posets/linear_extensions.py +10 -12
  34. sage/combinat/posets/moebius_algebra.py +4 -4
  35. sage/combinat/posets/poset_examples.py +70 -23
  36. sage/combinat/posets/posets.py +294 -103
  37. sage/databases/knotinfo_db.py +2 -1
  38. sage/graphs/asteroidal_triples.cpython-312-darwin.so +0 -0
  39. sage/graphs/asteroidal_triples.pyx +24 -3
  40. sage/graphs/base/boost_graph.cpython-312-darwin.so +0 -0
  41. sage/graphs/base/boost_graph.pxd +3 -3
  42. sage/graphs/base/c_graph.cpython-312-darwin.so +0 -0
  43. sage/graphs/base/c_graph.pyx +1 -1
  44. sage/graphs/base/dense_graph.cpython-312-darwin.so +0 -0
  45. sage/graphs/base/dense_graph.pxd +5 -3
  46. sage/graphs/base/dense_graph.pyx +44 -0
  47. sage/graphs/base/graph_backends.cpython-312-darwin.so +0 -0
  48. sage/graphs/base/sparse_graph.cpython-312-darwin.so +0 -0
  49. sage/graphs/base/static_dense_graph.cpython-312-darwin.so +0 -0
  50. sage/graphs/base/static_sparse_backend.cpython-312-darwin.so +0 -0
  51. sage/graphs/base/static_sparse_backend.pyx +8 -5
  52. sage/graphs/base/static_sparse_graph.cpython-312-darwin.so +0 -0
  53. sage/graphs/base/static_sparse_graph.pyx +86 -15
  54. sage/graphs/bipartite_graph.py +59 -36
  55. sage/graphs/centrality.cpython-312-darwin.so +0 -0
  56. sage/graphs/centrality.pyx +82 -9
  57. sage/graphs/cographs.py +1 -1
  58. sage/graphs/comparability.cpython-312-darwin.so +0 -0
  59. sage/graphs/comparability.pyx +64 -26
  60. sage/graphs/connectivity.cpython-312-darwin.so +0 -0
  61. sage/graphs/convexity_properties.cpython-312-darwin.so +0 -0
  62. sage/graphs/convexity_properties.pyx +52 -9
  63. sage/graphs/digraph.py +439 -95
  64. sage/graphs/digraph_generators.py +174 -102
  65. sage/graphs/distances_all_pairs.cpython-312-darwin.so +0 -0
  66. sage/graphs/dot2tex_utils.py +1 -1
  67. sage/graphs/edge_connectivity.cpython-312-darwin.so +0 -0
  68. sage/graphs/generators/basic.py +1 -1
  69. sage/graphs/generators/distance_regular.cpython-312-darwin.so +0 -0
  70. sage/graphs/generators/distance_regular.pyx +1 -1
  71. sage/graphs/generators/families.py +37 -27
  72. sage/graphs/generators/random.py +2 -2
  73. sage/graphs/generators/smallgraphs.py +3 -3
  74. sage/graphs/generic_graph.py +558 -86
  75. sage/graphs/generic_graph_pyx.cpython-312-darwin.so +0 -0
  76. sage/graphs/generic_graph_pyx.pyx +58 -11
  77. sage/graphs/genus.cpython-312-darwin.so +0 -0
  78. sage/graphs/genus.pyx +3 -4
  79. sage/graphs/graph.py +291 -8
  80. sage/graphs/graph_coloring.cpython-312-darwin.so +0 -0
  81. sage/graphs/graph_database.py +67 -12
  82. sage/graphs/graph_decompositions/bandwidth.cpython-312-darwin.so +0 -0
  83. sage/graphs/graph_decompositions/clique_separators.cpython-312-darwin.so +0 -0
  84. sage/graphs/graph_decompositions/clique_separators.pyx +24 -3
  85. sage/graphs/graph_decompositions/cutwidth.cpython-312-darwin.so +0 -0
  86. sage/graphs/graph_decompositions/fast_digraph.cpython-312-darwin.so +0 -0
  87. sage/graphs/graph_decompositions/fast_digraph.pyx +1 -1
  88. sage/graphs/graph_decompositions/graph_products.cpython-312-darwin.so +0 -0
  89. sage/graphs/graph_decompositions/graph_products.pyx +67 -21
  90. sage/graphs/graph_decompositions/modular_decomposition.cpython-312-darwin.so +0 -0
  91. sage/graphs/graph_decompositions/slice_decomposition.cpython-312-darwin.so +0 -0
  92. sage/graphs/graph_decompositions/slice_decomposition.pyx +34 -8
  93. sage/graphs/graph_decompositions/tree_decomposition.cpython-312-darwin.so +0 -0
  94. sage/graphs/graph_decompositions/vertex_separation.cpython-312-darwin.so +0 -0
  95. sage/graphs/graph_generators.py +45 -32
  96. sage/graphs/graph_generators_pyx.cpython-312-darwin.so +0 -0
  97. sage/graphs/graph_generators_pyx.pyx +15 -15
  98. sage/graphs/graph_latex.py +1 -1
  99. sage/graphs/graph_list.py +52 -9
  100. sage/graphs/graph_plot.py +7 -0
  101. sage/graphs/hyperbolicity.cpython-312-darwin.so +0 -0
  102. sage/graphs/hyperbolicity.pyx +2 -0
  103. sage/graphs/independent_sets.cpython-312-darwin.so +0 -0
  104. sage/graphs/isoperimetric_inequalities.cpython-312-darwin.so +0 -0
  105. sage/graphs/isoperimetric_inequalities.pyx +42 -6
  106. sage/graphs/line_graph.cpython-312-darwin.so +0 -0
  107. sage/graphs/line_graph.pyx +153 -37
  108. sage/graphs/matching_covered_graph.py +84 -60
  109. sage/graphs/orientations.py +3 -18
  110. sage/graphs/path_enumeration.cpython-312-darwin.so +0 -0
  111. sage/graphs/path_enumeration.pyx +2 -2
  112. sage/graphs/spanning_tree.cpython-312-darwin.so +0 -0
  113. sage/graphs/strongly_regular_db.cpython-312-darwin.so +0 -0
  114. sage/graphs/strongly_regular_db.pyx +15 -15
  115. sage/graphs/traversals.cpython-312-darwin.so +0 -0
  116. sage/graphs/traversals.pyx +13 -12
  117. sage/graphs/trees.cpython-312-darwin.so +0 -0
  118. sage/graphs/tutte_polynomial.py +1 -1
  119. sage/graphs/views.cpython-312-darwin.so +0 -0
  120. sage/graphs/weakly_chordal.cpython-312-darwin.so +0 -0
  121. sage/graphs/weakly_chordal.pyx +50 -8
  122. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-312-darwin.so +0 -0
  123. sage/knots/free_knotinfo_monoid.py +3 -3
  124. sage/knots/knotinfo.py +102 -82
  125. sage/knots/link.py +72 -39
  126. sage/topology/cubical_complex.py +4 -5
  127. sage/topology/delta_complex.py +4 -4
  128. sage/topology/simplicial_complex.py +0 -1
  129. sage/topology/simplicial_complex_catalog.py +6 -0
  130. sage/topology/simplicial_complex_examples.py +4 -16
  131. {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/WHEEL +0 -0
  132. {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/top_level.txt +0 -0
@@ -244,7 +244,7 @@ can be applied on both. Here is what it can do:
244
244
  :meth:`~GenericGraph.blocks_and_cut_vertices` | Compute the blocks and cut vertices of the graph.
245
245
  :meth:`~GenericGraph.blocks_and_cuts_tree` | Compute the blocks-and-cuts tree of the graph.
246
246
  :meth:`~GenericGraph.is_cut_edge` | Check whether the input edge is a cut-edge or a bridge.
247
- :meth:`~GenericGraph.`is_edge_cut` | Check whether the input edges form an edge cut.
247
+ :meth:`~GenericGraph.is_edge_cut` | Check whether the input edges form an edge cut.
248
248
  :meth:`~GenericGraph.is_cut_vertex` | Check whether the input vertex is a cut-vertex.
249
249
  :meth:`~GenericGraph.is_vertex_cut` | Check whether the input vertices form a vertex cut.
250
250
  :meth:`~GenericGraph.edge_cut` | Return a minimum edge cut between vertices `s` and `t`
@@ -325,6 +325,7 @@ can be applied on both. Here is what it can do:
325
325
  :meth:`~GenericGraph.subgraph_search` | Return a copy of ``G`` in ``self``.
326
326
  :meth:`~GenericGraph.subgraph_search_count` | Return the number of labelled occurrences of ``G`` in ``self``.
327
327
  :meth:`~GenericGraph.subgraph_search_iterator` | Return an iterator over the labelled copies of ``G`` in ``self``.
328
+ :meth:`~GenericGraph.subgraph_decompositions` | Return an iterator over the graph decompositions into isometric copies of another graph.
328
329
  :meth:`~GenericGraph.characteristic_polynomial` | Return the characteristic polynomial of the adjacency matrix of the (di)graph.
329
330
  :meth:`~GenericGraph.genus` | Return the minimal genus of the graph.
330
331
  :meth:`~GenericGraph.crossing_number` | Return the crossing number of the graph.
@@ -1011,7 +1012,7 @@ class GenericGraph(GenericGraph_pyx):
1011
1012
  the same cluster subgraph are drawn together, with the entire
1012
1013
  drawing of the cluster contained within a bounding rectangle.
1013
1014
 
1014
- Additionnal keywords arguments are forwarded to
1015
+ Additional keywords arguments are forwarded to
1015
1016
  :meth:`sage.graphs.graph_latex.GraphLatex.set_option`.
1016
1017
 
1017
1018
  The following inputs define the preamble of the latex standalone
@@ -1235,6 +1236,65 @@ class GenericGraph(GenericGraph_pyx):
1235
1236
 
1236
1237
  # Formats
1237
1238
 
1239
+ def _copy_attribute_from(self, other, attribute):
1240
+ r"""
1241
+ Helper method to copy in ``self`` an attribute from ``other``.
1242
+
1243
+ INPUT:
1244
+
1245
+ - ``other`` -- the (di)graph from which to copy attributes
1246
+
1247
+ - ``attribute`` -- string; the attribute to copy, for example
1248
+ ``_assoc``, ``_embedding``, ``_pos``, etc.
1249
+
1250
+ EXAMPLES::
1251
+
1252
+ sage: G = graphs.CycleGraph(4)
1253
+ sage: G.get_pos()
1254
+ {0: (0.0, 1.0), 1: (-1.0, 0.0), 2: (0.0, -1.0), 3: (1.0, 0.0)}
1255
+ sage: D = DiGraph(4)
1256
+ sage: D._copy_attribute_from(G, '_pos')
1257
+ sage: D.get_pos()
1258
+ {0: (0.0, 1.0), 1: (-1.0, 0.0), 2: (0.0, -1.0), 3: (1.0, 0.0)}
1259
+
1260
+ TESTS::
1261
+
1262
+ sage: G = Graph([(0, 1)])
1263
+ sage: D = DiGraph([(0, 1)])
1264
+ sage: G.set_vertices({0: graphs.CycleGraph(3), 1: 'abc'})
1265
+ sage: G.get_vertices()
1266
+ {0: Cycle graph: Graph on 3 vertices, 1: 'abc'}
1267
+ sage: D.get_vertices()
1268
+ {0: None, 1: None}
1269
+ sage: D._copy_attribute_from(G, '_assoc')
1270
+ sage: D.get_vertices()
1271
+ {0: Cycle graph: Graph on 3 vertices, 1: 'abc'}
1272
+ sage: G.get_vertices()
1273
+ {0: Cycle graph: Graph on 3 vertices, 1: 'abc'}
1274
+ sage: G.get_embedding()
1275
+
1276
+ sage: # needs planarity
1277
+ sage: G.genus()
1278
+ 0
1279
+ sage: G.get_embedding()
1280
+ {0: [1], 1: [0]}
1281
+ sage: D._copy_attribute_from(G, '_embedding')
1282
+ sage: D.get_embedding()
1283
+ {0: [1], 1: [0]}
1284
+ """
1285
+ if hasattr(other, attribute):
1286
+ copy_attr = {}
1287
+ old_attr = getattr(other, attribute)
1288
+ if isinstance(old_attr, dict):
1289
+ for v, value in old_attr.items():
1290
+ try:
1291
+ copy_attr[v] = value.copy()
1292
+ except AttributeError:
1293
+ copy_attr[v] = copy(value)
1294
+ setattr(self, attribute, copy_attr)
1295
+ else:
1296
+ setattr(self, attribute, copy(old_attr))
1297
+
1238
1298
  def copy(self, weighted=None, data_structure=None, sparse=None, immutable=None, hash_labels=None):
1239
1299
  """
1240
1300
  Change the graph implementation.
@@ -1508,20 +1568,9 @@ class GenericGraph(GenericGraph_pyx):
1508
1568
  weighted=weighted, hash_labels=hash_labels,
1509
1569
  data_structure=data_structure)
1510
1570
 
1511
- attributes_to_copy = ('_assoc', '_embedding')
1512
- for attr in attributes_to_copy:
1513
- if hasattr(self, attr):
1514
- copy_attr = {}
1515
- old_attr = getattr(self, attr)
1516
- if isinstance(old_attr, dict):
1517
- for v, value in old_attr.items():
1518
- try:
1519
- copy_attr[v] = value.copy()
1520
- except AttributeError:
1521
- copy_attr[v] = copy(value)
1522
- setattr(G, attr, copy_attr)
1523
- else:
1524
- setattr(G, attr, copy(old_attr))
1571
+ # Copy attributes '_assoc' and '_embedding' if set
1572
+ G._copy_attribute_from(self, '_assoc')
1573
+ G._copy_attribute_from(self, '_embedding')
1525
1574
 
1526
1575
  return G
1527
1576
 
@@ -3286,6 +3335,14 @@ class GenericGraph(GenericGraph_pyx):
3286
3335
  sage: G.add_edge(0, 7)
3287
3336
  sage: G.get_embedding() is None
3288
3337
  True
3338
+
3339
+ TESTS::
3340
+
3341
+ sage: G = Graph('A_', immutable=True)
3342
+ sage: G.set_embedding({0: [1], 1: [0]})
3343
+ sage: H = G.subgraph([0])
3344
+ sage: H.get_embedding()
3345
+ {0: []}
3289
3346
  """
3290
3347
  try:
3291
3348
  embedding = self._embedding
@@ -3293,12 +3350,11 @@ class GenericGraph(GenericGraph_pyx):
3293
3350
  embedding = None
3294
3351
  if embedding is not None:
3295
3352
  # remove vertices not anymore in the graph
3296
- to_remove = set(v for v in embedding if v not in self)
3297
- if to_remove:
3298
- for v in to_remove:
3353
+ for v in list(embedding):
3354
+ if v not in self:
3299
3355
  del embedding[v]
3300
- for v in embedding:
3301
- embedding[v] = [w for w in embedding[v] if w not in to_remove]
3356
+ for v in embedding:
3357
+ embedding[v] = [w for w in embedding[v] if w in self]
3302
3358
 
3303
3359
  # remove edges not anymore in the graph
3304
3360
  for u in embedding:
@@ -6855,11 +6911,11 @@ class GenericGraph(GenericGraph_pyx):
6855
6911
  return []
6856
6912
 
6857
6913
  # Which embedding should we use ?
6858
- if embedding is None:
6859
- # Is self._embedding available ?
6860
- if self._check_embedding_validity():
6861
- embedding = self._embedding
6862
- else:
6914
+ if embedding is not None:
6915
+ self._check_embedding_validity(embedding, boolean=False)
6916
+ else:
6917
+ embedding = self.get_embedding()
6918
+ if embedding is None:
6863
6919
  if self.is_planar(set_embedding=True):
6864
6920
  embedding = self._embedding
6865
6921
  self._embedding = None
@@ -6964,10 +7020,8 @@ class GenericGraph(GenericGraph_pyx):
6964
7020
  return len(self.faces(embedding))
6965
7021
 
6966
7022
  if embedding is None:
6967
- # Is self._embedding available ?
6968
- if self._check_embedding_validity():
6969
- embedding = self._embedding
6970
- else:
7023
+ embedding = self.get_embedding()
7024
+ if embedding is None:
6971
7025
  if self.is_planar():
6972
7026
  # We use Euler's formula: V-E+F-C=1
6973
7027
  C = self.connected_components_number()
@@ -7074,7 +7128,7 @@ class GenericGraph(GenericGraph_pyx):
7074
7128
  verts = [tuple(f) for f in self.faces(embedding=embedding)]
7075
7129
  edges = []
7076
7130
  for v1, v2 in combinations(verts, 2):
7077
- e = set([tuple(reversed(e)) for e in v1]).intersection(v2)
7131
+ e = {tuple(reversed(e)) for e in v1}.intersection(v2)
7078
7132
  if e:
7079
7133
  e = e.pop() # just one edge since self and its dual are simple
7080
7134
  edges.append([v1, v2, self.edge_label(e[0], e[1])])
@@ -8385,6 +8439,9 @@ class GenericGraph(GenericGraph_pyx):
8385
8439
  True
8386
8440
  sage: D.longest_cycle(induced=False, use_edge_labels=True, immutable=False)[1].is_immutable()
8387
8441
  False
8442
+ sage: # check that https://houseofgraphs.org/graphs/752 has circumference 6:
8443
+ sage: Graph('EJ~w', immutable=True).longest_cycle().order()
8444
+ 6
8388
8445
  """
8389
8446
  self._scream_if_not_simple()
8390
8447
  G = self
@@ -8540,7 +8597,7 @@ class GenericGraph(GenericGraph_pyx):
8540
8597
  hh = h.subgraph(vertices=c)
8541
8598
  if total_weight(hh) > best_w:
8542
8599
  best = hh
8543
- best.name(name)
8600
+ best._name = name
8544
8601
  best_w = total_weight(best)
8545
8602
 
8546
8603
  # Add subtour elimination constraints
@@ -9279,7 +9336,7 @@ class GenericGraph(GenericGraph_pyx):
9279
9336
  return (0, None) if use_edge_labels else None
9280
9337
 
9281
9338
  tsp.delete_vertices(extra_vertices)
9282
- tsp.name("Hamiltonian path from {}".format(self.name()))
9339
+ tsp._name = "Hamiltonian path from {}".format(self.name())
9283
9340
  if immutable:
9284
9341
  tsp = tsp.copy(immutable=True)
9285
9342
 
@@ -9554,7 +9611,7 @@ class GenericGraph(GenericGraph_pyx):
9554
9611
  (vv, uu, self.edge_label(vv, uu))]
9555
9612
  answer = self.subgraph(edges=edges, immutable=self.is_immutable())
9556
9613
  answer.set_pos(self.get_pos())
9557
- answer.name("TSP from "+self.name())
9614
+ answer._name = "TSP from "+self.name()
9558
9615
  return answer
9559
9616
  else:
9560
9617
  if self.allows_multiple_edges() and len(self.edge_label(uu, vv)) > 1:
@@ -9564,7 +9621,7 @@ class GenericGraph(GenericGraph_pyx):
9564
9621
  edges = self.edges(sort=True, key=weight)[:2]
9565
9622
  answer = self.subgraph(edges=edges, immutable=self.is_immutable())
9566
9623
  answer.set_pos(self.get_pos())
9567
- answer.name("TSP from " + self.name())
9624
+ answer._name = "TSP from " + self.name()
9568
9625
  return answer
9569
9626
 
9570
9627
  raise EmptySetError("the given graph is not Hamiltonian")
@@ -9713,7 +9770,7 @@ class GenericGraph(GenericGraph_pyx):
9713
9770
  # We can now return the TSP !
9714
9771
  answer = self.subgraph(edges=h.edges(sort=False), immutable=self.is_immutable())
9715
9772
  answer.set_pos(self.get_pos())
9716
- answer.name("TSP from "+g.name())
9773
+ answer._name = "TSP from "+g.name()
9717
9774
  return answer
9718
9775
 
9719
9776
  #################################################
@@ -9785,7 +9842,7 @@ class GenericGraph(GenericGraph_pyx):
9785
9842
  f_val = p.get_values(f, convert=bool, tolerance=integrality_tolerance)
9786
9843
  tsp.add_vertices(g.vertex_iterator())
9787
9844
  tsp.set_pos(g.get_pos())
9788
- tsp.name("TSP from " + g.name())
9845
+ tsp._name = "TSP from " + g.name()
9789
9846
  if g.is_directed():
9790
9847
  tsp.add_edges((u, v, l) for u, v, l in g.edge_iterator() if f_val[u, v] == 1)
9791
9848
  else:
@@ -12060,7 +12117,7 @@ class GenericGraph(GenericGraph_pyx):
12060
12117
  2: Moebius-Kantor Graph: Graph on 16 vertices}
12061
12118
  """
12062
12119
  if verts is None:
12063
- verts = list(self)
12120
+ verts = self
12064
12121
 
12065
12122
  if not hasattr(self, '_assoc'):
12066
12123
  return dict.fromkeys(verts, None)
@@ -14452,7 +14509,7 @@ class GenericGraph(GenericGraph_pyx):
14452
14509
  G._pos3d = {v: pos3d[v] for v in G if v in pos3d}
14453
14510
  embedding = self.get_embedding()
14454
14511
  if embedding is not None:
14455
- G._embedding = {u: [v for v in embedding[u] if u in G] for u in G}
14512
+ G._embedding = embedding
14456
14513
 
14457
14514
  if immutable is None:
14458
14515
  immutable = self.is_immutable()
@@ -15053,6 +15110,118 @@ class GenericGraph(GenericGraph_pyx):
15053
15110
  yield self.subgraph(g, edges=[(G_to_g[u], G_to_g[v])
15054
15111
  for u, v in G.edge_iterator(labels=False)])
15055
15112
 
15113
+ def subgraph_decompositions(self, H, induced=False):
15114
+ r"""
15115
+ Return an iterator over the `H`-decompositions of a graph.
15116
+
15117
+ For a graph `G`, we say a collection of graphs `H_1,\dots,H_m` is a
15118
+ *decomposition* of `G`, if `G` is an edge-disjoint union of
15119
+ `H_1,\dots,H_m`. See :arxiv:`2308.11613`.
15120
+
15121
+ For graphs `G` and `H`, an `H`-*decomposition* of `G` is a
15122
+ partition of the edges of `G` into subgraphs isomorphic to `H`.
15123
+ See :arxiv:`1401.3665`.
15124
+
15125
+ INPUT:
15126
+
15127
+ - ``H`` -- the graph whose copies we are looking for in ``self``
15128
+ - ``induced`` -- boolean (default: ``False``); whether or not to
15129
+ consider only the isometric copies of ``H`` which are induced
15130
+ subgraphs of the graph ``self``
15131
+
15132
+ OUTPUT:
15133
+
15134
+ An iterator of lists of lists of edges
15135
+
15136
+ EXAMPLES::
15137
+
15138
+ sage: G1 = Graph( [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3),
15139
+ ....: (1, 5), (2, 3), (2, 4), (3, 5), (4, 6), (4, 7), (5, 6), (5, 7), (6, 8),
15140
+ ....: (6, 10), (7, 9), (7, 11), (8, 9), (8, 10), (9, 11), (10, 11)])
15141
+ sage: claw = graphs.ClawGraph()
15142
+ sage: list(G1.subgraph_decompositions(claw))
15143
+ []
15144
+
15145
+ ::
15146
+
15147
+ sage: G2 = Graph([(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4),
15148
+ ....: (1, 5), (2, 4), (2, 5), (3, 5), (4, 5)])
15149
+ sage: it = G2.subgraph_decompositions(claw)
15150
+ sage: next(it) # random
15151
+ [[(0, 1), (0, 2), (0, 3)],
15152
+ [(0, 4), (1, 4), (2, 4)],
15153
+ [(1, 2), (1, 3), (1, 5)],
15154
+ [(2, 5), (3, 5), (4, 5)]]
15155
+
15156
+ It has no claw-decomposition if we restrict to claws that are
15157
+ induced subraphs::
15158
+
15159
+ sage: list(G2.subgraph_decompositions(claw, induced=True))
15160
+ []
15161
+
15162
+ It has no claw-decomposition if the number of edges of the graph is
15163
+ not a multiple of the number of edges of the claw (3)::
15164
+
15165
+ sage: G = Graph([(0, 1), (0, 2), (0, 3), (0, 4)])
15166
+ sage: list(G.subgraph_decompositions(claw))
15167
+ []
15168
+
15169
+ Works for digraphs::
15170
+
15171
+ sage: G = DiGraph([(0,1), (1,2), (2,3), (3,4)], format='list_of_edges')
15172
+ sage: H = DiGraph([(0,1), (1,2)], format='list_of_edges')
15173
+ sage: sorted(sorted(edges) for edges in G.subgraph_decompositions(H))
15174
+ [[[(0, 1), (1, 2)], [(2, 3), (3, 4)]]]
15175
+
15176
+ ::
15177
+
15178
+ sage: G = DiGraph([(0,1), (1,2), (2,3), (4,3)], format='list_of_edges')
15179
+ sage: H = DiGraph([(0,1), (1,2)], format='list_of_edges')
15180
+ sage: sorted(sorted(edges) for edges in G.subgraph_decompositions(H))
15181
+ []
15182
+
15183
+ ::
15184
+
15185
+ sage: G = DiGraph([(0,1), (1,0), (1,2), (2,1)], format='list_of_edges')
15186
+ sage: H = DiGraph([(0,1), (1,2)], format='list_of_edges')
15187
+ sage: sorted(sorted(edges) for edges in G.subgraph_decompositions(H))
15188
+ [[[(0, 1), (1, 2)], [(1, 0), (2, 1)]]]
15189
+
15190
+ TESTS:
15191
+
15192
+ The graph ``G`` needs to be a simple graph::
15193
+
15194
+ sage: G = DiGraph([(0,1), (0,1), (1,2), (1,2), (2,3), (3,4)],
15195
+ ....: format='list_of_edges', multiedges=True)
15196
+ sage: H = DiGraph([(0,1), (1,2)], format='list_of_edges')
15197
+ sage: list(G.subgraph_decompositions(H))
15198
+ Traceback (most recent call last):
15199
+ ...
15200
+ ValueError: This method is not known to work on graphs with
15201
+ multiedges. Perhaps this method can be updated to handle them,
15202
+ but in the meantime if you want to use it please disallow
15203
+ multiedges using allow_multiple_edges().
15204
+ """
15205
+ # number of edges of H must divide the number of edges of self
15206
+ if self.num_edges() % H.num_edges():
15207
+ return
15208
+
15209
+ from sage.combinat.matrices.dancing_links import dlx_solver
15210
+
15211
+ edges = list(self.edges(labels=False))
15212
+ edge_to_column_id = {edge:i for i, edge in enumerate(edges)}
15213
+
15214
+ rows = set()
15215
+ for h in self.subgraph_search_iterator(H, induced=induced, return_graphs=True):
15216
+ h_edges = h.edges(labels=False)
15217
+ L = sorted(edge_to_column_id[edge] for edge in h_edges)
15218
+ rows.add(tuple(L))
15219
+ dlx = dlx_solver(rows)
15220
+ rows = dlx.rows() # the list of rows in the order used by the solver
15221
+
15222
+ for solution in dlx.solutions_iterator():
15223
+ yield [[edges[j] for j in rows[i]] for i in solution]
15224
+
15056
15225
  def random_subgraph(self, p, inplace=False):
15057
15226
  """
15058
15227
  Return a random subgraph containing each vertex with probability ``p``.
@@ -16243,21 +16412,21 @@ class GenericGraph(GenericGraph_pyx):
16243
16412
  sage: import numpy
16244
16413
  sage: if int(numpy.version.short_version[0]) > 1:
16245
16414
  ....: numpy.set_printoptions(legacy="1.25")
16246
- sage: (graphs.FruchtGraph()).clustering_coeff(weight=True)
16415
+ sage: graphs.FruchtGraph().clustering_coeff(weight=True)
16247
16416
  {0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0,
16248
16417
  3: 0.3333333333333333, 4: 0.3333333333333333,
16249
16418
  5: 0.3333333333333333, 6: 0.3333333333333333,
16250
16419
  7: 0.3333333333333333, 8: 0, 9: 0.3333333333333333,
16251
16420
  10: 0.3333333333333333, 11: 0}
16252
16421
 
16253
- sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2])
16422
+ sage: graphs.FruchtGraph().clustering_coeff(nodes=[0,1,2])
16254
16423
  {0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0.0}
16255
16424
 
16256
- sage: (graphs.FruchtGraph()).clustering_coeff(nodes=[0,1,2], # needs networkx
16425
+ sage: graphs.FruchtGraph().clustering_coeff(nodes=[0,1,2], # needs networkx
16257
16426
  ....: weight=True)
16258
16427
  {0: 0.3333333333333333, 1: 0.3333333333333333, 2: 0}
16259
16428
 
16260
- sage: (graphs.GridGraph([5,5])).clustering_coeff(nodes=[(0,0),(0,1),(2,2)])
16429
+ sage: graphs.GridGraph([5,5]).clustering_coeff(nodes=[(0,0),(0,1),(2,2)])
16261
16430
  {(0, 0): 0.0, (0, 1): 0.0, (2, 2): 0.0}
16262
16431
 
16263
16432
  TESTS:
@@ -20540,7 +20709,7 @@ class GenericGraph(GenericGraph_pyx):
20540
20709
  return GT([vertices, edges], format='vertices_and_edges',
20541
20710
  loops=loops, immutable=immutable)
20542
20711
 
20543
- def transitive_closure(self, loops=True):
20712
+ def transitive_closure(self, loops=None, immutable=None):
20544
20713
  r"""
20545
20714
  Return the transitive closure of the (di)graph.
20546
20715
 
@@ -20552,11 +20721,18 @@ class GenericGraph(GenericGraph_pyx):
20552
20721
  acyclic graph is a directed acyclic graph representing the full partial
20553
20722
  order.
20554
20723
 
20555
- .. NOTE::
20724
+ INPUT:
20556
20725
 
20557
- If the (di)graph allows loops, its transitive closure will by
20558
- default have one loop edge per vertex. This can be prevented by
20559
- disallowing loops in the (di)graph (``self.allow_loops(False)``).
20726
+ - ``loops`` -- boolean (default: ``None``); whether to allow loops in
20727
+ the returned (di)graph. By default (``None``), if the (di)graph allows
20728
+ loops, its transitive closure will have one loop edge per vertex. This
20729
+ can be prevented by disallowing loops in the (di)graph
20730
+ (``self.allow_loops(False)``).
20731
+
20732
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20733
+ mutable/immutable transitive closure. ``immutable=None`` (default)
20734
+ means that the (di)graph and its transitive closure will behave the
20735
+ same way.
20560
20736
 
20561
20737
  EXAMPLES::
20562
20738
 
@@ -20584,21 +20760,49 @@ class GenericGraph(GenericGraph_pyx):
20584
20760
  sage: G.transitive_closure().loop_edges(labels=False)
20585
20761
  [(0, 0), (1, 1), (2, 2)]
20586
20762
 
20587
- ::
20763
+ Check the behavior of parameter ``loops``::
20588
20764
 
20589
20765
  sage: G = graphs.CycleGraph(3)
20590
20766
  sage: G.transitive_closure().loop_edges(labels=False)
20591
20767
  []
20768
+ sage: G.transitive_closure(loops=True).loop_edges(labels=False)
20769
+ [(0, 0), (1, 1), (2, 2)]
20592
20770
  sage: G.allow_loops(True)
20593
20771
  sage: G.transitive_closure().loop_edges(labels=False)
20594
20772
  [(0, 0), (1, 1), (2, 2)]
20773
+ sage: G.transitive_closure(loops=False).loop_edges(labels=False)
20774
+ []
20775
+
20776
+ Check the behavior of parameter ``immutable``::
20777
+
20778
+ sage: G = Graph([(0, 1)])
20779
+ sage: G.transitive_closure().is_immutable()
20780
+ False
20781
+ sage: G.transitive_closure(immutable=True).is_immutable()
20782
+ True
20783
+ sage: G = Graph([(0, 1)], immutable=True)
20784
+ sage: G.transitive_closure().is_immutable()
20785
+ True
20786
+ sage: G.transitive_closure(immutable=False).is_immutable()
20787
+ False
20595
20788
  """
20596
- G = copy(self)
20597
- G.name('Transitive closure of ' + self.name())
20598
- G.add_edges(((u, v) for u in G for v in G.breadth_first_search(u)), loops=None)
20599
- return G
20789
+ name = f"Transitive closure of {self.name()}"
20790
+ if immutable is None:
20791
+ immutable = self.is_immutable()
20792
+ if loops is None:
20793
+ loops = self.allows_loops()
20794
+ if loops:
20795
+ edges = ((u, v) for u in self for v in self.depth_first_search(u))
20796
+ else:
20797
+ edges = ((u, v) for u in self for v in self.depth_first_search(u) if u != v)
20798
+ if self.is_directed():
20799
+ from sage.graphs.digraph import DiGraph as GT
20800
+ else:
20801
+ from sage.graphs.graph import Graph as GT
20802
+ return GT([self, edges], format='vertices_and_edges', loops=loops,
20803
+ immutable=immutable, name=name)
20600
20804
 
20601
- def transitive_reduction(self):
20805
+ def transitive_reduction(self, immutable=None):
20602
20806
  r"""
20603
20807
  Return a transitive reduction of a graph.
20604
20808
 
@@ -20611,6 +20815,13 @@ class GenericGraph(GenericGraph_pyx):
20611
20815
  A transitive reduction of a complete graph is a tree. A transitive
20612
20816
  reduction of a tree is itself.
20613
20817
 
20818
+ INPUT:
20819
+
20820
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
20821
+ mutable/immutable transitive closure. ``immutable=None`` (default)
20822
+ means that the (di)graph and its transitive closure will behave the
20823
+ same way.
20824
+
20614
20825
  EXAMPLES::
20615
20826
 
20616
20827
  sage: g = graphs.PathGraph(4)
@@ -20619,38 +20830,72 @@ class GenericGraph(GenericGraph_pyx):
20619
20830
  sage: g = graphs.CompleteGraph(5)
20620
20831
  sage: h = g.transitive_reduction(); h.size()
20621
20832
  4
20833
+ sage: (2*g).transitive_reduction().size()
20834
+ 8
20622
20835
  sage: g = DiGraph({0: [1, 2], 1: [2, 3, 4, 5], 2: [4, 5]})
20623
20836
  sage: g.transitive_reduction().size()
20624
20837
  5
20838
+ sage: (2*g).transitive_reduction().size()
20839
+ 10
20840
+
20841
+ TESTS:
20842
+
20843
+ Check the behavior of parameter ``immutable``::
20844
+
20845
+ sage: G = Graph([(0, 1)])
20846
+ sage: G.transitive_reduction().is_immutable()
20847
+ False
20848
+ sage: G.transitive_reduction(immutable=True).is_immutable()
20849
+ True
20850
+ sage: G = Graph([(0, 1)], immutable=True)
20851
+ sage: G.transitive_reduction().is_immutable()
20852
+ True
20853
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)])
20854
+ sage: G.transitive_reduction().is_immutable()
20855
+ False
20856
+ sage: G.transitive_reduction(immutable=True).is_immutable()
20857
+ True
20858
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20859
+ sage: G.transitive_reduction().is_immutable()
20860
+ True
20625
20861
  """
20862
+ if immutable is None:
20863
+ immutable = self.is_immutable()
20864
+
20626
20865
  if self.is_directed():
20627
20866
  if self.is_directed_acyclic():
20628
20867
  from sage.graphs.generic_graph_pyx import transitive_reduction_acyclic
20629
- return transitive_reduction_acyclic(self)
20868
+ return transitive_reduction_acyclic(self, immutable=immutable)
20630
20869
 
20631
- G = copy(self)
20870
+ G = self.copy(immutable=False)
20632
20871
  G.allow_multiple_edges(False)
20633
20872
  n = G.order()
20634
- for e in G.edges(sort=False):
20873
+ for e in list(G.edges(sort=False)):
20635
20874
  # Try deleting the edge, see if we still have a path between
20636
20875
  # the vertices.
20637
20876
  G.delete_edge(e)
20638
20877
  if G.distance(e[0], e[1]) > n:
20639
20878
  # oops, we shouldn't have deleted it
20640
20879
  G.add_edge(e)
20880
+ if immutable:
20881
+ return G.copy(immutable=True)
20641
20882
  return G
20642
20883
 
20643
20884
  # The transitive reduction of each connected component of an
20644
20885
  # undirected graph is a spanning tree
20645
- from sage.graphs.graph import Graph
20646
20886
  if self.is_connected():
20647
- return Graph(self.min_spanning_tree(weight_function=lambda e: 1))
20648
- G = Graph(list(self))
20649
- for cc in self.connected_components(sort=False):
20650
- if len(cc) > 1:
20651
- edges = self.subgraph(cc).min_spanning_tree(weight_function=lambda e: 1)
20652
- G.add_edges(edges)
20653
- return G
20887
+ CC = [self]
20888
+ else:
20889
+ CC = (self.subgraph(c)
20890
+ for c in self.connected_components(sort=False) if len(c) > 1)
20891
+
20892
+ def edges():
20893
+ for g in CC:
20894
+ yield from g.min_spanning_tree(weight_function=lambda e: 1)
20895
+
20896
+ from sage.graphs.graph import Graph
20897
+ return Graph([self, edges()], format='vertices_and_edges',
20898
+ immutable=immutable)
20654
20899
 
20655
20900
  def is_transitively_reduced(self):
20656
20901
  r"""
@@ -20672,13 +20917,27 @@ class GenericGraph(GenericGraph_pyx):
20672
20917
  sage: d = DiGraph({0: [1, 2], 1: [2], 2: []})
20673
20918
  sage: d.is_transitively_reduced()
20674
20919
  False
20920
+
20921
+ TESTS:
20922
+
20923
+ Check the behavior of the method for immutable (di)graphs::
20924
+
20925
+ sage: G = DiGraph([(0, 1), (1, 2), (2, 0)], immutable=True)
20926
+ sage: G.is_transitively_reduced()
20927
+ True
20928
+ sage: G = DiGraph(graphs.CompleteGraph(4), immutable=True)
20929
+ sage: G.is_transitively_reduced()
20930
+ False
20931
+ sage: G = Graph([(0, 1), (2, 3)], immutable=True)
20932
+ sage: G.is_transitively_reduced()
20933
+ True
20675
20934
  """
20676
20935
  if self.is_directed():
20677
20936
  if self.is_directed_acyclic():
20678
20937
  return self == self.transitive_reduction()
20679
20938
 
20680
20939
  from sage.rings.infinity import Infinity
20681
- G = copy(self)
20940
+ G = self.copy(immutable=False)
20682
20941
  for e in self.edge_iterator():
20683
20942
  G.delete_edge(e)
20684
20943
  if G.distance(e[0], e[1]) == Infinity:
@@ -20940,6 +21199,8 @@ class GenericGraph(GenericGraph_pyx):
20940
21199
  ....: print("option {} : {}".format(key, value))
20941
21200
  option by_component : Whether to do the spring layout by connected component -- boolean.
20942
21201
  option dim : The dimension of the layout -- 2 or 3.
21202
+ option external_face : A list of the vertices of the external face of the graph, used for Tutte embedding layout.
21203
+ option external_face_pos : A dictionary specifying the positions of the external face of the graph, used for Tutte embedding layout. If none specified, theexternal face is a regular polygon.
20943
21204
  option forest_roots : An iterable specifying which vertices to use as roots for the ``layout='forest'`` option. If no root is specified for a tree, then one is chosen close to the center of the tree. Ignored unless ``layout='forest'``.
20944
21205
  option heights : A dictionary mapping heights to the list of vertices at this height.
20945
21206
  option iterations : The number of times to execute the spring layout algorithm.
@@ -21573,6 +21834,95 @@ class GenericGraph(GenericGraph_pyx):
21573
21834
 
21574
21835
  return {key_to_vertex[key]: pos for key, pos in positions.items()}
21575
21836
 
21837
+ def layout_tutte(self, external_face=None, external_face_pos=None, **options):
21838
+ r"""
21839
+ Compute graph layout based on a Tutte embedding.
21840
+
21841
+ The graph must be 3-connected and planar.
21842
+
21843
+ INPUT:
21844
+
21845
+ - ``external_face`` -- list (default: ``None``); the external face to
21846
+ be made a polygon
21847
+
21848
+ - ``external_face_pos`` -- dictionary (default: ``None``); the positions
21849
+ of the vertices of the external face. If ``None``, will automatically
21850
+ generate a unit sided regular polygon.
21851
+
21852
+ - ``**options`` -- other parameters not used here
21853
+
21854
+ OUTPUT: a dictionary mapping vertices to positions
21855
+
21856
+ EXAMPLES::
21857
+
21858
+ sage: # needs sage.modules sage.plot
21859
+ sage: g = graphs.WheelGraph(n=7)
21860
+ sage: g.plot(layout='tutte', external_face=[0,1,2])
21861
+ Graphics object consisting of 20 graphics primitives
21862
+ sage: g = graphs.CubeGraph(n=3, embedding=2)
21863
+ sage: g.plot(layout='tutte', external_face=['101','111','001',
21864
+ ....: '011'], external_face_pos={'101':(1,0), '111':(0,0),
21865
+ ....: '001':(2,1), '011':(-1,1)})
21866
+ Graphics object consisting of 21 graphics primitives
21867
+ sage: g = graphs.CompleteGraph(n=5)
21868
+ sage: g.plot(layout='tutte', external_face=[0,1,2])
21869
+ Traceback (most recent call last):
21870
+ ...
21871
+ ValueError: graph must be planar
21872
+ sage: g = graphs.CycleGraph(n=10)
21873
+ sage: g.layout(layout='tutte', external_face=[0,1,2,3,4,5,6,7,8,9])
21874
+ Traceback (most recent call last):
21875
+ ...
21876
+ ValueError: graph must be 3-connected
21877
+ """
21878
+ from sage.matrix.constructor import zero_matrix
21879
+ from sage.rings.real_mpfr import RR
21880
+
21881
+ if (external_face is not None) and (len(external_face) < 3):
21882
+ raise ValueError("external face must have at least 3 vertices")
21883
+
21884
+ if not self.is_planar(set_embedding=True):
21885
+ raise ValueError("graph must be planar")
21886
+
21887
+ if not self.vertex_connectivity(k=3):
21888
+ raise ValueError("graph must be 3-connected")
21889
+
21890
+ faces_edges = self.faces()
21891
+ faces_vertices = [[edge[0] for edge in face] for face in faces_edges]
21892
+ if external_face is None:
21893
+ external_face = faces_vertices[0]
21894
+ else:
21895
+ # Check that external_face is a face and order it correctly
21896
+ matching_face = next((f for f in faces_vertices if sorted(external_face) == sorted(f)), None)
21897
+ if matching_face is None:
21898
+ raise ValueError("external face must be a face of the graph")
21899
+ external_face = matching_face
21900
+
21901
+ if external_face_pos is None:
21902
+ pos = self._circle_embedding(external_face, return_dict=True)
21903
+ else:
21904
+ pos = external_face_pos.copy()
21905
+
21906
+ n = self.order()
21907
+ M = zero_matrix(RR, n, n)
21908
+ b = zero_matrix(RR, n, 2)
21909
+
21910
+ vertices_to_indices = {v: i for i, v in enumerate(self)}
21911
+ for i, v in enumerate(self):
21912
+ if v in pos:
21913
+ M[i, i] = 1
21914
+ b[i, 0] = pos[v][0]
21915
+ b[i, 1] = pos[v][1]
21916
+ else:
21917
+ nv = self.neighbors(v)
21918
+ for u in nv:
21919
+ j = vertices_to_indices[u]
21920
+ M[i, j] = -1
21921
+ M[i, i] = len(nv)
21922
+
21923
+ sol = M.pseudoinverse()*b
21924
+ return dict(zip(self, sol))
21925
+
21576
21926
  def _layout_bounding_box(self, pos):
21577
21927
  """
21578
21928
  Return a bounding box around the specified positions.
@@ -21892,6 +22242,9 @@ class GenericGraph(GenericGraph_pyx):
21892
22242
  selected at random. Then the tree will be plotted in levels,
21893
22243
  depending on minimum distance for the root.
21894
22244
 
22245
+ - ``'tutte'`` -- uses the Tutte embedding algorithm. The graph must be
22246
+ a 3-connected, planar graph.
22247
+
21895
22248
  - ``vertex_labels`` -- boolean (default: ``True``); whether to print
21896
22249
  vertex labels
21897
22250
 
@@ -21958,6 +22311,13 @@ class GenericGraph(GenericGraph_pyx):
21958
22311
  appear on the bottom (resp., top) and the tree will grow upwards
21959
22312
  (resp. downwards). Ignored unless ``layout='tree'``.
21960
22313
 
22314
+ - ``external_face`` -- list of vertices (default: ``None``); the external face to be made a
22315
+ in the Tutte layout. Ignored unless ``layout='tutte''``.
22316
+
22317
+ - ``external_face_pos`` -- dictionary (default: ``None``). If specified,
22318
+ used as the positions for the external face in the Tutte layout.
22319
+ Ignored unless ``layout='tutte'``.
22320
+
21961
22321
  - ``save_pos`` -- boolean (default: ``False``); save position computed
21962
22322
  during plotting
21963
22323
 
@@ -23981,8 +24341,9 @@ class GenericGraph(GenericGraph_pyx):
23981
24341
 
23982
24342
  def degree_to_cell(self, vertex, cell):
23983
24343
  """
23984
- Return the number of edges from vertex to an edge in cell. In the
23985
- case of a digraph, returns a tuple (in_degree, out_degree).
24344
+ Return the number of edges from vertex to an edge in cell.
24345
+
24346
+ In the case of a digraph, this returns a tuple (in_degree, out_degree).
23986
24347
 
23987
24348
  EXAMPLES::
23988
24349
 
@@ -24005,8 +24366,9 @@ class GenericGraph(GenericGraph_pyx):
24005
24366
  (0, 2)
24006
24367
  """
24007
24368
  if self._directed:
24008
- in_neighbors_in_cell = set([a for a, _, _ in self.incoming_edges(vertex)]) & set(cell)
24009
- out_neighbors_in_cell = set([a for _, a, _ in self.outgoing_edges(vertex)]) & set(cell)
24369
+ s_cell = set(cell)
24370
+ in_neighbors_in_cell = s_cell.intersection(self.neighbor_in_iterator(vertex))
24371
+ out_neighbors_in_cell = s_cell.intersection(self.neighbor_out_iterator(vertex))
24010
24372
  return (len(in_neighbors_in_cell), len(out_neighbors_in_cell))
24011
24373
 
24012
24374
  neighbors_in_cell = set(self.neighbors(vertex)) & set(cell)
@@ -24159,10 +24521,9 @@ class GenericGraph(GenericGraph_pyx):
24159
24521
  raise TypeError("partition (%s) is not valid for this graph: there is a cell of length 0" % partition)
24160
24522
  if self.has_multiple_edges():
24161
24523
  raise TypeError("refinement function does not support multiple edges")
24162
- G = copy(self)
24163
- perm_from = list(G)
24524
+ perm_from = list(self)
24164
24525
  perm_to = {v: i for i, v in enumerate(perm_from)}
24165
- G.relabel(perm=perm_to)
24526
+ G = self.relabel(perm=perm_to, inplace=False, immutable=True)
24166
24527
  partition = [[perm_to[b] for b in cell] for cell in partition]
24167
24528
  n = G.order()
24168
24529
  if sparse:
@@ -24410,6 +24771,19 @@ class GenericGraph(GenericGraph_pyx):
24410
24771
  ....: partition=[V])
24411
24772
  sage: str(a2) == str(b2) # optional - bliss
24412
24773
  True
24774
+
24775
+ Check the behavior with immutable graphs::
24776
+
24777
+ sage: # needs sage.groups
24778
+ sage: G = Graph(graphs.PetersenGraph(), immutable=True)
24779
+ sage: G.automorphism_group(return_group=False, orbits=True, algorithm='sage')
24780
+ [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
24781
+ sage: G = graphs.PetersenGraph()
24782
+ sage: G.allow_multiple_edges(True)
24783
+ sage: G.add_edges(G.edges())
24784
+ sage: G = Graph(G, immutable=True)
24785
+ sage: G.automorphism_group(return_group=False, orbits=True, algorithm='sage')
24786
+ [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
24413
24787
  """
24414
24788
  from sage.features.bliss import Bliss
24415
24789
  have_bliss = Bliss().is_present()
@@ -24460,7 +24834,7 @@ class GenericGraph(GenericGraph_pyx):
24460
24834
  partition = [list(self)]
24461
24835
 
24462
24836
  if edge_labels or self.has_multiple_edges():
24463
- ret = graph_isom_equivalent_non_edge_labeled_graph(self, partition,
24837
+ ret = graph_isom_equivalent_non_edge_labeled_graph(self, partition=partition,
24464
24838
  return_relabeling=True,
24465
24839
  ignore_edge_labels=(not edge_labels))
24466
24840
  G, partition, relabeling = ret
@@ -24666,7 +25040,7 @@ class GenericGraph(GenericGraph_pyx):
24666
25040
 
24667
25041
  TESTS::
24668
25042
 
24669
- sage: g = graphs.ChvatalGraph()
25043
+ sage: g = graphs.ChvatalGraph().copy(immutable=True)
24670
25044
  sage: g.is_hamiltonian() # needs sage.numerical.mip
24671
25045
  True
24672
25046
 
@@ -24918,6 +25292,15 @@ class GenericGraph(GenericGraph_pyx):
24918
25292
  (True, {6: 'x', 7: 'y'})
24919
25293
  sage: A.is_isomorphic(B, certificate=True, edge_labels=True)
24920
25294
  (False, None)
25295
+
25296
+ Check the behavior with immutable graphs::
25297
+
25298
+ sage: A = DiGraph([(6,7,'a'), (6,7,'b')], multiedges=True, immutable=True)
25299
+ sage: B = DiGraph([('x','y','u'), ('x','y','v')], multiedges=True)
25300
+ sage: A.is_isomorphic(B, certificate=True)
25301
+ (True, {6: 'x', 7: 'y'})
25302
+ sage: B.is_isomorphic(A, certificate=True)
25303
+ (True, {'x': 6, 'y': 7})
24921
25304
  """
24922
25305
  if not self.order() and not other.order():
24923
25306
  return (True, None) if certificate else True
@@ -25011,7 +25394,8 @@ class GenericGraph(GenericGraph_pyx):
25011
25394
  return True, isom_trans
25012
25395
 
25013
25396
  def canonical_label(self, partition=None, certificate=False,
25014
- edge_labels=False, algorithm=None, return_graph=True):
25397
+ edge_labels=False, algorithm=None, return_graph=True,
25398
+ immutable=None):
25015
25399
  r"""
25016
25400
  Return the canonical graph.
25017
25401
 
@@ -25052,6 +25436,10 @@ class GenericGraph(GenericGraph_pyx):
25052
25436
  instead of the canonical graph; only available when ``'bliss'``
25053
25437
  is explicitly set as algorithm.
25054
25438
 
25439
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
25440
+ mutable/immutable (di)graph. ``immutable=None`` (default) means that
25441
+ the (di)graph and its canonical (di)graph will behave the same way.
25442
+
25055
25443
  EXAMPLES:
25056
25444
 
25057
25445
  Canonization changes isomorphism to equality::
@@ -25130,6 +25518,8 @@ class GenericGraph(GenericGraph_pyx):
25130
25518
  (Graph on 2 vertices, {'a': 0, 'b': 1})
25131
25519
  sage: G.canonical_label(algorithm='bliss', certificate=True) # optional - bliss
25132
25520
  (Graph on 2 vertices, {'a': 1, 'b': 0})
25521
+ sage: G.canonical_label(algorithm='bliss', return_graph=False) # optional - bliss
25522
+ [(1, 0, None)]
25133
25523
 
25134
25524
  Check for immutable graphs (:issue:`16602`)::
25135
25525
 
@@ -25138,6 +25528,23 @@ class GenericGraph(GenericGraph_pyx):
25138
25528
  Graph on 3 vertices
25139
25529
  sage: C.vertices(sort=True)
25140
25530
  [0, 1, 2]
25531
+ sage: G.canonical_label(algorithm='bliss').is_immutable() # optional - bliss
25532
+ True
25533
+ sage: G.canonical_label(algorithm='sage').is_immutable()
25534
+ True
25535
+ sage: G.canonical_label(algorithm='bliss', immutable=False).is_immutable() # optional - bliss
25536
+ False
25537
+ sage: G.canonical_label(algorithm='sage', immutable=False).is_immutable()
25538
+ False
25539
+ sage: G = Graph([[1, 2], [2, 3]])
25540
+ sage: G.canonical_label(algorithm='bliss').is_immutable() # optional - bliss
25541
+ False
25542
+ sage: G.canonical_label(algorithm='sage').is_immutable()
25543
+ False
25544
+ sage: G.canonical_label(algorithm='bliss', immutable=True).is_immutable() # optional - bliss
25545
+ True
25546
+ sage: G.canonical_label(algorithm='sage', immutable=True).is_immutable()
25547
+ True
25141
25548
 
25142
25549
  Corner cases::
25143
25550
 
@@ -25215,11 +25622,13 @@ class GenericGraph(GenericGraph_pyx):
25215
25622
 
25216
25623
  if algorithm == 'bliss':
25217
25624
  if return_graph:
25218
- vert_dict = canonical_form(self, partition, False, edge_labels, True)[1]
25625
+ vert_dict = canonical_form(self, partition=partition, return_graph=False,
25626
+ use_edge_labels=edge_labels, certificate=True)[1]
25219
25627
  if not certificate:
25220
- return self.relabel(vert_dict, inplace=False)
25221
- return (self.relabel(vert_dict, inplace=False), vert_dict)
25222
- return canonical_form(self, partition, return_graph, edge_labels, certificate)
25628
+ return self.relabel(vert_dict, inplace=False, immutable=immutable)
25629
+ return (self.relabel(vert_dict, inplace=False, immutable=immutable), vert_dict)
25630
+ return canonical_form(self, partition=partition, return_graph=False,
25631
+ use_edge_labels=edge_labels, certificate=certificate)
25223
25632
 
25224
25633
  # algorithm == 'sage':
25225
25634
  from sage.groups.perm_gps.partn_ref.refinement_graphs import search_tree
@@ -25231,7 +25640,8 @@ class GenericGraph(GenericGraph_pyx):
25231
25640
  if partition is None:
25232
25641
  partition = [list(self)]
25233
25642
  if edge_labels or self.has_multiple_edges():
25234
- G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition, return_relabeling=True)
25643
+ G, partition, relabeling = graph_isom_equivalent_non_edge_labeled_graph(self, partition=partition,
25644
+ return_relabeling=True)
25235
25645
  G_vertices = list(chain(*partition))
25236
25646
  G_to = {u: i for i, u in enumerate(G_vertices)}
25237
25647
  DoDG = DiGraph if self._directed else Graph
@@ -25243,7 +25653,6 @@ class GenericGraph(GenericGraph_pyx):
25243
25653
  partition = [[G_to[vv] for vv in cell] for cell in partition]
25244
25654
  a, b, c = search_tree(GC, partition, certificate=True, dig=dig)
25245
25655
  # c is a permutation to the canonical label of G, which depends only on isomorphism class of self.
25246
- H = copy(self)
25247
25656
  c_new = {v: c[G_to[relabeling[v]]] for v in self}
25248
25657
  else:
25249
25658
  G_vertices = list(chain(*partition))
@@ -25256,9 +25665,12 @@ class GenericGraph(GenericGraph_pyx):
25256
25665
  GC = HB.c_graph()[0]
25257
25666
  partition = [[G_to[vv] for vv in cell] for cell in partition]
25258
25667
  a, b, c = search_tree(GC, partition, certificate=True, dig=dig)
25259
- H = copy(self)
25260
25668
  c_new = {v: c[G_to[v]] for v in G_to}
25261
- H.relabel(c_new)
25669
+
25670
+ if immutable is None:
25671
+ immutable = self.is_immutable()
25672
+ H = self.relabel(perm=c_new, inplace=False, immutable=immutable)
25673
+
25262
25674
  if certificate:
25263
25675
  return H, c_new
25264
25676
  return H
@@ -25365,6 +25777,14 @@ class GenericGraph(GenericGraph_pyx):
25365
25777
  sage: graphs.CompleteBipartiteGraph(50, 50).is_cayley() # needs sage.groups
25366
25778
  True
25367
25779
 
25780
+ Check the behavior with immutable graphs::
25781
+
25782
+ sage: C7 = groups.permutation.Cyclic(7) # needs sage.groups
25783
+ sage: S = [(1,2,3,4,5,6,7), (1,3,5,7,2,4,6), (1,5,2,6,3,7,4)]
25784
+ sage: d = C7.cayley_graph(generators=S) # needs sage.groups
25785
+ sage: d.copy(immutable=True).is_cayley() # needs sage.groups
25786
+ True
25787
+
25368
25788
  TESTS::
25369
25789
 
25370
25790
  sage: graphs.EmptyGraph().is_cayley()
@@ -26090,7 +26510,8 @@ def tachyon_vertex_plot(g, bgcolor=(1, 1, 1),
26090
26510
 
26091
26511
  def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_label=None,
26092
26512
  return_relabeling=False, return_edge_labels=False,
26093
- inplace=False, ignore_edge_labels=False):
26513
+ inplace=False, ignore_edge_labels=False,
26514
+ immutable=None):
26094
26515
  r"""
26095
26516
  Helper function for canonical labeling of edge labeled (di)graphs.
26096
26517
 
@@ -26141,6 +26562,9 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab
26141
26562
  that attributes of ``g`` are *not* copied for speed issues, only
26142
26563
  edges and vertices.
26143
26564
 
26565
+ This parameter cannot be set to ``True`` if the input graph
26566
+ ``g`` is immutable.
26567
+
26144
26568
  - ``ignore_edge_labels`` -- boolean (default: ``False``); if
26145
26569
  ``True``, ignore edge labels, so when constructing the new
26146
26570
  graph, only multiple edges are replaced with vertices. Labels on
@@ -26149,6 +26573,12 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab
26149
26573
  graph correspond to right vertices in the same partition in the
26150
26574
  new graph.
26151
26575
 
26576
+ - ``immutable`` -- boolean (default: ``None``); whether to create a
26577
+ mutable/immutable (di)graph. ``immutable=None`` (default) means that the
26578
+ (di)graph and the returned (di)graph will behave the same way.
26579
+
26580
+ This parameter is ignored when ``inplace`` is ``True``.
26581
+
26152
26582
  OUTPUT:
26153
26583
 
26154
26584
  - if ``inplace`` is ``False``: the unlabeled graph without
@@ -26238,7 +26668,47 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab
26238
26668
  sage: g = graph_isom_equivalent_non_edge_labeled_graph(G)
26239
26669
  sage: g[0].is_bipartite()
26240
26670
  False
26671
+
26672
+ Check the behavior with immutable graphs::
26673
+
26674
+ sage: Him = Graph([(0, 1, 1), (1, 2), (2, 123), ('a', 123)], immutable=True)
26675
+ sage: graph_isom_equivalent_non_edge_labeled_graph(Him, inplace=True)
26676
+ Traceback (most recent call last):
26677
+ ...
26678
+ ValueError: parameter 'inplace' cannot be True for immutable graphs
26679
+ sage: g, _ = graph_isom_equivalent_non_edge_labeled_graph(Him)
26680
+ sage: g.is_immutable()
26681
+ True
26682
+ sage: g, _ = graph_isom_equivalent_non_edge_labeled_graph(Him, immutable=False)
26683
+ sage: g.is_immutable()
26684
+ False
26685
+ sage: H = Graph([(0, 1, 1), (1, 2), (2, 123), ('a', 123)], immutable=False)
26686
+ sage: g, _ = graph_isom_equivalent_non_edge_labeled_graph(H, immutable=True)
26687
+ sage: g.is_immutable()
26688
+ True
26689
+ sage: graph_isom_equivalent_non_edge_labeled_graph(H, inplace=True, immutable=True)
26690
+ [[[0, 1, 2, 3, 4], [5]]]
26691
+ sage: H.is_immutable()
26692
+ False
26693
+ sage: G = Graph(multiedges=True, sparse=True)
26694
+ sage: G.add_edges((0, 1, i) for i in range(10))
26695
+ sage: G.add_edge(1, 2, 'string')
26696
+ sage: G.add_edge(2, 123)
26697
+ sage: G.add_edge('a', 123)
26698
+ sage: g = graph_isom_equivalent_non_edge_labeled_graph(G)
26699
+ sage: g[0].is_immutable()
26700
+ False
26701
+ sage: Gim = G.copy(immutable=True)
26702
+ sage: g = graph_isom_equivalent_non_edge_labeled_graph(Gim, standard_label='string',
26703
+ ....: return_edge_labels=True)
26704
+ sage: g[0].is_immutable()
26705
+ True
26241
26706
  """
26707
+ if inplace and g.is_immutable():
26708
+ raise ValueError("parameter 'inplace' cannot be True for immutable graphs")
26709
+ if immutable is None:
26710
+ immutable = g.is_immutable()
26711
+
26242
26712
  from sage.graphs.graph import Graph
26243
26713
  from sage.graphs.digraph import DiGraph
26244
26714
  from itertools import chain
@@ -26286,7 +26756,7 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab
26286
26756
  if inplace:
26287
26757
  g._backend = G._backend
26288
26758
  elif not inplace:
26289
- G = copy(g)
26759
+ G = g.copy(immutable=False)
26290
26760
  else:
26291
26761
  G = g
26292
26762
 
@@ -26386,6 +26856,8 @@ def graph_isom_equivalent_non_edge_labeled_graph(g, partition=None, standard_lab
26386
26856
 
26387
26857
  return_data = []
26388
26858
  if not inplace:
26859
+ if immutable:
26860
+ G = G.copy(immutable=True)
26389
26861
  return_data.append(G)
26390
26862
  return_data.append(new_partition)
26391
26863
  if return_relabeling: