ClosureSystem.java

package org.thegalactic.lattice;

/*
 * ClosureSystem.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.io.IOException;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import org.thegalactic.dgraph.DAGraph;
import org.thegalactic.dgraph.ConcreteDGraph;
import org.thegalactic.dgraph.Node;
import org.thegalactic.util.ComparableSet;

/**
 * This class is an abstract class defining the common behavior of closure
 * systems, and specialy its closed set lattice generation.
 *
 * Both a context and an implicational system have properties of a closure
 * system, and therefore extend this class.
 *
 * A closure system is formaly defined by a set of indexed elements and a
 * closure operator (abstract methods {@link #getSet} and {@link #closure}).
 *
 * Abstract method {@link #save} also describe the common behavior of a closure
 * system.
 *
 * However, this abstract class provides both abstract and non abstract methods.
 *
 * Although abstract methods depends on data, and so have to be implemented by
 * each extended class, non abstract methods only used property of a closure
 * system. It is the case for methods {@link #nextClosure} (that computes the
 * next closure of the specified one according to the lectic order implemented
 * the well-known Wille algorithm) invoked by method {@link #allClosures} and
 * the main method {@link #closedSetLattice} (where lattice can be transitively
 * closed or reduced).
 *
 *
 * ![ClosureSystem](ClosureSystem.png)
 *
 * @uml ClosureSystem.png
 * !include resources/org/thegalactic/lattice/ClosureSystem.iuml
 *
 * hide members
 * show ClosureSystem members
 * class ClosureSystem #LightCyan
 * title ClosureSystem UML graph
 */
public abstract class ClosureSystem {

    /*
     * ------------- ABSTRACT METHODS ------------------
     */
    /**
     * Returns the set of elements of the closure system.
     *
     * @return the set of elements of the closure system
     */
    public abstract SortedSet<Comparable> getSet();

    /**
     * Returns the closure of the specified set.
     *
     * @param set The specified set
     *
     * @return The closure
     */
    public abstract TreeSet<Comparable> closure(TreeSet<Comparable> set);

    /**
     * Saves this component in a file which name is specified.
     *
     * @param file name of file
     *
     * @throws IOException When an IOException occurs
     */
    public abstract void save(String file) throws IOException;

    /*
     * ------------- IMPLEMENTED METHODS ------------------
     */
    /**
     * Returns the closed set lattice of this component.
     *
     * A true value of the boolean `diagram` indicates that the Hasse diagramm
     * of the lattice is computed (i.e. it is transitively reduced), whereas a
     * false value indicates that the lattice is transitively closed
     *
     * A transitively reduced lattice is generated by the static method
     * `ConceptLattice diagramLattice (ClosureSystem init)` that implements an
     * adaptation of Bordat's algorithm. This adaptation computes the dependance
     * graph while the lattice is generated, with the same complexity.
     *
     * A transitively closed lattice is generated bye well-known Next Closure
     * algorithm. In this case, the dependance graph of the lattice isn't
     * computed.
     *
     * @param diagram a boolean indicating if the Hasse diagramm of the lattice
     *                is computed or not.
     *
     * @return The concept lattice
     */
    public ConceptLattice closedSetLattice(boolean diagram) {
        if (diagram) {
            return ConceptLattice.diagramLattice(this);
        } else {
            return ConceptLattice.completeLattice(this);
        }
    }

    /**
     * Returns the lattice of this component.
     *
     * @return The concept lattice
     */
    public ConceptLattice lattice() {
        return this.closedSetLattice(true);
    }

    /**
     * Returns all the closed sets of the specified closure system (that can be
     * an IS or a context).
     *
     * Closed sets are generated in lecticaly order, with the emptyset's closure
     * as first closed set, using the Ganter's Next Closure algorithm.
     *
     * Therefore, closed sets have to be comparable using `ComparableSet` class.
     * This treatment is performed in O(cCl|S|^3) where S is the initial set of
     * elements, c is the number of closed sets that could be exponential in the
     * worst case, and Cl is the closure computation complexity.
     *
     * @return all the closeds set in the lectically order.
     */
    public Vector<Concept> allClosures() {
        Vector<Concept> allclosure = new Vector<Concept>();
        // first closure: closure of the empty set
        allclosure.add(new Concept(this.closure(new ComparableSet()), false));
        Concept cl = allclosure.firstElement();
        // next closures in lectically order
        boolean continu = true;
        do {
            cl = this.nextClosure(cl);
            if (allclosure.contains(cl)) {
                continu = false;
            } else {
                allclosure.add(cl);
            }
        } while (continu);

        return allclosure;
    }

    /**
     * Returns the lecticaly next closed set of the specified one.
     *
     * This treatment is an implementation of the best knowm algorithm of Wille
     * whose complexity is in O(Cl|S|^2), where S is the initial set of
     * elements, and Cl is the closure computation complexity.
     *
     * @param cl a concept
     *
     * @return the lecticaly next closed set
     */
    public Concept nextClosure(Concept cl) {
        TreeSet<Comparable> set = new TreeSet(this.getSet());
        boolean success = false;
        TreeSet setA = new TreeSet(cl.getSetA());
        Comparable ni = set.last();
        do {
            ni = (Comparable) set.last();
            set.remove(ni);
            if (!setA.contains(ni)) {
                setA.add(ni);
                TreeSet setB = this.closure(setA);
                setB.removeAll(setA);
                if (setB.isEmpty() || ((Comparable) setB.first()).compareTo(ni) >= 1) {
                    setA = this.closure(setA);
                    success = true;
                } else {
                    setA.remove(ni);
                }
            } else {
                setA.remove(ni);
            }
        } while (!success && ni.compareTo(this.getSet().first()) >= 1);
        return new Concept(setA, false);
    }

