DAGraph.java

package org.thegalactic.dgraph;

/*
 * DAGraph.java
 *
 * Copyright: 2010-2015 Karell Bertet, France
 * Copyright: 2015-2016 The Galactic Organization, France
 *
 * License: http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html CeCILL-B license
 *
 * This file is part of java-lattices.
 * You can redistribute it and/or modify it under the terms of the CeCILL-B license.
 */
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * This class extends the representation of a directed graph given by class
 * {@link ConcreteDGraph} for directed acyclic graph (DAG).
 *
 * The main property of a directed acyclic graph is to be a partially ordered
 * set (poset) when transitively closed, and a Hasse diagram when transitively
 * reduced.
 *
 * This property is not ensured for components of this class because it would
 * require a checking treatment over the graph whenever a new edge or node is
 * added. However, this property can be explicitely ckecked using method
 * {@link #isAcyclic}.
 *
 * This class provides methods implementing classical operation on a directed
 * acyclic graph: minorants and majorants, filter and ideal, transitive
 * reduction, ideal lattice, ...
 *
 * This class also provides a static method randomly generating a directed
 * acyclic graph, and a static method generating the graph of divisors.
 *
 * ![DAGraph](DAGraph.png)
 *
 * @param <N> Node content type
 * @param <E> Edge content type
 *
 * @todo Do we forbid to add an edge that breaks acyclic property by verifying
 * that the destination node has no successors? May be a DAGraph could contain a
 * ConcreteDGraph and export only interesting method by proxy
 *
 * @uml DAGraph.png
 * !include resources/org/thegalactic/dgraph/DAGraph.iuml
 * !include resources/org/thegalactic/dgraph/DGraph.iuml
 * !include resources/org/thegalactic/dgraph/Edge.iuml
 * !include resources/org/thegalactic/dgraph/Node.iuml
 *
 * hide members
 * show DAGraph members
 * class DAGraph #LightCyan
 * title DAGraph UML graph
 */
public class DAGraph<N, E> extends ConcreteDGraph<N, E> {

    /**
     * Constructs a new DAG with an empty set of node.
     */
    public DAGraph() {
        super();
    }

    /**
     * Constructs this component with the specified set of nodes, and empty
     * treemap of successors and predecessors.
     *
     * @param set the set of nodes
     */
    public DAGraph(final SortedSet<Node<N>> set) {
        super(set);
    }

    /**
     * Constructs this component as a copy of the specified directed graph.
     *
     * Acyclic property is checked for the specified DAG. When not verified,
     * this component is construct with the same set of nodes but with no edges.
     *
     * @param graph the ConcreteDGraph to be copied
     */
    public DAGraph(final ConcreteDGraph<N, E> graph) {
        super(graph);
        if (this.isAcyclic()) {
            this.reflexiveReduction();
        } else {
            TreeMap<Node<N>, TreeSet<Edge<N, E>>> successors = new TreeMap<Node<N>, TreeSet<Edge<N, E>>>();
            TreeMap<Node<N>, TreeSet<Edge<N, E>>> predecessors = new TreeMap<Node<N>, TreeSet<Edge<N, E>>>();
            for (Node<N> node : this.getNodes()) {
                successors.put(node, new TreeSet<Edge<N, E>>());
                predecessors.put(node, new TreeSet<Edge<N, E>>());
            }
            this.setSuccessors(successors);
            this.setPredecessors(predecessors);
        }
    }

    /*
     * --------------- DAG HANDLING METHODS ------------
     */
    /**
     * Returns the minimal elements of this component.
     *
     * @return the minimal elements
     */
    public final SortedSet<Node<N>> min() {
        return this.getSinks();
    }

    /**
     * Returns the maximal elements of this component.
     *
     * @return the maximal elements
     */
    public final SortedSet<Node<N>> max() {
        return this.getWells();
    }

    /**
     * Returns the set of majorants of the specified node.
     *
     * Majorants of a node are its successors in the transitive closure
     *
     * @param node the specified node
     *
     * @return the set of majorants
     */
    public final SortedSet<Node<N>> majorants(final Node<N> node) {
        final DAGraph graph = new DAGraph(this);
        graph.transitiveClosure();
        return graph.getSuccessorNodes(node);
    }

    /**
     * Returns the set of minorants of the specified node.
     *
     * Minorants of a node are its predecessors in the transitive closure
     *
     * @param node the specified node
     *
     * @return the set of minorants
     */
    public final SortedSet<Node<N>> minorants(final Node<N> node) {
        final DAGraph graph = new DAGraph(this);
        graph.transitiveClosure();
        return graph.getPredecessorNodes(node);
    }

    /**
     * Returns the subgraph induced by the specified node and its successors in
     * the transitive closure.
     *
     * @param node the specified node
     *
     * @return the subgraph
     */
    public final DAGraph<N, E> filter(final Node<N> node) {
        final TreeSet<Node<N>> set = new TreeSet<Node<N>>(this.majorants(node));
        set.add(node);
        return this.getSubgraphByNodes(set);
    }

    /**
     * Returns the subgraph induced by the specified node and its predecessors
     * in the transitive closure.
     *
     * @param node the specified node
     *
     * @return the subgraph
     */
    public final DAGraph<N, E> ideal(final Node<N> node) {
        final TreeSet<Node<N>> set = new TreeSet<Node<N>>(this.minorants(node));
        set.add(node);
        return this.getSubgraphByNodes(set);
    }

    /**
     * Returns the subgraph of this component induced by the specified set of
     * nodes.
     *
     * The subgraph only contains nodes of the specified set that also are in
     * this component.
     *
     * @param nodes The set of nodes
     *
     * @return The subgraph
     */
    @Override
    public DAGraph<N, E> getSubgraphByNodes(final Set<Node<N>> nodes) {
        ConcreteDGraph tmp = new ConcreteDGraph(this);
        tmp.transitiveClosure();
        ConcreteDGraph sub = tmp.getSubgraphByNodes(nodes);
        DAGraph sub2 = new DAGraph(sub);
        sub2.transitiveReduction();
        return sub2;
    }

    /*
     * --------------- DAG TREATMENT METHODS ------------
     */
    /**
     * Computes the transitive reduction of this component.
     *
     * The transitive reduction is not uniquely defined only when the acyclic
     * property is verified. In this case, it corresponds to the Hasse diagram
     * of the DAG.
     *
     * This method is an implementation of the Goralcikova-Koubeck algorithm
     * that can also compute the transitive closure. This tratment is performed
     * in O(n+nm_r+nm_c), where n corresponds to the number of nodes, m_r to the
     * numer of edges in the transitive closure, and m_r the number of edges in
     * the transitive reduction.
     *
     * @return the number of added edges
     */
    public int transitiveReduction() {

        // copy this component in a new DAG graph
        DAGraph<N, E> graph = new DAGraph<N, E>(this);
        graph.reflexiveReduction();
        // initalize this component with no edges
        this.setSuccessors(new TreeMap<Node<N>, TreeSet<Edge<N, E>>>());
        for (Node<N> node : this.getNodes()) {
            this.getSuccessors().put(node, new TreeSet<Edge<N, E>>());
        }
        this.setPredecessors(new TreeMap<Node<N>, TreeSet<Edge<N, E>>>());
        for (Node<N> node : this.getNodes()) {
            this.getPredecessors().put(node, new TreeSet<Edge<N, E>>());
        }
        int number = 0;
        // mark each node to false
        TreeMap<Node<N>, Boolean> mark = new TreeMap<Node<N>, Boolean>();
        for (Node<N> node : graph.getNodes()) {
            mark.put(node, Boolean.FALSE);
        }
        // treatment of nodes according to a topological sort
        List<Node<N>> sort = graph.topologicalSort();
        for (Node<N> x : sort) {
            TreeSet<Node<N>> set = new TreeSet<Node<N>>(graph.getSuccessorNodes(x));
            while (!set.isEmpty()) {
                // compute the smallest successor y of x according to the topological sort
                int i = 0;
                while (!set.contains(sort.get(i))) {
                    i++;
                }
                Node<N> y = sort.get(i);
                // when y is not not marked, x->y is a reduced edge
                if (y != null && !mark.get(y)) {
                    this.addEdge(x, y);
                    graph.addEdge(x, y);
                }
                for (Node<N> z : graph.getSuccessorNodes(y)) {
                    // treatment of z when not marked
                    if (!mark.get(z)) {
                        mark.put(z, Boolean.TRUE);
                        graph.addEdge(x, z);
                        number++;
                        set.add(z);
                    }
                }
                set.remove(y);
            }
            for (Node<N> y : graph.getSuccessorNodes(x)) {
                mark.put(y, Boolean.FALSE);
            }
        }
        return number;
    }

    /**
     * Computes the transitive closure of this component.
     *
     * This method overlaps the computation of the transitive closure for
     * directed graph in class {@link ConcreteDGraph} with an implementation of the
     * Goralcikova-Koubeck algorithm dedicated to acyclic directed graph. This
     * algorithm can also compute the transitive reduction of a directed acyclic
     * graph.
     *
     * This treatment is performed in O(n+nm_r+nm_c), where n corresponds to the
     * number of nodes, m_r to the numer of edges in the transitive closure, and
     * m_r the number of edges in the transitive reduction.
     *
     * @return the number of added edges
     */
    public int transitiveClosure() {
        int number = 0;
        // mark each node to false
        TreeMap<Node<N>, Boolean> mark = new TreeMap<Node<N>, Boolean>();
        for (Node<N> node : this.getNodes()) {
            mark.put(node, Boolean.FALSE);
        }
        // treatment of nodes according to a topological sort
        List<Node<N>> sort = this.topologicalSort();
        for (Node<N> x : sort) {
            TreeSet<Node<N>> set = new TreeSet<Node<N>>(this.getSuccessorNodes(x));
            while (!set.isEmpty()) {
                // compute the smallest successor y of x according to the topological sort
                int i = 0;
                do {
                    i++;
                } while (!set.contains(sort.get(i)));
                Node<N> y = sort.get(i);
                for (Node<N> z : this.getSuccessorNodes(y)) {
                    // treatment of z when not marked
                    if (!mark.get(z)) {
                        mark.put(z, Boolean.TRUE);
                        this.addEdge(x, z);
                        number++;
                        set.add(z);
                    }
                }
                set.remove(y);
            }
            for (Node<N> y : this.getSuccessorNodes(x)) {
                mark.put(y, Boolean.FALSE);
            }
        }
        return number;
    }
}