passagemath-graphs 10.5.43__cp313-cp313-macosx_14_0_arm64.whl → 10.6.1rc1__cp313-cp313-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.
- {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/METADATA +5 -6
- {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/RECORD +132 -130
- sage/combinat/abstract_tree.py +188 -17
- sage/combinat/cluster_algebra_quiver/interact.py +1 -2
- sage/combinat/cluster_algebra_quiver/mutation_type.py +518 -519
- sage/combinat/cluster_algebra_quiver/quiver.py +233 -205
- sage/combinat/designs/covering_design.py +2 -6
- sage/combinat/designs/database.py +11 -10
- sage/combinat/designs/designs_pyx.cpython-313-darwin.so +0 -0
- sage/combinat/designs/designs_pyx.pyx +2 -2
- sage/combinat/designs/evenly_distributed_sets.cpython-313-darwin.so +0 -0
- sage/combinat/designs/evenly_distributed_sets.pyx +4 -4
- sage/combinat/designs/gen_quadrangles_with_spread.cpython-313-darwin.so +0 -0
- sage/combinat/designs/latin_squares.py +53 -20
- sage/combinat/designs/orthogonal_arrays.py +2 -1
- sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-313-darwin.so +0 -0
- sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +22 -21
- sage/combinat/designs/resolvable_bibd.py +191 -157
- sage/combinat/designs/subhypergraph_search.cpython-313-darwin.so +0 -0
- sage/combinat/designs/subhypergraph_search.pyx +4 -4
- sage/combinat/designs/twographs.py +2 -2
- sage/combinat/finite_state_machine.py +6 -6
- sage/combinat/posets/bubble_shuffle.py +247 -0
- sage/combinat/posets/d_complete.py +3 -3
- sage/combinat/posets/elements.py +3 -3
- sage/combinat/posets/hasse_cython.cpython-313-darwin.so +0 -0
- sage/combinat/posets/hasse_cython.pyx +1 -1
- sage/combinat/posets/hasse_diagram.py +16 -22
- sage/combinat/posets/hochschild_lattice.py +158 -0
- sage/combinat/posets/incidence_algebras.py +14 -16
- sage/combinat/posets/lattices.py +51 -53
- sage/combinat/posets/linear_extension_iterator.cpython-313-darwin.so +0 -0
- sage/combinat/posets/linear_extensions.py +10 -12
- sage/combinat/posets/moebius_algebra.py +4 -4
- sage/combinat/posets/poset_examples.py +70 -23
- sage/combinat/posets/posets.py +294 -103
- sage/databases/knotinfo_db.py +2 -1
- sage/graphs/asteroidal_triples.cpython-313-darwin.so +0 -0
- sage/graphs/asteroidal_triples.pyx +24 -3
- sage/graphs/base/boost_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/boost_graph.pxd +3 -3
- sage/graphs/base/c_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/c_graph.pyx +1 -1
- sage/graphs/base/dense_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/dense_graph.pxd +5 -3
- sage/graphs/base/dense_graph.pyx +44 -0
- sage/graphs/base/graph_backends.cpython-313-darwin.so +0 -0
- sage/graphs/base/sparse_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/static_dense_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/static_sparse_backend.cpython-313-darwin.so +0 -0
- sage/graphs/base/static_sparse_backend.pyx +8 -5
- sage/graphs/base/static_sparse_graph.cpython-313-darwin.so +0 -0
- sage/graphs/base/static_sparse_graph.pyx +86 -15
- sage/graphs/bipartite_graph.py +59 -36
- sage/graphs/centrality.cpython-313-darwin.so +0 -0
- sage/graphs/centrality.pyx +82 -9
- sage/graphs/cographs.py +1 -1
- sage/graphs/comparability.cpython-313-darwin.so +0 -0
- sage/graphs/comparability.pyx +64 -26
- sage/graphs/connectivity.cpython-313-darwin.so +0 -0
- sage/graphs/convexity_properties.cpython-313-darwin.so +0 -0
- sage/graphs/convexity_properties.pyx +52 -9
- sage/graphs/digraph.py +439 -95
- sage/graphs/digraph_generators.py +174 -102
- sage/graphs/distances_all_pairs.cpython-313-darwin.so +0 -0
- sage/graphs/dot2tex_utils.py +1 -1
- sage/graphs/edge_connectivity.cpython-313-darwin.so +0 -0
- sage/graphs/generators/basic.py +1 -1
- sage/graphs/generators/distance_regular.cpython-313-darwin.so +0 -0
- sage/graphs/generators/distance_regular.pyx +1 -1
- sage/graphs/generators/families.py +37 -27
- sage/graphs/generators/random.py +2 -2
- sage/graphs/generators/smallgraphs.py +3 -3
- sage/graphs/generic_graph.py +558 -86
- sage/graphs/generic_graph_pyx.cpython-313-darwin.so +0 -0
- sage/graphs/generic_graph_pyx.pyx +58 -11
- sage/graphs/genus.cpython-313-darwin.so +0 -0
- sage/graphs/genus.pyx +3 -4
- sage/graphs/graph.py +291 -8
- sage/graphs/graph_coloring.cpython-313-darwin.so +0 -0
- sage/graphs/graph_database.py +67 -12
- sage/graphs/graph_decompositions/bandwidth.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/clique_separators.pyx +24 -3
- sage/graphs/graph_decompositions/cutwidth.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/fast_digraph.pyx +1 -1
- sage/graphs/graph_decompositions/graph_products.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/graph_products.pyx +67 -21
- sage/graphs/graph_decompositions/modular_decomposition.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/slice_decomposition.pyx +34 -8
- sage/graphs/graph_decompositions/tree_decomposition.cpython-313-darwin.so +0 -0
- sage/graphs/graph_decompositions/vertex_separation.cpython-313-darwin.so +0 -0
- sage/graphs/graph_generators.py +45 -32
- sage/graphs/graph_generators_pyx.cpython-313-darwin.so +0 -0
- sage/graphs/graph_generators_pyx.pyx +15 -15
- sage/graphs/graph_latex.py +1 -1
- sage/graphs/graph_list.py +52 -9
- sage/graphs/graph_plot.py +7 -0
- sage/graphs/hyperbolicity.cpython-313-darwin.so +0 -0
- sage/graphs/hyperbolicity.pyx +2 -0
- sage/graphs/independent_sets.cpython-313-darwin.so +0 -0
- sage/graphs/isoperimetric_inequalities.cpython-313-darwin.so +0 -0
- sage/graphs/isoperimetric_inequalities.pyx +42 -6
- sage/graphs/line_graph.cpython-313-darwin.so +0 -0
- sage/graphs/line_graph.pyx +153 -37
- sage/graphs/matching_covered_graph.py +84 -60
- sage/graphs/orientations.py +3 -18
- sage/graphs/path_enumeration.cpython-313-darwin.so +0 -0
- sage/graphs/path_enumeration.pyx +2 -2
- sage/graphs/spanning_tree.cpython-313-darwin.so +0 -0
- sage/graphs/strongly_regular_db.cpython-313-darwin.so +0 -0
- sage/graphs/strongly_regular_db.pyx +15 -15
- sage/graphs/traversals.cpython-313-darwin.so +0 -0
- sage/graphs/traversals.pyx +13 -12
- sage/graphs/trees.cpython-313-darwin.so +0 -0
- sage/graphs/tutte_polynomial.py +1 -1
- sage/graphs/views.cpython-313-darwin.so +0 -0
- sage/graphs/weakly_chordal.cpython-313-darwin.so +0 -0
- sage/graphs/weakly_chordal.pyx +50 -8
- sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-313-darwin.so +0 -0
- sage/knots/free_knotinfo_monoid.py +3 -3
- sage/knots/knotinfo.py +102 -82
- sage/knots/link.py +72 -39
- sage/topology/cubical_complex.py +4 -5
- sage/topology/delta_complex.py +4 -4
- sage/topology/simplicial_complex.py +0 -1
- sage/topology/simplicial_complex_catalog.py +6 -0
- sage/topology/simplicial_complex_examples.py +4 -16
- {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/WHEEL +0 -0
- {passagemath_graphs-10.5.43.dist-info → passagemath_graphs-10.6.1rc1.dist-info}/top_level.txt +0 -0
sage/graphs/generic_graph.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
1512
|
-
|
1513
|
-
|
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
|
-
|
3297
|
-
|
3298
|
-
for v in to_remove:
|
3353
|
+
for v in list(embedding):
|
3354
|
+
if v not in self:
|
3299
3355
|
del embedding[v]
|
3300
|
-
|
3301
|
-
|
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
|
-
|
6860
|
-
|
6861
|
-
|
6862
|
-
|
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
|
-
|
6968
|
-
if
|
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 =
|
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
|
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.
|
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.
|
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.
|
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.
|
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.
|
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 =
|
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 =
|
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:
|
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:
|
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:
|
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:
|
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=
|
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
|
-
|
20724
|
+
INPUT:
|
20556
20725
|
|
20557
|
-
|
20558
|
-
|
20559
|
-
|
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
|
-
|
20597
|
-
|
20598
|
-
|
20599
|
-
|
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(
|
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
|
-
|
20648
|
-
|
20649
|
-
|
20650
|
-
|
20651
|
-
|
20652
|
-
|
20653
|
-
|
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(
|
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.
|
23985
|
-
|
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
|
-
|
24009
|
-
|
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
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
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(
|
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:
|