Lattice.java

package org.thegalactic.lattice;

/*
 * Lattice.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 org.thegalactic.rule.Rule;
import org.thegalactic.rule.ImplicationalSystem;
import org.thegalactic.rule.AssociationRule;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.SortedSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import org.thegalactic.util.ComparableSet;
import org.thegalactic.context.Context;
import org.thegalactic.dgraph.DAGraph;
import org.thegalactic.dgraph.ConcreteDGraph;
import org.thegalactic.dgraph.Edge;
import org.thegalactic.dgraph.Node;

/**
 * This class extends class {@link org.thegalactic.dgraph.DAGraph} to provide
 * specific methods to manipulate a lattice.
 *
 * A lattice is a directed acyclic graph
 * ({@link org.thegalactic.dgraph.DAGraph}) containing particular nodes denoted
 * join and meet\ (a dag is a lattice if and only if each pair of nodes admits a
 * join and a meet).
 *
 * Since checking the lattice property is very time-expensive, this property is
 * not ensured for components of this class. However, it can be explicitely
 * ckecked using method {@link #isLattice}.
 *
 * This class provides methods implementing classical operation on a lattice
 * issued from join and meet operation and irreducibles elements, and methods
 * that returns a condensed representation of a lattice.
 *
 * A well-known condensed representation of a lattice is its table, obtained by
 * method {@link #getTable}, where join-irreducibles are in column and
 * meet-irreducibles are in rown.
 *
 * Another condensed representation is its dependency graph obtained by method
 * {@link #getDependencyGraph}.
 *
 * The dependency graph is a directed graph where nodes are join-irreducibles,
 * edges corresponds to the dependency relation between two join-irreducibles
 * and are valuated by a family of subsets of irreducibles.
 *
 * The dependency graph encodes two other condensed representation of a lattice
 * that are its minimal generators and its canonical direct basis that can be
 * obtained from the dependency graph by methods {@link #getMinimalGenerators}
 * and {@link #getCanonicalDirectBasis}.
 *
 * ![Lattice](Lattice.png)
 *
 * @param <N> Node content type
 * @param <E> Edge content type
 *
 * @todo remove useless comments: Karell
 *
 * @uml Lattice.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
 * !include resources/org/thegalactic/lattice/Lattice.iuml
 *
 * hide members
 * show Lattice members
 * class Lattice #LightCyan
 * title Lattice UML graph
 */
public class Lattice<N, E> extends DAGraph<N, E> {

    /*
     * ------------- FIELDS ------------------
     */
    /**
     * The dependency graph of a lattice.
     *
     * Nodes are join irreducibles elements, and edges correspond to the
     * dependency relation of the lattice (j -> j' if and only if there exists a
     * node x in the lattice such that x not greather than j and j', and x v j'
     * > j), and edges are labeled with the smallest subsets X of
     * join-irreducibles such that the join of elements of X corresponds to the
     * node x of the lattice.
     */
    private ConcreteDGraph dependencyGraph = null;

    /*
     * ------------- CONSTRUCTORS ------------------
     */
    /**
     * Constructs this component with an empty set of nodes.
     */
    public Lattice() {
        super();
    }

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

    /**
     * Constructs this component as a copy of the specified lattice.
     *
     * Lattice property is checked for the specified lattice.
     *
     * When not verified, this component is construct with an empty set of
     * nodes.
     *
     * @param graph the Lattice to be copied
     */
    public Lattice(DAGraph<N, E> graph) {
        super(graph);
        if (!this.isAcyclic()) {
            this.setNodes(new TreeSet<Node<N>>());
            this.setSuccessors(new TreeMap<Node<N>, TreeSet<Edge<N, E>>>());
            this.setPredecessors(new TreeMap<Node<N>, TreeSet<Edge<N, E>>>());
        }
    }

    /*
     * ------------- LATTICE CHEKING METHODS ------------------
     */
    /**
     * Check if this component is a lattice.
     *
     * There exists several caracterizations of a lattice. The characterization
     * implemented is the following: A lattice is a DAG if there exists a meet
     * for each pair of node, and a unique maximal node.
     *
     * This treatment is performed in O(n^3), where n is the number of nodes.
     *
     * @return the truth value for this property
     */
    public boolean isLattice() {
        if (!this.isAcyclic()) {
            return false;
        }
        for (Node<N> x : this.getNodes()) {
            for (Node<N> y : this.getNodes()) {
                if (this.meet(x, y) == null) {
                    return false;
                }
            }
        }
        return this.max().size() == 1;
    }

