Raw File
GraphReduction.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Tamaki_Tree_Decomp.Data_Structures;
using static Tamaki_Tree_Decomp.Data_Structures.Graph;

namespace Tamaki_Tree_Decomp
{
    /// <summary>
    /// A class for simplifying a graph according to the rules in
    ///     H. L. Bodlaender, A. M. C. A. Koster, F. van den Eijkhof, and L. C. van der Gaag. Preprocessing for triangulation of probabilistic networks.
    ///     In J. Breese and D. Koller, editors, Proceedings of the 17th Conference on Uncertainty in Artificial Intelligence, pages 32–39,
    ///     San Francisco, 2001. Morgan Kaufmann.
    /// </summary>
    public class GraphReduction
    {
        int low; // TODO: heuristic for setting low is given in the paper
        Graph graph;
        ReindexationMapping reconstructionIndexationMapping;
        readonly List<(BitSet, BitSet)> reconstructionBagsToAppend;     // a list of (bag, parentbag) for reconstructing the correct tree decomposition // TODO: HashSet instead?

        readonly List<String> reconstructionBagsDebug;  // TODO: remove
        readonly int originalVertexCount;   // for debugging only

        private readonly bool verbose;

        public static bool reduce = true;   // this can be set to false in order to easily disable
                                            // the graph reduction for debugging reasons

        public GraphReduction(Graph graph, int low = 0, bool verbose = true)
        {
            this.low = low;
            this.graph = graph;
            reconstructionBagsToAppend = new List<(BitSet, BitSet)>();

            reconstructionBagsDebug = new List<string>();
            originalVertexCount = graph.vertexCount;

            this.verbose = verbose;
        }

        /// <summary>
        /// performs as many reductions as possible
        /// </summary>
        /// <param name="out_low">a lower bound for the tree width of the graph. It can be increased on basis of the reduction rules.</param>
        /// <returns>true, iff at least one reduction could be performed</returns>
        public bool Reduce(ref int out_low)
        {
            if (!reduce)
            {
                graph = null;   // remove reference to the graph so that its ressources can be freed
                return false;
            }

            bool reduced = false;
            while (SimplicialVertexRule() || AlmostSimplicialVertexRule() || BuddyRule() || CubeRule())
            {
                reduced = true;
            }

            graph.Reduce(out reconstructionIndexationMapping);

            graph = null;   // remove reference to the graph so that its ressources can be freed
            out_low = low;
            return reduced;
        }

        /// <summary>
        /// tries to apply the simplicial vertex rule to the graph once
        /// TODO: perhaps pass an additional vertex parameter where to start and "wrap" the first loop around. Might be more efficient
        /// </summary>
        /// <returns>true iff a reduction could be performed</returns>
        public bool SimplicialVertexRule()
        {
            bool isReduced = false;

            // loop over each vertex ...
            for (int i = 0; i < graph.vertexCount && !isReduced; i++)
            {
                // ... that is not yet removed ...
                if (graph.notRemovedVertices[i])
                {
                    // ... and check if its neighbors form a clique
                    bool neighborsFormClique = true;
                    for (int j = 0; j < graph.adjacencyList[i].Count && neighborsFormClique; j++)
                    {
                        int u = graph.adjacencyList[i][j];
                        for (int k = j + 1; k < graph.adjacencyList[i].Count; k++)
                        {
                            int v = graph.adjacencyList[i][k];
                            if (!graph.openNeighborhood[u][v])
                            {
                                neighborsFormClique = false;
                                break;
                            }
                        }
                    }
                    if (neighborsFormClique)
                    {
                        isReduced = true;

                        graph.Remove(i);

                        if (graph.adjacencyList[i].Count > low)
                        {
                            low = graph.adjacencyList[i].Count;
                        }

                        // remember bag for reconstruction
                        BitSet bag = new BitSet(graph.openNeighborhood[i]);
                        bag[i] = true;
                        reconstructionBagsToAppend.Add((bag, new BitSet(graph.openNeighborhood[i])));
                        reconstructionBagsDebug.Add("simplicial");
                    }
                }
            }
            return isReduced;
        }

