Skip to content

Commit 197ec71

Browse files
Sync forest and Forest
1 parent 2712d85 commit 197ec71

6 files changed

Lines changed: 287 additions & 52 deletions

File tree

docs/source/data-structures/word-graph/helpers.rst

Lines changed: 0 additions & 48 deletions
This file was deleted.

docs/source/data-structures/word-graph/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ This page contains links to the documentation for the parts of
1717
:maxdepth: 1
1818

1919
forest
20+
forest-helpers
2021
gabow
2122
joiner
2223
meeter
2324
paths
2425
word-graph
2526
paths-helpers
26-
helpers
27+
word-graph-helpers

src/forest.cpp

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ then node ``i`` is a root node.
300300
R"pbdoc(
301301
:sig=(self: Forest, i: int) -> list[int]:
302302
303-
Returns a list containing the labels of the edges on the path from a root node
304-
to *i*.
303+
Returns a list containing the labels of the edges on the path from the node *i* to a root node.
305304
306305
:param i: the node.
307306
:type i: int
@@ -340,6 +339,185 @@ Set the parent and edge label for a node. This function sets the parent of
340339
if *node* or *parent* exceeds :any:`number_of_nodes()`.
341340
342341
:complexity: Constant.
342+
)pbdoc");
343+
344+
////////////////////////////////////////////////////////////////////////
345+
// Helpers
346+
////////////////////////////////////////////////////////////////////////
347+
348+
m.def(
349+
"forest_path_to_root",
350+
[](Forest const& f, node_type n) { return forest::path_to_root(f, n); },
351+
py::arg("f"),
352+
py::arg("n"),
353+
R"pbdoc(
354+
:sig=(f: Forest, n: int) -> list[int]:
355+
356+
Returns a list containing the labels of the edges on the path from node *n* to
357+
a root node.
358+
359+
:param f: the Forest.
360+
:type f: Forest
361+
362+
:param n: the node.
363+
:type n: int
364+
365+
:returns: The word labelling the path from the root to *n*.
366+
:rtype: list[int]
367+
368+
:raises LibsemigroupsError:
369+
if *n* is greater than or equal to :any:`Forest.number_of_nodes`.
370+
)pbdoc");
371+
372+
m.def(
373+
"forest_depth",
374+
[](Forest const& f, node_type n) { return forest::depth(f, n); },
375+
py::arg("f"),
376+
py::arg("n"),
377+
R"pbdoc(
378+
:sig=(f: Forest, n: int) -> int:
379+
380+
Returns the depth of a node in the forest, i.e. the distance, in terms of the
381+
number of edges, from a root.
382+
383+
This function returns the length of the word returned by
384+
:any:`path_to_root` and :any:`path_from_root`.
385+
386+
:param f: the Forest.
387+
:type f: Forest
388+
389+
:param n: the node.
390+
:type n: int
391+
392+
:returns: The depth of *n*.
393+
:rtype: int
394+
395+
:raises LibsemigroupsError:
396+
if *n* is out of bounds (i.e. it is greater than or equal to
397+
:any:`Forest.number_of_nodes`).
398+
)pbdoc");
399+
400+
m.def(
401+
"forest_dot",
402+
[](Forest const& f) { return forest::dot(f); },
403+
py::arg("f"),
404+
R"pbdoc(
405+
Returns a :any:`Dot` object representing a Forest.
406+
407+
This function returns a :any:`Dot` object representing the :any:`Forest` *f*.
408+
409+
:param f: the Forest.
410+
:type f: Forest
411+
412+
:returns: A :any:`Dot` object.
413+
414+
:rtype: Dot
415+
)pbdoc");
416+
417+
m.def(
418+
"forest_dot",
419+
[](Forest const& f, std::vector<std::string> const& labels) {
420+
return forest::dot(f, labels);
421+
},
422+
py::arg("f"),
423+
py::arg("labels"),
424+
R"pbdoc(
425+
:sig=(f: Forest, labels: list[str]) -> Dot:
426+
427+
Returns a :any:`Dot` object representing a Forest.
428+
429+
This function returns a :any:`Dot` object representing the :any:`Forest` *f*.
430+
If *labels* is not empty, then each node is labelled with the path from
431+
that node to the root of its tree with each letter replaced by the string
432+
in the corresponding position of *labels*. If *labels* is empty, then
433+
the nodes are not labelled by their paths.
434+
435+
:param f: the Forest.
436+
:type f: Forest
437+
438+
:param labels: substitute for each edge label.
439+
:type labels: list[str]
440+
441+
:returns: A :any:`Dot` object.
442+
:rtype: Dot
443+
444+
:raises LibsemigroupsError:
445+
if the size of *labels* is not the same as the :any:`max_label` plus one.
446+
)pbdoc");
447+
448+
m.def("forest_is_root",
449+
&forest::is_root,
450+
py::arg("f"),
451+
py::arg("n"),
452+
R"pbdoc(
453+
:sig=(f: Forest, n: int) -> bool:
454+
455+
Check if a node is the root of any tree in the :any:`Forest`.
456+
457+
This function returns ``True`` if the node *n* in the :any:`Forest` *f* is
458+
a root node, and ``False`` if it is not.
459+
460+
:param f: the Forest.
461+
:type f: Forest
462+
463+
:param n: the node.
464+
:type n: int
465+
466+
:returns: Whether or not *n* is a root of *f*.
467+
:rtype: bool
468+
469+
:raises LibsemigroupsError:
470+
if *n* is out of bounds (i.e. it is greater than or equal to
471+
:any:`Forest.number_of_nodes`).
472+
)pbdoc");
473+
474+
m.def(
475+
"forest_max_label",
476+
[](Forest const& f) -> int_or_unsigned_constant<Forest::label_type> {
477+
return from_int(forest::max_label(f));
478+
},
479+
py::arg("f"),
480+
R"pbdoc(
481+
:sig=(f: Forest) -> int | Undefined:
482+
483+
Returns the maximum label of any edge in a :any:`Forest`.
484+
485+
This function returns the maximum label of any edge in the :any:`Forest` *f*
486+
or :any:`UNDEFINED` if there are no edges.
487+
488+
:param f: the Forest.
489+
:type f: Forest
490+
491+
:returns: The maximum label or :any:`UNDEFINED`.
492+
:rtype: int | Undefined
493+
)pbdoc");
494+
495+
m.def(
496+
"forest_path_from_root",
497+
[](Forest const& f, Forest::node_type n) {
498+
return forest::path_from_root(f, n);
499+
},
500+
py::arg("f"),
501+
py::arg("n"),
502+
R"pbdoc(
503+
:sig=(f: Forest, n: int) -> list[int]:
504+
505+
Returns a word containing the labels of the edges on the path from a root node
506+
to *n*.
507+
508+
This function returns a word containing the labels of the edges on the path
509+
from a root node to the node *n*.
510+
511+
:param f: the forest.
512+
:type f: Forest
513+
514+
:param n: the node.
515+
:type n: int
516+
517+
:returns: The word labelling the path from a root node to *n*.
518+
:rtype: list[int]
519+
520+
.. seealso:: :any:`PathsFromRoots`
343521
)pbdoc");
344522
}
345523
} // namespace libsemigroups

