From 7a10fb165abfd2332a39439064a3403d18294806 Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Thu, 14 May 2026 19:53:16 +0100 Subject: [PATCH 1/3] dot: update for changes upstream --- src/dot.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dot.cpp b/src/dot.cpp index 47650306..33c613ca 100644 --- a/src/dot.cpp +++ b/src/dot.cpp @@ -74,6 +74,8 @@ The name of the node. &Dot::Node::add_attr, py::arg("key"), py::arg("val"), + py::arg("kind"), + // TODO add default value R"pbdoc(Add an attribute to a node. This function adds a new attribute, or replaces the value of an existing @@ -83,6 +85,8 @@ attribute of a :any:`Dot.Node`. :type key: str :param val: the value of the attribute. :type val: str +:param kind: whether to add quotes to *val* or not. +:type val: Dot.Attr :returns: *self* :rtype: Dot.Node @@ -121,6 +125,8 @@ The name (read-only `str`) of the tail of the edge. &Dot::Edge::add_attr, py::arg("key"), py::arg("val"), + py::arg("kind"), + // TODO add default value R"pbdoc(Add an attribute to an edge. This function adds a new attribute, or replaces the value of an existing @@ -130,6 +136,8 @@ attribute of an :any:`Edge`. :type key: str :param val: the value of the attribute. :type val: str +:param kind: whether to add quotes to *val* or not. +:type val: Dot.Attr :returns: *self* :rtype: Dot.Edge @@ -474,6 +482,8 @@ representation of the graph in the DOT_ language for Graphviz_. &Dot::add_attr, py::arg("key"), py::arg("val"), + py::arg("kind"), + // TODO add default value R"pbdoc(Add an attribute to a :any:`Dot` object. This function adds a new attribute, or replaces the value of an existing @@ -483,6 +493,8 @@ attribute of an :any:`Dot`. :type key: str :param val: the value of the attribute. :type val: str +:param kind: whether to add quotes to *val* or not. +:type val: Dot.Attr :returns: *self* :rtype: Dot From 6f5c03cc47f8b351e62f0f9fc16510e099e26c27 Mon Sep 17 00:00:00 2001 From: James Mitchell Date: Thu, 14 May 2026 20:16:36 +0100 Subject: [PATCH 2/3] dot: add Dot::Attr --- src/dot.cpp | 102 +++++++++++++++++++++++++++++----------------- tests/test_dot.py | 35 ++++++++++++---- 2 files changed, 90 insertions(+), 47 deletions(-) diff --git a/src/dot.cpp b/src/dot.cpp index 33c613ca..9c03c35e 100644 --- a/src/dot.cpp +++ b/src/dot.cpp @@ -49,6 +49,67 @@ its DOT_ source code string (:any:`Dot.to_string`). Write the source code to a file and render it with the Graphviz_ installation on your system. )pbdoc"); + //////////////////////////////////////////////////////////////////////////// + // Attr enum + //////////////////////////////////////////////////////////////////////////// + + py::options options; + options.disable_enum_members_docstring(); + + py::enum_ attr(dot, "Attr", R"pbdoc( +The values in this enum can be used to indicate how attribute values are +written in the DOT_ output. + +The valid values are: + +.. py:attribute:: Attr.string + :value: + + Value indicating that the attribute value should be written as a quoted + string. + +.. py:attribute:: Attr.html + :value: + + Value indicating that the attribute value should be written as an HTML-like + label. +)pbdoc"); + attr.value("string", Dot::Attr::string).value("html", Dot::Attr::html); + attr.def("__repr__", [](Dot::Attr const& val) { + return to_human_readable_repr(val, "."); + }); + + //////////////////////////////////////////////////////////////////////////// + // Kind enum + //////////////////////////////////////////////////////////////////////////// + + py::enum_ kind(dot, "Kind", R"pbdoc( +The values in this enum can be used to indicate the type of a graph. + +The valid values are: + +.. py:attribute:: Kind.digraph + :value: + + Value indicating that the represented graph has directed edges ->. + +.. py:attribute:: Kind.graph + :value: + + Value indicating that the represented graph has undirected edges --. + +.. py:attribute:: Kind.subgraph + :value: + + Value indicating that the represented graph is a subgraph of another Dot object. +)pbdoc"); + kind.value("digraph", Dot::Kind::digraph) + .value("graph", Dot::Kind::graph) + .value("subgraph", Dot::Kind::subgraph); + kind.def("__repr__", [](Dot::Kind const& knd) { + return to_human_readable_repr(knd, "."); + }); + //////////////////////////////////////////////////////////////////////////// // Node nested class //////////////////////////////////////////////////////////////////////////// @@ -74,8 +135,7 @@ The name of the node. &Dot::Node::add_attr, py::arg("key"), py::arg("val"), - py::arg("kind"), - // TODO add default value + py::arg("kind") = Dot::Attr::string, R"pbdoc(Add an attribute to a node. This function adds a new attribute, or replaces the value of an existing @@ -125,8 +185,7 @@ The name (read-only `str`) of the tail of the edge. &Dot::Edge::add_attr, py::arg("key"), py::arg("val"), - py::arg("kind"), - // TODO add default value + py::arg("kind") = Dot::Attr::string, R"pbdoc(Add an attribute to an edge. This function adds a new attribute, or replaces the value of an existing @@ -143,38 +202,6 @@ attribute of an :any:`Edge`. :rtype: Dot.Edge )pbdoc"); - //////////////////////////////////////////////////////////////////////////// - // Kind enum - //////////////////////////////////////////////////////////////////////////// - py::options options; - options.disable_enum_members_docstring(); - py::enum_ kind(dot, "Kind", R"pbdoc( -The values in this enum can be used to indicate the type of a graph. - -The valid values are: - -.. py:attribute:: Kind.digraph - :value: - - Value indicating that the represented graph has directed edges ->. - -.. py:attribute:: Kind.graph - :value: - - Value indicating that the represented graph has undirected edges --. - -.. py:attribute:: Kind.subgraph - :value: - - Value indicating that the represented graph is a subgraph of another Dot object. -)pbdoc"); - kind.value("digraph", Dot::Kind::digraph) - .value("graph", Dot::Kind::graph) - .value("subgraph", Dot::Kind::subgraph); - kind.def("__repr__", [](Dot::Kind const& knd) { - return to_human_readable_repr(knd, "."); - }); - //////////////////////////////////////////////////////////////////////////// // Dot members //////////////////////////////////////////////////////////////////////////// @@ -482,8 +509,7 @@ representation of the graph in the DOT_ language for Graphviz_. &Dot::add_attr, py::arg("key"), py::arg("val"), - py::arg("kind"), - // TODO add default value + py::arg("kind") = Dot::Attr::string, R"pbdoc(Add an attribute to a :any:`Dot` object. This function adds a new attribute, or replaces the value of an existing diff --git a/tests/test_dot.py b/tests/test_dot.py index 30e1df64..65c8f128 100644 --- a/tests/test_dot.py +++ b/tests/test_dot.py @@ -23,11 +23,11 @@ def test_edge(): assert len(edges) == 6 assert edges[0].head == "0" assert edges[0].tail == "0" - assert edges[0].attrs == {"color": "#00ff00"} + assert edges[0].attrs == {"color": '"#00ff00"'} edges[0].add_attr("style", "dashed") - assert edges[0].attrs == {"color": "#00ff00", "style": "dashed"} + assert edges[0].attrs == {"color": '"#00ff00"', "style": '"dashed"'} edges[0].attrs["color"] = "blue" # FIXME this should throw - assert edges[0].attrs == {"color": "#00ff00", "style": "dashed"} + assert edges[0].attrs == {"color": '"#00ff00"', "style": '"dashed"'} def test_node(): @@ -36,11 +36,11 @@ def test_node(): nodes = d.nodes() assert len(nodes) == 3 assert nodes[0].name == "0" - assert nodes[0].attrs == {"shape": "box"} + assert nodes[0].attrs == {"shape": '"box"'} nodes[0].add_attr("shape", "circle") - assert nodes[0].attrs == {"shape": "circle"} + assert nodes[0].attrs == {"shape": '"circle"'} nodes[0].attrs["color"] = "blue" # FIXME this should throw - assert nodes[0].attrs == {"shape": "circle"} + assert nodes[0].attrs == {"shape": '"circle"'} def test_dot_copy(): @@ -55,9 +55,9 @@ def test_dot_attrs(): d.add_attr("node [shape=circle]") assert d.attrs() == {"node [shape=circle]": ""} d.add_attr("splines", "line") - assert d.attrs() == {"node [shape=circle]": "", "splines": "line"} + assert d.attrs() == {"node [shape=circle]": "", "splines": '"line"'} del d.attrs()["splines"] # TODO(1) should raise - assert d.attrs() == {"node [shape=circle]": "", "splines": "line"} + assert d.attrs() == {"node [shape=circle]": "", "splines": '"line"'} def test_dot_add_node(): @@ -81,7 +81,7 @@ def test_dot_add_edge(): assert len(d.edges()) == 1 assert e.attrs == {} e.add_attr("color", "#00FF00") - assert e.attrs == {"color": "#00FF00"} + assert e.attrs == {"color": '"#00FF00"'} def test_dot_add_subgraph(): @@ -142,6 +142,23 @@ def test_dot_kind(): assert d.kind() == Dot.Kind.graph +def test_dot_attr(): + d = Dot() + d.add_attr("label", "pets", Dot.Attr.string) + d.add_node("cat").add_attr("label", "cat", Dot.Attr.html) + d.add_node("dog") + d.add_edge("cat", "dog").add_attr("label", "friend", Dot.Attr.string) + + assert repr(Dot.Attr.string) == "" + assert d.attrs() == {"label": '"pets"'} + assert d.node("cat").attrs == {"label": "cat"} + assert d.edge("cat", "dog").attrs == {"label": '"friend"'} + assert d.to_string() == ( + 'digraph {\n label="pets"\n cat [label=cat]\n dog\n ' + 'cat -> dog [label="friend"]\n}' + ) + + def test_dot_return_policy(): d = Dot() assert d.colors is not d.colors From 2fd53daeae3a3a0a7bba610584364b3ac29ccd0d Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 14 May 2026 20:56:49 +0100 Subject: [PATCH 3/3] froidure-pin: add dot_right/left_cayley_graph --- src/froidure-pin-base.cpp | 159 +++++++++++++++++++++ src/libsemigroups_pybind11/froidure_pin.py | 8 ++ tests/test_froidure_pin.py | 38 +++++ 3 files changed, 205 insertions(+) diff --git a/src/froidure-pin-base.cpp b/src/froidure-pin-base.cpp index f95e40ae..84e22439 100644 --- a/src/froidure-pin-base.cpp +++ b/src/froidure-pin-base.cpp @@ -17,6 +17,7 @@ // // libsemigroups headers +#include #include // pybind11.... @@ -528,6 +529,164 @@ Returns the position of the suffix of the element ``x`` in position *pos* py::options options; options.disable_function_signatures(); + m.def("froidure_pin_dot_current_right_cayley_graph", + py::overload_cast( + &froidure_pin::dot_current_right_cayley_graph), + py::arg("fp"), + R"pbdoc( +:sig=(fp: FroidurePin) -> Dot: + +Returns a :any:`Dot` object representing the current right Cayley graph. + +This function does not trigger a full enumeration of *fp*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_current_right_cayley_graph", + py::overload_cast( + &froidure_pin::dot_current_right_cayley_graph), + py::arg("fp"), + py::arg("gen_names"), + R"pbdoc( +:sig=(fp: FroidurePin, gen_names: str) -> Dot: + +Returns a :any:`Dot` object representing the current right Cayley graph. + +The generators are labelled using the characters in *gen_names*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin +:param gen_names: the labels for the generators. +:type gen_names: str + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_right_cayley_graph", + py::overload_cast( + &froidure_pin::dot_right_cayley_graph), + py::arg("fp"), + R"pbdoc( +:sig=(fp: FroidurePin) -> Dot: + +Returns a :any:`Dot` object representing the right Cayley graph. + +This function triggers a full enumeration of *fp*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_right_cayley_graph", + py::overload_cast( + &froidure_pin::dot_right_cayley_graph), + py::arg("fp"), + py::arg("gen_names"), + R"pbdoc( +:sig=(fp: FroidurePin, gen_names: str) -> Dot: + +Returns a :any:`Dot` object representing the right Cayley graph. + +This function triggers a full enumeration of *fp*. The generators are labelled +using the characters in *gen_names*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin +:param gen_names: the labels for the generators. +:type gen_names: str + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_current_left_cayley_graph", + py::overload_cast( + &froidure_pin::dot_current_left_cayley_graph), + py::arg("fp"), + R"pbdoc( +:sig=(fp: FroidurePin) -> Dot: + +Returns a :any:`Dot` object representing the current left Cayley graph. + +This function does not trigger a full enumeration of *fp*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_current_left_cayley_graph", + py::overload_cast( + &froidure_pin::dot_current_left_cayley_graph), + py::arg("fp"), + py::arg("gen_names"), + R"pbdoc( +:sig=(fp: FroidurePin, gen_names: str) -> Dot: + +Returns a :any:`Dot` object representing the current left Cayley graph. + +The generators are labelled using the characters in *gen_names*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin +:param gen_names: the labels for the generators. +:type gen_names: str + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_left_cayley_graph", + py::overload_cast( + &froidure_pin::dot_left_cayley_graph), + py::arg("fp"), + R"pbdoc( +:sig=(fp: FroidurePin) -> Dot: + +Returns a :any:`Dot` object representing the left Cayley graph. + +This function triggers a full enumeration of *fp*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + + m.def("froidure_pin_dot_left_cayley_graph", + py::overload_cast( + &froidure_pin::dot_left_cayley_graph), + py::arg("fp"), + py::arg("gen_names"), + R"pbdoc( +:sig=(fp: FroidurePin, gen_names: str) -> Dot: + +Returns a :any:`Dot` object representing the left Cayley graph. + +This function triggers a full enumeration of *fp*. The generators are labelled +using the characters in *gen_names*. + +:param fp: the :any:`FroidurePin` object. +:type fp: FroidurePin +:param gen_names: the labels for the generators. +:type gen_names: str + +:returns: A :any:`Dot` object. +:rtype: Dot +)pbdoc"); + m.def("froidure_pin_product_by_reduction", &froidure_pin::product_by_reduction, py::arg("fp"), diff --git a/src/libsemigroups_pybind11/froidure_pin.py b/src/libsemigroups_pybind11/froidure_pin.py index b77740ba..6a216784 100644 --- a/src/libsemigroups_pybind11/froidure_pin.py +++ b/src/libsemigroups_pybind11/froidure_pin.py @@ -75,6 +75,10 @@ froidure_pin_current_normal_forms as _froidure_pin_current_normal_forms, froidure_pin_current_position as _froidure_pin_current_position, froidure_pin_current_rules as _froidure_pin_current_rules, + froidure_pin_dot_current_left_cayley_graph as _froidure_pin_dot_current_left_cayley_graph, + froidure_pin_dot_current_right_cayley_graph as _froidure_pin_dot_current_right_cayley_graph, + froidure_pin_dot_left_cayley_graph as _froidure_pin_dot_left_cayley_graph, + froidure_pin_dot_right_cayley_graph as _froidure_pin_dot_right_cayley_graph, froidure_pin_equal_to as _froidure_pin_equal_to, froidure_pin_factorisation as _froidure_pin_factorisation, froidure_pin_minimal_factorisation as _froidure_pin_minimal_factorisation, @@ -261,6 +265,10 @@ def sorted_elements( # pylint: disable=missing-function-docstring # TODO(1) be good to get the notes about enumeration being triggered or not, in # this doc +dot_current_left_cayley_graph = _wrap_cxx_free_fn(_froidure_pin_dot_current_left_cayley_graph) +dot_current_right_cayley_graph = _wrap_cxx_free_fn(_froidure_pin_dot_current_right_cayley_graph) +dot_left_cayley_graph = _wrap_cxx_free_fn(_froidure_pin_dot_left_cayley_graph) +dot_right_cayley_graph = _wrap_cxx_free_fn(_froidure_pin_dot_right_cayley_graph) current_minimal_factorisation = _wrap_cxx_free_fn(_froidure_pin_current_minimal_factorisation) current_normal_forms = _wrap_cxx_free_fn(_froidure_pin_current_normal_forms) current_position = _wrap_cxx_free_fn(_froidure_pin_current_position) diff --git a/tests/test_froidure_pin.py b/tests/test_froidure_pin.py index eea7d86f..de4ab4e8 100644 --- a/tests/test_froidure_pin.py +++ b/tests/test_froidure_pin.py @@ -18,6 +18,7 @@ UNDEFINED, Bipartition, BMat8, + Dot, FroidurePin, KnuthBendix, LibsemigroupsError, @@ -161,13 +162,50 @@ def check_idempotents(S): def check_cayley_graphs(S): ReportGuard(False) + gen_names = "abcdefghijklmnopqrstuvwxyz"[: S.number_of_generators()] + + d = froidure_pin.dot_current_right_cayley_graph(S) + assert isinstance(d, Dot) + assert d.kind() == Dot.Kind.digraph + assert len(d.nodes()) == S.current_size() + + d = froidure_pin.dot_current_right_cayley_graph(S, gen_names) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.current_size() + + d = froidure_pin.dot_current_left_cayley_graph(S) + assert isinstance(d, Dot) + assert d.kind() == Dot.Kind.digraph + assert len(d.nodes()) == S.current_size() + + d = froidure_pin.dot_current_left_cayley_graph(S, gen_names) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.current_size() + g = S.right_cayley_graph() assert g.number_of_nodes() == S.size() assert g.out_degree() == S.number_of_generators() + + d = froidure_pin.dot_right_cayley_graph(S) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.size() + + d = froidure_pin.dot_right_cayley_graph(S, gen_names) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.size() + g = S.left_cayley_graph() assert g.number_of_nodes() == S.size() assert g.out_degree() == S.number_of_generators() + d = froidure_pin.dot_left_cayley_graph(S) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.size() + + d = froidure_pin.dot_left_cayley_graph(S, gen_names) + assert isinstance(d, Dot) + assert len(d.nodes()) == S.size() + def check_factor_prod_rels(S): ReportGuard(False)