    /**
     * Return true if this component is congruence normal.
     *
     * A lattice $L$ is in class CN (Congruence Normal) is there exists a
     * sequence $L_1,\ldots,L_p$ of lattices with $L_p=L$, together with a
     * sequence $C_1,\ldots,C_{p-1}$ such that $C_i$ is a convex of $L_i$ and
     * $L_{i+1}$ is obtained by doubling the convex $C_i$ in $L_i$.
     *
     * See {@link org.thegalactic.lattice.LatticeFactory} for the doubling
     * convex method which is not used here.
     *
     * This computation is done in O((|J|+|M|)^2|X|) from the transitive
     * reduction of L.
     *
     * This recognition algorithm was first written in : "Doubling convex serts
     * in lattices : characterizations and recognition algorithm", Bertet K.,
     * Caspard N. 2002.
     *
     * @return true if this component is congruence normal.
     */
    public boolean isCN() {
        TreeSet<Node<N>> joins = this.joinIrreducibles();
        TreeSet<Node<N>> meets = this.meetIrreducibles();
        ArrowRelation arrows = new ArrowRelation(this);
        Context dbl = arrows.getDoubleArrowTable();
        // steps are connected component of the double arrow table.
        ArrayList<Concept> steps = new ArrayList<Concept>();
        while (!joins.isEmpty()) {
            TreeSet<Comparable> setA = new TreeSet<Comparable>();
            TreeSet<Comparable> setB = new TreeSet<Comparable>();
            int cardA = setA.size();
            setA.add(joins.first());
            while (cardA != setA.size()) { // something added
                cardA = setA.size(); // update card
                for (Comparable c : setA) {
                    setB.addAll(dbl.getIntent(c));
                }
                for (Comparable c : setB) {
                    setA.addAll(dbl.getExtent(c));
                }
            }
            steps.add(new Concept(setA, setB));
            joins.removeAll(setA);
        }
        for (Concept c : steps) {
            if (c.getSetB().isEmpty()) { // to be verified :-)
                return false;
            }
            for (Comparable j : c.getSetA()) {
                for (Comparable m : c.getSetB()) {
                    if (arrows.getEdge((Node) j, (Node) m).getContent() != "UpDown"
                            && arrows.getEdge((Node) j, (Node) m).getContent() != "Circ") {
                        return false;
                    }
                }
            }
        }
        joins = this.joinIrreducibles();
        meets = this.meetIrreducibles();
        ConcreteDGraph phi = new ConcreteDGraph();
        for (Node<N> j : joins) {
            for (Node<N> m : meets) {
                int indJ = 0; // Search for the step containning j
                while (indJ < steps.size() && !steps.get(indJ).containsInA(j)) {
                    indJ++;
                }
                if (phi.getNodeByContent(indJ) == null && indJ != steps.size()) {
                    phi.addNode(new Node(indJ));
                }
                int indM = 0; // Search for the step containning m
                while (indM < steps.size() && !steps.get(indM).containsInB(m)) {
                    indM++;
                }
                if (phi.getNodeByContent(indM) == null && indM != steps.size()) {
                    phi.addNode(new Node(indM));
                }
                if (indM != steps.size() && indJ != steps.size()) {
                    if (arrows.getEdge((Node) j, (Node) m).getContent() == "Up") {
                        phi.addEdge(phi.getNodeByContent(indM), phi.getNodeByContent(indJ));
                    }
                    if (arrows.getEdge((Node) j, (Node) m).getContent() == "Down") {
                        phi.addEdge(phi.getNodeByContent(indJ), phi.getNodeByContent(indM));
                    }
                }
            }
        }
        return (phi.isAcyclic());
    }

    /**
     * Returns true if this component is an atomistic lattice.
     *
     * A lattice is atomistic if its join irreductibles are atoms (e.g.
     * successors of bottom)
     *
     * @return true if this component is atomistic.
     */
    public boolean isAtomistic() {
        TreeSet<Node<N>> join = this.joinIrreducibles();
        TreeSet<Node> atoms = new TreeSet<Node>(this.getSuccessorNodes(this.bottom()));
        return join.containsAll(atoms) && atoms.containsAll(join);
    }

    /**
     * Returns true if this component is an coatomistic lattice.
     *
     * A lattice is coatomistic if its mett irreductibles are coatoms (e.g.
     * predecessors of top)
     *
     * @return true if this component is coatomistic.
     */
    public boolean isCoAtomistic() {
        TreeSet<Node<N>> meet = this.meetIrreducibles();
        SortedSet<Node<N>> coatoms = this.getPredecessorNodes(this.top());
        return meet.containsAll(coatoms) && coatoms.containsAll(meet);
    }

    /*
     * --------------- LATTICE HANDLING METHODS ------------
     */
    /**
     * Returns the top of the lattice.
     *
     * @return the node which is at the top of the lattice or null if it is not
     *         unique
     */
    public Node<N> top() {
        TreeSet<Node<N>> max = new TreeSet<Node<N>>(this.max());
        if (max.size() == 1) {
            return max.first();
        }
        return null;
    }