        /// <summary>
        /// tries to apply the almost simplicial vertex rule to the graph once
        /// TODO: perhaps pass an additional vertex parameter where to start and "wrap" the first loop around. Might be more efficient
        /// </summary>
        /// <returns>true iff a reduction could be performed</returns>
        public bool AlmostSimplicialVertexRule()
        {
            bool isReduced = false;

            // loop over each vertex ...
            for (int i = 0; i < graph.vertexCount && !isReduced; i++)
            {
                // ... that is not yet removed and whose degree is smaller than low ...
                if (graph.notRemovedVertices[i] && low >= graph.adjacencyList[i].Count)
                {
                    // ... and check if all of its neighbors except one form a clique
                    bool neighborsFormAlmostClique = true;
                    int edgeUNotIn = -1;
                    int edgeVNotIn = -1;
                    int vertexNotIn = -1;
                    for (int j = 0; j < graph.adjacencyList[i].Count && neighborsFormAlmostClique; j++)
                    {
                        int u = graph.adjacencyList[i][j];
                        for (int k = j + 1; k < graph.adjacencyList[i].Count; k++)
                        {
                            int v = graph.adjacencyList[i][k];
                            if (!graph.openNeighborhood[u][v])
                            {
                                if (vertexNotIn == u || vertexNotIn == v)
                                {
                                    continue;
                                }
                                if (edgeUNotIn == -1)
                                {
                                    edgeUNotIn = u;
                                    edgeVNotIn = v;
                                    continue;
                                }
                                if (vertexNotIn == -1 && (edgeUNotIn == u || edgeVNotIn == u))
                                {
                                    vertexNotIn = u;
                                    continue;
                                }
                                if (vertexNotIn == -1 && (edgeUNotIn == v || edgeVNotIn == v))
                                {
                                    vertexNotIn = v;
                                    continue;
                                }

                                neighborsFormAlmostClique = false;
                                break;
                            }
                        }
                    }
                    // remove i and make all of its neighbors a clique
                    if (neighborsFormAlmostClique)
                    {
                        isReduced = true;

                        graph.Remove(i);
                        graph.MakeIntoClique(graph.adjacencyList[i]);
                        
                        // remember bag for reconstruction
                        BitSet bag = new BitSet(graph.openNeighborhood[i]);
                        bag[i] = true;
                        reconstructionBagsToAppend.Add((bag, new BitSet(graph.openNeighborhood[i])));
                        reconstructionBagsDebug.Add("almost simplicial");
                    }
                }
            }

            return isReduced;
        }

        /// <summary>
        /// tries to apply the buddy rule to the graph once
        /// TODO: perhaps pass an additional vertex parameter where to start and "wrap" the first loop around. Might be more efficient
        /// </summary>
        /// <returns>true iff a reduction could be performed</returns>
        public bool BuddyRule()
        {
            // the rule can only be applied if low >= 3
            if (low < 3)
            {
                return false;
            }

            bool isReduced = false;

            // for each vertex i ...
            for (int i = 0; i < graph.vertexCount && !isReduced; i++)
            {
                // ... that is not yet removed and has three neighbors ...
                if (graph.notRemovedVertices[i] && graph.adjacencyList[i].Count == 3)
                {
                    List<int> neighbors = graph.adjacencyList[i];
                    // ... test if a neighbor's ...
                    for (int j = 0; j < 3; j++)
                    {
                        int neighbor = neighbors[j];
                        // ... neighbor ...
                        for (int k = 0; k < graph.adjacencyList[neighbor].Count; k++)
                        {
                            // ... is a buddy
                            int buddy = graph.adjacencyList[neighbor][k];
                            if (buddy != i && graph.openNeighborhood[i].Equals(graph.openNeighborhood[buddy]))
                            {
                                isReduced = true;

                                graph.Remove(i);
                                graph.Remove(buddy);

                                graph.MakeIntoClique(neighbors);                                

                                // remember bags for reconstruction
                                BitSet bag1 = new BitSet(graph.closedNeighborhood[i]);                                // TODO: all of them don't need to be copied (?)
                                reconstructionBagsToAppend.Add((bag1, new BitSet(graph.openNeighborhood[i])));   
                                BitSet bag2 = new BitSet(graph.closedNeighborhood[buddy]);
                                reconstructionBagsToAppend.Add((bag2, new BitSet(graph.openNeighborhood[buddy])));
                                reconstructionBagsDebug.Add("buddy 1");
                                reconstructionBagsDebug.Add("buddy 2");
                                break;
                            }
                        }
                        if (isReduced)
                        {
                            break;
                        }
                    }
                }
            }
            return isReduced;
        }

