-
Notifications
You must be signed in to change notification settings - Fork 821
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
Nice tree decomposition for chordal graph #616
base: master
Are you sure you want to change the base?
Changes from 1 commit
0544d8e
68632d5
623652d
6f9d6f9
b39e28d
53f8dff
1251473
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
/* | ||
* (C) Copyright 2018-2018, by Ira Justus Fesefeldt and Contributors. | ||
* | ||
* JGraphT : a free Java graph-theory library | ||
* | ||
* This program and the accompanying materials are dual-licensed under | ||
* either | ||
* | ||
* (a) the terms of the GNU Lesser General Public License version 2.1 | ||
* as published by the Free Software Foundation, or (at your option) any | ||
* later version. | ||
* | ||
* or (per the licensee's choosing) | ||
* | ||
* (b) the terms of the Eclipse Public License v1.0 as published by | ||
* the Eclipse Foundation. | ||
*/ | ||
package org.jgrapht.alg.decomposition; | ||
|
||
import java.util.*; | ||
|
||
import org.jgrapht.*; | ||
import org.jgrapht.alg.cycle.*; | ||
|
||
/** | ||
* A builder for a nice decomposition for chordal graphs. See {@link NiceDecompositionBuilder} for | ||
* an explanation of nice decomposition. | ||
* <p> | ||
* This builder uses the perfect elimination order from {@link ChordalityInspector} to iterate over | ||
* the graph. For every node it generates a node for the predecessors of the current node according | ||
* to the perfect elimination order and builds a path to such a node from the node where the | ||
* greatest predecessor was introduced. | ||
* <p> | ||
* The complexity of this algorithm is in $\mathcal{O}(|V|(|V|+|E|))$.<br> | ||
* Consider the every node in the nice tree decomposition: There are exactly $|V|$ many forget | ||
* nodes. There are at most $2|V|$ additionally nodes because of a join nodes. Every join node | ||
* creates one additional path from root to a leaf, every such path can contain for every vertex at | ||
* most one introduce node, which yields $|V|^2$ introduce nodes. Now considering the bags of the | ||
* introduce nodes. On a path from root to a leaf we have at most one introduce node for every | ||
* introduced vertex. Since this introduced vertex is part of the clique of the least ancestor | ||
* forget node, the corresponding bag of the introduce node is smaller than the set of neighbors of | ||
* this vertex. Thus the time complexity is in $\mathcal{O}(|V|(|V|+|E|))$. | ||
* <p> | ||
* This is a non-recursive adaption for nice tree decomposition of algorithm 2 from here: <br> | ||
* Hans L. Bodlaender, Arie M.C.A. Koster, Treewidth computations I. Upper bounds, Information and | ||
* Computation, Volume 208, Issue 3, 2010, Pages 259-275, | ||
* | ||
* @author Ira Justus Fesefeldt (PhoenixIra) | ||
* @author Timofey Chudakov | ||
* | ||
* @since June 2018 | ||
* | ||
* @param <V> the vertex type of the graph | ||
* @param <E> the edge type of the graph | ||
*/ | ||
public class ChordalNiceDecompositionBuilder<V, E> | ||
extends | ||
NiceDecompositionBuilder<V> | ||
{ | ||
// the chordal graph | ||
Graph<V, E> graph; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why the fields aren't private? |
||
|
||
// the perfect elimination order of graph | ||
List<V> perfectOrder; | ||
|
||
// another representation of the perfect elimination order of graph | ||
Map<V, Integer> vertexInOrder; | ||
|
||
/** | ||
* Factory method for the nice decomposition builder of chordal graphs. Returns null, if the | ||
* graph is not chordal. | ||
* | ||
* @param <V> the vertex type of graph | ||
* @param <E> the edge type of graph | ||
* @param graph the chordal graph for which a decomposition should be created | ||
* @return a nice decomposition builder for the graph if the graph was chordal, else null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are going with @Toptachamann, which I believe is a good idea for this class, don't forget to add |
||
*/ | ||
public static <V, E> ChordalNiceDecompositionBuilder<V, E> create(Graph<V, E> graph) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same thing could be achieved without static methods. You can throw an IllegalArgumentException if the graph is not chordal. |
||
ChordalityInspector<V, E> inspec = new ChordalityInspector<V, E>(graph); | ||
if (!inspec.isChordal()) | ||
return null; | ||
ChordalNiceDecompositionBuilder<V, E> builder = | ||
new ChordalNiceDecompositionBuilder<>(graph, inspec.getPerfectEliminationOrder()); | ||
builder.computeNiceDecomposition(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should mention that it doesn't simply return a builder but it also builds the nice decomposition. The user might expect it to be lazy which is not the case here. In the case of builders, it is a good idea to mention whether the builder actually does something when created. |
||
return builder; | ||
|
||
} | ||
|
||
/** | ||
* Factory method for the nice decomposition builder of chordal graphs. This method needs the | ||
* perfect elimination order. It does not check whether the order is correct. This method may | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extract this information in a separate line. "Note: This method does NOT check whether the order is correct". And have this is a separate paragraph or at least a separate line. "This method may behave arbitrarily if the perfect elimination order is incorrect" is not actually needed as it doesn't provide any extra information for the caller. |
||
* behave arbitrary if the perfect elimination order is incorrect. | ||
* | ||
* @param <V> the vertex type of graph | ||
* @param <E> the edge type of graph | ||
* @param graph the chordal graph for which a decomposition should be created | ||
* @param perfectEliminationOrder the perfect elimination order of the graph | ||
* @return a nice decomposition builder for the graph if the graph was chordal, else null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. {@code null} |
||
*/ | ||
public static <V, E> ChordalNiceDecompositionBuilder<V, E> create( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
Graph<V, E> graph, List<V> perfectEliminationOrder) | ||
{ | ||
ChordalNiceDecompositionBuilder<V, E> builder = | ||
new ChordalNiceDecompositionBuilder<>(graph, perfectEliminationOrder); | ||
builder.computeNiceDecomposition(); | ||
return builder; | ||
|
||
} | ||
|
||
/** | ||
* Creates a nice decomposition builder for chordal graphs. | ||
* | ||
* @param graph the chordal graph | ||
* @param perfectOrder the perfect elimination order of graph | ||
*/ | ||
private ChordalNiceDecompositionBuilder(Graph<V, E> graph, List<V> perfectOrder) | ||
{ | ||
super(); | ||
this.graph = graph; | ||
this.perfectOrder = perfectOrder; | ||
vertexInOrder = getVertexInOrder(); | ||
} | ||
|
||
/** | ||
* Computes the nice decomposition of the graph. We iterate over the perfect elimination order | ||
* of the chordal graph and try to add a node containing the predecessors regarding the | ||
* perfect elimination as a bag to the tree | ||
*/ | ||
private void computeNiceDecomposition() | ||
{ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra line. |
||
// map from vertices to decomposition-nodes where the decomposition-node has all its | ||
// predecessors | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's mention that here the decomposition nodes are forget nodes and they also contain corresponding vertices:
|
||
Map<V, Integer> forgetNodeMap = new HashMap<V, Integer>(graph.vertexSet().size()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can make use of type inference and have |
||
|
||
// set current node to the root | ||
Integer decompNode = getRoot(); | ||
|
||
// iterate over the perfect elimination order | ||
for (V vertex : perfectOrder) { | ||
|
||
// get the predecessors regarding the perfect elimination order | ||
Set<V> predecessors = getOrderPredecessors(vertexInOrder, vertex); | ||
|
||
// calculate nearest successors according to perfect elimination order | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nearest predecessor |
||
V lastVertex = null; | ||
for (V predecessor : predecessors) { | ||
if (lastVertex == null) | ||
lastVertex = predecessor; | ||
if (vertexInOrder.get(predecessor) > vertexInOrder.get(lastVertex)) | ||
lastVertex = predecessor; | ||
} | ||
|
||
// get node with clique of last vertex, else we use the last node | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the current vertex has no predecessors, then we can use any decomposition node to build a path to, can't we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. Thats why I implicial just use the last one here. |
||
if (lastVertex != null) | ||
decompNode = forgetNodeMap.get(lastVertex); | ||
|
||
// if this node is not a leaf node, create a join node | ||
if (Graphs.vertexHasSuccessors(decomposition, decompNode)) { | ||
decompNode = addJoin(decompNode).getFirst(); | ||
} | ||
|
||
// calculate vertices of nearest successor, which needs to be handled | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the nearest successor? |
||
Set<V> clique = new HashSet<V>(predecessors); | ||
clique.add(vertex); | ||
Set<V> toIntroduce = new HashSet<V>(decompositionMap.get(decompNode)); | ||
toIntroduce.removeAll(clique); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or simply have |
||
|
||
// first remove unnecessary nodes | ||
for (V introduce : toIntroduce) { | ||
decompNode = addIntroduce(introduce, decompNode); | ||
} | ||
// now add new node! | ||
decompNode = addForget(vertex, decompNode); | ||
forgetNodeMap.put(vertex, decompNode); | ||
|
||
} | ||
//finish all unfinished paths | ||
leafClosure(); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra line. |
||
} | ||
|
||
/** | ||
* Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in | ||
* {@code vertexOrder}. | ||
* | ||
* @param vertexOrder a list with vertices. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method doesn't have a |
||
* @return a mapping of vertices from {@code vertexOrder} to their indices in | ||
* {@code vertexOrder}. | ||
*/ | ||
private Map<V, Integer> getVertexInOrder() | ||
{ | ||
Map<V, Integer> vertexInOrder = new HashMap<>(perfectOrder.size()); | ||
int i = 0; | ||
for (V vertex : perfectOrder) { | ||
vertexInOrder.put(vertex, i++); | ||
} | ||
return vertexInOrder; | ||
} | ||
|
||
/** | ||
* Returns the predecessors of {@code vertex} in the perfect elimination order defined by | ||
* {@code map}. More precisely, returns those of {@code vertex}, whose mapped index in | ||
* {@code map} is less then the index of {@code vertex}. | ||
* | ||
* @param map defines the mapping of vertices in {@code graph} to their indices in order. | ||
* @param vertex the vertex whose predecessors in order are to be returned. | ||
* @return the predecessors of {@code vertex} in order defines by {@code map}. | ||
*/ | ||
private Set<V> getOrderPredecessors(Map<V, Integer> map, V vertex) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we don't need to constant time lookups, this can be optimized to return a List. Please, do the same thing with the original method in ChordalityInspector. |
||
{ | ||
Set<V> predecessors = new HashSet<>(); | ||
Integer vertexPosition = map.get(vertex); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int |
||
Set<E> edges = graph.edgesOf(vertex); | ||
for (E edge : edges) { | ||
V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); | ||
Integer destPosition = map.get(oppositeVertex); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int |
||
if (destPosition < vertexPosition) { | ||
predecessors.add(oppositeVertex); | ||
} | ||
} | ||
return predecessors; | ||
} | ||
} |
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.
Probably, exactly |V| - 1 forget nodes since for the root we do not add a forget node. And are there at most 2|V| - 2 auxiliary nodes produced by adding join nodes?
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.
Correct, yes. First one is due to changing the condition from |b(root)|=0 to |b(root)|=1, second one was just an overapproximation, but I changed both accordingly.