    /**
     * Returns the bottom of the lattice.
     *
     * @return the node which is at the bottom of the lattice or null if it is
     *         not unique
     */
    public Node<N> bottom() {
        TreeSet<Node<N>> min = new TreeSet<Node<N>>(this.min());
        if (min.size() == 1) {
            return min.first();
        }
        return null;
    }

    /**
     * Returns the meet of the two specified nodes if it exists.
     *
     * @param x the first node
     * @param y the second node
     *
     * @return the node which is at the meet of the nodes or null if it does not
     *         exist
     *
     * @todo this.minorants should return an iterator
     */
    public Node<N> meet(Node<N> x, Node<N> y) {
        // TODO minorants should return an iterator
        SortedSet<Node<N>> xMinorants = new TreeSet<Node<N>>(this.minorants(x));
        xMinorants.add(x);

        SortedSet<Node<N>> yMinorants = new TreeSet<Node<N>>(this.minorants(y));
        yMinorants.add(y);

        xMinorants.retainAll(yMinorants);
        DAGraph<N, E> graph = this.getSubgraphByNodes(xMinorants);
        TreeSet<Node> meet = new TreeSet<Node>(graph.max());
        if (meet.size() == 1) {
            return meet.first();
        }
        return null;
    }

    /**
     * Returns the join of the two specified nodes if it exists.
     *
     * @param x the first node
     * @param y the second node
     *
     * @return the node which is at the join of the nodes or null if it does not
     *         exist
     *
     * @todo this.majorants should return an iterator
     */
    public Node<N> join(Node<N> x, Node<N> y) {
        // TODO this.majorants should return an iterator
        SortedSet<Node<N>> xMajorants = new TreeSet<Node<N>>(this.majorants(x));
        xMajorants.add(x);

        SortedSet<Node<N>> yMajorants = new TreeSet<Node<N>>(this.majorants(y));
        yMajorants.add(y);

        xMajorants.retainAll(yMajorants);
        DAGraph<N, E> graph = this.getSubgraphByNodes(xMajorants);
        TreeSet<Node> join = new TreeSet<Node>(graph.min());
        if (join.size() == 1) {
            return join.first();
        }
        return null;
    }

    /*
     * ------------- IRREDUCIBLES RELATIVE METHODS ------------------
     */
    /**
     * Returns the set of join irreducibles of this component.
     *
     * Join irreducibles are nodes with an unique immediate predecessor in the
     * transitive and reflexive reduction. This component is first reduced
     * reflexively and transitively.
     *
     * @return the set of join irreducibles of this component
     */
    public TreeSet<Node<N>> joinIrreducibles() {
        DAGraph<N, E> graph = new DAGraph(this);
        graph.reflexiveReduction();
        graph.transitiveReduction();
        TreeSet<Node<N>> set = new TreeSet();
        for (Node<N> node : graph.getNodes()) {
            if (graph.getPredecessorNodes(node).size() == 1) {
                set.add(node);
            }
        }
        return set;
    }

    /**
     * Returns the set of meet irreducibles of this component.
     *
     * Meet irreducibles are nodes with an unique immediate successor in the
     * transitive and reflexiv reduction. This component is first reduced
     * reflexively and transitively.
     *
     * @return the set of meet irreducibles of this component.
     */
    public TreeSet<Node<N>> meetIrreducibles() {
        DAGraph<N, E> graph = new DAGraph(this);
        graph.reflexiveReduction();
        graph.transitiveReduction();
        TreeSet<Node<N>> set = new TreeSet();
        for (Node<N> node : graph.getNodes()) {
            if (graph.getSuccessorNodes(node).size() == 1) {
                set.add(node);
            }
        }
        return set;
    }

    /**
     * Returns the set of join-irreducibles that are minorants of the specified
     * node.
     *
     * @param node a specified node
     *
     * @return the set of join-irreducibles thar are minorants of the specified
     *         node
     */
    public TreeSet<Node<N>> joinIrreducibles(Node<N> node) {
        TreeSet<Node<N>> join = new TreeSet<Node<N>>(this.joinIrreducibles());
        TreeSet<Node<N>> min = new TreeSet<Node<N>>(this.minorants(node));
        min.add(node);
        min.retainAll(join);
        return min;
    }

    /**
     * Returns the set of meet-irreducibles thar are majorants of the specified
     * node.
     *
     * @param node a specified node
     *
     * @return the set of meet-irreducibles thar are majorants of the specified
     *         node
     */
    public TreeSet<Node<N>> meetIrreducibles(Node<N> node) {
        TreeSet<Node<N>> meet = new TreeSet<Node<N>>(this.meetIrreducibles());
        TreeSet<Node<N>> maj = new TreeSet<Node<N>>(this.majorants(node));
        maj.retainAll(meet);
        return maj;
    }