        /// <summary>
        /// tries to apply the buddy rule to the graph once
        /// TODO: perhaps pass an additional vertex parameter where to start and "wrap" the first loop around. Might be more efficient
        /// </summary>
        /// <returns>true iff a reduction could be performed</returns>
        public bool CubeRule()
        {
            bool isReduced = false;

            // for each vertex i ...
            for (int i = 0; i < graph.vertexCount && !isReduced; i++)
            {
                if (graph.notRemovedVertices[i] && graph.adjacencyList[i].Count == 3)
                {
                    List<int> neighbors = graph.adjacencyList[i];
                    if (graph.adjacencyList[neighbors[0]].Count == 3 && graph.adjacencyList[neighbors[1]].Count == 3 && graph.adjacencyList[neighbors[2]].Count == 3)
                    {
                        /*
                         *  idea: the conditions for the cube rule are fulfilled, iff the neighbors of the three neighbors are neighbors of
                         *  exactly two of those three neighbors (except i which is a three times neighbor).
                         *  So we use a dictionary to count for each u,v,w how many times it is the neighbor of a,b or c.
                         *  If always two times, then the cube rule can be applied
                         */
                        Dictionary<int, int> neighborsNeighborsCount = new Dictionary<int, int>(3);
                        for (int j = 0; j < 3; j++)
                        {
                            for (int k = 0; k < 3; k++)
                            {
                                int neighborsNeighbor = graph.adjacencyList[neighbors[j]][k];
                                if (neighborsNeighbor != i)
                                {
                                    if (neighborsNeighborsCount.ContainsKey(neighborsNeighbor))
                                    {
                                        neighborsNeighborsCount[neighborsNeighbor]++;
                                    }
                                    else
                                    {
                                        neighborsNeighborsCount.Add(neighborsNeighbor, 1);
                                    }
                                }
                            }
                        }
                        bool isCube = true;
                        List<int> uvw = new List<int>();
                        foreach (KeyValuePair<int, int> keyvalue in neighborsNeighborsCount)
                        {
                            if (keyvalue.Value != 2)
                            {
                                isCube = false;
                                break;
                            }
                            else
                            {
                                uvw.Add(keyvalue.Key);
                            }
                        }
                        if (isCube)
                        {
                            Debug.Assert(uvw.Count == 3);
                            isReduced = true;

                            // update low
                            if (low < 3)
                            {
                                low = 3;
                            }

                            graph.MakeIntoClique(uvw);

                            // remember bags for reconstruction
                            BitSet appendTo = new BitSet(graph.vertexCount);
                            appendTo[uvw[0]] = true;
                            appendTo[uvw[1]] = true;
                            appendTo[uvw[2]] = true;
                            BitSet append_center = new BitSet(appendTo);
                            append_center[i] = true;
                            reconstructionBagsToAppend.Add((append_center, appendTo));
                            reconstructionBagsDebug.Add("cube i");

                            for (int j = 0; j < 3; j++)
                            {
                                BitSet append_corner = new BitSet(graph.closedNeighborhood[neighbors[j]]);
                                reconstructionBagsToAppend.Add((append_corner, append_center));
                                reconstructionBagsDebug.Add("cube neighbor " + j);
                            }

                            graph.Remove(i);
                            graph.Remove(neighbors[0]);
                            graph.Remove(neighbors[1]);
                            graph.Remove(neighbors[2]);
                        }
                    }
                }
            }

            return isReduced;
        }
        
