Generation of trees¶
This module gathers methods related to the generation of trees.
All these methods appear in sage.graphs.graph_generators.
Return the perfectly balanced tree of height \(h \geq 1\), whose root has degree \(r \geq 2\). |
|
Return a tree with the given degree sequence. |
|
Return the graph of the Fibonacci Tree \(F_{n}\). |
|
Return a random lobster. |
|
Return a random tree on \(n\) nodes numbered \(0\) through \(n-1\). |
|
Return a tree with a power law degree distribution, or |
|
Return a generator of the distinct trees on a fixed number of vertices. |
|
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
>>> 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
>>> 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
>>> 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 nodeh– 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 backbonep– probability of adding an edge to the backboneq– probability of adding an edge (claw) to the armsseed– arandom.Randomseed or a Pythonintfor 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 treeseed– arandom.Randomseed or a Pythonintfor 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
Falseon 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 verticesgamma– exponent of power law distributiontries– number of attempts to adjust sequence to make a treeseed– arandom.Randomseed or a Pythonintfor 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:
objectThis 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 togentreegas 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); ifTruethe first line ofgentreeg’s output to standard error is captured and the first call to the generator’snext()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
gentreegto 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 treesEXAMPLES:
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
debugswitch can be used to examinegentreeg’s reaction to the input in theoptionsstring. We illustrate success. (A failure will be a string beginning with “>E”.) Passing the “-q” switch togentreegwill suppress the indicator of a successful initiation, and so the first returned value might be an empty string ifdebugisTrue: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
nvertices. 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.treesmodule.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]