    /**
     * Returns the subgraph induced by the join irreducibles nodes of this
     * component.
     *
     * @return the subgraph induced by the join irreducibles nodes of this
     *         component
     */
    public DAGraph<N, E> joinIrreduciblesSubgraph() {
        TreeSet<Node<N>> irr = this.joinIrreducibles();
        return this.getSubgraphByNodes(irr);
    }

    /**
     * Returns the subgraph induced by the meet irreducibles nodes of this
     * component.
     *
     * @return the subgraph induced by the meet irreducibles nodes of this
     *         component
     */
    public DAGraph<N, E> meetIrreduciblesSubgraph() {
        TreeSet<Node<N>> irr = this.meetIrreducibles();
        return this.getSubgraphByNodes(irr);
    }

    /**
     * Returns the subgraph induced by the irreducibles nodes of this component.
     *
     * @return the subgraph induced by the irreducibles nodes of this component
     */
    public DAGraph<N, E> irreduciblesSubgraph() {
        TreeSet<Node<N>> irr = this.meetIrreducibles();
        irr.addAll(this.joinIrreducibles());
        return this.getSubgraphByNodes(irr);
    }

    /**
     * Generates and returns the isomorphic closed set lattice defined on the
     * join irreducibles set.
     *
     * Each node of this component is replaced by a node containing its join
     * irreducibles predecessors stored in a closed set.
     *
     * @return the isomorphic closed set lattice defined on the join
     *         irreducibles set
     */
    public ConceptLattice joinClosure() {
        ConceptLattice csl = new ConceptLattice();
        // associates each node to a new closed set of join irreducibles
        TreeSet<Node<N>> join = this.joinIrreducibles();
        TreeMap<Node, Concept> closure = new TreeMap<Node, Concept>();
        Lattice<N, E> lattice = new Lattice(this);
        lattice.transitiveClosure();
        lattice.reflexiveClosure();
        for (Node<N> target : lattice.getNodes()) {
            ComparableSet jx = new ComparableSet();
            for (Node<N> source : lattice.getPredecessorNodes(target)) {
                if (join.contains(source)) {
                    jx.add(source.getContent());
                }
            }
            closure.put(target, new Concept(jx, false));
        }
        // addition of nodes
        for (Node<N> node : this.getNodes()) {
            csl.addNode(closure.get(node));
        }
        // addition of edges
        for (Edge ed : this.getEdges()) {
            csl.addEdge(closure.get(ed.getSource()), closure.get(ed.getTarget()));
        }
        return csl;
    }

    /**
     * Generates and returns the isomorphic closed set lattice defined on the
     * meet irreducibles set.
     *
     * Each node of this component is replaced by a node containing its meet
     * irreducibles successors stored in a closed set.
     *
     * @return the isomorphic closed set lattice defined on the meet
     *         irreducibles set
     */
    public ConceptLattice meetClosure() {
        ConceptLattice csl = new ConceptLattice();
        // associates each node to a new closed set of join irreducibles
        TreeSet<Node<N>> meet = this.meetIrreducibles();
        TreeMap<Node, Concept> closure = new TreeMap<Node, Concept>();
        Lattice<N, E> lattice = new Lattice(this);
        lattice.transitiveClosure();
        lattice.reflexiveClosure();
        for (Node<N> target : lattice.getNodes()) {
            ComparableSet mx = new ComparableSet();
            for (Node<N> source : lattice.getSuccessorNodes(target)) {
                if (meet.contains(source)) {
                    mx.add(source);
                }
            }
            closure.put(target, new Concept(false, mx));
        }
        // addition of nodes
        for (Node node : this.getNodes()) {
            csl.addNode(closure.get(node));
        }
        // addition of edges
        for (Edge ed : this.getEdges()) {
            csl.addEdge(closure.get(ed.getSource()), closure.get(ed.getTarget()));
        }
        return csl;
    }

    /**
     * Generates and returns the isomorphic concept lattice defined on the join
     * and meet irreducibles sets.
     *
     * Each node of this component is replaced by a node containing its meet
     * irreducibles successors and its join irreducibles predecessors stored in
     * a concept.
     *
     * @return the irreducible closure
     */
    public ConceptLattice irreducibleClosure() {
        ConceptLattice conceptLatice = new ConceptLattice();
        // associates each node to a new closed set of join irreducibles
        TreeSet<Node<N>> meet = this.meetIrreducibles();
        TreeSet<Node<N>> join = this.joinIrreducibles();
        TreeMap<Node, Concept> closure = new TreeMap<Node, Concept>();
        Lattice<N, E> lattice = new Lattice(this);
        lattice.transitiveClosure();
        lattice.reflexiveClosure();
        for (Node<N> target : lattice.getNodes()) {
            ComparableSet jx = new ComparableSet();
            for (Node<N> source : lattice.getPredecessorNodes(target)) {
                if (join.contains(source)) {
                    jx.add(source);
                }
            }
            ComparableSet mx = new ComparableSet();
            for (Node source : lattice.getSuccessorNodes(target)) {
                if (meet.contains(source)) {
                    mx.add(source);
                }
            }
            closure.put(target, new Concept(jx, mx));
        }
        // addition of nodes
        for (Node node : this.getNodes()) {
            conceptLatice.addNode(closure.get(node));
        }
        // addition of edges
        for (Edge ed : this.getEdges()) {
            conceptLatice.addEdge(closure.get(ed.getSource()), closure.get(ed.getTarget()));
        }
        return conceptLatice;
    }

