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 * 
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 }