Generation of trees

This module gathers methods related to the generation of trees. All these methods appear in sage.graphs.graph_generators.

BalancedTree()

Return the perfectly balanced tree of height \(h \geq 1\), whose root has degree \(r \geq 2\).

DegreeSequenceTree()

Return a tree with the given degree sequence.

FibonacciTree()

Return the graph of the Fibonacci Tree \(F_{n}\).

RandomLobster()

Return a random lobster.

RandomTree()

Return a random tree on \(n\) nodes numbered \(0\) through \(n-1\).

RandomTreePowerlaw()

Return a tree with a power law degree distribution, or False on failure.

trees()

Return a generator of the distinct trees on a fixed number of vertices.

nauty_gentreeg()

Return a generator which creates non-isomorphic trees from nauty’s gentreeg program.

There are different ways to enumerate all non-isomorphic trees of order \(n\). We can use the implementation of the algorithm proposed in [WROM1986]:

sage: gen = graphs.trees(10)
sage: T = next(gen); T
Graph on 10 vertices
sage: T.is_tree()
True
>>> from sage.all import *
>>> gen = graphs.trees(Integer(10))
>>> T = next(gen); T
Graph on 10 vertices
>>> T.is_tree()
True

We can also use nauty’s gentreeg:

sage: gen = graphs.nauty_gentreeg("10")
sage: T = next(gen); T
Graph on 10 vertices
sage: T.is_tree()
True
[Python]
>>> from sage.all import *
>>> gen = graphs.nauty_gentreeg("10")
>>> T = next(gen); T
Graph on 10 vertices
>>> T.is_tree()
True

Note that nauty’s gentreeg can only be used to generate trees with \(1 \leq n \leq 128\) vertices:

sage: next(graphs.nauty_gentreeg("0"))
Traceback (most recent call last):
...
ValueError: wrong format of parameter options
sage: next(graphs.nauty_gentreeg("1"))
Graph on 1 vertex
sage: next(graphs.nauty_gentreeg("128"))
Graph on 128 vertices
sage: next(graphs.nauty_gentreeg("129"))
Traceback (most recent call last):
...
ValueError: wrong format of parameter options
>>> from sage.all import *
>>> next(graphs.nauty_gentreeg("0"))
Traceback (most recent call last):
...
ValueError: wrong format of parameter options
>>> next(graphs.nauty_gentreeg("1"))
Graph on 1 vertex
>>> next(graphs.nauty_gentreeg("128"))
Graph on 128 vertices
>>> next(graphs.nauty_gentreeg("129"))
Traceback (most recent call last):
...
ValueError: wrong format of parameter options

We don’t have this limitation with method trees():

sage: next(graphs.trees(0))
Graph on 0 vertices
sage: next(graphs.trees(129))
Graph on 129 vertices
sage: next(graphs.trees(1000))
Graph on 1000 vertices
[Python]
>>> from sage.all import *
>>> next(graphs.trees(Integer(0)))
Graph on 0 vertices
>>> next(graphs.trees(Integer(129)))
Graph on 129 vertices
>>> next(graphs.trees(Integer(1000)))
Graph on 1000 vertices

Nauty’s gentreeg can be used to generate trees with bounded maximum degree:

sage: gen = graphs.nauty_gentreeg("8 -D3")
sage: all(max(g.degree()) <= 3 for g in gen)
True
sage: len(list(graphs.nauty_gentreeg("8 -D2")))
1
>>> from sage.all import *
>>> gen = graphs.nauty_gentreeg("8 -D3")
>>> all(max(g.degree()) <= Integer(3) for g in gen)
True
>>> len(list(graphs.nauty_gentreeg("8 -D2")))
1

Nauty’s gentreeg can be used to generate trees with bounded diameter:

sage: all(g.diameter() == 3 for g in graphs.nauty_gentreeg("8 -Z3"))
True
sage: len(list(graphs.nauty_gentreeg("8 -Z3")))
3
[Python]
>>> from sage.all import *
>>> all(g.diameter() == Integer(3) for g in graphs.nauty_gentreeg("8 -Z3"))
True
>>> len(list(graphs.nauty_gentreeg("8 -Z3")))
3

The number of trees on the first few vertex counts agrees with OEIS sequence A000055:

sage: [len(list(graphs.trees(i))) for i in range(15)]
[1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
sage: [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(1, 15)]
[1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
>>> from sage.all import *
>>> [len(list(graphs.trees(i))) for i in range(Integer(15))]
[1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
>>> [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(Integer(1), Integer(15))]
[1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]

Methods

sage.graphs.generators.trees.BalancedTree(r, h)

Return the perfectly balanced tree of height \(h \geq 1\), whose root has degree \(r \geq 2\).

The number of vertices of this graph is \(1 + r + r^2 + \cdots + r^h\), that is, \(\frac{r^{h+1} - 1}{r - 1}\). The number of edges is one less than the number of vertices.

INPUT:

  • r – positive integer \(\geq 2\); the degree of the root node

  • h – positive integer \(\geq 1\); the height of the balanced tree

OUTPUT:

The perfectly balanced tree of height \(h \geq 1\) and whose root has degree \(r \geq 2\).

EXAMPLES:

A balanced tree whose root node has degree \(r = 2\), and of height \(h = 1\), has order 3 and size 2:

sage: G = graphs.BalancedTree(2, 1); G
Balanced tree: Graph on 3 vertices
sage: G.order()
3
sage: G.size()
2
sage: r = 2; h = 1
sage: v = 1 + r
sage: v; v - 1
3
2
>>> from sage.all import *
>>> G = graphs.BalancedTree(Integer(2), Integer(1)); G
Balanced tree: Graph on 3 vertices
>>> G.order()
3
>>> G.size()
2
>>> r = Integer(2); h = Integer(1)
>>> v = Integer(1) + r
>>> v; v - Integer(1)
3
2

Plot a balanced tree of height 5, whose root node has degree \(r = 3\):

sage: G = graphs.BalancedTree(3, 5)
sage: G.plot()                          # long time                             # needs sage.plot
Graphics object consisting of 728 graphics primitives
[Python]
>>> from sage.all import *
>>> G = graphs.BalancedTree(Integer(3), Integer(5))
>>> G.plot()                          # long time                             # needs sage.plot
Graphics object consisting of 728 graphics primitives

A tree is bipartite. If its vertex set is finite, then it is planar:

sage: # needs networkx
sage: r = randint(2, 5); h = randint(1, 7)
sage: T = graphs.BalancedTree(r, h)
sage: T.is_bipartite()
True
sage: T.is_planar()
True
sage: v = (r^(h + 1) - 1) / (r - 1)
sage: T.order() == v
True
sage: T.size() == v - 1
True
>>> from sage.all import *
>>> # needs networkx
>>> r = randint(Integer(2), Integer(5)); h = randint(Integer(1), Integer(7))
>>> T = graphs.BalancedTree(r, h)
>>> T.is_bipartite()
True
>>> T.is_planar()
True
>>> v = (r**(h + Integer(1)) - Integer(1)) / (r - Integer(1))
>>> T.order() == v
True
>>> T.size() == v - Integer(1)
True
sage.graphs.generators.trees.Caterpillar(spine)

Return the caterpillar tree with given spine sequence.

A caterpillar tree consists of leaves attached to a path (the “spine”).

INPUT:

  • spine – list of nonnegative integers in the form

    \([a_1, a_2, \dots, a_n]\), where \(a_i\) is the number of leaves adjacent to the \(i\)-th vertex on the spine (except for the first and last vertex, which have \(a_1 + 1\) and \(a_n + 1\) leaf-neighbors, respectively)

OUTPUT:

A caterpillar tree of diameter \(n+1\) on \(n + 2 + \sum_{i=1}^n a_i\) vertices, \(n\) of which are not leaves.

PLOTTING: Upon construction, the position dictionary is filled to override the spring-layout algorithm if the returned graph does not have too many vertices. The spine vertices are positioned on a straight line together with two leaves at its ends. Every edge in the drawing has unit length.

EXAMPLES:

Caterpillars with all-zero spine sequence are paths:

sage: graphs.Caterpillar([]).is_isomorphic(graphs.PathGraph(2))
True
sage: graphs.Caterpillar([0]).is_isomorphic(graphs.PathGraph(3))
True
sage: graphs.Caterpillar([0, 0]).is_isomorphic(graphs.PathGraph(4))
True
>>> from sage.all import *
>>> graphs.Caterpillar([]).is_isomorphic(graphs.PathGraph(Integer(2)))
True
>>> graphs.Caterpillar([Integer(0)]).is_isomorphic(graphs.PathGraph(Integer(3)))
True
>>> graphs.Caterpillar([Integer(0), Integer(0)]).is_isomorphic(graphs.PathGraph(Integer(4)))
True

Caterpillars with singleton spine are stars:

sage: graphs.Caterpillar([1]).is_isomorphic(graphs.StarGraph(3))
True
sage: graphs.Caterpillar([2]).is_isomorphic(graphs.StarGraph(4))
True
sage: graphs.Caterpillar([3]).is_isomorphic(graphs.StarGraph(5))
True
[Python]
>>> from sage.all import *
>>> graphs.Caterpillar([Integer(1)]).is_isomorphic(graphs.StarGraph(Integer(3)))
True
>>> graphs.Caterpillar([Integer(2)]).is_isomorphic(graphs.StarGraph(Integer(4)))
True
>>> graphs.Caterpillar([Integer(3)]).is_isomorphic(graphs.StarGraph(Integer(5)))
True

Distinct spine sequences can yield isomorphic caterpillars:

sage: graphs.Caterpillar([1,1,2]).is_isomorphic(graphs.Caterpillar([2,1,1]))
True
>>> from sage.all import *
>>> graphs.Caterpillar([Integer(1),Integer(1),Integer(2)]).is_isomorphic(graphs.Caterpillar([Integer(2),Integer(1),Integer(1)]))
True
sage.graphs.generators.trees.FibonacciTree(n)

Return the graph of the Fibonacci Tree \(F_{n}\).

The Fibonacci tree \(F_{n}\) is recursively defined as the tree with a root vertex and two attached child trees \(F_{n-1}\) and \(F_{n-2}\), where \(F_{1}\) is just one vertex and \(F_{0}\) is empty.

INPUT:

  • n – the recursion depth of the Fibonacci Tree

EXAMPLES:

sage: g = graphs.FibonacciTree(3)                                               # needs sage.libs.pari
sage: g.is_tree()                                                               # needs sage.libs.pari
True
>>> from sage.all import *
>>> g = graphs.FibonacciTree(Integer(3))                                               # needs sage.libs.pari
>>> g.is_tree()                                                               # needs sage.libs.pari
True

sage: l1 = [graphs.FibonacciTree(_).order() + 1 for _ in range(6)]              # needs sage.libs.pari
sage: l2 = list(fibonacci_sequence(2,8))                                        # needs sage.libs.pari
sage: l1 == l2                                                                  # needs sage.libs.pari
True
[Python]
>>> from sage.all import *
>>> l1 = [graphs.FibonacciTree(_).order() + Integer(1) for _ in range(Integer(6))]              # needs sage.libs.pari
>>> l2 = list(fibonacci_sequence(Integer(2),Integer(8)))                                        # needs sage.libs.pari
>>> l1 == l2                                                                  # needs sage.libs.pari
True

AUTHORS:

  • Harald Schilly and Yann Laigle-Chapuy (2010-03-25)

sage.graphs.generators.trees.RandomLobster(n, p, q, seed=None)

Return a random lobster.

A lobster is a tree that reduces to a caterpillar when pruning all leaf vertices. A caterpillar is a tree that reduces to a path when pruning all leaf vertices (\(q=0\)).

INPUT:

  • n – expected number of vertices in the backbone

  • p – probability of adding an edge to the backbone

  • q – probability of adding an edge (claw) to the arms

  • seed – a random.Random seed or a Python int for the random number generator (default: None)

EXAMPLES:

We check a random graph with 12 backbone nodes and probabilities \(p = 0.7\) and \(q = 0.3\):

sage: # needs networkx
sage: G = graphs.RandomLobster(12, 0.7, 0.3)
sage: leaves = [v for v in G.vertices(sort=False) if G.degree(v) == 1]
sage: G.delete_vertices(leaves)                                 # caterpillar
sage: leaves = [v for v in G.vertices(sort=False) if G.degree(v) == 1]
sage: G.delete_vertices(leaves)                                 # path
sage: s = G.degree_sequence()
sage: if G:
....:     if G.n_vertices() == 1:
....:         assert s == [0]
....:     else:
....:         assert s[-2:] == [1, 1]
....:     assert all(d == 2 for d in s[:-2])
>>> from sage.all import *
>>> # needs networkx
>>> G = graphs.RandomLobster(Integer(12), RealNumber('0.7'), RealNumber('0.3'))
>>> leaves = [v for v in G.vertices(sort=False) if G.degree(v) == Integer(1)]
>>> G.delete_vertices(leaves)                                 # caterpillar
>>> leaves = [v for v in G.vertices(sort=False) if G.degree(v) == Integer(1)]
>>> G.delete_vertices(leaves)                                 # path
>>> s = G.degree_sequence()
>>> if G:
...     if G.n_vertices() == Integer(1):
...         assert s == [Integer(0)]
...     else:
...         assert s[-Integer(2):] == [Integer(1), Integer(1)]
...     assert all(d == Integer(2) for d in s[:-Integer(2)])

sage: G = graphs.RandomLobster(9, .6, .3)                                       # needs networkx
sage: G.show()                          # long time                             # needs networkx sage.plot
[Python]
>>> from sage.all import *
>>> G = graphs.RandomLobster(Integer(9), RealNumber('.6'), RealNumber('.3'))                                       # needs networkx
>>> G.show()                          # long time                             # needs networkx sage.plot
sage.graphs.generators.trees.RandomTree(n, seed=None)

Return a random tree on \(n\) nodes numbered \(0\) through \(n-1\).

By Cayley’s theorem, there are \(n^{n-2}\) trees with vertex set \(\{0,1,\dots,n-1\}\). This constructor chooses one of these uniformly at random.

ALGORITHM:

The algorithm works by generating an \((n-2)\)-long random sequence of numbers chosen independently and uniformly from \(\{0,1,\dots,n-1\}\) and then applies an inverse Prufer transformation.

INPUT:

  • n – number of vertices in the tree

  • seed – a random.Random seed or a Python int for the random number generator (default: None)

EXAMPLES:

sage: G = graphs.RandomTree(10)
sage: G.is_tree()
True
sage: G.show()                          # long time                             # needs sage.plot
>>> from sage.all import *
>>> G = graphs.RandomTree(Integer(10))
>>> G.is_tree()
True
>>> G.show()                          # long time                             # needs sage.plot
sage.graphs.generators.trees.RandomTreePowerlaw(n, gamma=3, tries=1000, seed=None)

Return a tree with a power law degree distribution, or False on failure.

From the NetworkX documentation: a trial power law degree sequence is chosen and then elements are swapped with new elements from a power law distribution until the sequence makes a tree (size = order - 1).

INPUT:

  • n – number of vertices

  • gamma – exponent of power law distribution

  • tries – number of attempts to adjust sequence to make a tree

  • seed – a random.Random seed or a Python int for the random number generator (default: None)

EXAMPLES:

We check that the generated graph is a tree:

sage: G = graphs.RandomTreePowerlaw(10, 3)                                      # needs networkx
sage: G.is_tree()                                                               # needs networkx
True
sage: G.order(), G.size()                                                       # needs networkx
(10, 9)
>>> from sage.all import *
>>> G = graphs.RandomTreePowerlaw(Integer(10), Integer(3))                                      # needs networkx
>>> G.is_tree()                                                               # needs networkx
True
>>> G.order(), G.size()                                                       # needs networkx
(10, 9)

sage: G = graphs.RandomTreePowerlaw(15, 2)                                      # needs networkx
sage: if G:                             # random output         # long time, needs networkx sage.plot
....:     G.show()
[Python]
>>> from sage.all import *
>>> G = graphs.RandomTreePowerlaw(Integer(15), Integer(2))                                      # needs networkx
>>> if G:                             # random output         # long time, needs networkx sage.plot
...     G.show()
class sage.graphs.generators.trees.TreeIterator

Bases: object

This class iterates over all trees with n vertices (up to isomorphism).

EXAMPLES:

sage: from sage.graphs.generators.trees import TreeIterator
sage: def check_trees(n):
....:     trees = []
....:     for t in TreeIterator(n):
....:         if not t.is_tree():
....:             return False
....:         if t.n_vertices() != n:
....:             return False
....:         if t.n_edges() != n - 1:
....:             return False
....:         for tree in trees:
....:             if tree.is_isomorphic(t):
....:                 return False
....:         trees.append(t)
....:     return True
sage: check_trees(10)
True
>>> from sage.all import *
>>> from sage.graphs.generators.trees import TreeIterator
>>> def check_trees(n):
...     trees = []
...     for t in TreeIterator(n):
...         if not t.is_tree():
...             return False
...         if t.n_vertices() != n:
...             return False
...         if t.n_edges() != n - Integer(1):
...             return False
...         for tree in trees:
...             if tree.is_isomorphic(t):
...                 return False
...         trees.append(t)
...     return True
>>> check_trees(Integer(10))
True

sage: from sage.graphs.generators.trees import TreeIterator
sage: count = 0
sage: for t in TreeIterator(15):
....:     count += 1
sage: count
7741
[Python]
>>> from sage.all import *
>>> from sage.graphs.generators.trees import TreeIterator
>>> count = Integer(0)
>>> for t in TreeIterator(Integer(15)):
...     count += Integer(1)
>>> count
7741
sage.graphs.generators.trees.nauty_gentreeg(options='', debug=False)

Return a generator which creates non-isomorphic trees from nauty’s gentreeg program.

INPUT:

  • options – string (default: ""); a string passed to gentreeg as if it was run at a system command line. At a minimum, you must pass the number of vertices you desire. Sage expects the graphs to be in nauty’s “sparse6” format, do not set an option to change this default or results will be unpredictable.

  • debug – boolean (default: False); if True the first line of gentreeg’s output to standard error is captured and the first call to the generator’s next() function will return this line as a string. A line leading with “>A” indicates a successful initiation of the program with some information on the arguments, while a line beginning with “>E” indicates an error with the input.

The possible options, obtained as output of gentreeg -help:

   n            : the number of vertices. Must be in range 1..128
res/mod         : only generate subset res out of subsets 0..mod-1
  -D<int>       : an upper bound for the maximum degree
  -Z<int>:<int> : bounds on the diameter
  -q            : suppress auxiliary output

Options which cause gentreeg to use an output format different than the sparse6 format are not listed above (-p, -l, -u) as they will confuse the creation of a Sage graph. The res/mod option can be useful when using the output in a routine run several times in parallel.

OUTPUT:

A generator which will produce the graphs as Sage graphs. These will be simple graphs: no loops, no multiple edges, no directed edges.

See also

trees() – another generator of trees

EXAMPLES:

The generator can be used to construct trees for testing, one at a time (usually inside a loop). Or it can be used to create an entire list all at once if there is sufficient memory to contain it:

sage: gen = graphs.nauty_gentreeg("4")
sage: next(gen)
Graph on 4 vertices
sage: next(gen)
Graph on 4 vertices
sage: next(gen)
Traceback (most recent call last):
...
StopIteration
>>> from sage.all import *
>>> gen = graphs.nauty_gentreeg("4")
>>> next(gen)
Graph on 4 vertices
>>> next(gen)
Graph on 4 vertices
>>> next(gen)
Traceback (most recent call last):
...
StopIteration

The number of trees on the first few vertex counts. This agrees with OEIS sequence A000055:

sage: [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(1, 15)]
[1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
[Python]
>>> from sage.all import *
>>> [len(list(graphs.nauty_gentreeg(str(i)))) for i in range(Integer(1), Integer(15))]
[1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]

The debug switch can be used to examine gentreeg’s reaction to the input in the options string. We illustrate success. (A failure will be a string beginning with “>E”.) Passing the “-q” switch to gentreeg will suppress the indicator of a successful initiation, and so the first returned value might be an empty string if debug is True:

sage: gen = graphs.nauty_gentreeg("4", debug=True)
sage: print(next(gen))
>A ...gentreeg ...
sage: gen = graphs.nauty_gentreeg("4 -q", debug=True)
sage: next(gen)
''
>>> from sage.all import *
>>> gen = graphs.nauty_gentreeg("4", debug=True)
>>> print(next(gen))
>A ...gentreeg ...
>>> gen = graphs.nauty_gentreeg("4 -q", debug=True)
>>> next(gen)
''
sage.graphs.generators.trees.trees(n)

Return a generator of the distinct trees on a fixed number of vertices.

INPUT:

  • n – integer; the size of the trees created

OUTPUT:

A generator which creates an exhaustive, duplicate-free listing of the connected free (unlabeled) trees with n vertices. A tree is a graph with no cycles.

ALGORITHM:

Uses the algorithm that generates each new tree in constant time described in [WROM1986]. See the documentation for, and implementation of, the sage.graphs.generators.trees module.

EXAMPLES:

We create an iterator, then loop over its elements:

sage: tree_iterator = graphs.trees(7)
sage: for T in tree_iterator:
....:     print(T.degree_sequence())
[2, 2, 2, 2, 2, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[4, 2, 2, 1, 1, 1, 1]
[3, 3, 2, 1, 1, 1, 1]
[3, 3, 2, 1, 1, 1, 1]
[4, 3, 1, 1, 1, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[4, 2, 2, 1, 1, 1, 1]
[5, 2, 1, 1, 1, 1, 1]
[6, 1, 1, 1, 1, 1, 1]
>>> from sage.all import *
>>> tree_iterator = graphs.trees(Integer(7))
>>> for T in tree_iterator:
...     print(T.degree_sequence())
[2, 2, 2, 2, 2, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[4, 2, 2, 1, 1, 1, 1]
[3, 3, 2, 1, 1, 1, 1]
[3, 3, 2, 1, 1, 1, 1]
[4, 3, 1, 1, 1, 1, 1]
[3, 2, 2, 2, 1, 1, 1]
[4, 2, 2, 1, 1, 1, 1]
[5, 2, 1, 1, 1, 1, 1]
[6, 1, 1, 1, 1, 1, 1]

The number of trees on the first few vertex counts. This is sequence A000055 in Sloane’s OEIS:

sage: [len(list(graphs.trees(i))) for i in range(15)]
[1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]
[Python]
>>> from sage.all import *
>>> [len(list(graphs.trees(i))) for i in range(Integer(15))]
[1, 1, 1, 1, 2, 3, 6, 11, 23, 47, 106, 235, 551, 1301, 3159]