ImplicationalSystem.java

package org.thegalactic.rule;

/*
 * ImplicationalSystem.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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

import org.thegalactic.dgraph.ConcreteDGraph;
import org.thegalactic.dgraph.Edge;
import org.thegalactic.dgraph.Node;
import org.thegalactic.io.Filer;
import org.thegalactic.lattice.ClosureSystem;
import org.thegalactic.lattice.io.ImplicationalSystemIOFactory;
import org.thegalactic.util.ComparableSet;

/**
 * This class gives a representation for an implicational system
 * (ImplicationalSystem), a set of rules.
 *
 * An ImplicationalSystem is composed of a TreeSet of comparable elements, and a
 * TreeSet of rules defined by class {@link Rule}.
 *
 * This class provides methods implementing classical transformation of an
 * implicational system : make proper, make minimum, make right maximal, make
 * left minimal, make unary, make canonical basis, make canonical direct basis
 * and reduction.
 *
 * An implicational system owns properties of a closure system, and thus extends
 * the abstract class {@link ClosureSystem} and implements methods
 * {@link #getSet} and {@link #closure}.
 *
 * Therefore, the closed set lattice of an ImplicationalSystem can be generated
 * by invoking method {@link #closedSetLattice} of a closure system.
 *
 * An implicational system can be instancied from and save to a text file in the
 * following format: - a list of elements separated by a space in the first line
 * ; - then, each rule on a line, written like [premise] -> [conclusion] where
 * elements are separated by a space.
 *
 * ~~~
 * a b c d e
 * a b -> c d
 * c d -> e
 * ~~~
 *
 * ![ImplicationalSystem](ImplicationalSystem.png)
 *
 * @uml ImplicationalSystem.png
 * !include resources/org/thegalactic/lattice/ImplicationalSystem.iuml
 * !include resources/org/thegalactic/lattice/ClosureSystem.iuml
 *
 * hide members
 * show ImplicationalSystem members
 * class ImplicationalSystem #LightCyan
 * title ImplicationalSystem UML graph
 */
public class ImplicationalSystem extends ClosureSystem {

    /*
     * --------------- FIELDS -----------------
     */
    /**
     * Generates a random ImplicationalSystem with a specified number of nodes
     * and rules.
     *
     * @param nbS the number of nodes of the generated ImplicationalSystem
     * @param nbR the number of rules of the generated ImplicationalSystem
     *
     * @return a random implicational system with a specified number of nodes
     *         and rules.
     */
    public static ImplicationalSystem random(int nbS, int nbR) {
        ImplicationalSystem sigma = new ImplicationalSystem();
        // addition of elements
        for (int i = 0; i < nbS; i++) {
            sigma.addElement(Integer.valueOf(i));
        }
        // addition of rules
        //for (int i = 0; i < nbR; i++) { One could get twice the same rule ...
        while (sigma.getRules().size() < nbR) {
            ComparableSet conclusion = new ComparableSet();
            int choice = (int) Math.rint(nbS * Math.random());
            int j = 1;
            for (Comparable c : sigma.getSet()) {
                if (j == choice) {
                    conclusion.add(c);
                }
                j++;
            }
            ComparableSet premisse = new ComparableSet();
            for (Comparable c : sigma.getSet()) {
                choice = (int) Math.rint(nbS * Math.random());
                if (choice < nbS / 5) {
                    premisse.add(c);
                }
            }
            //if (!premisse.isEmpty()) {
            sigma.addRule(new Rule(premisse, conclusion));
            //}
        }
        return sigma;
    }

    /**
     * For the implicational rules of this component.
     */
    private TreeSet<Rule> sigma;

    /**
     * For the elements space of this component.
     */
    private TreeSet<Comparable> set;

    /*
     * --------------- CONSTRUCTORS -----------
     */
    /**
     * Constructs a new empty component.
     */
    public ImplicationalSystem() {
        this.init();
    }