    /**
     * Returns the smallest set of nodes of this component containing S such
     * that if a,b in JS then join(a,b) in JS.
     *
     * @param s set of nodes to be closed
     *
     * @return (JS) join-closure of s
     */
    public ComparableSet joinClosure(ComparableSet s) {
        // Algorithm is true because join is idempotent & commutative
        ComparableSet stack = new ComparableSet();
        stack.addAll(s); // Nodes to be explored
        ComparableSet result = new ComparableSet();
        while (!stack.isEmpty()) {
            Node c = (Node) stack.first();
            stack.remove(c);
            result.add(c);
            Iterator<Node> it = stack.iterator();
            ComparableSet newNodes = new ComparableSet(); // Node to be added. Must be done AFTER while
            while (it.hasNext()) {
                Node node = this.join(it.next(), c);
                if (!result.contains(node) && !stack.contains(node)) {
                    newNodes.add(node);
                }
            }
            stack.addAll(newNodes);
        }
        return result;
    }

    /**
     * Returns the smallest set of nodes of this component containing S such
     * that if a,b in MS then meet(a,b) in MS.
     *
     * @param s set of nodes to be closed
     *
     * @return (MS) meet-closure of s
     */
    public ComparableSet meetClosure(ComparableSet s) {
        // Algorithm is true because meet is idempotent & commutative
        ComparableSet stack = new ComparableSet();
        stack.addAll(s); // Nodes to be explored
        ComparableSet result = new ComparableSet();
        while (!stack.isEmpty()) {
            Node c = (Node) stack.first();
            stack.remove(c);
            result.add(c);
            Iterator<Node> it = stack.iterator();
            ComparableSet newNodes = new ComparableSet(); // Node to be added. Must be done AFTER while
            while (it.hasNext()) {
                Node node = this.meet(it.next(), c);
                if (!result.contains(node) && !stack.contains(node)) {
                    newNodes.add(node);
                }
            }
            stack.addAll(newNodes);
        }
        return result;
    }

    /**
     * Returns the smallest sublattice of this component containing s.
     *
     * @param s set of nodes to be closed.
     *
     * @return the smallest sublattice of this component containing s.
     */
    public ComparableSet fullClosure(ComparableSet s) {
        ComparableSet result = new ComparableSet();
        result.addAll(s);
        int present = result.size();
        int previous = 0;
        while (previous != present) {
            previous = present;
            result = this.joinClosure(result);
            result = this.meetClosure(result);
            present = result.size();
        }
        return result;
    }

    /**
     * Returns the list of all sets of nodes that generates all nodes. Both join
     * and meet operations are allowed and the sets are minimal for inclusion.
     *
     * @return : List of all hybridGenerators families.
     */
    public TreeSet<ComparableSet> hybridGenerators() {
        TreeSet<Node<N>> joinIrr = this.joinIrreducibles();
        TreeSet<Node<N>> meetIrr = this.meetIrreducibles();
        ComparableSet bothIrr = new ComparableSet();
        for (Node<N> node : joinIrr) {
            if (meetIrr.contains(node)) {
                bothIrr.add(node);
            }
        } // bothIrr contains nodes that are join and meet irreductibles.

        TreeSet<ComparableSet> generators = new TreeSet<ComparableSet>();
        // First point is that all minimal families have the same number of nodes.
        LinkedList<ComparableSet> list = new LinkedList<ComparableSet>(); // Family of sets to be examined
        list.add(bothIrr);
        while (!list.isEmpty()) {
            int test;
            if (generators.isEmpty()) {
                test = this.sizeNodes();
            } else {
                test = generators.first().size();
            }
            if (test < list.peek().size()) {
                // Elements are sorted by size, thus if the first is to large, all are.
                list.clear();
            } else {
                ComparableSet family = list.poll(); // Retrieve and remove head.
                if (this.fullClosure(family).size() == this.sizeNodes()) {
                    // This family generates l
                    generators.add(family);
                } else {
                    for (Node node : this.getNodes()) {
                        ComparableSet newFamily = new ComparableSet();
                        newFamily.addAll(family);
                        newFamily.add(node);
                        if (!list.contains(newFamily)) {
                            list.add(newFamily); // add at the end, bigger families
                        }
                    }
                }
            }
        }
        return generators;
    }