        /// <summary>
        /// turns a tree decomposition of the reduced graph into a tree decomposition of the original input graph
        /// </summary>
        /// <param name="td">the tree decomposition of the reduced graph</param>
        public void RebuildTreeDecomposition(ref PTD td)
        {
            // return if there is no bag to append
            if (reconstructionBagsToAppend.Count == 0)
            {
                return;
            }

            td.Reindex(reconstructionIndexationMapping);

            // if graph has been completely reduced, take the last bag and use it as the root
            if (td == null || td.Bag.IsEmpty())
            {
                int lastIndex = reconstructionBagsToAppend.Count - 1;
                td = new PTD(reconstructionBagsToAppend[lastIndex].Item1);
                reconstructionBagsToAppend.RemoveAt(lastIndex);
            }

            // put all bags in the tree into a list
            Stack<PTD> nodeStack = new Stack<PTD>();
            nodeStack.Push(td);
            List<PTD> reconstructedNodes = new List<PTD>();
            while (nodeStack.Count > 0)
            {
                PTD currentNode = nodeStack.Pop();                
                reconstructedNodes.Add(currentNode);

                // push children onto stack
                for (int i = 0; i < currentNode.children.Count; i++)
                {
                    nodeStack.Push(currentNode.children[i]);
                }
            }

            // add the bags with the vertices that have been removed during reduction
            while (reconstructionBagsToAppend.Count > 0)
            {
                bool hasChanged = false;
                for (int i = 0; i < reconstructionBagsToAppend.Count; i++)
                {
                    (BitSet child, BitSet parent) = reconstructionBagsToAppend[i];
                    foreach(PTD node in reconstructedNodes)     // TODO: bad loop for searching parent bags,
                                                                //       could use a better data structure
                    {
                        if (node.Bag.IsSupersetOf(parent))
                        {
                            PTD childNode = new PTD(child);
                            node.children.Add(childNode);
                            reconstructedNodes.Add(childNode);
                            reconstructionBagsToAppend.RemoveAt(i);
                            reconstructionBagsDebug.RemoveAt(i);
                            i--;
                            hasChanged = true;
                            break;
                        }
                    }
                }

                // debug stuff. TODO: remove eventually
                if (!hasChanged)
                {
                    ;
                    throw new Exception("tree decomposition could not be rebuilt");
                }
            }

            // --- make tree decomposition canonical again ---

            Dictionary<PTD, PTD> parents = new Dictionary<PTD, PTD> { [td] = null };

            // remove the nodes where the child is a subset of a node (this occurs
            //because the simplicial vertex rule is also applied to vertices in cliques)
            nodeStack.Push(td);
            while (nodeStack.Count > 0)
            {
                PTD currentNode = nodeStack.Pop();

                // consider all children
                for (int i = 0; i < currentNode.children.Count; i++)
                {
                    PTD child = currentNode.children[i];
                    // if child is a subset, remove the child and adopt grandchildren
                    if (currentNode.Bag.IsSupersetOf(child.Bag))
                    {
                        currentNode.children.RemoveAt(i);
                        i--;
                        currentNode.children.AddRange(child.children);
                    }
                    // else push the child onto the stack
                    else
                    {
                        nodeStack.Push(child);
                        parents[child] = currentNode;
                    }
                }
            }

            // remove the nodes where a node is the subset of its child (this occurs
            // because the simplicial vertex rule is also applied to vertices in cliques)

            // get nodes in post order
            Stack<PTD> postOrderHelperStack = new Stack<PTD>();
            postOrderHelperStack.Push(td);
            while (postOrderHelperStack.Count > 0)
            {
                PTD currentNode = postOrderHelperStack.Pop();
                for (int i = 0; i < currentNode.children.Count; i++)
                {
                    postOrderHelperStack.Push(currentNode.children[i]);
                }
                nodeStack.Push(currentNode);
            }

            // remove the nodes where a node is the subset of its child
            while (nodeStack.Count > 0)
            {
                PTD currentNode = nodeStack.Pop();
                for (int i = 0; i < currentNode.children.Count; i++)
                {

                    PTD child = currentNode.children[i];
                    if (child.Bag.IsSupersetOf(currentNode.Bag))
                    {
                        PTD parent = parents[currentNode];
                        if (parent != null)     // if current node is not the root, attach child to its grandparent
                        {
                            // replace the parent in the grandparent's children list
                            parents[child] = parent;
                            int index = parent.children.IndexOf(currentNode);
                            parent.children[index] = child;

                            // add former parent's children to this node
                            currentNode.children.RemoveAt(i);
                            child.children.AddRange(currentNode.children);
                        }
                        else                    // if current node is the root, make the child the root instead
                        {
                            parents[child] = null;
                            td.children.RemoveAt(i);
                            child.children.AddRange(td.children);
                            td = child;
                        }
                        break;
                    }
                }
            }

            CheckVertexCover(td);
        }

        [Conditional("DEBUG")]
        private void CheckVertexCover(PTD td)
        {
            BitSet covered = new BitSet(originalVertexCount);

            Stack<PTD> nodeStack = new Stack<PTD>();
            nodeStack.Push(td);

            while(nodeStack.Count > 0)
            {
                PTD current = nodeStack.Pop();
                covered.UnionWith(current.Bag);
                foreach(PTD child in current.children)
                {
                    nodeStack.Push(child);
                }
            }

            Debug.Assert(covered.Equals(BitSet.All(originalVertexCount)));
        }
    }
}
back to top