    /**
     * Constructs this component from the specified set of rules `sigma`.
     *
     * @param sigma the set of rules.
     */
    public ImplicationalSystem(Collection<Rule> sigma) {
        this.sigma = new TreeSet<Rule>(sigma);
        this.set = new TreeSet<Comparable>();
        for (Rule rule : this.sigma) {
            set.addAll(rule.getPremise());
            set.addAll(rule.getConclusion());
        }
    }

    /**
     * Constructs this component as a copy of the specified ImplicationalSystem
     * `is`.
     *
     * Only structures (conataining reference of indexed elements) are copied.
     *
     * @param is the implicational system to be copied
     */
    public ImplicationalSystem(ImplicationalSystem is) {
        this.sigma = new TreeSet<Rule>(is.getRules());
        this.set = new TreeSet<Comparable>(is.getSet());
    }

    /**
     * Constructs this component from the specified file.
     *
     * The file have to respect a certain format:
     *
     * An implicational system can be instancied from and save to a text file in
     * the following format: - A list of elements separated by a space in the
     * first line ; - then, each rule on a line, written like [premise] ->
     * [conclusion] where elements are separated by a space.
     *
     * ~~~
     * a b c d e a b -> c d c d -> e
     * ~~~
     *
     * Each element must be declared on the first line, otherwise, it is not
     * added in the rule Each rule must have a non empty concusion, otherwise,
     * it is not added in the component
     *
     * @param filename the name of the file
     *
     * @throws IOException When an IOException occurs
     */
    public ImplicationalSystem(String filename) throws IOException {
        this.parse(filename);
    }

    /**
     * Initialise the implicational system.
     *
     * @return this for chaining
     */
    public ImplicationalSystem init() {
        this.sigma = new TreeSet<Rule>();
        this.set = new TreeSet<Comparable>();
        return this;
    }

    /*
     * ------------- ACCESSORS METHODS ------------------
     */
    /**
     * Returns the set of rules.
     *
     * @return the set of rules of this component.
     */
    public SortedSet<Rule> getRules() {
        return Collections.unmodifiableSortedSet((SortedSet<Rule>) this.sigma);
    }

    /**
     * Returns the set of indexed elements.
     *
     * @return the elements space of this component.
     */
    public SortedSet<Comparable> getSet() {
        return Collections.unmodifiableSortedSet((SortedSet<Comparable>) this.set);
    }

    /**
     * Returns the number of elements in the set S of this component.
     *
     * @return the number of elements in the elements space of this component.
     */
    public int sizeElements() {
        return this.set.size();
    }

    /**
     * Returns the number of rules of this component.
     *
     * @return the number of rules of this component.
     */
    public int sizeRules() {
        return this.sigma.size();
    }

    /*
     * ------------- MODIFICATION METHODS ------------------
     */
    /**
     * Adds the specified element to the set `S` of this component.
     *
     * @param e the comparable to be added
     *
     * @return true if the element has been added to `S`
     */
    public boolean addElement(Comparable e) {
        return set.add(e);
    }

    /**
     * Adds the specified element to the set `S` of this component.
     *
     * @param x the treeset of comparable to be added
     *
     * @return true if the element has been added to `S`
     */
    public boolean addAllElements(TreeSet<Comparable> x) {
        boolean all = true;
        for (Comparable e : x) {
            if (!set.add(e)) {
                all = false;
            }
        }
        return all;
    }

