Skip to content

Commit

Permalink
gh-36587: Created functions to generate random k-tree and partial k-t…
Browse files Browse the repository at this point in the history
…ree graphs

    
<!-- ^^^^^
Please provide a concise, informative and self-explanatory title.
Don't put issue numbers in there, do this in the PR body below.
For example, instead of "Fixes #1234" use "Introduce new method to
calculate 1+1"
-->
<!-- Describe your changes here in detail -->
Implemented graph generators which produce a random k-tree and a random
partial k-tree. The k-tree algorithm first generates a complete graph,
before adding vertices one by one, creating a clique utilising random
existing vertices and the new vertex. Partial k-trees are generated by
producing a k-tree and then randomly removing edges from it.

<!-- Why is this change required? What problem does it solve? -->
Previously SageMath lacked a way to generat randomised graphs which have
a fixed treewidth value. This provides a methodology to do so .
<!-- If this PR resolves an open issue, please link to it here. For
example "Fixes #12345". -->
Addresses #36586
<!-- If your change requires a documentation PR, please link it
appropriately. -->

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->
<!-- If your change requires a documentation PR, please link it
appropriately -->
<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
<!-- Feel free to remove irrelevant items. -->

- [x] The title is concise, informative, and self-explanatory.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.


<!-- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->
    
URL: #36587
Reported by: Damian Basso
Reviewer(s): Damian Basso, David Coudert
  • Loading branch information
Release Manager committed Feb 21, 2024
2 parents be3a116 + faa737b commit 34e5618
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
189 changes: 189 additions & 0 deletions src/sage/graphs/generators/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,195 @@ def RandomTreePowerlaw(n, gamma=3, tries=1000, seed=None):
return False


def RandomKTree(n, k, seed=None):
r"""
Return a random `k`-tree on `n` nodes numbered `0` through `n-1`.
ALGORITHM:
The algorithm first generates a complete graph on `k + 1` vertices.
Vertices are subsequently generated by randomly choosing one of the
existing cliques in the graph, and creating a new clique by replacing
one of the vertices in the selected clique with a newly created one.
INPUT:
- ``n`` -- number of vertices in the `k`-tree
- ``k`` -- within a clique each vertex is connected to `k` vertices. `k`
also corresponds to the treewidth of the `k`-tree
- ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
number generator (default: ``None``)
TESTS::
sage: g=graphs.RandomKTree(50,5)
sage: g.size()
235
sage: g.order()
50
sage: g.treewidth()
5
sage: graphs.RandomKTree(-5, 5)
Traceback (most recent call last):
...
ValueError: n must not be negative
sage: graphs.RandomKTree(5, -5)
Traceback (most recent call last):
...
ValueError: k must not be negative
sage: graphs.RandomKTree(2, 5)
Traceback (most recent call last):
...
ValueError: n must be greater than k
sage: G = graphs.RandomKTree(50, 0)
sage: G.treewidth()
0
EXAMPLES::
sage: G = graphs.RandomKTree(50, 5)
sage: G.treewidth()
5
sage: G.show() # not tested
"""
if n < 0:
raise ValueError("n must not be negative")

if k < 0:
raise ValueError("k must not be negative")

# A graph with treewidth 0 has no edges
if k == 0:
g = Graph(n, name=f"Random 0-tree")
return g

if n < k + 1:
raise ValueError("n must be greater than k")

if seed is not None:
set_random_seed(seed)

g = Graph(name=f"Random {k}-tree")
g.add_clique(list(range(k + 1)))

cliques = [list(range(k+1))]

# Randomly choose a row, and copy 1 of the cliques
# One of those vertices is then replaced with a new vertex
for newVertex in range(k + 1, n):
copiedClique = cliques[randint(0, len(cliques)-1)].copy()
copiedClique[randint(0, k)] = newVertex
cliques.append(copiedClique)
for u in copiedClique:
if u != newVertex:
g.add_edge(u, newVertex)
return g