    /**
     * Returns the table of the lattice, composed of the join and meet
     * irreducibles nodes.
     *
     * Each attribute of the table is a copy of a join irreducibles node. Each
     * observation of the table is a copy of a meet irreducibles node. An
     * attribute is extent of an observation when its join irreducible node is
     * greater than the meet irreducible node in the lattice.
     *
     * @return the table of the lattice
     */
    public Context getTable() {
        // generation of attributes
        TreeSet<Node<N>> join = this.joinIrreducibles();
        //TreeMap<Node,Node> JoinContent = new TreeMap();
        Context context = new Context();
        for (Node<N> j : join) {
            //  Node<N> nj = new Node(j);
            //JoinContent.put(j,nj);
            context.addToAttributes(j);
        }
        // generation of observations
        TreeSet<Node<N>> meet = this.meetIrreducibles();
        //TreeMap<Node,Node> MeetContent = new TreeMap();
        for (Node<N> m : meet) {
            //    Node nm = new Node(m);
            context.addToObservations(m);
            //    MeetContent.put(m,nm);
        }
        // generation of extent-intent
        Lattice tmp = new Lattice(this);
        tmp.transitiveClosure();
        for (Node j : join) {
            for (Node m : meet) {
                if (j.equals(m) || tmp.getSuccessorNodes(j).contains(m)) {
                    context.addExtentIntent(m, j);
                    //T.addExtentIntent(MeetContent.get(m),JoinContent.get(j));
                }
            }
        }
        return context;
    }

    /**
     * Returns an ImplicationalSystem of the lattice defined on the join
     * irreducibles nodes.
     *
     * Each element of the ImplicationalSystem is a copy of a join irreducible
     * node.
     *
     * @return an implicational system
     */
    public ImplicationalSystem getImplicationalSystem() {
        // initialisation of ImplicationalSystem
        TreeSet<Node<N>> join = this.joinIrreducibles();
        ImplicationalSystem sigma = new ImplicationalSystem();
        for (Node<N> j : join) {
            sigma.addElement((Comparable) j.getContent());
        }
        // generation of the family of closures
        TreeSet<ComparableSet> family = new TreeSet<ComparableSet>();
        Lattice lattice = new Lattice(this);
        ConceptLattice conceptLattice = lattice.joinClosure();
        for (Object node : conceptLattice.getNodes()) {
            Concept concept = (Concept) node;
            ComparableSet setA = new ComparableSet(concept.getSetA());
            family.add(setA);
        }
        // rules generation
        for (ComparableSet jx : family) {
            for (Node j : join) {
                ComparableSet p = new ComparableSet();
                p.add(j.getContent());
                p.addAll(jx);
                if (!family.contains(p)) {
                    ComparableSet min = new ComparableSet();
                    min.addAll(family.last());
                    for (ComparableSet c : family) {
                        //System.out.println("min: "+min.getClass()+" -C:"+C.getClass());
                        if (c.containsAll(p) && !p.containsAll(c) && min.containsAll(c)) {
                            min = c.clone();
                        }
                    }
                    Rule r = new Rule();
                    r.addAllToPremise(p);
                    min.removeAll(p);
                    r.addAllToConclusion(min);
                    sigma.addRule(r);
                }
            }
        }

        /**
         * for (Node j : join) for (Node m : meet) if (j.equals(m) ||
         * tmp.getSuccessorNodes(j).contains(m)) T.addExtentIntent (m,j);
         * //T.addExtentIntent (MeetContent.get(m),JoinContent.get(j)); return
         * T;*
         */
        sigma.makeRightMaximal();
        return sigma;
    }

