-
-
Notifications
You must be signed in to change notification settings - Fork 487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Functions for nice tree decomposition and its labelling #36504
Conversation
If you need, I have a code that takes a tree decomposition and returns a nice tree decomposition. It is not optimized but it can be useful. I had in my todo list to add it to Sagemath, as well as other methods... |
Merci pour la suggestion. But at least for the nice tree decomposition one, I would like to implement it myself first. What other methods do you have in mind? The ones mentioned in TODO in the code files? Also, by "optimization", do you mean at the level of math/algo or code (like making the tree more balanced?) |
approximation of tree length based on BFS layering. I have a code for computing the "leveled tree" or "layered tree" as proposed by Chepoi et al. I need to add it and extend it to get a tree decomposition. The same can be done using lex-M and MCS-M.
Make it faster at least. |
Sounds interesting. Currently I don't know too much about these, but can check the literatures. |
May I see the code for reference, or improve upon it...? |
My code is below. I hope it is correct def nice_tree_decomposition(T):
r"""
Return a *nice* tree-decomposition of the tree-decomposition `T`.
https://kam.mff.cuni.cz/~ashutosh/lectures/lec06.pdf
A *nice* tree-decomposition is a rooted binary tree with four types of
nodes:
- Leaf nodes have no children and bag size 1;
- Introduce nodes have one child. The child has the same vertices as the
parent with one deleted;
- Forget nodes have one child. The child has the same vertices as the parent
with one added.
- Join nodes have two children, both identical to the parent.
.. WARNING::
This method assumes that the vertices of the input tree `T` are hashable
and have attribute ``issuperset``, e.g., ``frozenset`` or
:class:`~sage.sets.set.Set_object_enumerated_with_category`.
INPUT:
- ``T`` -- a tree-decomposition
"""
from sage.graphs.graph import Graph
if not isinstance(T, Graph):
raise ValueError("T must be a tree-decomposition")
name = "Nice tree-decomposition of {}".format(T.name())
if not T:
return Graph(name=name)
# P1: The tree is rooted.
# We choose a root and orient the edges in root to leaves direction.
leaves = [u for u in T if T.degree(u) == 1]
root = leaves.pop()
from sage.graphs.digraph import DiGraph
DT = DiGraph(T.breadth_first_search(start=root, edges=True),
format='list_of_edges')
# We relabel the graph in range 0..|T|-1
bag_to_int = DT.relabel(inplace=True, return_map=True)
# get the new name of the root
root = bag_to_int[root]
# and force bags to be of type Set to simplify the code
bag = {ui: Set(u) for u, ui in bag_to_int.items()}
# P2: The root and the leaves have empty bags.
# To do so, we add to each leaf node l of DT a children with empty bag.
# We also add a new root with empty bag.
root, old_root = DT.add_vertex(), root
DT.add_edge(root, old_root)
bag[root] = Set()
for vi, u in enumerate(leaves, start=root + 1):
DT.add_edge(bag_to_int[u], vi)
bag[vi] = Set()
# P3: Ensure that each vertex of DT has at most 2 children.
# If b has k > 2 children (c1, c2, ..., c_k). We disconnect (c1, ... ck-1)
# from b, introduce k - 2 new vertices (b1, b2, .., bk-2), make ci the
# children of bi for 1 <= i <= k-2, make ck-1 the second children of bk-2,
# make bi the second children of b_i-1, and finally make b1 the second
# children of b. Each vertex bi has the same bag has b.
for ui in list(DT): # the list(..) is needed since we modify DT
if DT.out_degree(ui) > 2:
children = DT.neighbors_out(ui)
children.pop() # one vertex remains a child of ui
DT.delete_edges((ui, vi) for v in children)
new_vertices = [DT.add_vertex() for _ in range(len(children) - 1)]
DT.add_edge(ui, new_vertices[0])
DT.add_path(new_vertices)
DT.add_edges(zip(new_vertices, children))
DT.add_edge(new_vertices[-1], children[-1])
bag.update((vi, bag[ui]) for vi in new_vertices)
# P4: If b has 2 children c1 and c2, then bag[b] == bag[c1] == bag[c2]
for ui in list(DT):
if DT.out_degree(ui) < 2:
continue
for vi in DT.neighbor_out_iterator(ui):
if bag[ui] != bag[vi]:
DT.delete_edge(ui, vi)
wi = DT.add_vertex()
DT.add_path([ui, wi, vi])
bag[wi] = bag[ui]
# P5: if b has a single child c, then one of the following conditions holds:
# (i) bag[c] is a subset of bag[b] and |bag[b]| == |bag[c]| + 1
# (ii) bag[b] is a subset of bag[c] and |bag[c]| == |bag[b]| + 1
def add_path_of_introduce(ui, vi):
"""
Replace arc (ui, vi) by a path of introduce vertices.
"""
if len(bag[ui]) + 1 == len(bag[vi]):
return
diff = list(bag[vi] - bag[ui])
diff.pop() # when all vertices are added, we are on vi
xi = ui
for w in diff:
wi = DT.add_vertex()
bag[wi] = bag[xi].union(Set((w,)))
DT.add_edge(xi, wi)
xi = wi
DT.add_edge(xi, vi)
DT.delete_edge(ui, vi)
def add_path_of_forget(ui, vi):
"""
Replace arc (ui, vi) by a path of forget vertices.
"""
if len(bag[vi]) + 1 == len(bag[ui]):
return
diff = list(bag[ui] - bag[vi])
diff.pop() # when all vertices are removed, we are on vi
xi = ui
for w in diff:
wi = DT.add_vertex()
bag[wi] = bag[xi] - Set((w,))
DT.add_edge(xi, wi)
xi = wi
DT.add_edge(xi, vi)
DT.delete_edge(ui, vi)
for ui in list(DT):
if DT.out_degree(ui) != 1:
continue
vi = next(DT.neighbor_out_iterator(ui))
bag_ui, bag_vi = bag[ui], bag[vi]
if bag_ui == bag_vi:
# We can merge the vertices
if DT.in_degree(ui) == 1:
parent = next(DT.neighbor_in_iterator(ui))
DT.add_edge(parent, vi)
else:
root = vi
DT.delete_vertex(ui)
elif bag_ui.issubset(bag_vi):
add_path_of_introduce(ui, vi)
elif bag_vi.issubset(bag_ui):
add_path_of_forget(ui, vi)
else:
# We must first forget some nodes and then introduce new nodes
wi = DT.add_vertex()
bag[wi] = bag[ui] & bag[vi]
DT.add_path([ui, wi, vi])
DT.delete_edge(ui, vi)
add_path_of_forget(ui, wi)
add_path_of_introduce(wi, vi)
# We now return the result
nice = Graph(DT, name=name)
nice.relabel(inplace=True,
perm={u: (i, bag[u]) for i, u in enumerate(nice.breadth_first_search(start=root))})
return nice |
@dcoudert Thank you for the reviews and suggestions. I didn't think you would review and comment until I add the label |
You should add examples for which we can check that the solution is what we expect. |
Do intend to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be you can add parameter nice
to method treewidth
in order to return a nice tree decomposition ?
I agree with your suggestion. Just added. For some reason |
This is certainly due to the use of clique separators. Make sure to transform the result before returning the result. |
I have uploaded the latest code. I don't think this is the case. The following was run with the latest code. As you can see on line 786, I have The nice tree decomp of the Petersen graph should have 28 nodes.
|
You forgot to pass the parameter to a call diff --git a/src/sage/graphs/graph_decompositions/tree_decomposition.pyx b/src/sage/graphs/graph_decompositions/tree_decomposition.pyx
index 8ce5c433c9..f18f419c0c 100644
--- a/src/sage/graphs/graph_decompositions/tree_decomposition.pyx
+++ b/src/sage/graphs/graph_decompositions/tree_decomposition.pyx
@@ -702,7 +702,7 @@ def treewidth(g, k=None, kmin=None, certificate=False, algorithm=None, nice=Fals
# Forcing k to be defined
if k is None:
for i in range(max(kmin, g.clique_number() - 1, min(g.degree())), g.order()):
- ans = g.treewidth(algorithm=algorithm, k=i, certificate=certificate)
+ ans = g.treewidth(algorithm=algorithm, k=i, certificate=certificate, nice=nice)
if ans:
return ans if certificate else i |
You are right; I thought it was caused by line 693. |
b78a77c
to
629e7a8
Compare
I am thinking about testing the changes with So I was wondering if you have any suggestions? Also, one of the TO-DO items is |
…osition`; Make example correct
bc4568e
to
a489f3c
Compare
@dcoudert I am not sure if this is the right place to ask, if not, I will either email or resort to other channel(s): As continuation of this PR, I am working on some functionality related to homomorphism counting. I am aware that Sage already has It seems that this function was/is implemented with some LP solver. Would it be possible to extend it to support returning all homomorphisms? |
It's better to open a specific issue to discuss that. It is certainly possible. |
Can you try to clean this PR. it has conflicts. |
Done: #36717 |
Documentation preview for this PR (built with commit 2df952d; changes) is ready! 🎉 |
…ling <!-- ^^^^^ 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 sagemath#1234" use "Introduce new method to calculate 1+1" --> <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If this PR resolves an open issue, please link to it here. For example "Fixes sagemath#12345". --> <!-- If your change requires a documentation PR, please link it appropriately. --> This PR intends to add two new functions: 1. `make_nice_tree_decomposition` takes a graph and its tree decomposition. It returns a *nice* tree decomposition. 2. `label_nice_tree_decomposition` takes a tree decomposition, assumed to be nice. It returns the same result, with vertices labelled accordingly. ### 📝 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. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - sagemath#12345: short description why this is a dependency - sagemath#34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> URL: sagemath#36504 Reported by: Jing Guo Reviewer(s): David Coudert, Jing Guo
This is causing a test failure on Arch:
|
@antonio-rojas There's indeed a bug in this code, I have fixed it in #36846. Does the new PR fix your issue? |
No, I'm getting the exact same failure with #36846 |
This PR intends to add two new functions:
make_nice_tree_decomposition
takes a graph and its tree decomposition. It returns a nice tree decomposition.label_nice_tree_decomposition
takes a tree decomposition, assumed to be nice. It returns the same result, with vertices labelled accordingly.📝 Checklist
⌛ Dependencies