    /**
     * Returns the precedence graph of this component.
     *
     * Nodes of the graph are elements of this component. There is an edge from
     * element a to element b when a belongs to the closure of b.
     *
     * The rule a -> a isn't added to the precedence graph
     *
     * When precedence graph is acyclic, then this component is a reduced one.
     *
     * @return the precedence graph
     */
    public ConcreteDGraph<Comparable, ?> precedenceGraph() {
        // compute a TreeMap of closures for each element of the component
        TreeMap<Comparable, TreeSet<Comparable>> closures = new TreeMap<Comparable, TreeSet<Comparable>>();
        for (Comparable x : this.getSet()) {
            ComparableSet setX = new ComparableSet();
            setX.add(x);
            closures.put(x, this.closure(setX));
        }
        // nodes of the graph are elements
        ConcreteDGraph<Comparable, ?> prec = new ConcreteDGraph<Comparable, Object>();
        TreeMap<Comparable, Node> nodeCreated = new TreeMap<Comparable, Node>();
        for (Comparable x : this.getSet()) {
            Node node = new Node(x);
            prec.addNode(node);
            nodeCreated.put(x, node);
        }
        // edges of the graph are closures containments
        for (Comparable source : this.getSet()) {
            for (Comparable target : this.getSet()) {
                if (!source.equals(target)) {
                    // check if source belongs to the closure of target
                    if (closures.get(target).contains(source)) {
                        prec.addEdge(nodeCreated.get(source), nodeCreated.get(target));
                    }
                }
            }
        }
        return prec;
    }

    /**
     * This function returns all reducible elements.
     *
     * A reducible elements is equivalent by closure to one or more other
     * attributes. Reducible elements are computed using the precedence graph of
     * the closure system. Complexity is in O()
     *
     * @return The map of reductible attributes with their equivalent attributes
     */
    public TreeMap<Object, TreeSet> getReducibleElements() {
        // If you can't remove nodes, put them in the rubbish bin ...
        TreeSet<Node> rubbishBin = new TreeSet<Node>();
        // Initialise a map Red of reducible attributes
        TreeMap<Object, TreeSet> red = new TreeMap();
        // Initialise the precedence graph G of the closure system
        ConcreteDGraph<Comparable, ?> graph = this.precedenceGraph();
        // First, compute each group of equivalent attributes
        // This group will be a strongly connected component on the graph.
        // Then, only one element of each group is skipped, others will be deleted.
        DAGraph<SortedSet<Node<Comparable>>, ?> cfc = graph.getStronglyConnectedComponent();
        for (Node<SortedSet<Node<Comparable>>> node : cfc.getNodes()) {
            // Get list of node of this component
            SortedSet<Node<Comparable>> sCC = node.getContent();
            if (sCC.size() > 1) {
                Node<?> y = sCC.first();
                TreeSet yClass = new TreeSet();
                yClass.add(y.getContent());
                for (Node x : sCC) {
                    if (!x.getContent().equals(y.getContent())) {
                        rubbishBin.add(x); // instead of : graph.removeNode(x);
                        red.put(x.getContent(), yClass);
                    }
                }
            }
        }
        // Next, check if an attribute is equivalent to emptyset
        // i.e. its closure is equal to emptyset closure
        TreeSet<Node> sinks = new TreeSet<Node>(graph.getSinks());
        sinks.removeAll(rubbishBin);
        if (sinks.size() == 1) {
            Node s = sinks.first();
            red.put(s.getContent(), new TreeSet());
            rubbishBin.add(s); // instead of : graph.removeNode(s);
        }
        // Finaly, checking a remaining attribute equivalent to its predecessors or not may reduce more attributes.
        // Check all remaining nodes of graph G
        TreeSet<Node> remainingNodes = new TreeSet<Node>();
        for (Node node : graph.getNodes()) {
            remainingNodes.add(node);
        }
        remainingNodes.removeAll(rubbishBin);
        for (Node x : remainingNodes) {
            // TODO getPredecessorNodes must return an iterator
            SortedSet<Node> predecessors = new TreeSet<Node>(graph.getPredecessorNodes(x));
            predecessors.removeAll(rubbishBin);
            if (predecessors.size() > 1) {
                // Create the closure of x
                TreeSet set = new TreeSet();
                set.add(x.getContent());
                TreeSet closureSet = this.closure(set);
                // Create the closure of predecessors
                TreeSet<Comparable> pred = new TreeSet<Comparable>();
                for (Node node : predecessors) {
                    pred.add((Comparable) node.getContent());
                }
                TreeSet<Comparable> closureP = this.closure(pred);
                // Check the equality of two closures
                if (closureSet.containsAll(closureP) && closureP.containsAll(closureSet)) {
                    red.put(x.getContent(), pred);
                }
            }
        }
        // Finally, return the list of reducible elements with their equivalent attributes.
        return red;
    }
}