    /*
     * ------------- dependency GRAPH RELATIVE METHODS ------------------
     */
    /**
     * Returns the dependency graph of this component.
     *
     * The dependency graph is a condensed representation of a lattice that
     * encodes its minimal generators, and its canonical direct basis.
     *
     * In the dependency graph, nodes are join irreducibles, egdes correspond to
     * the dependency relation between join-irreducibles (j -> j' if and only if
     * there exists a node x in the lattice such that x not greather than j and
     * j', and x v j' > j), and edges are labeled with the smallest subsets X of
     * join-irreducibles such that the join of elements of X corresponds to the
     * node x of the lattice.
     *
     * The dependency graph could has been already computed in the case where
     * this component has been instancied as the diagramm of the closed set
     * lattice of a closure system using the static method
     * {@link ConceptLattice#diagramLattice} This method implements an
     * adaptation adaptation of Bordat's where the dependency graph is computed
     * while the lattice is generated.
     *
     * However, it is generated in O(nj^3) where n is the number of nodes of the
     * lattice, and j is the number of join-irreducibles of the lattice.
     *
     * @return the dependency graph
     */
    public ConcreteDGraph getDependencyGraph() {
        if (!(this.dependencyGraph == null)) {
            return this.dependencyGraph;
        }
        this.dependencyGraph = new ConcreteDGraph();
        // nodes of the dependency graph are join-irreducibles
        TreeSet<Node<N>> joins = this.joinIrreducibles();
        for (Node<N> j : joins) {
            this.dependencyGraph.addNode(j);
        }
        // computes the transitive closure of the join-irreducibles subgraph of this compnent
        DAGraph joinG = this.irreduciblesSubgraph();
        joinG.transitiveClosure();
        // edges of the dependency graph are dependency relation between join-irreducibles
        // they are first valuated by nodes of the lattice
        for (Node<N> j1 : joins) {
            SortedSet<Node<N>> majj1 = this.majorants(j1);
            for (Node<N> j2 : joins) {
                if (!j1.equals(j2)) {
                    // computes the set S of nodes not greather than j1 and j2
                    TreeSet<Node<N>> set = new TreeSet<Node<N>>(this.getNodes());
                    set.removeAll(majj1);
                    set.removeAll(this.majorants(j2));
                    set.remove(j1);
                    set.remove(j2);
                    for (Node<N> x : set) {
                        // when j2 V x greather than j1 then add a new edge from j1 to J2
                        // or only a new valuation when the edge already exists
                        if (majj1.contains(this.join(j2, x))) {
                            Edge ed = this.dependencyGraph.getEdge(j1, j2);
                            if (ed == null) {
                                ed = new Edge(j1, j2, new TreeSet<ComparableSet>());
                                this.dependencyGraph.addEdge(ed);
                            }
                            // add {Jx minus predecessors in joinG of j in Jx} as valuation of edge
                            // from j1 to j2
                            TreeSet<ComparableSet> valEd = (TreeSet<ComparableSet>) ed.getContent();
                            ComparableSet newValByNode = new ComparableSet(this.joinIrreducibles(x));
                            for (Node<N> j : this.joinIrreducibles(x)) {
                                newValByNode.removeAll(joinG.getPredecessorNodes(j));
                            }
                            ComparableSet newVal = new ComparableSet();
                            for (Object j : newValByNode) {
                                Node node = (Node) j;
                                newVal.add(node.getContent());
                            }
                            ((TreeSet<ComparableSet>) ed.getContent()).add(newVal);
                            // Minimalisation by inclusion of valuations on edge j1->j2
                            valEd = new TreeSet<ComparableSet>((TreeSet<ComparableSet>) ed.getContent());
                            for (ComparableSet x1 : valEd) {
                                if (x1.containsAll(newVal) && !newVal.containsAll(x1)) {
                                    ((TreeSet<ComparableSet>) ed.getContent()).remove(x1);
                                }
                                if (!x1.containsAll(newVal) && newVal.containsAll(x1)) {
                                    ((TreeSet<ComparableSet>) ed.getContent()).remove(newVal);
                                }
                            }
                        }
                    }
                }
            }
        }
        // minimalisation of edge's content to get only inclusion-minimal valuation for each edge
        /**
         * for (Edge ed : this.dependencyGraph.getEdges()) {
         * TreeSet<ComparableSet> valEd = new
         * TreeSet<ComparableSet>(((TreeSet<ComparableSet>)ed.getContent()));
         * for (ComparableSet X1 : valEd) for (ComparableSet X2 : valEd) if
         * (X1.containsAll(X2) && !X2.containsAll(X1))
         * ((TreeSet<ComparableSet>)ed.getContent()).remove(X1); }*
         */
        return this.dependencyGraph;
    }

    /**
     * Set the dependency graph.
     *
     * @param graph the dependency graph
     *
     * @return this for chaining
     */
    protected Lattice setDependencyGraph(ConcreteDGraph graph) {
        this.dependencyGraph = graph;
        return this;
    }

    /**
     * Test if this component has a dependency graph.
     *
     * @return the truth value for this property
     */
    protected boolean hasDependencyGraph() {
        return this.dependencyGraph != null;
    }

    /**
     * Returns the canonical direct basis of the lattice.
     *
     * The canonical direct basis is a condensed representation of a lattice
     * encoding by the dependency graph.
     *
     * This canonical direct basis is deduced from the dependency graph of the
     * lattice: for each edge b -> a valuated by a subset X, the rule a+X->b is
     * a rule of the canonical direct basis.
     *
     * If not yet exists, the dependency graph of this component has to be
     * generated by method {@link #getDependencyGraph}.
     *
     * @return the canonical direct basis of the lattice
     */
    public ImplicationalSystem getCanonicalDirectBasis() {
        ConcreteDGraph odGraph = this.getDependencyGraph();
        // initialise elements of the ImplicationalSystem with nodes of the ODGraph
        ImplicationalSystem bcd = new ImplicationalSystem();
        for (Object node : odGraph.getNodes()) {
            bcd.addElement((Comparable) ((Node) node).getContent());
        }
        // computes rules of the BCD from edges of the ODGraph
        for (Object ed : odGraph.getEdges()) {
            Node source = ((Edge) ed).getSource();
            Node target = ((Edge) ed).getTarget();
            TreeSet<ComparableSet> l = (TreeSet<ComparableSet>) ((Edge) ed).getContent();
            for (ComparableSet set : l) {
                ComparableSet premise = new ComparableSet(set);
                premise.add((Comparable) target.getContent());
                ComparableSet conclusion = new ComparableSet();
                conclusion.add((Comparable) source.getContent());
                bcd.addRule(new Rule(premise, conclusion));
            }
        }
        //bcd.makeLeftMinimal();
        bcd.makeCompact();
        return bcd;
    }

