diff --git a/src/sage/graphs/digraph_generators.py b/src/sage/graphs/digraph_generators.py index d484b948eb7..592dd2bc9ea 100644 --- a/src/sage/graphs/digraph_generators.py +++ b/src/sage/graphs/digraph_generators.py @@ -150,6 +150,21 @@ class DiGraphGenerators: dense data structure. See the documentation of :class:`~sage.graphs.graph.Graph`. + - ``copy`` -- boolean (default: ``True``); whether to make copies of the + digraphs before returning them. If set to ``False`` the method returns the + digraph it is working on. The second alternative is faster, but modifying + any of the digraph instances returned by the method may break the + function's behaviour, as it is using these digraphs to compute the next + ones: only use ``copy = False`` when you stick to *reading* the digraphs + returned. + + This parameter is ignored when ``immutable`` is set to ``True``, in which + case returned graphs are always copies. + + - ``immutable`` -- boolean (default: ``False``); whether to return immutable + or mutable digraphs. When set to ``True``, this parameter implies + ``copy=True``. + EXAMPLES: Print digraphs on 2 or less vertices:: @@ -1379,7 +1394,7 @@ def Kautz(self, k, D, vertices='strings'): G.name("Kautz digraph (k={}, D={})".format(k, D)) return G - def RandomDirectedAcyclicGraph(self, n, p, weight_max=None): + def RandomDirectedAcyclicGraph(self, n, p, weight_max=None, immutable=False): r""" Return a random (weighted) directed acyclic graph of order `n`. @@ -1399,6 +1414,9 @@ def RandomDirectedAcyclicGraph(self, n, p, weight_max=None): unweighted. When ``weight_max`` is set to a positive integer, edges are assigned a random integer weight between ``1`` and ``weight_max``. + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + EXAMPLES:: sage: D = digraphs.RandomDirectedAcyclicGraph(5, .5); D @@ -1449,21 +1467,22 @@ def RandomDirectedAcyclicGraph(self, n, p, weight_max=None): pp = int(round(float(p * RAND_MAX_f))) if weight_max is None: - D = DiGraph(n, name=f"RandomDAG({n}, {p})") - D.add_edges((i, j) for i in range(n) for j in range(i) if random() < pp) + name = f"RandomDAG({n}, {p})" + edges = ((i, j) for i in range(n) for j in range(i) if random() < pp) else: from sage.rings.integer_ring import ZZ if weight_max in ZZ and weight_max < 1: raise ValueError("parameter weight_max must be a positive integer") - D = DiGraph(n, name=f"RandomWeightedDAG({n}, {p}, {weight_max})") - D.add_edges((i, j, randint(1, weight_max)) - for i in range(n) for j in range(i) if random() < pp) + name = f"RandomWeightedDAG({n}, {p}, {weight_max})" + edges = ((i, j, randint(1, weight_max)) + for i in range(n) for j in range(i) if random() < pp) - return D + return DiGraph([range(n), edges], format='vertices_and_edges', + name=name, immutable=immutable) - def RandomDirectedGN(self, n, kernel=None, seed=None): + def RandomDirectedGN(self, n, kernel=None, seed=None, immutable=False): r""" Return a random growing network (GN) digraph with `n` vertices. @@ -1483,6 +1502,9 @@ def RandomDirectedGN(self, n, kernel=None, seed=None): - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random number generator (default: ``None``) + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + EXAMPLES:: sage: # needs networkx @@ -1502,9 +1524,10 @@ def RandomDirectedGN(self, n, kernel=None, seed=None): if seed is None: seed = int(current_randstate().long_seed() % sys.maxsize) import networkx - return DiGraph(networkx.gn_graph(n, kernel, seed=seed)) + return DiGraph(networkx.gn_graph(n, kernel, seed=seed), + immutable=immutable) - def RandomDirectedGNC(self, n, seed=None): + def RandomDirectedGNC(self, n, seed=None, immutable=False): r""" Return a random growing network with copying (GNC) digraph with `n` vertices. @@ -1522,6 +1545,9 @@ def RandomDirectedGNC(self, n, seed=None): - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random number generator (default: ``None``) + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + EXAMPLES:: sage: # needs networkx @@ -1535,9 +1561,9 @@ def RandomDirectedGNC(self, n, seed=None): if seed is None: seed = int(current_randstate().long_seed() % sys.maxsize) import networkx - return DiGraph(networkx.gnc_graph(n, seed=seed)) + return DiGraph(networkx.gnc_graph(n, seed=seed), immutable=immutable) - def RandomDirectedGNP(self, n, p, loops=False, seed=None): + def RandomDirectedGNP(self, n, p, loops=False, seed=None, immutable=False): r""" Return a random digraph on `n` nodes. @@ -1556,6 +1582,9 @@ def RandomDirectedGNP(self, n, p, loops=False, seed=None): - ``seed`` -- integer (default: ``None``); seed for random number generator + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. @@ -1574,9 +1603,10 @@ def RandomDirectedGNP(self, n, p, loops=False, seed=None): if seed is None: seed = current_randstate().long_seed() - return RandomGNP(n, p, directed=True, loops=loops, seed=seed) + return RandomGNP(n, p, directed=True, loops=loops, seed=seed, + immutable=immutable) - def RandomDirectedGNM(self, n, m, loops=False): + def RandomDirectedGNM(self, n, m, loops=False, immutable=False): r""" Return a random labelled digraph on `n` nodes and `m` arcs. @@ -1588,6 +1618,9 @@ def RandomDirectedGNM(self, n, m, loops=False): - ``loops`` -- boolean (default: ``False``); whether to allow loops + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + PLOTTING: When plotting, this graph will use the default spring-layout algorithm, unless a position dictionary is specified. @@ -1630,10 +1663,6 @@ def RandomDirectedGNM(self, n, m, loops=False): # When the graph is dense, we actually compute its complement. This will # prevent us from drawing the same pair u,v too many times. - from sage.misc.prandom import _pyrand - rand = _pyrand() - D = DiGraph(n, loops=loops) - # Ensuring the parameters n,m make sense. # # If the graph is dense, we actually want to build its complement. We @@ -1674,9 +1703,10 @@ def RandomDirectedGNM(self, n, m, loops=False): adj = {i: dict() for i in range(n)} - # We fill the dictionary structure, but add the corresponding edge in - # the graph only if is_dense is False. If it is true, we will add the - # edges in a second phase. + # We fill the dictionary structure. + + from sage.misc.prandom import _pyrand + rand = _pyrand() while m > 0: @@ -1692,21 +1722,19 @@ def RandomDirectedGNM(self, n, m, loops=False): if (u != v or loops) and (v not in adj[u]): adj[u][v] = 1 m -= 1 - if not is_dense: - D.add_edge(u, v) - # If is_dense is True, it means the graph has not been built. We fill D - # with the complement of the edges stored in the adj dictionary + # If is_dense is True, we fill the digraph with the complement of the + # edges stored in the adj dictionary if is_dense: - for u in range(n): - for v in range(n): - if ((u != v) or loops) and (v not in adj[u]): - D.add_edge(u, v) + edges = ((u, v) for u in range(n) for v in range(n) + if ((u != v) or loops) and (v not in adj[u])) + return DiGraph([range(n), edges], format='vertices_and_edges', + loops=loops, immutable=immutable) - return D + return DiGraph(adj, format='dict_of_lists', loops=loops) - def RandomDirectedGNR(self, n, p, seed=None): + def RandomDirectedGNR(self, n, p, seed=None, immutable=False): r""" Return a random growing network with redirection (GNR) digraph with `n` vertices and redirection probability `p`. @@ -1726,6 +1754,9 @@ def RandomDirectedGNR(self, n, p, seed=None): - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random number generator (default: ``None``) + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + EXAMPLES:: sage: # needs networkx @@ -1739,9 +1770,9 @@ def RandomDirectedGNR(self, n, p, seed=None): if seed is None: seed = int(current_randstate().long_seed() % sys.maxsize) import networkx - return DiGraph(networkx.gnr_graph(n, p, seed=seed)) + return DiGraph(networkx.gnr_graph(n, p, seed=seed), immutable=immutable) - def RandomSemiComplete(self, n): + def RandomSemiComplete(self, n, immutable=False): r""" Return a random semi-complete digraph on `n` vertices. @@ -1761,6 +1792,9 @@ def RandomSemiComplete(self, n): - ``n`` -- integer; the number of nodes + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable digraph. + .. SEEALSO:: - :meth:`~sage.graphs.digraph_generators.DiGraphGenerators.Complete` @@ -1778,22 +1812,26 @@ def RandomSemiComplete(self, n): ... ValueError: the number of vertices cannot be strictly negative """ - G = DiGraph(n, name="Random Semi-Complete digraph") + if n < 0: + raise ValueError('the number of vertices cannot be strictly negative') # For each pair u,v we choose a random number ``coin`` in [1,3]. # We select edge `(u,v)` if `coin==1` or `coin==2`. # We select edge `(v,u)` if `coin==2` or `coin==3`. import itertools from sage.misc.prandom import randint - for u, v in itertools.combinations(range(n), 2): - coin = randint(1, 3) - if coin <= 2: - G.add_edge(u, v) - if coin >= 2: - G.add_edge(v, u) - G._circle_embedding(list(range(n))) + def edges(): + for u, v in itertools.combinations(range(n), 2): + coin = randint(1, 3) + if coin <= 2: + yield (u, v) + if coin >= 2: + yield (v, u) + G = DiGraph([range(n), edges()], format='vertices_and_edges', + immutable=immutable, name="Random Semi-Complete digraph") + G._circle_embedding(list(range(n))) return G # ############################################################################## @@ -1801,7 +1839,7 @@ def RandomSemiComplete(self, n): # ############################################################################## def __call__(self, vertices=None, property=lambda x: True, augment='edges', - size=None, sparse=True, copy=True): + size=None, sparse=True, copy=True, immutable=False): """ Access the generator of isomorphism class representatives [McK1998]_. Iterates over distinct, exhaustive representatives. @@ -1845,6 +1883,13 @@ def __call__(self, vertices=None, property=lambda x: True, augment='edges', compute the next ones: only use ``copy = False`` when you stick to *reading* the digraphs returned. + This parameter is ignored when ``immutable`` is set to ``True``, in + which case returned graphs are always copies. + + - ``immutable`` -- boolean (default: ``False``); whether to return + immutable or mutable digraphs. When set to ``True``, this parameter + implies ``copy=True``. + EXAMPLES: Print digraphs on 2 or less vertices:: @@ -1871,7 +1916,6 @@ def __call__(self, vertices=None, property=lambda x: True, augment='edges', sage: digraphs? # not tested """ - from copy import copy as copyfun if size is not None: def extra_property(x): return x.size() == size @@ -1886,14 +1930,15 @@ def extra_property(x): g = DiGraph(sparse=sparse) for gg in canaug_traverse_vert(g, [], vertices, property, dig=True, sparse=sparse): if extra_property(gg): - yield copyfun(gg) if copy else gg + yield gg.copy(immutable=immutable) if copy or immutable else gg elif augment == 'edges': if vertices is None: vertices = 0 while True: - yield from self(vertices, sparse=sparse, copy=copy) + yield from self(vertices, sparse=sparse, copy=copy, + immutable=immutable) vertices += 1 from sage.graphs.graph_generators import canaug_traverse_edge @@ -1907,7 +1952,7 @@ def extra_property(x): gens.append(gen) for gg in canaug_traverse_edge(g, gens, property, dig=True, sparse=sparse): if extra_property(gg): - yield copyfun(gg) if copy else gg + yield gg.copy(immutable=immutable) if copy or immutable else gg else: raise NotImplementedError() diff --git a/src/sage/graphs/graph_generators_pyx.pyx b/src/sage/graphs/graph_generators_pyx.pyx index 04b20d3229b..e66ef2234c9 100644 --- a/src/sage/graphs/graph_generators_pyx.pyx +++ b/src/sage/graphs/graph_generators_pyx.pyx @@ -17,7 +17,8 @@ from sage.misc.randstate cimport random from sage.misc.randstate import set_random_seed -def RandomGNP(n, p, bint directed=False, bint loops=False, seed=None): +def RandomGNP(n, p, bint directed=False, bint loops=False, seed=None, + immutable=False): r""" Return a random graph or a digraph on `n` nodes. @@ -38,6 +39,9 @@ def RandomGNP(n, p, bint directed=False, bint loops=False, seed=None): - ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random number generator (default: ``None``) + - ``immutable`` -- boolean (default: ``False``); whether to return an + immutable or mutable (di)graph. + REFERENCES: - [ER1959]_ @@ -51,7 +55,8 @@ def RandomGNP(n, p, bint directed=False, bint loops=False, seed=None): sage: D.num_verts() 10 sage: D.edges(sort=True, labels=False) - [(0, 2), (0, 5), (1, 5), (1, 7), (4, 1), (4, 2), (4, 9), (5, 0), (5, 2), (5, 3), (5, 7), (6, 5), (7, 1), (8, 2), (8, 6), (9, 4)] + [(0, 3), (0, 6), (1, 7), (1, 9), (4, 6), (4, 7), (5, 4), (5, 6), + (5, 8), (5, 9), (6, 3), (7, 2), (7, 9), (8, 5), (9, 1), (9, 5)] TESTS:: @@ -72,23 +77,18 @@ def RandomGNP(n, p, bint directed=False, bint loops=False, seed=None): cdef int pp = int(round(float(p * RAND_MAX_f))) if directed: - from sage.graphs.digraph import DiGraph - G = DiGraph(loops=loops) + from sage.graphs.digraph import DiGraph as GT else: - from sage.graphs.graph import Graph - G = Graph() if loops: raise ValueError("parameter 'loops' can be set to True only when 'directed' is True") - G.name('Random' + ('Directed' if directed else '') + 'GNP(%s,%s)' % (n, p)) + from sage.graphs.graph import Graph as GT - G.add_vertices(range(n)) + name = 'Random' + ('Directed' if directed else '') + 'GNP(%s,%s)' % (n, p) - # Standard random GNP generator for Graph and DiGraph cdef int i, j - for i in range(n): - for j in range((0 if directed else i + 1), n): - if random() < pp: - if i != j or loops: - G.add_edge(i, j) + edges = ((i, j) for i in range(n) + for j in range((0 if directed else i + 1), n) + if (i != j or loops) and random() < pp) - return G + return GT([range(n), edges], format='vertices_and_edges', + loops=directed and loops, name=name, immutable=immutable)