def RandomPartialKTree(n, k, x, seed=None):
r"""
Return a random partial `k`-tree on `n` nodes.
A partial `k`-tree is defined as a subgraph of a `k`-tree. This can also be
described as a graph with treewidth at most `k`.
INPUT:
- ``n`` -- number of vertices in the `k`-tree
- ``k`` -- within a clique each vertex is connected to `k` vertices. `k`
also corresponds to the treewidth of the `k`-tree
- ``x`` -- how many edges are deleted from the `k`-tree
- ``seed`` -- a ``random.Random`` seed or a Python ``int`` for the random
number generator (default: ``None``)
TESTS::
sage: g=graphs.RandomPartialKTree(50,5,2)
sage: g.order()
50
sage: g.size()
233
sage: g.treewidth()
5
sage: graphs.RandomPartialKTree(-5, 5, 2)
Traceback (most recent call last):
...
ValueError: n must not be negative
sage: graphs.RandomPartialKTree(5, -5, 2)
Traceback (most recent call last):
...
ValueError: k must not be negative
sage: G = graphs.RandomPartialKTree(2, 5, 2)
Traceback (most recent call last):
...
ValueError: n must be greater than k
sage: G = graphs.RandomPartialKTree(5, 2, 100)
Traceback (most recent call last):
...
ValueError: x must be less than the number of edges in the `k`-tree with `n` nodes
sage: G = graphs.RandomPartialKTree(50, 0, 0)
sage: G.treewidth()
0
sage: G = graphs.RandomPartialKTree(5, 2, 7)
sage: G.treewidth()
0
sage: G.size()
0
EXAMPLES::
sage: G = graphs.RandomPartialKTree(50,5,2)
sage: G.treewidth()
5
sage: G.show() # not tested
"""
if n < 0:
raise ValueError("n must not be negative")

if k < 0:
raise ValueError("k must not be negative")

# A graph with treewidth 0 has no edges
if k == 0:
g = Graph(n, name=f"Random partial 0-tree")
return g

if n < k + 1:
raise ValueError("n must be greater than k")

if seed is not None:
set_random_seed(seed)

# This formula calculates how many edges are in a `k`-tree with `n` nodes
edgesInKTree = (k ^ 2 + k) / 2 + (n - k - 1) * k

# Check that x doesn't delete too many edges
if x > edgesInKTree:
raise ValueError("x must be less than the number of edges in the `k`-tree with `n` nodes")

# The graph will have no edges
if x == edgesInKTree:
g = Graph(n, name=f"Random partial {k}-tree")
return g

g = RandomKTree(n, k, seed)

from sage.misc.prandom import shuffle

edges = list(g.edges())
# Deletes x random edges from the graph
shuffle(edges)
g.delete_edges(edges[:x])

g.name(f"Random partial {k}-tree")
return g


def RandomRegular(d, n, seed=None):
r"""
Return a random `d`-regular graph on `n` vertices, or ``False`` on failure.
Expand Down
4 changes: 4 additions & 0 deletions src/sage/graphs/graph_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ def wrap_name(x):
"RandomHolmeKim",
"RandomChordalGraph",
"RandomIntervalGraph",
"RandomKTree",
"RandomPartialKTree",
"RandomLobster",
"RandomNewmanWattsStrogatz",
"RandomRegular",
Expand Down Expand Up @@ -2755,6 +2757,8 @@ def quadrangulations(self, order, minimum_degree=None, minimum_connectivity=None
RandomNewmanWattsStrogatz = staticmethod(random.RandomNewmanWattsStrogatz)
RandomRegular = staticmethod(random.RandomRegular)
RandomShell = staticmethod(random.RandomShell)
RandomKTree = staticmethod(random.RandomKTree)
RandomPartialKTree = staticmethod(random.RandomPartialKTree)
RandomToleranceGraph = staticmethod(random.RandomToleranceGraph)
RandomTreePowerlaw = staticmethod(random.RandomTreePowerlaw)
RandomTree = staticmethod(random.RandomTree)
Expand Down

0 comments on commit 34e5618

Please sign in to comment.