src/libsemigroups_pybind11/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import libsemigroups_pybind11.blocks
1616
import libsemigroups_pybind11.bmat8
1717
import libsemigroups_pybind11.congruence
18+
import libsemigroups_pybind11.forest
1819
import libsemigroups_pybind11.froidure_pin
1920
import libsemigroups_pybind11.kambites
2021
import libsemigroups_pybind11.knuth_bendix
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright (c) 2025 J. D. Mitchell
4+
#
5+
# Distributed under the terms of the GPL license version 3.
6+
#
7+
# The full license is in the file LICENSE, distributed with this software.
8+
9+
"""
10+
This page contains the documentation for various helper functions for
11+
manipulating :any:`Forest` objects. All such functions are contained in
12+
the submodule ``forest``.
13+
"""
14+
15+
from typing_extensions import Self as _Self
16+
17+
from _libsemigroups_pybind11 import ( # pylint: disable=no-name-in-module
18+
forest_depth as depth,
19+
forest_dot as dot,
20+
forest_is_root as is_root,
21+
forest_path_to_root as path_to_root,
22+
forest_path_from_root as path_from_root,
23+
forest_max_label as max_label,
24+
)

tests/test_forest.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import pytest
1717

18-
from libsemigroups_pybind11 import Forest, UNDEFINED
18+
from libsemigroups_pybind11 import Forest, UNDEFINED, forest
1919

2020

2121
@pytest.fixture(name="f")
@@ -30,3 +30,82 @@ def test_forest_return_policy(f):
3030
assert f.parents() is not f.parents()
3131
assert f.labels() is not f.labels()
3232
assert f.set_parent_and_label(1, 0, 10) is f
33+
34+
35+
def test_forest_depth(f):
36+
assert [forest.depth(f, n) for n in range(f.number_of_nodes())] == [
37+
0,
38+
1,
39+
2,
40+
3,
41+
4,
42+
]
43+
44+
45+
def test_forest_path_to_root(f):
46+
assert [forest.path_to_root(f, n) for n in range(f.number_of_nodes())] == [
47+
[],
48+
[0],
49+
[0] * 2,
50+
[0] * 3,
51+
[0] * 4,
52+
]
53+
54+
55+
def test_forest_path_from_root(f):
56+
assert [forest.path_from_root(f, n) for n in range(f.number_of_nodes())] == [
57+
[],
58+
[0],
59+
[0] * 2,
60+
[0] * 3,
61+
[0] * 4,
62+
]
63+
64+
65+
def test_forest_dot(f):
66+
assert (
67+
str(forest.dot(f))
68+
== """digraph Forest {
69+
rankdir="BT"
70+
0 [label="0: ε", shape="box"]
71+
1 [label="1: 0", shape="box"]
72+
2 [label="2: 00", shape="box"]
73+
3 [label="3: 000", shape="box"]
74+
4 [label="4: 0000", shape="box"]
75+
1 -> 0 [color="#00ff00"]
76+
2 -> 1 [color="#00ff00"]
77+
3 -> 2 [color="#00ff00"]
78+
4 -> 3 [color="#00ff00"]
79+
}"""
80+
)
81+
assert (
82+
str(forest.dot(f, ["a"]))
83+
== """digraph Forest {
84+
rankdir="BT"
85+
0 [label="0: ε", shape="box"]
86+
1 [label="1: a", shape="box"]
87+
2 [label="2: aa", shape="box"]
88+
3 [label="3: aaa", shape="box"]
89+
4 [label="4: aaaa", shape="box"]
90+
1 -> 0 [color="#00ff00"]
91+
2 -> 1 [color="#00ff00"]
92+
3 -> 2 [color="#00ff00"]
93+
4 -> 3 [color="#00ff00"]
94+
}"""
95+
)
96+
97+
98+
def test_forest_is_root(f):
99+
assert [forest.is_root(f, n) for n in range(f.number_of_nodes())] == [
100+
True,
101+
False,
102+
False,
103+
False,
104+
False,
105+
]
106+
107+
108+
def test_forest_max_label(f):
109+
assert forest.max_label(f) == 0
110+
f = Forest()
111+
assert forest.max_label(f) == UNDEFINED

0 commit comments

Comments
 (0)