    /**
     * Delete the specified element from the set `S` of this component and from
     * all the rule containing it.
     *
     * @param e the comparable to be added
     *
     * @return true if the element has been added to `S`
     */
    public boolean deleteElement(Comparable e) {
        if (set.contains(e)) {
            set.remove(e);
            ImplicationalSystem save = new ImplicationalSystem(this);
            for (Rule rule : save.sigma) {
                Rule newR = new Rule(rule.getPremise(), rule.getConclusion());
                newR.removeFromPremise(e);
                newR.removeFromConclusion(e);
                if (!rule.equals(newR)) {
                    if (newR.getConclusion().size() != 0) {
                        this.replaceRule(rule, newR);
                    } else {
                        this.removeRule(rule);
                    }
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Checks if the set S of this component contains the elements of the
     * specified rule.
     *
     * @param rule the rule to be checked
     *
     * @return true if `S` contains all the elements of the rule
     */
    public boolean checkRuleElements(Rule rule) {
        for (Object e : rule.getPremise()) {
            if (!set.contains(e)) {
                return false;
            }
        }
        for (Object e : rule.getConclusion()) {
            if (!set.contains(e)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Checks if this component already contains the specified rule.
     *
     * Rules are compared according to their premise and conclusion
     *
     * @param rule the rule to be tested
     *
     * @return true if `sigma` contains the rule
     */
    public boolean containsRule(Rule rule) {
        return this.sigma.contains(rule);
    }

    /**
     * Adds the specified rule to this component.
     *
     * @param rule the rule to be added
     *
     * @return true the rule has been added to if `sigma`
     */
    public boolean addRule(Rule rule) {
        if (!this.containsRule(rule) && this.checkRuleElements(rule)) {
            return this.sigma.add(rule);
        }
        return false;
    }

    /**
     * Removes the specified rule from the set of rules of this component.
     *
     * @param rule the rule to be removed
     *
     * @return true if the rule has been removed
     */
    public boolean removeRule(Rule rule) {
        return this.sigma.remove(rule);
    }

    /**
     * Replaces the first specified rule by the second one.
     *
     * @param rule1 the rule to be replaced by `rule2`
     * @param rule2 the replacing rule
     *
     * @return true the rule has been replaced
     */
    public boolean replaceRule(Rule rule1, Rule rule2) {
        return this.removeRule(rule1) && this.addRule(rule2);
    }

    /*
     * ----------- SAVING METHODS --------------------
     */
    /**
     * Returns a string representation of this component.
     *
     * The following format is used:
     *
     * An implicational system can be instancied from and save to a text file in
     * the following format:
     * - A list of elements separated by a space in the first line ;
     * - then, each rule on a line, written like [premise] -> [conclusion] where
     * elements are separated by a space.
     *
     * ~~~
     * a b c d e
     * a b -> c
     * d c d -> e
     * ~~~
     *
     * @return a string representation of this component.
     */
    @Override
    public String toString() {
        StringBuilder s = new StringBuilder();
        // first line : All elements of S separated by a space
        // a StringTokenizer is used to delete spaces in the
        // string description of each element of S
        for (Comparable e : this.set) {
            StringTokenizer st = new StringTokenizer(e.toString());
            while (st.hasMoreTokens()) {
                s.append(st.nextToken());
            }
            s.append(" ");
        }
        String newLine = System.getProperty("line.separator");
        s.append(newLine);
        // next lines : a rule on each line, described by:
        // [elements of the premise separated by a space] -> [elements of the conclusion separated by a space]
        for (Rule rule : this.sigma) {
            s.append(rule.toString()).append(newLine);
        }
        return s.toString();
    }

    /**
     * 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
     */
    public void save(final String filename) throws IOException {
        Filer.getInstance().save(this, ImplicationalSystemIOFactory.getInstance(), filename);
    }

    /**
     * Parse the description of this component from a file whose name is
     * specified.
     *
     * @param filename the name of the file
     *
     * @return this for chaining
     *
     * @throws IOException When an IOException occurs
     */
    public ImplicationalSystem parse(final String filename) throws IOException {
        this.init();
        Filer.getInstance().parse(this, ImplicationalSystemIOFactory.getInstance(), filename);
        return this;
    }

    /*
     * ----------- PROPERTIES TEST METHODS --------------------
     */
    /**
     * Returns true if this component is a proper ImplicationalSystem.
     *
     * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
     *
     * @return true if and only if this component is a proper implicational
     *         system.
     */
    public boolean isProper() {
        for (Rule rule : this.sigma) {
            for (Object c : rule.getConclusion()) {
                if (rule.getPremise().contains(c)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Returns true if this component is an unary ImplicationalSystem.
     *
     * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
     *
     * @return true if this component is an unary ImplicationalSystem.
     */
    public boolean isUnary() {
        for (Rule rule : this.sigma) {
            if (rule.getConclusion().size() > 1) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if this component is a compact ImplicationalSystem.
     *
     * This test is perfomed in O(|Sigma|^2|S|) by testing premises of each pair
     * of rules
     *
     * @return true if this component is a compact ImplicationalSystem.
     */
    public boolean isCompact() {
        for (Rule rule1 : this.sigma) {
            for (Rule rule2 : this.sigma) {
                if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Returns true if conclusion of rules of this component are closed.
     *
     * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
     *
     * @return true if conclusion of rules of this component are closed.
     */
    public boolean isRightMaximal() {
        for (Rule rule : this.sigma) {
            if (!rule.getConclusion().containsAll(this.closure(rule.getConclusion()))) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if this component is left minimal.
     *
     * This test is perfomed in O(|Sigma|^2|S|) by testing conclusions of each
     * pair of rules
     *
     * @return true if this component is left minimal.
     */
    public boolean isLeftMinimal() {
        for (Rule rule1 : this.sigma) {
            for (Rule rule2 : this.sigma) {
                if (!rule1.equals(rule2)
                        && rule1.getPremise().containsAll(rule2.getPremise())
                        && rule1.getConclusion().equals(rule2.getConclusion())) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Returns true if this component is direct.
     *
     * This test is perfomed in O(|Sigma|^2|S|) by testing if closure of the
     * premisse of each conclusion can be obtained by only one iteration on the
     * set of rules.
     *
     * @return true if this component is direct.
     */
    public boolean isDirect() {
        for (Rule rule1 : this.sigma) {
            TreeSet<Comparable> onePass = new TreeSet(rule1.getPremise());
            for (Rule rule2 : this.sigma) {
                if (rule1.getPremise().containsAll(rule2.getPremise())) {
                    onePass.addAll(rule2.getConclusion());
                }
            }
            if (!onePass.equals(this.closure(rule1.getPremise()))) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if this component is minimum.
     *
     * This test is perfomed in O(|Sigma|^2|S|) by testing if closure of the
     * premisse of each conclusion can be obtained by only one iteration on the
     * set of rules.
     *
     * @return true if this component is minimum.
     */
    public boolean isMinimum() {
        ImplicationalSystem tmp = new ImplicationalSystem(this);
        tmp.makeRightMaximal();
        for (Rule rule : sigma) {
            ImplicationalSystem epsilon = new ImplicationalSystem(tmp);
            epsilon.removeRule(rule);
            TreeSet<Comparable> clThis = this.closure(rule.getPremise());
            TreeSet<Comparable> clEpsilon = epsilon.closure(rule.getPremise());
            if (clThis.equals(clEpsilon)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if this component is equal to its canonical direct basis.
     *
     * The canonical direct basis is computed before to be compare with this
     * component.
     *
     * This test is performed in O(d|S|), where d corresponds to the number of
     * rules that have to be added by the direct treatment. This number is
     * exponential in the worst case.
     *
     * @return true if this component is equal to its canonical direct basis.
     */
    public boolean isCanonicalDirectBasis() {
        ImplicationalSystem cdb = new ImplicationalSystem(this);
        cdb.makeCanonicalDirectBasis();
        return this.isIncludedIn(cdb) && cdb.isIncludedIn(this);
    }

    /**
     * Returns true if this component is equal to its canonical basis.
     *
     * The canonical basis is computed before to be compare with this component.
     *
     * This treatment is performed in (|Sigma||S|cl) where O(cl) is the
     * computation of a closure.
     *
     * @return true if this component is equal to its canonical basis.
     */
    public boolean isCanonicalBasis() {
        ImplicationalSystem cb = new ImplicationalSystem(this);
        cb.makeCanonicalBasis();
        return this.isIncludedIn(cb) && cb.isIncludedIn(this);
    }

    /**
     * Compares by inclusion of the proper and unary form of this component with
     * the specified one.
     *
     * @param is another ImplicationalSystem
     *
     * @return true if really include in this componenet.
     */
    public boolean isIncludedIn(ImplicationalSystem is) {
        ImplicationalSystem tmp = new ImplicationalSystem(this);
        tmp.makeProper();
        tmp.makeUnary();
        is.makeProper();
        is.makeUnary();
        for (Rule rule : tmp.sigma) {
            if (!is.containsRule(rule)) {
                return false;
            }
        }
        return true;
    }

    /*
     * ----------- PROPERTIES MODIFICATION METHODS --------------------
     */
    /**
     * Makes this component a proper ImplicationalSystem.
     *
     * Elements that are at once in the conclusion and in the premise are
     * deleted from the conclusion. When the obtained conclusion is an empty
     * set, the rule is deleted from this component
     *
     * This treatment is performed in O(|Sigma||S|).
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeProper() {
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule : save.sigma) {
            // deletes elements of conclusion which are in the premise
            Rule newR = new Rule(rule.getPremise(), rule.getConclusion());
            for (Object e : rule.getConclusion()) {
                if (newR.getPremise().contains(e)) {
                    newR.removeFromConclusion(e);
                }
            }
            // replace the rule by the new rule is it has been modified
            if (!rule.equals(newR)) {
                this.replaceRule(rule, newR);
            }
            // delete rule with an empty conclusion
            if (newR.getConclusion().isEmpty()) {
                this.removeRule(newR);
            }
        }
        return save.sizeRules() - this.sizeRules();
    }

    /**
     * Makes this component an unary ImplicationalSystem.
     *
     * This treatment is performed in O(|Sigma||S|)
     *
     * A rule with a non singleton as conclusion is replaced with a sets of
     * rule, one rule for each element of the conclusion.
     *
     * This treatment is performed in O(|Sigma||S|).
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeUnary() {
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule : save.sigma) {
            if (rule.getConclusion().size() > 1) {
                this.removeRule(rule);
                TreeSet<Comparable> conclusion = rule.getConclusion();
                TreeSet<Comparable> premise = rule.getPremise();
                for (Comparable e : conclusion) {
                    TreeSet<Comparable> newC = new TreeSet();
                    newC.add(e);
                    Rule newRule = new Rule(premise, newC);
                    this.addRule(newRule);
                }
            }
        }
        return save.sizeRules() - this.sizeRules();
    }

    /**
     * Replaces rules of same premise by only one rule.
     *
     * This treatment is performed in O(|sigma|^2|S|)
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeCompact() {
        ImplicationalSystem save = new ImplicationalSystem(this);
        int before = this.sigma.size();
        this.sigma = new TreeSet();

        while (save.sigma.size() > 0) {
            Rule rule1 = save.sigma.first();
            ComparableSet newConc = new ComparableSet();
            Iterator<Rule> it2 = save.sigma.iterator();
            while (it2.hasNext()) {
                Rule rule2 = it2.next();
                if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())) {
                    newConc.addAll(rule2.getConclusion());
                    it2.remove();
                }
            }
            if (newConc.size() > 0) {
                newConc.addAll(rule1.getConclusion());
                Rule newR = new Rule(rule1.getPremise(), newConc);
                this.addRule(newR);
            } else {
                this.addRule(rule1);
            }
            save.removeRule(rule1);
        }
        return before - this.sigma.size();
    }

    /**
     * Replaces association rules of same premise, same support and same
     * confidence by only one rule.
     *
     * This treatment is performed in O(|sigma|^2|S|)
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeCompactAssociation() {
        ImplicationalSystem save = new ImplicationalSystem(this);
        int before = this.sigma.size();
        this.sigma = new TreeSet();

        while (save.sigma.size() > 0) {
            AssociationRule rule1 = (AssociationRule) save.sigma.first();
            ComparableSet newConc = new ComparableSet();
            Iterator<Rule> it2 = save.sigma.iterator();
            while (it2.hasNext()) {
                AssociationRule rule2 = (AssociationRule) it2.next();
                if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())
                        && rule1.getConfidence() == rule2.getConfidence() && rule1.getSupport() == rule2.getSupport()) {
                    newConc.addAll(rule2.getConclusion());
                    it2.remove();
                }
            }
            if (newConc.size() > 0) {
                newConc.addAll(rule1.getConclusion());
                AssociationRule newR = new AssociationRule(rule1.getPremise(), newConc, rule1.getSupport(), rule1.getConfidence());
                this.addRule(newR);
            } else {
                this.addRule(rule1);
            }
            save.removeRule(rule1);
        }
        return before - this.sigma.size();
    }

    /**
     * Replaces conclusion of each rule with their closure without the premise.
     *
     * This treatment is performed in O(|sigma||S|cl), where O(cl) is the
     * computation of a closure.
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeRightMaximal() {
        int s = this.sizeRules();
        this.makeCompact();
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule : save.sigma) {
            Rule newR = new Rule(rule.getPremise(), this.closure(rule.getPremise()));
            if (!rule.equals(newR)) {
                this.replaceRule(rule, newR);
            }
        }
        return s - this.sizeRules();
    }

    /**
     * Makes this component a left minimal and compact ImplicationalSystem.
     *
     * The unary form of this componant is first computed: if two rules have the
     * same unary conclusion, the rule with the inclusion-maximal premise is
     * deleted.
     *
     * Then, the left-minimal treatment is performed in O(|sigma|^2|S|))
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeLeftMinimal() {
        this.makeUnary();
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule1 : save.sigma) {
            for (Rule rule2 : save.sigma) {
                if (!rule1.equals(rule2)
                        && rule2.getPremise().containsAll(rule1.getPremise())
                        && rule1.getConclusion().equals(rule2.getConclusion())) {
                    this.sigma.remove(rule2);
                }
            }
        }
        this.makeCompact();
        return save.sizeRules() - this.sizeRules();
    }

    /**
     * Makes this component a compact and direct ImplicationalSystem.
     *
     * The unary and proper form of this componant is first computed. For two
     * given rules rule1 and rule2, if the conclusion of rule1 contains the
     * premise of rule1, then a new rule is addes, with rule1.premisse +
     * rule2.premisse - rule1.conclusion as premise, and rule2.conclusion as
     * conlusion. This treatment is performed in a recursive way until no new
     * rule is added.
     *
     * This treatment is performed in O(d|S|), where d corresponds to the number
     * of rules that have to be added by the direct treatment, that can be
     * exponential in the worst case.
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeDirect() {
        this.makeUnary();
        this.makeProper();
        int s = this.sizeRules();
        boolean ok = true;
        while (ok) {
            ImplicationalSystem save = new ImplicationalSystem(this);
            for (Rule rule1 : save.sigma) {
                for (Rule rule2 : save.sigma) {
                    if (!rule1.equals(rule2) && !rule1.getPremise().containsAll(rule2.getConclusion())) {
                        ComparableSet c = new ComparableSet(rule2.getPremise());
                        c.removeAll(rule1.getConclusion());
                        c.addAll(rule1.getPremise());
                        if (!c.containsAll(rule2.getPremise())) {
                            Rule newR = new Rule(c, rule2.getConclusion());
                            // new_rule.addAllToPremise (rule1.getPremise());
                            // new_rule.addAllToPremise (rule2.getPremise());
                            // new_rule.removeAllFromPremise(rule1.getConclusion());
                            // new_rule.addAllToConclusion(rule2.getConclusion() );
                            this.addRule(newR);
                        }
                    }
                }
            }
            if (this.sizeRules() == save.sizeRules()) {
                ok = false;
            }
        }
        this.makeCompact();
        return s - this.sizeRules();
    }

    /**
     * Makes this component a minimum and proper ImplicationalSystem.
     *
     * A rule is deleted when the closure of its premisse remains the same even
     * if this rule is suppressed.
     *
     * This treatment is performed in O(|sigma||S|cl) where O(cl) is the
     * computation of a closure.
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeMinimum() {
        this.makeRightMaximal();
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule : save.sigma) {
            ImplicationalSystem epsilon = new ImplicationalSystem(this);
            epsilon.removeRule(rule);
            if (epsilon.closure(rule.getPremise()).equals(this.closure(rule.getPremise()))) {
                this.removeRule(rule);
            }
        }
        return save.sizeRules() - this.sizeRules();
    }

    /**
     * Replace this component by its canonical direct basis.
     *
     * The proper, unary and left minimal form of this component is first
     * computed, before to apply the recursive directe treatment, then the left
     * minimal treatment.
     *
     * This treatment is performed in O(d), where d corresponds to the number of
     * rules that have to be added by the direct treatment. This number is
     * exponential in the worst case.
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeCanonicalDirectBasis() {
        int s = this.sizeRules();
        this.makeProper();
        this.makeLeftMinimal();
        this.makeDirect();
        this.makeLeftMinimal();
        this.makeCompact();
        return s - this.sizeRules();
    }

    /**
     * Replace this component by the canonical basis.
     *
     * Conclusion of each rule is first replaced by its closure. Then, premise
     * of each rule r is replaced by its closure in ImplicationalSystem \ rule.
     * This treatment is performed in (|Sigma||S|cl) where O(cl) is the
     * computation of a closure.
     *
     * @return the difference between the number of rules of this component
     *         before and after this treatment
     */
    public int makeCanonicalBasis() {
        this.makeMinimum();
        ImplicationalSystem save = new ImplicationalSystem(this);
        for (Rule rule : save.sigma) {
            ImplicationalSystem epsilon = new ImplicationalSystem(this);
            epsilon.removeRule(rule);
            Rule tmp = new Rule(epsilon.closure(rule.getPremise()), rule.getConclusion());
            if (!rule.equals(tmp)) {
                this.replaceRule(rule, tmp);
            }
        }
        this.makeProper();
        return save.sizeRules() - this.sizeRules();
    }

    /*
     * --------------- METHODS BASED ON GRAPH ------------
     */
    /**
     * Returns the representative graph of this component.
     *
     * Nodes of the graph are attributes of this components. For each proper
     * rule X+b->a, there is an {X}-valuated edge from a to b. Notice that, for
     * a rule b->a, the edge from a to b is valuated by emptyset. and for the
     * two rules X+b->a and Y+b->a, the edge from a to b is valuated by {X,Y}.
     *
     * @return the representative graph of this component.
     */
    public ConcreteDGraph representativeGraph() {
        ImplicationalSystem tmp = new ImplicationalSystem(this);
        tmp.makeUnary();
        // nodes of the graph are elements not belonging to X
        ConcreteDGraph pred = new ConcreteDGraph();
        TreeMap<Comparable, Node> nodeCreated = new TreeMap<Comparable, Node>();
        for (Comparable x : tmp.getSet()) {
            Node node = new Node(x);
            pred.addNode(node);
            nodeCreated.put(x, node);
        }
        // an edge is added from b to a when there exists a rule X+a -> b or a -> b
        for (Rule rule : tmp.getRules()) {
            for (Object a : rule.getPremise()) {
                ComparableSet diff = new ComparableSet(rule.getPremise());
                diff.remove(a);
                Node source = nodeCreated.get(rule.getConclusion().first());
                Node target = nodeCreated.get(a);
                Edge ed;
                if (pred.containsEdge(source, target)) {
                    ed = pred.getEdge(source, target);
                } else {
                    ed = new Edge(source, target, new TreeSet<ComparableSet>());
                    pred.addEdge(ed);
                }
                ((TreeSet<ComparableSet>) ed.getContent()).add(diff);
            }
        }
        return pred;
    }

    /**
     * Returns the dependency graph of this component.
     *
     * Dependency graph of this component is the representative graph of the
     * canonical direct basis. Therefore, the canonical direct basis has to be
     * generated before to compute its representativ graph, and this treatment
     * is performed in O(d), as for the canonical direct basis generation, where
     * d corresponds to the number of rules that have to be added by the direct
     * treatment. This number is exponential in the worst case.
     *
     * @return the dependency graph of this component.
     */
    public ConcreteDGraph dependencyGraph() {
        ImplicationalSystem bcd = new ImplicationalSystem(this);
        bcd.makeCanonicalDirectBasis();
        bcd.makeUnary();
        return bcd.representativeGraph();
    }

    /**
     * Removes from this component reducible elements.
     *
     * Reducible elements are elements equivalent by closure to others elements.
     * They are computed by `getReducibleElements` of `ClosureSystem` in
     * O(O(|Sigma||S|^2)
     *
     * @return the set of reducibles removed elements, with their equivalent
     *         elements
     */
    public TreeMap<Comparable, TreeSet<Comparable>> reduction() {
        // compute the reducible elements
        TreeMap red = this.getReducibleElements();
        // collect elements implied by nothing
        TreeSet<Comparable> truth = this.closure(new TreeSet<Comparable>());
        // modify each rule
        for (Object x : red.keySet()) {
            TreeSet<Rule> rules = this.sigma;
            rules = (TreeSet<Rule>) rules.clone();
            for (Rule rule : rules) {
                Rule rule2 = new Rule();
                boolean modif = false;
                // replace the reducible element by its equivalent in the premise
                TreeSet premise = rule.getPremise();
                premise = (TreeSet) premise.clone();
                if (premise.contains(x)) {
                    premise.remove(x);
                    premise.addAll((TreeSet) red.get(x));
                    rule2.addAllToPremise(premise);
                    modif = true;
                } else {
                    rule2.addAllToPremise(premise);
                }
                // replace the reducible element by its equivalent in the conclusion
                TreeSet conclusion = rule.getConclusion();
                conclusion = (TreeSet) conclusion.clone();
                if (conclusion.contains(x)) {
                    conclusion.remove(x);
                    conclusion.addAll((TreeSet) red.get(x));
                    rule2.addAllToConclusion(conclusion);
                    modif = true;
                } else {
                    rule2.addAllToConclusion(conclusion);
                }
                // replace the rule if modified
                if (modif) {
                    if (truth.containsAll(rule2.getConclusion())) {
                        this.removeRule(rule); // Conclusions of this rule are always true, thus the rule is useless
                    } else {
                        this.replaceRule(rule, rule2);
                    }
                } else if (truth.containsAll(rule.getConclusion())) {
                    this.removeRule(rule); // Conclusions of this rule are always true, thus the rule is useless
                }
            }
            // remove the reducible elements from the elements set
            this.deleteElement((Comparable) x);
        }
        return red;
    }

    /**
     * Return true if this component is reduced.
     *
     * @return true if this component is reduced.
     */
    public boolean isReduced() {
        // Copy this component not to modify it
        ImplicationalSystem tmp = new ImplicationalSystem(this);
        return tmp.reduction().isEmpty();
    }

    /*
     * --------------- IMPLEMENTATION OF CLOSURESYSTEM ABSTRACT METHODS ------------
     */
    /**
     * Builds the closure of a set X of indexed elements.
     *
     * The closure is initialised with X. The closure is incremented with the
     * conclusion of each rule whose premise is included in it. Iterations over
     * the rules are performed until no new element has to be added in the
     * closure.
     *
     * For direct ImplicationalSystem, only one iteration is needed, and the
     * treatment is performed in O(|Sigma||S|).
     *
     * For non direct ImplicationalSystem, at most |S| iterations are needed,
     * and this tratment is performed in O(|Sigma||S|^2).
     *
     * @param x a TreeSet of indexed elements
     *
     * @return the closure of X for this component
     */
    public TreeSet<Comparable> closure(TreeSet<Comparable> x) {
        TreeSet<Comparable> oldES = new TreeSet<Comparable>();
        // all the attributes are in their own closure
        TreeSet<Comparable> newES = new TreeSet<Comparable>(x);
        do {
            oldES.addAll(newES);
            for (Rule rule : this.sigma) {
                if (newES.containsAll(rule.getPremise()) || rule.getPremise().isEmpty()) {
                    newES.addAll(rule.getConclusion());
                }
            }
        } while (!oldES.equals(newES));
        return newES;
    }
}