ConceptLattice.java
package org.thegalactic.lattice;
/*
* ConceptLattice.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.context.Context;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.io.IOException;
import java.util.List;
import org.thegalactic.util.ComparableSet;
import org.thegalactic.dgraph.DAGraph;
import org.thegalactic.dgraph.ConcreteDGraph;
import org.thegalactic.dgraph.Edge;
import org.thegalactic.dgraph.Node;
import org.thegalactic.io.Filer;
import org.thegalactic.lattice.io.ConceptLatticeIOFactory;
/**
* This class extends class {@link Lattice} to provide specific methods to
* manipulate both a concept lattice or a closed set lattice.
*
* This class provides methods implementing classical operation on a concept
* lattice: join and meet reduction, concepts sets reduction, ...
*
* This class also provides two static method generating a concept lattice:
* methods {@link #diagramLattice} and {@link #completeLattice} both computes
* the closed set lattice of a given closure system. The firt one computes the
* hasse diagram of the closed set lattice by invoking method
* {@link #immediateSuccessors}. This method implements an adaptation of the
* well-known Bordat algorithm that also computes the dependance graph of the
* lattice where at once the minimal generators and the canonical direct basis
* of the lattice are encoded. The second static method computes the
* transitively closure of the lattice as the inclusion relation defined on all
* the closures generated by method {@link ClosureSystem#allClosures} that
* implements the well-known Wille algorithm.
*
* ![ConceptLattice](ConceptLattice.png)
*
* @uml ConceptLattice.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
* !include resources/org/thegalactic/lattice/ConceptLattice.iuml
* !include resources/org/thegalactic/lattice/Concept.iuml
*
* hide members
* show ConceptLattice members
* class ConceptLattice #LightCyan
* title ConceptLattice UML graph
*/
public class ConceptLattice extends Lattice {
/**
* Generate the lattice composed of all the antichains of this component
* ordered with the inclusion relation.
*
* This treatment is performed in O(??) by implementation of Nourine
* algorithm that consists in a sequence of doubling intervals of nodes.
*
* @param dag a directed acyclic graph
*
* @return the concept lattice
*/
public static ConceptLattice idealLattice(DAGraph dag) {
if (!dag.isAcyclic()) {
return null;
}
// initialise the poset of ideals with the emptyset
ConceptLattice conceptLattice = new ConceptLattice();
conceptLattice.addNode(new Concept(true, false));
// travel on graph according to a topological sort
DAGraph graph = new DAGraph(dag);
graph.transitiveClosure();
// treatment of nodes according to a topological sort
List<Node> sort = graph.topologicalSort();
for (Node x : sort) {
// computation of Jx
TreeSet<Node> jxmoins = new TreeSet<Node>(graph.getPredecessorNodes(x));
// storage of new ideals in a set
TreeSet<Concept> toAdd = new TreeSet<Concept>();
for (Object j1 : conceptLattice.getNodes()) {
if (((Concept) j1).containsAllInA(jxmoins)) {
Concept newJ = new Concept(true, false);
newJ.addAllToA(((TreeSet) ((Concept) j1).getSetA()));
newJ.addToA(x);
toAdd.add(newJ);
}
}
// addition of the new ideals in conceptLattice
for (Concept newJ : toAdd) {
conceptLattice.addNode(newJ);
}
}
// computation of the inclusion relaton
for (Object node1 : conceptLattice.getNodes()) {
for (Object node2 : conceptLattice.getNodes()) {
if (((Concept) node1).containsAllInA(((Concept) node2).getSetA())) {
conceptLattice.addEdge((Node) node2, (Node) node1);
}
}
}
conceptLattice.transitiveReduction();
return conceptLattice;
}
/*
* -------- STATIC CLOSEDSET LATTICE GENERATION FROM AN ImplicationalSystem OR A CONTEXT ------------------
*/
/**
* Generates and returns the complete (i.e. transitively closed) closed set
* lattice of the specified closure system, that can be an implicational
* system (ImplicationalSystem) or a context.
*
* The lattice is generated using the well-known Next Closure algorithm. All
* closures are first generated using the method:
* {@link ClosureSystem#allClosures} that implements the well-known Next
* Closure algorithm. Then, all concepts are ordered by inclusion.
*
* @param init a closure system (an ImplicationalSystem or a Context)
*
* @return a concept lattice
*/
public static ConceptLattice completeLattice(ClosureSystem init) {
ConceptLattice lattice = new ConceptLattice();
// compute all the closed set with allClosures
Vector<Concept> allclosure = init.allClosures();
for (Concept cl : allclosure) {
lattice.addNode(cl);
}
// an edge corresponds to an inclusion between two closed sets
for (Object source : lattice.getNodes()) {
for (Object target : lattice.getNodes()) {
if (((Concept) target).containsAllInA(((Concept) source).getSetA())) {
lattice.addEdge((Node) source, (Node) target);
}
}
}
// Hasse diagram is computed
return lattice;
}
/**
* Generates and returns the Hasse diagram of the closed set lattice of the
* specified closure system, that can be an implicational system
* (ImplicationalSystem) or a context.
*
* The Hasse diagramm of the closed set lattice is obtained by a recursively
* generation of immediate successors of a given closed set, starting from
* the botom closed set. Implemented algorithm is an adaptation of Bordat's
* algorithm where the dependance graph is computed while the lattice is
* generated. This treatment is performed in O(cCl|S|^3log g) where S is the
* initial set of elements, c is the number of closed sets that could be
* exponential in the worst case, Cl is the closure computation complexity
* and g is the number of minimal generators of the lattice.
*
* The dependance graph of the lattice is also computed while the lattice
* generation. The dependance graph of a lattice encodes at once the minimal
* generators and the canonical direct basis of the lattice .
*
* @param init a closure system (an ImplicationalSystem or a Context)
*
* @return a concept lattice
*/
public static ConceptLattice diagramLattice(ClosureSystem init) {
ConceptLattice lattice = new ConceptLattice();
//if (Diagram) {
// computes the dependance graph of the closure system
// addition of nodes in the precedence graph
ConcreteDGraph graph = new ConcreteDGraph();
for (Comparable c : init.getSet()) {
graph.addNode(new Node(c));
}
lattice.setDependencyGraph(graph);
// intialize the close set lattice with botom element
Concept bot = new Concept(init.closure(new ComparableSet()), false);
lattice.addNode(bot);
// recursive genaration from the botom element with diagramLattice
lattice.recursiveDiagramLattice(bot, init);
// minimalisation of edge's content to get only inclusion-minimal valuation for each edge
/**
* for (Edge ed : lattice.dependanceGraph.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 lattice;
}
/**
* Generates and returns the Hasse diagram of the closed set iceberg of the
* specified context.
*
* The Hasse diagram of the closed set iceberg is obtained by a recursively
* generation of immediate successors of a given closed set, starting from
* the bottom closed set. Implemented algorithm is an adaptation of Bordat's
* algorithm where the dependence graph is computed while the lattice is
* generated. This treatment is performed in O(cCl|S|^3log g) where S is the
* initial set of elements, c is the number of closed sets that could be
* exponential in the worst case, Cl is the closure computation complexity
* and g is the number of minimal generators of the lattice. The iceberg
* stops when the immediate successors support is inferior to the support
* value.
*
* The dependence graph of the lattice is also computed while the lattice
* generation. The dependence graph of a lattice encodes at once the minimal
* generators and the canonical direct basis of the lattice .
*
* @param init a closure system (an ImplicationalSystem or a Context)
* @param support a support value, between 0 and 1.
*
* @return a concept iceberg
*/
public static ConceptLattice diagramIceberg(Context init, double support) {
ConceptLattice lattice = new ConceptLattice();
// computes the dependance graph of the closure system
// addition of nodes in the precedence graph
ConcreteDGraph graph = new ConcreteDGraph();
for (Comparable c : init.getSet()) {
graph.addNode(new Node(c));
}
lattice.setDependencyGraph(graph);
// intialize the close set lattice with bottom element
Concept bot = new Concept(init.closure(new ComparableSet()), false);
lattice.addNode(bot);
int threshold = (int) (support * init.getExtent(bot.getSetA()).size());
// recursive genaration from the botom element with diagramLattice
lattice.recursiveDiagramIceberg(bot, init, threshold);
return lattice;
}
/*
* ------------- CONSTRUCTORS ------------------
*/
/**
* Constructs this component with an empty set of nodes.
*/
public ConceptLattice() {
super();
}
/**
* Constructs this component with the specified set of concepts, and empty
* treemap of successors and predecessors.
*
* @param set the set of nodes
*/
public ConceptLattice(TreeSet<Concept> set) {
super((TreeSet) set);
}
/**
* Constructs this component as a shallow copy of the specified lattice.
*
* Concept lattice property is checked for the specified lattice. When not
* verified, this component is constructed with an empty set of nodes.
*
* @param lattice the lattice to be copied
*/
public ConceptLattice(Lattice lattice) {
super(lattice);
if (!this.isConceptLattice()) {
this.setNodes(new TreeSet<Node>());
this.setSuccessors(new TreeMap<Node, SortedSet<Edge>>());
this.setPredecessors(new TreeMap<Node, SortedSet<Edge>>());
}
}
/*
* ------------- CONCEPT LATTICE CHEKING METHOD ------------------
*/
/**
* Check if nodes of this component are concepts.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean containsConcepts() {
for (Object node : this.getNodes()) {
if (!(node instanceof Concept)) {
return false;
}
}
return true;
}
/**
* Check if this component is a lattice whose nodes are concepts.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean isConceptLattice() {
return this.isLattice() && this.containsConcepts();
}
/**
* Check if this component is a lattice whose nodes are concepts with non
* null set A.
*
* @return a boolean
*
* @todo Comment the return: conception
*/
public boolean containsAllSetA() {
if (!this.containsConcepts()) {
return false;
}
for (Object node : this.getNodes()) {
if (!((Concept) node).hasSetA()) {
return false;
}
}
return true;
}
/**
* Check if this component is a lattice whose nodes are concepts with non
* null set A.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean containsAllSetB() {
if (!this.containsConcepts()) {
return false;
}
for (Object node : this.getNodes()) {
if (!((Concept) node).hasSetB()) {
return false;
}
}
return true;
}
/**
* Returns a clone of this component composed of a clone of each concept and
* each edge.
*
* @return a concept lattice
*/
@Override
public ConceptLattice clone() {
ConceptLattice conceptLattice = new ConceptLattice();
TreeMap<Concept, Concept> copy = new TreeMap<Concept, Concept>();
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
Concept c2 = c.clone();
copy.put(c, c2);
conceptLattice.addNode(c2);
}
for (Object ed : this.getEdges()) {
conceptLattice.addEdge(new Edge(
copy.get(((Edge) ed).getSource()),
copy.get(((Edge) ed).getTarget()),
((Edge) ed).getContent()
));
}
return conceptLattice;
}
/*
* ------------- SET A AND SET B HANDLING METHOD ------------------
*/
/**
* Returns concept defined by setA and setB; null if not found.
*
* @param setA intent of the concept to find
* @param setB extent of the concept to find
*
* @return concept defined by setA and setB; null if not found.
*/
public Concept getConcept(ComparableSet setA, ComparableSet setB) {
SortedSet<Node> setNodes = this.getNodes();
Concept cpt = null;
for (Node node : setNodes) {
if ((setA.equals(((Concept) node).getSetA())) && (setB.equals(((Concept) node).getSetB()))) {
cpt = (Concept) node;
}
}
return cpt;
}
/**
* Replace set A in each concept of the lattice with the null value.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean removeAllSetA() {
if (!this.containsConcepts()) {
return false;
}
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
c.putSetA(null);
}
return true;
}
/**
* Replace set B in each concept of the lattice with the null value.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean removeAllSetB() {
if (!this.containsConcepts()) {
return false;
}
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
c.putSetB(null);
}
return true;
}
/**
* Replace null set A in each join irreducible concept with a set containing
* node ident.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean initialiseSetAForJoin() {
if (!this.containsConcepts()) {
return false;
}
TreeSet<Node> joinIrr = this.joinIrreducibles();
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
if (!c.hasSetA() && joinIrr.contains(c)) {
ComparableSet setX = new ComparableSet();
setX.add(Integer.valueOf(c.getIdentifier()));
c.putSetA(setX);
}
}
return true;
}
/**
* Replace null set B in each meet irreducible concept with a set containing
* node ident.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean initialiseSetBForMeet() {
if (!this.containsConcepts()) {
return false;
}
TreeSet<Node> meetIrr = this.meetIrreducibles();
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
if (!c.hasSetB() && meetIrr.contains(c)) {
ComparableSet setX = new ComparableSet();
setX.add(Integer.valueOf(c.getIdentifier()));
c.putSetB(setX);
}
}
return true;
}
/*
* --------------- INCLUSION REDUCTION METHODS ------------
*/
/**
* Replaces, if not empty, set A of each concept with the difference between
* itself and set A of its predecessors; Then replaces, if not empty, set B
* of each concept by the difference between itself and set B of its
* successors.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean makeInclusionReduction() {
if (!this.containsConcepts()) {
return false;
}
boolean setA = this.containsAllSetA();
boolean setB = this.containsAllSetB();
if (!setA && !setB) {
return false;
}
// makes setA inclusion reduction
if (setA) {
// computation of an inverse topological sort
this.transpose();
List<Node> sort = this.topologicalSort();
this.transpose();
// reduction of set A
for (Node to : sort) {
Concept cto = (Concept) to;
for (Object source : this.getPredecessorNodes(to)) {
Concept csource = (Concept) source;
cto.getSetA().removeAll(csource.getSetA());
}
}
}
// makes setB inclusion reduction
if (setB) {
// computation of a topological sort
List<Node> sort = this.topologicalSort();
// reduction of set B
for (Node to : sort) {
Concept cto = (Concept) to;
for (Object source : this.getSuccessorNodes(to)) {
Concept csource = (Concept) source;
cto.getSetB().removeAll(csource.getSetB());
}
}
}
return true;
}
/**
* Replaces set A of each join irreducible node by the difference between
* itself and set A of the unique predecessor.
*
* Others closed sets are replaced by an emptyset.
*
* @return a boolean
*
* @todo Comment the return
*/
public boolean makeIrreduciblesReduction() {
// make inclusion reduction
if (this.makeInclusionReduction()) {
// check if not set A reduced concepts are join irreducibles
// and if not set B reduced concepts are meet irreducibles
TreeSet<Node> joinIrr = this.joinIrreducibles();
TreeSet<Node> meetIrr = this.meetIrreducibles();
for (Object node : this.getNodes()) {
Concept c = (Concept) node;
if (c.hasSetA() && !c.getSetA().isEmpty() && !joinIrr.contains(c)) {
c.putSetA(new ComparableSet());
}
if (c.hasSetB() && !c.getSetB().isEmpty() && !meetIrr.contains(c)) {
c.putSetB(new ComparableSet());
}
}
}
return true;
}
/**
* Returns a lattice where edges are valuated by the difference between set
* A of two adjacent concepts.
*
* @return a boolean
*
* @todo Change comment
*/
public boolean makeEdgeValuation() {
if (!this.containsConcepts()) {
return false;
}
for (Object n1 : this.getNodes()) {
for (Object ed : this.getSuccessorEdges((Node) n1)) {
if (!((Edge) ed).hasContent()) {
Node n2 = ((Edge) ed).getTarget();
TreeSet diff = new TreeSet();
diff.addAll(((Concept) n2).getSetA());
diff.removeAll(((Concept) n1).getSetA());
((Edge) ed).setContent(diff);
}
}
}
return true;
}
/*
* --------------- LATTICE GENERATION METHODS ------------
*/
/**
* Returns a lattice where join irreducibles node's content is replaced by
* the first element of set A.
*
* Other nodes are replaced by a new comparable.
*
* @return a lattice
*/
public Lattice getJoinReduction() {
if (!this.containsConcepts()) {
return null;
}
if (!this.containsAllSetA()) {
return null;
}
Lattice lattice = new Lattice();
//ConceptLattice csl = new ConceptLattice (this);
ConceptLattice csl = this.clone();
csl.makeIrreduciblesReduction();
TreeSet<Node> joinIrr = csl.joinIrreducibles();
// addition to lattice of a comparable issued from each reduced closed set
TreeMap<Node, Node> reduced = new TreeMap<Node, Node>();
for (Object node : csl.getNodes()) {
Concept c = (Concept) node;
Node nred;
if (c.hasSetA() && joinIrr.contains(node)) {
nred = new Node(c.getSetA().first());
} else {
nred = new Node();
}
reduced.put((Node) node, nred);
}
// addtion of nodes to lattice
for (Object node : csl.getNodes()) {
lattice.addNode(reduced.get(node));
}
// addtion of edges to lattice
for (Object source : csl.getNodes()) {
for (Object target : csl.getSuccessorNodes((Node) source)) {
lattice.addEdge(reduced.get(source), reduced.get(target));
}
}
return lattice;
}
/**
* Returns a lattice where meet irreducibles node's content is replaced by
* the first element of set B.
*
* Other nodes are replaced by a new comparable.
*
* @return a lattice
*/
public Lattice getMeetReduction() {
if (!this.containsConcepts()) {
return null;
}
if (!this.containsAllSetB()) {
return null;
}
Lattice lattice = new Lattice();
if (!this.containsConcepts()) {
return lattice;
}
//ConceptLattice csl = new ConceptLattice (this);
ConceptLattice csl = this.clone();
csl.makeIrreduciblesReduction();
TreeSet<Node> meetIrr = csl.meetIrreducibles();
// addition to lattice of a comparable issued from each reduced closed set
TreeMap<Node, Node> reduced = new TreeMap<Node, Node>();
for (Object node : csl.getNodes()) {
Concept c = (Concept) node;
Node nred;
if (c.hasSetB() && meetIrr.contains(node)) {
nred = new Node(c.getSetB().first());
} else {
nred = new Node();
}
reduced.put((Node) node, nred);
}
for (Object node : csl.getNodes()) {
lattice.addNode(reduced.get(node));
}
// addtion of edges to lattice
for (Object source : csl.getNodes()) {
for (Object target : csl.getSuccessorNodes((Node) source)) {
lattice.addEdge(reduced.get(source), reduced.get(target));
}
}
return lattice;
}
/**
* Returns a lattice where each join irreducible concept is replaced by a
* node containing the first element of set A, and each meet irreducible
* concept is replaced by a node contining the first element of set B.
*
* A concept that is at once join and meet irreducible is replaced by a node
* containing the first element of set A and the first element of set B in a
* set. Other nodes are replaced by an empty node.
*
* @return a lattice
*/
public Lattice getIrreduciblesReduction() {
Lattice lattice = new Lattice();
if (!this.containsConcepts()) {
return lattice;
}
//ConceptLattice csl = new ConceptLattice (this);
ConceptLattice csl = this.clone();
csl.makeIrreduciblesReduction();
TreeSet<Node> joinIrr = csl.joinIrreducibles();
TreeSet<Node> meetIrr = csl.meetIrreducibles();
// addition to lattice of a comparable issued from each reduced closed set
TreeMap<Node, Node> reduced = new TreeMap<Node, Node>();
for (Object node : csl.getNodes()) {
Concept c = (Concept) node;
// create a new Node with two indexed elements: the first of set A and the first of set B
if (c.hasSetA() && c.hasSetB() && meetIrr.contains(c) && joinIrr.contains(c)) {
TreeSet<Comparable> content = new TreeSet<Comparable>();
content.add(c.getSetA().first());
content.add(c.getSetB().first());
Node nred = new Node(content);
reduced.put((Node) node, nred);
} else if (c.hasSetA() && joinIrr.contains(node)) {
// create a new Node with the first element of set A
Node nred = new Node(((Concept) node).getSetA().first());
reduced.put((Node) node, nred);
} else if (c.hasSetB() && meetIrr.contains(node)) {
// create a new Node with the first element of set A
Node nred = new Node(((Concept) node).getSetB().first());
reduced.put((Node) node, nred);
} else {
reduced.put((Node) node, new Node());
}
}
// addtion of nodes to lattice
for (Object node : csl.getNodes()) {
lattice.addNode(reduced.get(node));
}
// addtion of edges to lattice
for (Object source : csl.getNodes()) {
for (Object target : csl.getSuccessorNodes((Node) source)) {
lattice.addEdge(reduced.get(source), reduced.get(target));
}
}
return lattice;
}
/**
* Returns iceberg lattice whose concept contains enough observations.
*
* @deprecated use getConceptIceberg method from ClosureSystem class
* instead. Are kept only concept whose number of observation is over
* threshold. A top node is added to keep the lattice structure.
*
* @param threshold used to determine nodes to be kept.
*
* @return iceberg lattice whose concept contains enough observations.
*/
@Deprecated
public ConceptLattice iceberg(float threshold) {
ConceptLattice l = new ConceptLattice();
Concept b = (Concept) this.bottom();
int card = b.getSetB().size();
for (Object node : this.getNodes()) {
Concept cpt = (Concept) node;
if ((float) cpt.getSetB().size() / (float) card >= threshold) {
l.addNode((Node) node);
}
}
for (Object f : l.getNodes()) {
for (Object t : l.getNodes()) {
if (this.containsEdge((Node) f, (Node) t)) {
l.addEdge((Node) f, (Node) t);
}
}
}
Node t = this.top();
l.addNode(t);
// TODO use Node<Concept>
for (Object node : l.getWells()) {
if (!node.equals(t)) {
l.addEdge((Node) node, t);
}
}
return l;
}
/**
* Returns the Hasse diagramme of the closed set lattice of the specified
* closure system issued from the specified concept.
*
* Immediate successors generation is an adaptation of Bordat's theorem
* stating that there is a bijection between minimal strongly connected
* component of the precedence subgraph issued from the specified node, and
* its immediate successors.
*
* This treatment is performed in O(cCl|S|^3log g) where S is the initial
* set of elements, c is the number of closed sets that could be exponential
* in the worst case, Cl is the closure computation complexity and g is the
* number of minimal generators of the lattice.
*
* @param n a concept
* @param init a closure system
*/
public void recursiveDiagramLattice(Concept n, ClosureSystem init) {
Vector<TreeSet<Comparable>> immSucc = this.immediateSuccessors(n, init);
for (TreeSet<Comparable> setX : immSucc) {
Concept c = new Concept(new TreeSet(setX), false);
Concept ns = (Concept) this.getNode(c);
if (ns != null) {
// when ns already exists, addition of a new edge
this.addEdge(n, ns);
} else { // when ns don't already exists, addition of a new node and recursive treatment
this.addNode(c);
this.addEdge(n, c);
this.recursiveDiagramLattice(c, init);
}
}
}
/**
* Returns the Hasse diagramme of the closed set iceberg of the specified
* closure system issued from the specified concept.
*
* Immediate successors generation is an adaptation of Bordat's theorem
* stating that there is a bijection between minimal strongly connected
* component of the precedence subgraph issued from the specified node, and
* its immediate successors.
*
* This treatment is performed in O(cCl|S|^3log g) where S is the initial
* set of elements, c is the number of closed sets that could be exponential
* in the worst case, Cl is the closure computation complexity and g is the
* number of minimal generators of the lattice.
*
* @param n a concept
* @param init a closure system
* @param threshold a support threshold, as a number of observations
*/
private void recursiveDiagramIceberg(Concept n, ClosureSystem init, int threshold) {
Context context = (Context) init;
Vector<TreeSet<Comparable>> immSucc = this.immediateSuccessors(n, init);
for (TreeSet<Comparable> setX : immSucc) {
if (context.getExtentNb(setX) >= threshold) {
Concept c = new Concept(new TreeSet(setX), false);
Concept ns = (Concept) this.getNode(c);
if (ns != null) {
this.addEdge(n, ns);
} else {
this.addNode(c);
this.addEdge(n, c);
this.recursiveDiagramIceberg(c, init, threshold);
}
}
}
}
/**
* Returns the list of immediate successors of a given node of the lattice.
*
* This treatment is an adaptation of Bordat's theorem stating that there is
* a bijection between minimal strongly connected component of the
* precedence subgraph issued from the specified node, and its immediate
* successors.
*
* This treatment is performed in O(Cl|S|^3log g) where S is the initial set
* of elements, Cl is the closure computation complexity and g is the number
* of minimal generators of the lattice.
*
* This treatment is recursively invoked by method recursiveDiagramlattice.
* In this case, the dependance graph is initialised by method
* recursiveDiagramMethod, and updated by this method, with addition some
* news edges and/or new valuations on existing edges. When this treatment
* is not invoked by method recursiveDiagramLattice, then the dependance
* graph is initialised, but it may be not complete. It is the case for
* example for on-line generation of the concept lattice.
*
* @param n a node
* @param init a closure system
*
* @return a set of immediate successors
*/
public Vector<TreeSet<Comparable>> immediateSuccessors(Node n, ClosureSystem init) {
// Initialisation of the dependance graph when not initialised by method recursiveDiagramLattice
if (!this.hasDependencyGraph()) {
ConcreteDGraph graph = new ConcreteDGraph();
for (Comparable c : init.getSet()) {
graph.addNode(new Node(c));
}
this.setDependencyGraph(graph);
}
// computes newVal, the subset to be used to valuate every new dependance relation
// newVal = F\predecessors of F in the precedence graph of the closure system
// For a non reduced closure system, the precedence graph is not acyclic,
// and therefore strongly connected components have to be used.
ComparableSet setF = new ComparableSet(((Concept) n).getSetA());
ConcreteDGraph prec = init.precedenceGraph();
DAGraph acyclPrec = prec.getStronglyConnectedComponent();
ComparableSet newVal = new ComparableSet();
newVal.addAll(setF);
for (Object x : setF) {
// computes nx, the strongly connected component containing x
Node nx = null;
for (Object cc : acyclPrec.getNodes()) {
TreeSet<Node> setCC = (TreeSet<Node>) ((Node) cc).getContent();
for (Node y : setCC) {
if (x.equals(y.getContent())) {
nx = (Node) cc;
}
}
}
// computes the minorants of nx in the acyclic graph
SortedSet<Node> ccMinNx = acyclPrec.minorants(nx);
// removes from newVal every minorants of nx
for (Node cc : ccMinNx) {
TreeSet<Node> setCC = (TreeSet<Node>) cc.getContent();
for (Node y : setCC) {
newVal.remove(y.getContent());
}
}
}
// computes the node belonging in S\F
TreeSet<Node> nodes = new TreeSet<Node>();
for (Object in : this.getDependencyGraph().getNodes()) {
if (!setF.contains(((Node) in).getContent())) {
nodes.add((Node) in);
}
}
// computes the dependance relation between nodes in S\F
// and valuated this relation by the subset of S\F
TreeSet<Edge> edges = new TreeSet<Edge>();
for (Node source : nodes) {
for (Node target : nodes) {
if (!source.equals(target)) {
// check if source is in dependance relation with target
// i.e. "source" belongs to the closure of "F+target"
ComparableSet fPlusTo = new ComparableSet(setF);
fPlusTo.add(target.getContent());
fPlusTo = new ComparableSet(init.closure(fPlusTo));
if (fPlusTo.contains(source.getContent())) {
// there is a dependance relation between source and target
// search for an existing edge between source and target
Edge ed = this.getDependencyGraph().getEdge(source, target);
if (ed == null) {
ed = new Edge(source, target, new TreeSet<ComparableSet>());
this.getDependencyGraph().addEdge(ed);
}
edges.add(ed);
// check if F is a minimal set closed for dependance relation between source and target
((TreeSet<ComparableSet>) ed.getContent()).add(newVal);
TreeSet<ComparableSet> 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);
}
}
}
}
}
}
// computes the dependance subgraph of the closed set F as the reduction
// of the dependance graph composed of nodes in S\A and edges of the dependance relation
ConcreteDGraph sub = this.getDependencyGraph().getSubgraphByNodes(nodes);
ConcreteDGraph delta = sub.getSubgraphByEdges(edges);
// computes the sources of the CFC of the dependance subgraph
// that corresponds to successors of the closed set F
DAGraph cfc = delta.getStronglyConnectedComponent();
SortedSet<Node> sccMin = cfc.getSinks();
Vector<TreeSet<Comparable>> immSucc = new Vector<TreeSet<Comparable>>();
for (Node n1 : sccMin) {
TreeSet s = new TreeSet(setF);
TreeSet<Node> toadd = (TreeSet<Node>) n1.getContent();
for (Node n2 : toadd) {
s.add(n2.getContent());
}
immSucc.add(s);
}
return immSucc;
}
/**
* Save the description of this component in a file whose name is specified.
*
* @param filename the name of the file
*
* @throws IOException When an IOException occurs
*/
@Override
public void save(final String filename) throws IOException {
Filer.getInstance().save(this, ConceptLatticeIOFactory.getInstance(), filename);
}
}