    /**
     * Returns a set of association rules based on a confidence threshold.
     *
     * The canonical direct basis is computed. For each generated rule, a set of
     * approximative rules (above the confidence threshold) is generated.
     *
     * @param context    a context
     * @param support    a support threshold, between 0 and 1
     * @param confidence a confidence threshold, between 0 and 1
     *
     * @return a set of approximative rules
     */
    public ImplicationalSystem getAssociationBasis(Context context, double support, double confidence) {

        //nb of observations in current context
        int nbObs = context.getObservations().size();

        //start by getting exact rules
        ImplicationalSystem exactRules = this.getCanonicalDirectBasis();
        ImplicationalSystem appRules = new ImplicationalSystem();

        //copy elements from exact rules to approximate rules
        for (Comparable e : exactRules.getSet()) {
            appRules.addElement(e);
        }

        for (Rule rule : exactRules.getRules()) {

            //we get the premisse of the rule, aka the closed set minimal generator
            TreeSet<Comparable> gm = rule.getPremise();
            //then we retrieve the closed set from the minimal generator
            TreeSet<Comparable> closedSet = context.closure(gm);

            //we get the cardinality of its extent to compute confidence later
            double supportClosedSet = context.getExtentNb(closedSet);
            if (supportClosedSet / nbObs > support) {
                //we get the immediate successors of the concept made of the set
                ArrayList<TreeSet<Comparable>> succs = new Concept(closedSet, new TreeSet()).immediateSuccessorsLOA(context);
                for (TreeSet<Comparable> succ : succs) {
                    //we compute the support of the rule as the ratio between closed set and successor extent
                    double ex = context.getExtentNb(succ);
                    double supportSucc = ex / supportClosedSet;

                    //the rule conclusion is made of the successors minus the minimal generator
                    TreeSet<Comparable> conclusions = new TreeSet(succ);
                    conclusions.removeAll(gm);

                    //if the ratio support exceed the confidence threshold, the rule is created
                    if (supportSucc > confidence) {
                        appRules.addRule(new AssociationRule(gm, conclusions, ex / nbObs, supportSucc));
                    }
                }
                //the exact rule is copied in the output rule set
                appRules.addRule(new AssociationRule(rule.getPremise(), rule.getConclusion(), supportClosedSet / nbObs, 1));
            }
        }
        appRules.makeCompactAssociation();
        return appRules;
    }

    /**
     * Returns the minimal generators of the lattice.
     *
     * Minimal generators a condensed representation of a lattice encoding by
     * the dependency graph.
     *
     * Minimal generators are premises of the canonical direct basis. that is
     * deduced from the dependency graph of the lattice.
     *
     * If not yet exists, the dependency graph of this component has to be
     * generated by method {@link #getDependencyGraph}.
     *
     * @return a TreeSet of the minimal generators
     */
    public TreeSet getMinimalGenerators() {
        ImplicationalSystem bcd = this.getCanonicalDirectBasis();
        TreeSet genMin = new TreeSet();
        for (Rule r : bcd.getRules()) {
            genMin.add(r.getPremise());
        }
        return genMin;
    }

    /**
     * The arrowRelation method encodes arrow relations between meet &
     * join-irreductibles of this component.
     *
     * Let m and j be respectively meet and join irreductibles of a lattice.
     * Recall that m has a unique successor say m+ and j has a unique
     * predecessor say j-, then :
     *
     * - j "Up Arrow" m (stored has "Up") iff j is not less or equal than m and j is less than m+
     * - j "Down Arrow" m (stored has "Down") iff j is not less or equal than m and j- is less than m
     * - j "Up Down Arrow" m (stored has "UpDown") iff j "Up" m and j "Down" m
     * - j "Cross" m (stored has "Cross") iff j is less or equal than m
     * - j "Circ" m (stored has "Circ") iff neither j "Up" m nor j "Down" m nor j "Cross" m
     *
     * @return ConcreteDGraph whose :
         - Nodes are join or meet irreductibles of the lattice.
         - Edges content encodes arrows as String "Up", "Down", "UpDown", "Cross", "Circ".
     *
     */
    public ArrowRelation getArrowRelation() {
        return new ArrowRelation(this);
    }
}