View Javadoc
1   package org.thegalactic.rule;
2   
3   /*
4    * ImplicationalSystem.java
5    *
6    * Copyright: 2010-2015 Karell Bertet, France
7    * Copyright: 2015-2016 The Galactic Organization, France
8    *
9    * License: http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html CeCILL-B license
10   *
11   * This file is part of java-lattices.
12   * You can redistribute it and/or modify it under the terms of the CeCILL-B license.
13   */
14  import java.io.IOException;
15  import java.util.Collection;
16  import java.util.Collections;
17  import java.util.Iterator;
18  import java.util.SortedSet;
19  import java.util.StringTokenizer;
20  import java.util.TreeMap;
21  import java.util.TreeSet;
22  
23  import org.thegalactic.dgraph.ConcreteDGraph;
24  import org.thegalactic.dgraph.Edge;
25  import org.thegalactic.dgraph.Node;
26  import org.thegalactic.io.Filer;
27  import org.thegalactic.lattice.ClosureSystem;
28  import org.thegalactic.lattice.io.ImplicationalSystemIOFactory;
29  import org.thegalactic.util.ComparableSet;
30  
31  /**
32   * This class gives a representation for an implicational system
33   * (ImplicationalSystem), a set of rules.
34   *
35   * An ImplicationalSystem is composed of a TreeSet of comparable elements, and a
36   * TreeSet of rules defined by class {@link Rule}.
37   *
38   * This class provides methods implementing classical transformation of an
39   * implicational system : make proper, make minimum, make right maximal, make
40   * left minimal, make unary, make canonical basis, make canonical direct basis
41   * and reduction.
42   *
43   * An implicational system owns properties of a closure system, and thus extends
44   * the abstract class {@link ClosureSystem} and implements methods
45   * {@link #getSet} and {@link #closure}.
46   *
47   * Therefore, the closed set lattice of an ImplicationalSystem can be generated
48   * by invoking method {@link #closedSetLattice} of a closure system.
49   *
50   * An implicational system can be instancied from and save to a text file in the
51   * following format: - a list of elements separated by a space in the first line
52   * ; - then, each rule on a line, written like [premise] -> [conclusion] where
53   * elements are separated by a space.
54   *
55   * ~~~
56   * a b c d e
57   * a b -> c d
58   * c d -> e
59   * ~~~
60   *
61   * ![ImplicationalSystem](ImplicationalSystem.png)
62   *
63   * @uml ImplicationalSystem.png
64   * !include resources/org/thegalactic/lattice/ImplicationalSystem.iuml
65   * !include resources/org/thegalactic/lattice/ClosureSystem.iuml
66   *
67   * hide members
68   * show ImplicationalSystem members
69   * class ImplicationalSystem #LightCyan
70   * title ImplicationalSystem UML graph
71   */
72  public class ImplicationalSystem extends ClosureSystem {
73  
74      /*
75       * --------------- FIELDS -----------------
76       */
77      /**
78       * Generates a random ImplicationalSystem with a specified number of nodes
79       * and rules.
80       *
81       * @param nbS the number of nodes of the generated ImplicationalSystem
82       * @param nbR the number of rules of the generated ImplicationalSystem
83       *
84       * @return a random implicational system with a specified number of nodes
85       *         and rules.
86       */
87      public static ImplicationalSystem random(int nbS, int nbR) {
88          ImplicationalSystem sigma = new ImplicationalSystem();
89          // addition of elements
90          for (int i = 0; i < nbS; i++) {
91              sigma.addElement(Integer.valueOf(i));
92          }
93          // addition of rules
94          //for (int i = 0; i < nbR; i++) { One could get twice the same rule ...
95          while (sigma.getRules().size() < nbR) {
96              ComparableSet conclusion = new ComparableSet();
97              int choice = (int) Math.rint(nbS * Math.random());
98              int j = 1;
99              for (Comparable c : sigma.getSet()) {
100                 if (j == choice) {
101                     conclusion.add(c);
102                 }
103                 j++;
104             }
105             ComparableSet premisse = new ComparableSet();
106             for (Comparable c : sigma.getSet()) {
107                 choice = (int) Math.rint(nbS * Math.random());
108                 if (choice < nbS / 5) {
109                     premisse.add(c);
110                 }
111             }
112             //if (!premisse.isEmpty()) {
113             sigma.addRule(new Rule(premisse, conclusion));
114             //}
115         }
116         return sigma;
117     }
118 
119     /**
120      * For the implicational rules of this component.
121      */
122     private TreeSet<Rule> sigma;
123 
124     /**
125      * For the elements space of this component.
126      */
127     private TreeSet<Comparable> set;
128 
129     /*
130      * --------------- CONSTRUCTORS -----------
131      */
132     /**
133      * Constructs a new empty component.
134      */
135     public ImplicationalSystem() {
136         this.init();
137     }
138 
139     /**
140      * Constructs this component from the specified set of rules `sigma`.
141      *
142      * @param sigma the set of rules.
143      */
144     public ImplicationalSystem(Collection<Rule> sigma) {
145         this.sigma = new TreeSet<Rule>(sigma);
146         this.set = new TreeSet<Comparable>();
147         for (Rule rule : this.sigma) {
148             set.addAll(rule.getPremise());
149             set.addAll(rule.getConclusion());
150         }
151     }
152 
153     /**
154      * Constructs this component as a copy of the specified ImplicationalSystem
155      * `is`.
156      *
157      * Only structures (conataining reference of indexed elements) are copied.
158      *
159      * @param is the implicational system to be copied
160      */
161     public ImplicationalSystem(ImplicationalSystem is) {
162         this.sigma = new TreeSet<Rule>(is.getRules());
163         this.set = new TreeSet<Comparable>(is.getSet());
164     }
165 
166     /**
167      * Constructs this component from the specified file.
168      *
169      * The file have to respect a certain format:
170      *
171      * An implicational system can be instancied from and save to a text file in
172      * the following format: - A list of elements separated by a space in the
173      * first line ; - then, each rule on a line, written like [premise] ->
174      * [conclusion] where elements are separated by a space.
175      *
176      * ~~~
177      * a b c d e a b -> c d c d -> e
178      * ~~~
179      *
180      * Each element must be declared on the first line, otherwise, it is not
181      * added in the rule Each rule must have a non empty concusion, otherwise,
182      * it is not added in the component
183      *
184      * @param filename the name of the file
185      *
186      * @throws IOException When an IOException occurs
187      */
188     public ImplicationalSystem(String filename) throws IOException {
189         this.parse(filename);
190     }
191 
192     /**
193      * Initialise the implicational system.
194      *
195      * @return this for chaining
196      */
197     public ImplicationalSystem init() {
198         this.sigma = new TreeSet<Rule>();
199         this.set = new TreeSet<Comparable>();
200         return this;
201     }
202 
203     /*
204      * ------------- ACCESSORS METHODS ------------------
205      */
206     /**
207      * Returns the set of rules.
208      *
209      * @return the set of rules of this component.
210      */
211     public SortedSet<Rule> getRules() {
212         return Collections.unmodifiableSortedSet((SortedSet<Rule>) this.sigma);
213     }
214 
215     /**
216      * Returns the set of indexed elements.
217      *
218      * @return the elements space of this component.
219      */
220     public SortedSet<Comparable> getSet() {
221         return Collections.unmodifiableSortedSet((SortedSet<Comparable>) this.set);
222     }
223 
224     /**
225      * Returns the number of elements in the set S of this component.
226      *
227      * @return the number of elements in the elements space of this component.
228      */
229     public int sizeElements() {
230         return this.set.size();
231     }
232 
233     /**
234      * Returns the number of rules of this component.
235      *
236      * @return the number of rules of this component.
237      */
238     public int sizeRules() {
239         return this.sigma.size();
240     }
241 
242     /*
243      * ------------- MODIFICATION METHODS ------------------
244      */
245     /**
246      * Adds the specified element to the set `S` of this component.
247      *
248      * @param e the comparable to be added
249      *
250      * @return true if the element has been added to `S`
251      */
252     public boolean addElement(Comparable e) {
253         return set.add(e);
254     }
255 
256     /**
257      * Adds the specified element to the set `S` of this component.
258      *
259      * @param x the treeset of comparable to be added
260      *
261      * @return true if the element has been added to `S`
262      */
263     public boolean addAllElements(TreeSet<Comparable> x) {
264         boolean all = true;
265         for (Comparable e : x) {
266             if (!set.add(e)) {
267                 all = false;
268             }
269         }
270         return all;
271     }
272 
273     /**
274      * Delete the specified element from the set `S` of this component and from
275      * all the rule containing it.
276      *
277      * @param e the comparable to be added
278      *
279      * @return true if the element has been added to `S`
280      */
281     public boolean deleteElement(Comparable e) {
282         if (set.contains(e)) {
283             set.remove(e);
284             ImplicationalSystem save = new ImplicationalSystem(this);
285             for (Rule rule : save.sigma) {
286                 Rule newR = new Rule(rule.getPremise(), rule.getConclusion());
287                 newR.removeFromPremise(e);
288                 newR.removeFromConclusion(e);
289                 if (!rule.equals(newR)) {
290                     if (newR.getConclusion().size() != 0) {
291                         this.replaceRule(rule, newR);
292                     } else {
293                         this.removeRule(rule);
294                     }
295                 }
296             }
297             return true;
298         }
299         return false;
300     }
301 
302     /**
303      * Checks if the set S of this component contains the elements of the
304      * specified rule.
305      *
306      * @param rule the rule to be checked
307      *
308      * @return true if `S` contains all the elements of the rule
309      */
310     public boolean checkRuleElements(Rule rule) {
311         for (Object e : rule.getPremise()) {
312             if (!set.contains(e)) {
313                 return false;
314             }
315         }
316         for (Object e : rule.getConclusion()) {
317             if (!set.contains(e)) {
318                 return false;
319             }
320         }
321         return true;
322     }
323 
324     /**
325      * Checks if this component already contains the specified rule.
326      *
327      * Rules are compared according to their premise and conclusion
328      *
329      * @param rule the rule to be tested
330      *
331      * @return true if `sigma` contains the rule
332      */
333     public boolean containsRule(Rule rule) {
334         return this.sigma.contains(rule);
335     }
336 
337     /**
338      * Adds the specified rule to this component.
339      *
340      * @param rule the rule to be added
341      *
342      * @return true the rule has been added to if `sigma`
343      */
344     public boolean addRule(Rule rule) {
345         if (!this.containsRule(rule) && this.checkRuleElements(rule)) {
346             return this.sigma.add(rule);
347         }
348         return false;
349     }
350 
351     /**
352      * Removes the specified rule from the set of rules of this component.
353      *
354      * @param rule the rule to be removed
355      *
356      * @return true if the rule has been removed
357      */
358     public boolean removeRule(Rule rule) {
359         return this.sigma.remove(rule);
360     }
361 
362     /**
363      * Replaces the first specified rule by the second one.
364      *
365      * @param rule1 the rule to be replaced by `rule2`
366      * @param rule2 the replacing rule
367      *
368      * @return true the rule has been replaced
369      */
370     public boolean replaceRule(Rule rule1, Rule rule2) {
371         return this.removeRule(rule1) && this.addRule(rule2);
372     }
373 
374     /*
375      * ----------- SAVING METHODS --------------------
376      */
377     /**
378      * Returns a string representation of this component.
379      *
380      * The following format is used:
381      *
382      * An implicational system can be instancied from and save to a text file in
383      * the following format:
384      * - A list of elements separated by a space in the first line ;
385      * - then, each rule on a line, written like [premise] -> [conclusion] where
386      * elements are separated by a space.
387      *
388      * ~~~
389      * a b c d e
390      * a b -> c
391      * d c d -> e
392      * ~~~
393      *
394      * @return a string representation of this component.
395      */
396     @Override
397     public String toString() {
398         StringBuilder s = new StringBuilder();
399         // first line : All elements of S separated by a space
400         // a StringTokenizer is used to delete spaces in the
401         // string description of each element of S
402         for (Comparable e : this.set) {
403             StringTokenizer st = new StringTokenizer(e.toString());
404             while (st.hasMoreTokens()) {
405                 s.append(st.nextToken());
406             }
407             s.append(" ");
408         }
409         String newLine = System.getProperty("line.separator");
410         s.append(newLine);
411         // next lines : a rule on each line, described by:
412         // [elements of the premise separated by a space] -> [elements of the conclusion separated by a space]
413         for (Rule rule : this.sigma) {
414             s.append(rule.toString()).append(newLine);
415         }
416         return s.toString();
417     }
418 
419     /**
420      * Save the description of this component in a file whose name is specified.
421      *
422      * @param filename the name of the file
423      *
424      * @throws IOException When an IOException occurs
425      */
426     public void save(final String filename) throws IOException {
427         Filer.getInstance().save(this, ImplicationalSystemIOFactory.getInstance(), filename);
428     }
429 
430     /**
431      * Parse the description of this component from a file whose name is
432      * specified.
433      *
434      * @param filename the name of the file
435      *
436      * @return this for chaining
437      *
438      * @throws IOException When an IOException occurs
439      */
440     public ImplicationalSystem parse(final String filename) throws IOException {
441         this.init();
442         Filer.getInstance().parse(this, ImplicationalSystemIOFactory.getInstance(), filename);
443         return this;
444     }
445 
446     /*
447      * ----------- PROPERTIES TEST METHODS --------------------
448      */
449     /**
450      * Returns true if this component is a proper ImplicationalSystem.
451      *
452      * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
453      *
454      * @return true if and only if this component is a proper implicational
455      *         system.
456      */
457     public boolean isProper() {
458         for (Rule rule : this.sigma) {
459             for (Object c : rule.getConclusion()) {
460                 if (rule.getPremise().contains(c)) {
461                     return false;
462                 }
463             }
464         }
465         return true;
466     }
467 
468     /**
469      * Returns true if this component is an unary ImplicationalSystem.
470      *
471      * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
472      *
473      * @return true if this component is an unary ImplicationalSystem.
474      */
475     public boolean isUnary() {
476         for (Rule rule : this.sigma) {
477             if (rule.getConclusion().size() > 1) {
478                 return false;
479             }
480         }
481         return true;
482     }
483 
484     /**
485      * Returns true if this component is a compact ImplicationalSystem.
486      *
487      * This test is perfomed in O(|Sigma|^2|S|) by testing premises of each pair
488      * of rules
489      *
490      * @return true if this component is a compact ImplicationalSystem.
491      */
492     public boolean isCompact() {
493         for (Rule rule1 : this.sigma) {
494             for (Rule rule2 : this.sigma) {
495                 if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())) {
496                     return false;
497                 }
498             }
499         }
500         return true;
501     }
502 
503     /**
504      * Returns true if conclusion of rules of this component are closed.
505      *
506      * This test is perfomed in O(|Sigma||S|) by testing conclusion of each rule
507      *
508      * @return true if conclusion of rules of this component are closed.
509      */
510     public boolean isRightMaximal() {
511         for (Rule rule : this.sigma) {
512             if (!rule.getConclusion().containsAll(this.closure(rule.getConclusion()))) {
513                 return false;
514             }
515         }
516         return true;
517     }
518 
519     /**
520      * Returns true if this component is left minimal.
521      *
522      * This test is perfomed in O(|Sigma|^2|S|) by testing conclusions of each
523      * pair of rules
524      *
525      * @return true if this component is left minimal.
526      */
527     public boolean isLeftMinimal() {
528         for (Rule rule1 : this.sigma) {
529             for (Rule rule2 : this.sigma) {
530                 if (!rule1.equals(rule2)
531                         && rule1.getPremise().containsAll(rule2.getPremise())
532                         && rule1.getConclusion().equals(rule2.getConclusion())) {
533                     return false;
534                 }
535             }
536         }
537         return true;
538     }
539 
540     /**
541      * Returns true if this component is direct.
542      *
543      * This test is perfomed in O(|Sigma|^2|S|) by testing if closure of the
544      * premisse of each conclusion can be obtained by only one iteration on the
545      * set of rules.
546      *
547      * @return true if this component is direct.
548      */
549     public boolean isDirect() {
550         for (Rule rule1 : this.sigma) {
551             TreeSet<Comparable> onePass = new TreeSet(rule1.getPremise());
552             for (Rule rule2 : this.sigma) {
553                 if (rule1.getPremise().containsAll(rule2.getPremise())) {
554                     onePass.addAll(rule2.getConclusion());
555                 }
556             }
557             if (!onePass.equals(this.closure(rule1.getPremise()))) {
558                 return false;
559             }
560         }
561         return true;
562     }
563 
564     /**
565      * Returns true if this component is minimum.
566      *
567      * This test is perfomed in O(|Sigma|^2|S|) by testing if closure of the
568      * premisse of each conclusion can be obtained by only one iteration on the
569      * set of rules.
570      *
571      * @return true if this component is minimum.
572      */
573     public boolean isMinimum() {
574         ImplicationalSystem tmp = new ImplicationalSystem(this);
575         tmp.makeRightMaximal();
576         for (Rule rule : sigma) {
577             ImplicationalSystem epsilon = new ImplicationalSystem(tmp);
578             epsilon.removeRule(rule);
579             TreeSet<Comparable> clThis = this.closure(rule.getPremise());
580             TreeSet<Comparable> clEpsilon = epsilon.closure(rule.getPremise());
581             if (clThis.equals(clEpsilon)) {
582                 return false;
583             }
584         }
585         return true;
586     }
587 
588     /**
589      * Returns true if this component is equal to its canonical direct basis.
590      *
591      * The canonical direct basis is computed before to be compare with this
592      * component.
593      *
594      * This test is performed in O(d|S|), where d corresponds to the number of
595      * rules that have to be added by the direct treatment. This number is
596      * exponential in the worst case.
597      *
598      * @return true if this component is equal to its canonical direct basis.
599      */
600     public boolean isCanonicalDirectBasis() {
601         ImplicationalSystem cdb = new ImplicationalSystem(this);
602         cdb.makeCanonicalDirectBasis();
603         return this.isIncludedIn(cdb) && cdb.isIncludedIn(this);
604     }
605 
606     /**
607      * Returns true if this component is equal to its canonical basis.
608      *
609      * The canonical basis is computed before to be compare with this component.
610      *
611      * This treatment is performed in (|Sigma||S|cl) where O(cl) is the
612      * computation of a closure.
613      *
614      * @return true if this component is equal to its canonical basis.
615      */
616     public boolean isCanonicalBasis() {
617         ImplicationalSystem cb = new ImplicationalSystem(this);
618         cb.makeCanonicalBasis();
619         return this.isIncludedIn(cb) && cb.isIncludedIn(this);
620     }
621 
622     /**
623      * Compares by inclusion of the proper and unary form of this component with
624      * the specified one.
625      *
626      * @param is another ImplicationalSystem
627      *
628      * @return true if really include in this componenet.
629      */
630     public boolean isIncludedIn(ImplicationalSystem is) {
631         ImplicationalSystem tmp = new ImplicationalSystem(this);
632         tmp.makeProper();
633         tmp.makeUnary();
634         is.makeProper();
635         is.makeUnary();
636         for (Rule rule : tmp.sigma) {
637             if (!is.containsRule(rule)) {
638                 return false;
639             }
640         }
641         return true;
642     }
643 
644     /*
645      * ----------- PROPERTIES MODIFICATION METHODS --------------------
646      */
647     /**
648      * Makes this component a proper ImplicationalSystem.
649      *
650      * Elements that are at once in the conclusion and in the premise are
651      * deleted from the conclusion. When the obtained conclusion is an empty
652      * set, the rule is deleted from this component
653      *
654      * This treatment is performed in O(|Sigma||S|).
655      *
656      * @return the difference between the number of rules of this component
657      *         before and after this treatment
658      */
659     public int makeProper() {
660         ImplicationalSystem save = new ImplicationalSystem(this);
661         for (Rule rule : save.sigma) {
662             // deletes elements of conclusion which are in the premise
663             Rule newR = new Rule(rule.getPremise(), rule.getConclusion());
664             for (Object e : rule.getConclusion()) {
665                 if (newR.getPremise().contains(e)) {
666                     newR.removeFromConclusion(e);
667                 }
668             }
669             // replace the rule by the new rule is it has been modified
670             if (!rule.equals(newR)) {
671                 this.replaceRule(rule, newR);
672             }
673             // delete rule with an empty conclusion
674             if (newR.getConclusion().isEmpty()) {
675                 this.removeRule(newR);
676             }
677         }
678         return save.sizeRules() - this.sizeRules();
679     }
680 
681     /**
682      * Makes this component an unary ImplicationalSystem.
683      *
684      * This treatment is performed in O(|Sigma||S|)
685      *
686      * A rule with a non singleton as conclusion is replaced with a sets of
687      * rule, one rule for each element of the conclusion.
688      *
689      * This treatment is performed in O(|Sigma||S|).
690      *
691      * @return the difference between the number of rules of this component
692      *         before and after this treatment
693      */
694     public int makeUnary() {
695         ImplicationalSystem save = new ImplicationalSystem(this);
696         for (Rule rule : save.sigma) {
697             if (rule.getConclusion().size() > 1) {
698                 this.removeRule(rule);
699                 TreeSet<Comparable> conclusion = rule.getConclusion();
700                 TreeSet<Comparable> premise = rule.getPremise();
701                 for (Comparable e : conclusion) {
702                     TreeSet<Comparable> newC = new TreeSet();
703                     newC.add(e);
704                     Rule newRule = new Rule(premise, newC);
705                     this.addRule(newRule);
706                 }
707             }
708         }
709         return save.sizeRules() - this.sizeRules();
710     }
711 
712     /**
713      * Replaces rules of same premise by only one rule.
714      *
715      * This treatment is performed in O(|sigma|^2|S|)
716      *
717      * @return the difference between the number of rules of this component
718      *         before and after this treatment
719      */
720     public int makeCompact() {
721         ImplicationalSystem save = new ImplicationalSystem(this);
722         int before = this.sigma.size();
723         this.sigma = new TreeSet();
724 
725         while (save.sigma.size() > 0) {
726             Rule rule1 = save.sigma.first();
727             ComparableSet newConc = new ComparableSet();
728             Iterator<Rule> it2 = save.sigma.iterator();
729             while (it2.hasNext()) {
730                 Rule rule2 = it2.next();
731                 if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())) {
732                     newConc.addAll(rule2.getConclusion());
733                     it2.remove();
734                 }
735             }
736             if (newConc.size() > 0) {
737                 newConc.addAll(rule1.getConclusion());
738                 Rule newR = new Rule(rule1.getPremise(), newConc);
739                 this.addRule(newR);
740             } else {
741                 this.addRule(rule1);
742             }
743             save.removeRule(rule1);
744         }
745         return before - this.sigma.size();
746     }
747 
748     /**
749      * Replaces association rules of same premise, same support and same
750      * confidence by only one rule.
751      *
752      * This treatment is performed in O(|sigma|^2|S|)
753      *
754      * @return the difference between the number of rules of this component
755      *         before and after this treatment
756      */
757     public int makeCompactAssociation() {
758         ImplicationalSystem save = new ImplicationalSystem(this);
759         int before = this.sigma.size();
760         this.sigma = new TreeSet();
761 
762         while (save.sigma.size() > 0) {
763             AssociationRule rule1 = (AssociationRule) save.sigma.first();
764             ComparableSet newConc = new ComparableSet();
765             Iterator<Rule> it2 = save.sigma.iterator();
766             while (it2.hasNext()) {
767                 AssociationRule rule2 = (AssociationRule) it2.next();
768                 if (!rule1.equals(rule2) && rule1.getPremise().equals(rule2.getPremise())
769                         && rule1.getConfidence() == rule2.getConfidence() && rule1.getSupport() == rule2.getSupport()) {
770                     newConc.addAll(rule2.getConclusion());
771                     it2.remove();
772                 }
773             }
774             if (newConc.size() > 0) {
775                 newConc.addAll(rule1.getConclusion());
776                 AssociationRule newR = new AssociationRule(rule1.getPremise(), newConc, rule1.getSupport(), rule1.getConfidence());
777                 this.addRule(newR);
778             } else {
779                 this.addRule(rule1);
780             }
781             save.removeRule(rule1);
782         }
783         return before - this.sigma.size();
784     }
785 
786     /**
787      * Replaces conclusion of each rule with their closure without the premise.
788      *
789      * This treatment is performed in O(|sigma||S|cl), where O(cl) is the
790      * computation of a closure.
791      *
792      * @return the difference between the number of rules of this component
793      *         before and after this treatment
794      */
795     public int makeRightMaximal() {
796         int s = this.sizeRules();
797         this.makeCompact();
798         ImplicationalSystem save = new ImplicationalSystem(this);
799         for (Rule rule : save.sigma) {
800             Rule newR = new Rule(rule.getPremise(), this.closure(rule.getPremise()));
801             if (!rule.equals(newR)) {
802                 this.replaceRule(rule, newR);
803             }
804         }
805         return s - this.sizeRules();
806     }
807 
808     /**
809      * Makes this component a left minimal and compact ImplicationalSystem.
810      *
811      * The unary form of this componant is first computed: if two rules have the
812      * same unary conclusion, the rule with the inclusion-maximal premise is
813      * deleted.
814      *
815      * Then, the left-minimal treatment is performed in O(|sigma|^2|S|))
816      *
817      * @return the difference between the number of rules of this component
818      *         before and after this treatment
819      */
820     public int makeLeftMinimal() {
821         this.makeUnary();
822         ImplicationalSystem save = new ImplicationalSystem(this);
823         for (Rule rule1 : save.sigma) {
824             for (Rule rule2 : save.sigma) {
825                 if (!rule1.equals(rule2)
826                         && rule2.getPremise().containsAll(rule1.getPremise())
827                         && rule1.getConclusion().equals(rule2.getConclusion())) {
828                     this.sigma.remove(rule2);
829                 }
830             }
831         }
832         this.makeCompact();
833         return save.sizeRules() - this.sizeRules();
834     }
835 
836     /**
837      * Makes this component a compact and direct ImplicationalSystem.
838      *
839      * The unary and proper form of this componant is first computed. For two
840      * given rules rule1 and rule2, if the conclusion of rule1 contains the
841      * premise of rule1, then a new rule is addes, with rule1.premisse +
842      * rule2.premisse - rule1.conclusion as premise, and rule2.conclusion as
843      * conlusion. This treatment is performed in a recursive way until no new
844      * rule is added.
845      *
846      * This treatment is performed in O(d|S|), where d corresponds to the number
847      * of rules that have to be added by the direct treatment, that can be
848      * exponential in the worst case.
849      *
850      * @return the difference between the number of rules of this component
851      *         before and after this treatment
852      */
853     public int makeDirect() {
854         this.makeUnary();
855         this.makeProper();
856         int s = this.sizeRules();
857         boolean ok = true;
858         while (ok) {
859             ImplicationalSystem save = new ImplicationalSystem(this);
860             for (Rule rule1 : save.sigma) {
861                 for (Rule rule2 : save.sigma) {
862                     if (!rule1.equals(rule2) && !rule1.getPremise().containsAll(rule2.getConclusion())) {
863                         ComparableSet c = new ComparableSet(rule2.getPremise());
864                         c.removeAll(rule1.getConclusion());
865                         c.addAll(rule1.getPremise());
866                         if (!c.containsAll(rule2.getPremise())) {
867                             Rule newR = new Rule(c, rule2.getConclusion());
868                             // new_rule.addAllToPremise (rule1.getPremise());
869                             // new_rule.addAllToPremise (rule2.getPremise());
870                             // new_rule.removeAllFromPremise(rule1.getConclusion());
871                             // new_rule.addAllToConclusion(rule2.getConclusion() );
872                             this.addRule(newR);
873                         }
874                     }
875                 }
876             }
877             if (this.sizeRules() == save.sizeRules()) {
878                 ok = false;
879             }
880         }
881         this.makeCompact();
882         return s - this.sizeRules();
883     }
884 
885     /**
886      * Makes this component a minimum and proper ImplicationalSystem.
887      *
888      * A rule is deleted when the closure of its premisse remains the same even
889      * if this rule is suppressed.
890      *
891      * This treatment is performed in O(|sigma||S|cl) where O(cl) is the
892      * computation of a closure.
893      *
894      * @return the difference between the number of rules of this component
895      *         before and after this treatment
896      */
897     public int makeMinimum() {
898         this.makeRightMaximal();
899         ImplicationalSystem save = new ImplicationalSystem(this);
900         for (Rule rule : save.sigma) {
901             ImplicationalSystem epsilon = new ImplicationalSystem(this);
902             epsilon.removeRule(rule);
903             if (epsilon.closure(rule.getPremise()).equals(this.closure(rule.getPremise()))) {
904                 this.removeRule(rule);
905             }
906         }
907         return save.sizeRules() - this.sizeRules();
908     }
909 
910     /**
911      * Replace this component by its canonical direct basis.
912      *
913      * The proper, unary and left minimal form of this component is first
914      * computed, before to apply the recursive directe treatment, then the left
915      * minimal treatment.
916      *
917      * This treatment is performed in O(d), where d corresponds to the number of
918      * rules that have to be added by the direct treatment. This number is
919      * exponential in the worst case.
920      *
921      * @return the difference between the number of rules of this component
922      *         before and after this treatment
923      */
924     public int makeCanonicalDirectBasis() {
925         int s = this.sizeRules();
926         this.makeProper();
927         this.makeLeftMinimal();
928         this.makeDirect();
929         this.makeLeftMinimal();
930         this.makeCompact();
931         return s - this.sizeRules();
932     }
933 
934     /**
935      * Replace this component by the canonical basis.
936      *
937      * Conclusion of each rule is first replaced by its closure. Then, premise
938      * of each rule r is replaced by its closure in ImplicationalSystem \ rule.
939      * This treatment is performed in (|Sigma||S|cl) where O(cl) is the
940      * computation of a closure.
941      *
942      * @return the difference between the number of rules of this component
943      *         before and after this treatment
944      */
945     public int makeCanonicalBasis() {
946         this.makeMinimum();
947         ImplicationalSystem save = new ImplicationalSystem(this);
948         for (Rule rule : save.sigma) {
949             ImplicationalSystem epsilon = new ImplicationalSystem(this);
950             epsilon.removeRule(rule);
951             Rule tmp = new Rule(epsilon.closure(rule.getPremise()), rule.getConclusion());
952             if (!rule.equals(tmp)) {
953                 this.replaceRule(rule, tmp);
954             }
955         }
956         this.makeProper();
957         return save.sizeRules() - this.sizeRules();
958     }
959 
960     /*
961      * --------------- METHODS BASED ON GRAPH ------------
962      */
963     /**
964      * Returns the representative graph of this component.
965      *
966      * Nodes of the graph are attributes of this components. For each proper
967      * rule X+b->a, there is an {X}-valuated edge from a to b. Notice that, for
968      * a rule b->a, the edge from a to b is valuated by emptyset. and for the
969      * two rules X+b->a and Y+b->a, the edge from a to b is valuated by {X,Y}.
970      *
971      * @return the representative graph of this component.
972      */
973     public ConcreteDGraph representativeGraph() {
974         ImplicationalSystem tmp = new ImplicationalSystem(this);
975         tmp.makeUnary();
976         // nodes of the graph are elements not belonging to X
977         ConcreteDGraph pred = new ConcreteDGraph();
978         TreeMap<Comparable, Node> nodeCreated = new TreeMap<Comparable, Node>();
979         for (Comparable x : tmp.getSet()) {
980             Node node = new Node(x);
981             pred.addNode(node);
982             nodeCreated.put(x, node);
983         }
984         // an edge is added from b to a when there exists a rule X+a -> b or a -> b
985         for (Rule rule : tmp.getRules()) {
986             for (Object a : rule.getPremise()) {
987                 ComparableSet diff = new ComparableSet(rule.getPremise());
988                 diff.remove(a);
989                 Node source = nodeCreated.get(rule.getConclusion().first());
990                 Node target = nodeCreated.get(a);
991                 Edge ed;
992                 if (pred.containsEdge(source, target)) {
993                     ed = pred.getEdge(source, target);
994                 } else {
995                     ed = new Edge(source, target, new TreeSet<ComparableSet>());
996                     pred.addEdge(ed);
997                 }
998                 ((TreeSet<ComparableSet>) ed.getContent()).add(diff);
999             }
1000         }
1001         return pred;
1002     }
1003 
1004     /**
1005      * Returns the dependency graph of this component.
1006      *
1007      * Dependency graph of this component is the representative graph of the
1008      * canonical direct basis. Therefore, the canonical direct basis has to be
1009      * generated before to compute its representativ graph, and this treatment
1010      * is performed in O(d), as for the canonical direct basis generation, where
1011      * d corresponds to the number of rules that have to be added by the direct
1012      * treatment. This number is exponential in the worst case.
1013      *
1014      * @return the dependency graph of this component.
1015      */
1016     public ConcreteDGraph dependencyGraph() {
1017         ImplicationalSystem bcd = new ImplicationalSystem(this);
1018         bcd.makeCanonicalDirectBasis();
1019         bcd.makeUnary();
1020         return bcd.representativeGraph();
1021     }
1022 
1023     /**
1024      * Removes from this component reducible elements.
1025      *
1026      * Reducible elements are elements equivalent by closure to others elements.
1027      * They are computed by `getReducibleElements` of `ClosureSystem` in
1028      * O(O(|Sigma||S|^2)
1029      *
1030      * @return the set of reducibles removed elements, with their equivalent
1031      *         elements
1032      */
1033     public TreeMap<Comparable, TreeSet<Comparable>> reduction() {
1034         // compute the reducible elements
1035         TreeMap red = this.getReducibleElements();
1036         // collect elements implied by nothing
1037         TreeSet<Comparable> truth = this.closure(new TreeSet<Comparable>());
1038         // modify each rule
1039         for (Object x : red.keySet()) {
1040             TreeSet<Rule> rules = this.sigma;
1041             rules = (TreeSet<Rule>) rules.clone();
1042             for (Rule rule : rules) {
1043                 Rule rule2 = new Rule();
1044                 boolean modif = false;
1045                 // replace the reducible element by its equivalent in the premise
1046                 TreeSet premise = rule.getPremise();
1047                 premise = (TreeSet) premise.clone();
1048                 if (premise.contains(x)) {
1049                     premise.remove(x);
1050                     premise.addAll((TreeSet) red.get(x));
1051                     rule2.addAllToPremise(premise);
1052                     modif = true;
1053                 } else {
1054                     rule2.addAllToPremise(premise);
1055                 }
1056                 // replace the reducible element by its equivalent in the conclusion
1057                 TreeSet conclusion = rule.getConclusion();
1058                 conclusion = (TreeSet) conclusion.clone();
1059                 if (conclusion.contains(x)) {
1060                     conclusion.remove(x);
1061                     conclusion.addAll((TreeSet) red.get(x));
1062                     rule2.addAllToConclusion(conclusion);
1063                     modif = true;
1064                 } else {
1065                     rule2.addAllToConclusion(conclusion);
1066                 }
1067                 // replace the rule if modified
1068                 if (modif) {
1069                     if (truth.containsAll(rule2.getConclusion())) {
1070                         this.removeRule(rule); // Conclusions of this rule are always true, thus the rule is useless
1071                     } else {
1072                         this.replaceRule(rule, rule2);
1073                     }
1074                 } else if (truth.containsAll(rule.getConclusion())) {
1075                     this.removeRule(rule); // Conclusions of this rule are always true, thus the rule is useless
1076                 }
1077             }
1078             // remove the reducible elements from the elements set
1079             this.deleteElement((Comparable) x);
1080         }
1081         return red;
1082     }
1083 
1084     /**
1085      * Return true if this component is reduced.
1086      *
1087      * @return true if this component is reduced.
1088      */
1089     public boolean isReduced() {
1090         // Copy this component not to modify it
1091         ImplicationalSystem tmp = new ImplicationalSystem(this);
1092         return tmp.reduction().isEmpty();
1093     }
1094 
1095     /*
1096      * --------------- IMPLEMENTATION OF CLOSURESYSTEM ABSTRACT METHODS ------------
1097      */
1098     /**
1099      * Builds the closure of a set X of indexed elements.
1100      *
1101      * The closure is initialised with X. The closure is incremented with the
1102      * conclusion of each rule whose premise is included in it. Iterations over
1103      * the rules are performed until no new element has to be added in the
1104      * closure.
1105      *
1106      * For direct ImplicationalSystem, only one iteration is needed, and the
1107      * treatment is performed in O(|Sigma||S|).
1108      *
1109      * For non direct ImplicationalSystem, at most |S| iterations are needed,
1110      * and this tratment is performed in O(|Sigma||S|^2).
1111      *
1112      * @param x a TreeSet of indexed elements
1113      *
1114      * @return the closure of X for this component
1115      */
1116     public TreeSet<Comparable> closure(TreeSet<Comparable> x) {
1117         TreeSet<Comparable> oldES = new TreeSet<Comparable>();
1118         // all the attributes are in their own closure
1119         TreeSet<Comparable> newES = new TreeSet<Comparable>(x);
1120         do {
1121             oldES.addAll(newES);
1122             for (Rule rule : this.sigma) {
1123                 if (newES.containsAll(rule.getPremise()) || rule.getPremise().isEmpty()) {
1124                     newES.addAll(rule.getConclusion());
1125                 }
1126             }
1127         } while (!oldES.equals(newES));
1128         return newES;
1129     }
1130 }