Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

add parameter immutable to generators in sage/graphs/digraph_generators.py (part 3) #39269

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 91 additions & 46 deletions src/sage/graphs/digraph_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@
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::
Expand Down Expand Up @@ -1379,7 +1394,7 @@
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`.

Expand All @@ -1399,6 +1414,9 @@
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
Expand Down Expand Up @@ -1449,21 +1467,22 @@
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.

Expand All @@ -1483,6 +1502,9 @@
- ``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
Expand All @@ -1502,9 +1524,10 @@
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.
Expand All @@ -1522,6 +1545,9 @@
- ``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
Expand All @@ -1535,9 +1561,9 @@
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.

Expand All @@ -1556,6 +1582,9 @@
- ``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.

Expand All @@ -1574,9 +1603,10 @@
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.

Expand All @@ -1588,6 +1618,9 @@

- ``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.

Expand Down Expand Up @@ -1630,10 +1663,6 @@
# 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
Expand Down Expand Up @@ -1674,9 +1703,10 @@

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:

Expand All @@ -1692,21 +1722,19 @@
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`.
Expand All @@ -1726,6 +1754,9 @@
- ``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
Expand All @@ -1739,9 +1770,9 @@
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.

Expand All @@ -1761,6 +1792,9 @@

- ``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`
Expand All @@ -1778,30 +1812,34 @@
...
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

# ##############################################################################
# DiGraph Iterators
# ##############################################################################

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.
Expand Down Expand Up @@ -1845,6 +1883,13 @@
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::
Expand All @@ -1871,7 +1916,6 @@

sage: digraphs? # not tested
"""
from copy import copy as copyfun
if size is not None:
def extra_property(x):
return x.size() == size
Expand All @@ -1886,14 +1930,15 @@
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,

Check warning on line 1940 in src/sage/graphs/digraph_generators.py

View check run for this annotation

Codecov / codecov/patch

src/sage/graphs/digraph_generators.py#L1940

Added line #L1940 was not covered by tests
immutable=immutable)
vertices += 1

from sage.graphs.graph_generators import canaug_traverse_edge
Expand All @@ -1907,7 +1952,7 @@
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()

Expand Down
30 changes: 15 additions & 15 deletions src/sage/graphs/graph_generators_pyx.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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]_
Expand All @@ -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::

Expand All @@ -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)
Loading