001    package aima.logic.fol.kb.data;
002    
003    import java.util.ArrayList;
004    import java.util.Collections;
005    import java.util.Comparator;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import java.util.LinkedHashMap;
009    import java.util.LinkedHashSet;
010    import java.util.List;
011    import java.util.Map;
012    import java.util.Set;
013    
014    import aima.logic.fol.StandardizeApart;
015    import aima.logic.fol.StandardizeApartIndexical;
016    import aima.logic.fol.StandardizeApartIndexicalFactory;
017    import aima.logic.fol.SubstVisitor;
018    import aima.logic.fol.SubsumptionElimination;
019    import aima.logic.fol.Unifier;
020    import aima.logic.fol.VariableCollector;
021    import aima.logic.fol.inference.proof.ProofStep;
022    import aima.logic.fol.inference.proof.ProofStepClauseBinaryResolvent;
023    import aima.logic.fol.inference.proof.ProofStepClauseFactor;
024    import aima.logic.fol.inference.proof.ProofStepPremise;
025    import aima.logic.fol.parsing.FOLVisitor;
026    import aima.logic.fol.parsing.ast.AtomicSentence;
027    import aima.logic.fol.parsing.ast.ConnectedSentence;
028    import aima.logic.fol.parsing.ast.Constant;
029    import aima.logic.fol.parsing.ast.Function;
030    import aima.logic.fol.parsing.ast.NotSentence;
031    import aima.logic.fol.parsing.ast.Predicate;
032    import aima.logic.fol.parsing.ast.QuantifiedSentence;
033    import aima.logic.fol.parsing.ast.Sentence;
034    import aima.logic.fol.parsing.ast.Term;
035    import aima.logic.fol.parsing.ast.TermEquality;
036    import aima.logic.fol.parsing.ast.Variable;
037    import aima.util.MixedRadixNumber;
038    
039    /**
040     * A Clause: A disjunction of literals.
041     * 
042     */
043    
044    /**
045     * @author Ciaran O'Reilly
046     * 
047     */
048    public class Clause {
049            //
050            private static StandardizeApartIndexical _saIndexical = StandardizeApartIndexicalFactory
051                            .newStandardizeApartIndexical('c');
052            private static Unifier _unifier = new Unifier();
053            private static SubstVisitor _substVisitor = new SubstVisitor();
054            private static VariableCollector _variableCollector = new VariableCollector();
055            private static StandardizeApart _standardizeApart = new StandardizeApart();
056            private static LiteralsSorter _literalSorter = new LiteralsSorter();
057            //
058            private final Set<Literal> literals = new LinkedHashSet<Literal>();
059            private final List<Literal> positiveLiterals = new ArrayList<Literal>();
060            private final List<Literal> negativeLiterals = new ArrayList<Literal>();
061            private boolean immutable = false;
062            private boolean saCheckRequired = true;
063            private String equalityIdentity = "";
064            private Set<Clause> factors = null;
065            private Set<Clause> nonTrivialFactors = null;
066            private String stringRep = null;
067            private ProofStep proofStep = null;
068    
069            public Clause() {
070                    // i.e. the empty clause
071            }
072    
073            public Clause(List<Literal> lits) {
074                    this.literals.addAll(lits);
075                    for (Literal l : literals) {
076                            if (l.isPositiveLiteral()) {
077                                    this.positiveLiterals.add(l);
078                            } else {
079                                    this.negativeLiterals.add(l);
080                            }
081                    }
082                    recalculateIdentity();
083            }
084    
085            public Clause(List<Literal> lits1, List<Literal> lits2) {
086                    literals.addAll(lits1);
087                    literals.addAll(lits2);
088                    for (Literal l : literals) {
089                            if (l.isPositiveLiteral()) {
090                                    this.positiveLiterals.add(l);
091                            } else {
092                                    this.negativeLiterals.add(l);
093                            }
094                    }
095                    recalculateIdentity();
096            }
097    
098            public ProofStep getProofStep() {
099                    if (null == proofStep) {
100                            // Assume was a premise
101                            proofStep = new ProofStepPremise(this);
102                    }
103                    return proofStep;
104            }
105    
106            public void setProofStep(ProofStep proofStep) {
107                    this.proofStep = proofStep;
108            }
109    
110            public boolean isImmutable() {
111                    return immutable;
112            }
113    
114            public void setImmutable() {
115                    immutable = true;
116            }
117    
118            public boolean isStandardizedApartCheckRequired() {
119                    return saCheckRequired;
120            }
121    
122            public void setStandardizedApartCheckNotRequired() {
123                    saCheckRequired = false;
124            }
125    
126            public boolean isEmpty() {
127                    return literals.size() == 0;
128            }
129    
130            public boolean isUnitClause() {
131                    return literals.size() == 1;
132            }
133    
134            public boolean isDefiniteClause() {
135                    // A Definite Clause is a disjunction of literals of which exactly 1 is
136                    // positive.
137                    return !isEmpty() && positiveLiterals.size() == 1;
138            }
139    
140            public boolean isImplicationDefiniteClause() {
141                    // An Implication Definite Clause is a disjunction of literals of
142                    // which exactly 1 is positive and there is 1 or more negative
143                    // literals.
144                    return isDefiniteClause() && negativeLiterals.size() >= 1;
145            }
146    
147            public boolean isHornClause() {
148                    // A Horn clause is a disjunction of literals of which at most one is
149                    // positive.
150                    return !isEmpty() && positiveLiterals.size() <= 1;
151            }
152    
153            public boolean isTautology() {
154    
155                    for (Literal pl : positiveLiterals) {
156                            // Literals in a clause must be exact complements
157                            // for tautology elimination to apply. Do not
158                            // remove non-identical literals just because
159                            // they are complements under unification, see pg16:
160                            // http://logic.stanford.edu/classes/cs157/2008/notes/chap09.pdf
161                            for (Literal nl : negativeLiterals) {
162                                    if (pl.getAtomicSentence().equals(nl.getAtomicSentence())) {
163                                            return true;
164                                    }
165                            }
166                    }
167    
168                    return false;
169            }
170    
171            public void addLiteral(Literal literal) {
172                    if (isImmutable()) {
173                            throw new IllegalStateException(
174                                            "Clause is immutable, cannot be updated.");
175                    }
176                    int origSize = literals.size();
177                    literals.add(literal);
178                    if (literals.size() > origSize) {
179                            if (literal.isPositiveLiteral()) {
180                                    positiveLiterals.add(literal);
181                            } else {
182                                    negativeLiterals.add(literal);
183                            }
184                    }
185                    recalculateIdentity();
186            }
187    
188            public void addPositiveLiteral(AtomicSentence atom) {
189                    addLiteral(new Literal(atom));
190            }
191    
192            public void addNegativeLiteral(AtomicSentence atom) {
193                    addLiteral(new Literal(atom, true));
194            }
195    
196            public int getNumberLiterals() {
197                    return literals.size();
198            }
199    
200            public int getNumberPositiveLiterals() {
201                    return positiveLiterals.size();
202            }
203    
204            public int getNumberNegativeLiterals() {
205                    return negativeLiterals.size();
206            }
207    
208            public Set<Literal> getLiterals() {
209                    return Collections.unmodifiableSet(literals);
210            }
211    
212            public List<Literal> getPositiveLiterals() {
213                    return Collections.unmodifiableList(positiveLiterals);
214            }
215    
216            public List<Literal> getNegativeLiterals() {
217                    return Collections.unmodifiableList(negativeLiterals);
218            }
219    
220            public Set<Clause> getFactors() {
221                    if (null == factors) {
222                            calculateFactors(null);
223                    }
224                    return Collections.unmodifiableSet(factors);
225            }
226    
227            public Set<Clause> getNonTrivialFactors() {
228                    if (null == nonTrivialFactors) {
229                            calculateFactors(null);
230                    }
231                    return Collections.unmodifiableSet(nonTrivialFactors);
232            }
233    
234            public boolean subsumes(Clause othC) {
235                    boolean subsumes = false;
236    
237                    // Equality is not subsumption
238                    if (!(this == othC)) {
239                            // Ensure this has less literals total and that
240                            // it is a subset of the other clauses positive and negative counts
241                            if (this.getNumberLiterals() < othC.getNumberLiterals()
242                                            && this.getNumberPositiveLiterals() <= othC
243                                                            .getNumberPositiveLiterals()
244                                            && this.getNumberNegativeLiterals() <= othC
245                                                            .getNumberNegativeLiterals()) {
246    
247                                    Map<String, List<Literal>> thisToTry = collectLikeLiterals(this.literals);
248                                    Map<String, List<Literal>> othCToTry = collectLikeLiterals(othC.literals);
249                                    // Ensure all like literals from this clause are a subset
250                                    // of the other clause.
251                                    if (othCToTry.keySet().containsAll(thisToTry.keySet())) {
252                                            boolean isAPossSubset = true;
253                                            // Ensure that each set of same named literals
254                                            // from this clause is a subset of the other
255                                            // clauses same named literals.
256                                            for (String pk : thisToTry.keySet()) {
257                                                    if (thisToTry.get(pk).size() > othCToTry.get(pk).size()) {
258                                                            isAPossSubset = false;
259                                                            break;
260                                                    }
261                                            }
262                                            if (isAPossSubset) {
263                                                    // At this point I know this this Clause's
264                                                    // literal/arity names are a subset of the
265                                                    // other clauses literal/arity names
266                                                    subsumes = checkSubsumes(othC, thisToTry, othCToTry);
267                                            }
268                                    }
269                            }
270                    }
271    
272                    return subsumes;
273            }
274    
275            // Note: Applies binary resolution rule and factoring
276            // Note: returns a set with an empty clause if both clauses
277            // are empty, otherwise returns a set of binary resolvents.
278            public Set<Clause> binaryResolvents(Clause othC) {
279                    Set<Clause> resolvents = new LinkedHashSet<Clause>();
280                    // Resolving two empty clauses
281                    // gives you an empty clause
282                    if (isEmpty() && othC.isEmpty()) {
283                            resolvents.add(new Clause());
284                            return resolvents;
285                    }
286    
287                    // Ensure Standardized Apart
288                    // Before attempting binary resolution
289                    othC = saIfRequired(othC);
290    
291                    List<Literal> allPosLits = new ArrayList<Literal>();
292                    List<Literal> allNegLits = new ArrayList<Literal>();
293                    allPosLits.addAll(this.positiveLiterals);
294                    allPosLits.addAll(othC.positiveLiterals);
295                    allNegLits.addAll(this.negativeLiterals);
296                    allNegLits.addAll(othC.negativeLiterals);
297    
298                    List<Literal> trPosLits = new ArrayList<Literal>();
299                    List<Literal> trNegLits = new ArrayList<Literal>();
300                    List<Literal> copyRPosLits = new ArrayList<Literal>();
301                    List<Literal> copyRNegLits = new ArrayList<Literal>();
302    
303                    for (int i = 0; i < 2; i++) {
304                            trPosLits.clear();
305                            trNegLits.clear();
306    
307                            if (i == 0) {
308                                    // See if this clauses positives
309                                    // unify with the other clauses
310                                    // negatives
311                                    trPosLits.addAll(this.positiveLiterals);
312                                    trNegLits.addAll(othC.negativeLiterals);
313                            } else {
314                                    // Try the other way round now
315                                    trPosLits.addAll(othC.positiveLiterals);
316                                    trNegLits.addAll(this.negativeLiterals);
317                            }
318    
319                            // Now check to see if they resolve
320                            Map<Variable, Term> copyRBindings = new LinkedHashMap<Variable, Term>();
321                            for (Literal pl : trPosLits) {
322                                    for (Literal nl : trNegLits) {
323                                            copyRBindings.clear();
324                                            if (null != _unifier.unify(pl.getAtomicSentence(), nl
325                                                            .getAtomicSentence(), copyRBindings)) {
326                                                    copyRPosLits.clear();
327                                                    copyRNegLits.clear();
328                                                    boolean found = false;
329                                                    for (Literal l : allPosLits) {
330                                                            if (!found && pl.equals(l)) {
331                                                                    found = true;
332                                                                    continue;
333                                                            }
334                                                            copyRPosLits.add(_substVisitor.subst(copyRBindings,
335                                                                            l));
336                                                    }
337                                                    found = false;
338                                                    for (Literal l : allNegLits) {
339                                                            if (!found && nl.equals(l)) {
340                                                                    found = true;
341                                                                    continue;
342                                                            }
343                                                            copyRNegLits.add(_substVisitor.subst(copyRBindings,
344                                                                            l));
345                                                    }
346                                                    // Ensure the resolvents are standardized apart
347                                                    Map<Variable, Term> renameSubstitituon = _standardizeApart
348                                                                    .standardizeApart(copyRPosLits, copyRNegLits,
349                                                                                    _saIndexical);
350                                                    Clause c = new Clause(copyRPosLits, copyRNegLits);
351                                                    c.setProofStep(new ProofStepClauseBinaryResolvent(c,
352                                                                    this, othC, copyRBindings, renameSubstitituon));
353                                                    if (isImmutable()) {
354                                                            c.setImmutable();
355                                                    }
356                                                    if (!isStandardizedApartCheckRequired()) {
357                                                            c.setStandardizedApartCheckNotRequired();
358                                                    }
359                                                    resolvents.add(c);
360                                            }
361                                    }
362                            }
363                    }
364    
365                    return resolvents;
366            }
367    
368            public String toString() {
369                    if (null == stringRep) {
370                            List<Literal> sortedLiterals = new ArrayList<Literal>(literals);
371                            Collections.sort(sortedLiterals, _literalSorter);
372    
373                            stringRep = sortedLiterals.toString();
374                    }
375                    return stringRep;
376            }
377    
378            public int hashCode() {
379                    return equalityIdentity.hashCode();
380            }
381    
382            public boolean equals(Object othObj) {
383                    if (null == othObj) {
384                            return false;
385                    }
386                    if (this == othObj) {
387                            return true;
388                    }
389                    if (!(othObj instanceof Clause)) {
390                            return false;
391                    }
392                    Clause othClause = (Clause) othObj;
393    
394                    return equalityIdentity.equals(othClause.equalityIdentity);
395            }
396    
397            public String getEqualityIdentity() {
398                    return equalityIdentity;
399            }
400    
401            //
402            // PRIVATE METHODS
403            //
404            private void recalculateIdentity() {
405                    synchronized (equalityIdentity) {
406    
407                            // Sort the literals first based on negation, atomic sentence,
408                            // constant, function and variable.
409                            List<Literal> sortedLiterals = new ArrayList<Literal>(literals);
410                            Collections.sort(sortedLiterals, _literalSorter);
411    
412                            // All variables are considered the same as regards
413                            // sorting. Therefore, to determine if two clauses
414                            // are equivalent you need to determine
415                            // the # of unique variables they contain and
416                            // there positions across the clauses
417                            ClauseEqualityIdentityConstructor ceic = new ClauseEqualityIdentityConstructor(
418                                            sortedLiterals, _literalSorter);
419    
420                            equalityIdentity = ceic.getIdentity();
421    
422                            // Reset, these as will need to re-calcualte
423                            // if requested for again, best to only
424                            // access lazily.
425                            factors = null;
426                            nonTrivialFactors = null;
427                            // Reset the objects string representation
428                            // until it is requested for.
429                            stringRep = null;
430                    }
431            }
432    
433            private void calculateFactors(Set<Clause> parentFactors) {
434                    nonTrivialFactors = new LinkedHashSet<Clause>();
435    
436                    Map<Variable, Term> theta = new HashMap<Variable, Term>();
437                    List<Literal> lits = new ArrayList<Literal>();
438                    for (int i = 0; i < 2; i++) {
439                            lits.clear();
440                            if (i == 0) {
441                                    // Look at the positive literals
442                                    lits.addAll(positiveLiterals);
443                            } else {
444                                    // Look at the negative literals
445                                    lits.addAll(negativeLiterals);
446                            }
447                            for (int x = 0; x < lits.size(); x++) {
448                                    for (int y = x + 1; y < lits.size(); y++) {
449                                            Literal litX = lits.get(x);
450                                            Literal litY = lits.get(y);
451    
452                                            theta.clear();
453                                            Map<Variable, Term> substitution = _unifier.unify(litX
454                                                            .getAtomicSentence(), litY.getAtomicSentence(),
455                                                            theta);
456                                            if (null != substitution) {
457                                                    List<Literal> posLits = new ArrayList<Literal>();
458                                                    List<Literal> negLits = new ArrayList<Literal>();
459                                                    if (i == 0) {
460                                                            posLits
461                                                                            .add(_substVisitor
462                                                                                            .subst(substitution, litX));
463                                                    } else {
464                                                            negLits
465                                                                            .add(_substVisitor
466                                                                                            .subst(substitution, litX));
467                                                    }
468                                                    for (Literal pl : positiveLiterals) {
469                                                            if (pl == litX || pl == litY) {
470                                                                    continue;
471                                                            }
472                                                            posLits.add(_substVisitor.subst(substitution, pl));
473                                                    }
474                                                    for (Literal nl : negativeLiterals) {
475                                                            if (nl == litX || nl == litY) {
476                                                                    continue;
477                                                            }
478                                                            negLits.add(_substVisitor.subst(substitution, nl));
479                                                    }
480                                                    // Ensure the non trivial factor is standardized apart
481                                                    _standardizeApart.standardizeApart(posLits, negLits,
482                                                                    _saIndexical);
483                                                    Clause c = new Clause(posLits, negLits);
484                                                    c.setProofStep(new ProofStepClauseFactor(c, this));
485                                                    if (isImmutable()) {
486                                                            c.setImmutable();
487                                                    }
488                                                    if (!isStandardizedApartCheckRequired()) {
489                                                            c.setStandardizedApartCheckNotRequired();
490                                                    }
491                                                    if (null == parentFactors) {
492                                                            c.calculateFactors(nonTrivialFactors);
493                                                            nonTrivialFactors.addAll(c.getFactors());
494                                                    } else {
495                                                            if (!parentFactors.contains(c)) {
496                                                                    c.calculateFactors(nonTrivialFactors);
497                                                                    nonTrivialFactors.addAll(c.getFactors());
498                                                            }
499                                                    }
500                                            }
501                                    }
502                            }
503                    }
504    
505                    factors = new LinkedHashSet<Clause>();
506                    // Need to add self, even though a non-trivial
507                    // factor. See: slide 30
508                    // http://logic.stanford.edu/classes/cs157/2008/lectures/lecture10.pdf
509                    // for example of incompleteness when
510                    // trivial factor not included.
511                    factors.add(this);
512                    factors.addAll(nonTrivialFactors);
513            }
514    
515            private Clause saIfRequired(Clause othClause) {
516    
517                    // If performing resolution with self
518                    // then need to standardize apart in
519                    // order to work correctly.
520                    if (isStandardizedApartCheckRequired() || this == othClause) {
521                            Set<Variable> mVariables = _variableCollector
522                                            .collectAllVariables(this);
523                            Set<Variable> oVariables = _variableCollector
524                                            .collectAllVariables(othClause);
525    
526                            Set<Variable> cVariables = new HashSet<Variable>();
527                            cVariables.addAll(mVariables);
528                            cVariables.addAll(oVariables);
529    
530                            if (cVariables.size() < (mVariables.size() + oVariables.size())) {
531                                    othClause = _standardizeApart.standardizeApart(othClause,
532                                                    _saIndexical);
533                            }
534                    }
535    
536                    return othClause;
537            }
538    
539            private Map<String, List<Literal>> collectLikeLiterals(Set<Literal> literals) {
540                    Map<String, List<Literal>> likeLiterals = new HashMap<String, List<Literal>>();
541                    for (Literal l : literals) {
542                            // Want to ensure P(a, b) is considered different than P(a, b, c)
543                            // i.e. consider an atom's arity P/#.
544                            String literalName = (l.isNegativeLiteral() ? "~" : "")
545                                            + l.getAtomicSentence().getSymbolicName() + "/"
546                                            + l.getAtomicSentence().getArgs().size();
547                            List<Literal> like = likeLiterals.get(literalName);
548                            if (null == like) {
549                                    like = new ArrayList<Literal>();
550                                    likeLiterals.put(literalName, like);
551                            }
552                            like.add(l);
553                    }
554                    return likeLiterals;
555            }
556    
557            private boolean checkSubsumes(Clause othC,
558                            Map<String, List<Literal>> thisToTry,
559                            Map<String, List<Literal>> othCToTry) {
560                    boolean subsumes = false;
561    
562                    List<Term> thisTerms = new ArrayList<Term>();
563                    List<Term> othCTerms = new ArrayList<Term>();
564    
565                    // Want to track possible number of permuations
566                    List<Integer> radixs = new ArrayList<Integer>();
567                    for (String literalName : thisToTry.keySet()) {
568                            int sizeT = thisToTry.get(literalName).size();
569                            int sizeO = othCToTry.get(literalName).size();
570    
571                            if (sizeO > 1) {
572                                    // The following is being used to
573                                    // track the number of permutations
574                                    // that can be mapped from the
575                                    // other clauses like literals to this
576                                    // clauses like literals.
577                                    // i.e. n!/(n-r)!
578                                    // where n=sizeO and r =sizeT
579                                    for (int i = 0; i < sizeT; i++) {
580                                            int r = sizeO - i;
581                                            if (r > 1) {
582                                                    radixs.add(r);
583                                            }
584                                    }
585                            }
586                            // Track the terms for this clause
587                            for (Literal tl : thisToTry.get(literalName)) {
588                                    thisTerms.addAll(tl.getAtomicSentence().getArgs());
589                            }
590                    }
591    
592                    MixedRadixNumber permutation = null;
593                    long numPermutations = 1L;
594                    if (radixs.size() > 0) {
595                            permutation = new MixedRadixNumber(0, radixs);
596                            numPermutations = permutation.getMaxAllowedValue() + 1;
597                    }
598                    // Want to ensure none of the othCVariables are
599                    // part of the key set of a unification as
600                    // this indicates it is not a legal subsumption.
601                    Set<Variable> othCVariables = _variableCollector
602                                    .collectAllVariables(othC);
603                    Map<Variable, Term> theta = new LinkedHashMap<Variable, Term>();
604                    List<Literal> literalPermuations = new ArrayList<Literal>();
605                    for (long l = 0L; l < numPermutations; l++) {
606                            // Track the other clause's terms for this
607                            // permutation.
608                            othCTerms.clear();
609                            int radixIdx = 0;
610                            for (String literalName : thisToTry.keySet()) {
611                                    int sizeT = thisToTry.get(literalName).size();
612                                    literalPermuations.clear();
613                                    literalPermuations.addAll(othCToTry.get(literalName));
614                                    int sizeO = literalPermuations.size();
615    
616                                    if (sizeO > 1) {
617                                            for (int i = 0; i < sizeT; i++) {
618                                                    int r = sizeO - i;
619                                                    if (r > 1) {
620                                                            // If not a 1 to 1 mapping then you need
621                                                            // to use the correct permuation
622                                                            int numPos = permutation
623                                                                            .getCurrentNumeralValue(radixIdx);
624                                                            othCTerms.addAll(literalPermuations.remove(numPos)
625                                                                            .getAtomicSentence().getArgs());
626                                                            radixIdx++;
627                                                    } else {
628                                                            // is the last mapping, therefore
629                                                            // won't be on the radix
630                                                            othCTerms.addAll(literalPermuations.get(0)
631                                                                            .getAtomicSentence().getArgs());
632                                                    }
633                                            }
634                                    } else {
635                                            // a 1 to 1 mapping
636                                            othCTerms.addAll(literalPermuations.get(0)
637                                                            .getAtomicSentence().getArgs());
638                                    }
639                            }
640    
641                            // Note: on unifier
642                            // unifier.unify(P(w, x), P(y, z)))={w=y, x=z}
643                            // unifier.unify(P(y, z), P(w, x)))={y=w, z=x}
644                            // Therefore want this clause to be the first
645                            // so can do the othCVariables check for an invalid
646                            // subsumes.
647                            theta.clear();
648                            if (null != _unifier.unify(thisTerms, othCTerms, theta)) {
649                                    boolean containsAny = false;
650                                    for (Variable v : theta.keySet()) {
651                                            if (othCVariables.contains(v)) {
652                                                    containsAny = true;
653                                                    break;
654                                            }
655                                    }
656                                    if (!containsAny) {
657                                            subsumes = true;
658                                            break;
659                                    }
660                            }
661    
662                            // If there is more than 1 mapping
663                            // keep track of where I am in the
664                            // possible number of mapping permutations.
665                            if (null != permutation) {
666                                    permutation.increment();
667                            }
668                    }
669    
670                    return subsumes;
671            }
672    }
673    
674    class LiteralsSorter implements Comparator<Literal> {
675            public int compare(Literal o1, Literal o2) {
676                    int rVal = 0;
677                    // If literals are not negated the same
678                    // then positive literals are considered
679                    // (by convention here) to be of higher
680                    // order than negative literals
681                    if (o1.isPositiveLiteral() != o2.isPositiveLiteral()) {
682                            if (o1.isPositiveLiteral()) {
683                                    return 1;
684                            }
685                            return -1;
686                    }
687    
688                    // Check their symbolic names for order first
689                    rVal = o1.getAtomicSentence().getSymbolicName().compareTo(
690                                    o2.getAtomicSentence().getSymbolicName());
691    
692                    // If have same symbolic names
693                    // then need to compare individual arguments
694                    // for order.
695                    if (0 == rVal) {
696                            rVal = compareArgs(o1.getAtomicSentence().getArgs(), o2
697                                            .getAtomicSentence().getArgs());
698                    }
699    
700                    return rVal;
701            }
702    
703            private int compareArgs(List<Term> args1, List<Term> args2) {
704                    int rVal = 0;
705    
706                    // Compare argument sizes first
707                    rVal = args1.size() - args2.size();
708    
709                    if (0 == rVal && args1.size() > 0) {
710                            // Move forward and compare the
711                            // first arguments
712                            Term t1 = args1.get(0);
713                            Term t2 = args2.get(0);
714    
715                            if (t1.getClass() == t2.getClass()) {
716                                    // Note: Variables are considered to have
717                                    // the same order
718                                    if (t1 instanceof Constant) {
719                                            rVal = t1.getSymbolicName().compareTo(t2.getSymbolicName());
720                                    } else if (t1 instanceof Function) {
721                                            rVal = t1.getSymbolicName().compareTo(t2.getSymbolicName());
722                                            if (0 == rVal) {
723                                                    // Same function names, therefore
724                                                    // compare the function arguments
725                                                    rVal = compareArgs(t1.getArgs(), t2.getArgs());
726                                            }
727                                    }
728    
729                                    // If the first args are the same
730                                    // then compare the ordering of the
731                                    // remaining arguments
732                                    if (0 == rVal) {
733                                            rVal = compareArgs(args1.subList(1, args1.size()), args2
734                                                            .subList(1, args2.size()));
735                                    }
736                            } else {
737                                    // Order for different Terms is:
738                                    // Constant > Function > Variable
739                                    if (t1 instanceof Constant) {
740                                            rVal = 1;
741                                    } else if (t2 instanceof Constant) {
742                                            rVal = -1;
743                                    } else if (t1 instanceof Function) {
744                                            rVal = 1;
745                                    } else {
746                                            rVal = -1;
747                                    }
748                            }
749                    }
750    
751                    return rVal;
752            }
753    }
754    
755    class ClauseEqualityIdentityConstructor implements FOLVisitor {
756            private StringBuilder identity = new StringBuilder();
757            private int noVarPositions = 0;
758            private int[] clauseVarCounts = null;
759            private int currentLiteral = 0;
760            private Map<String, List<Integer>> varPositions = new HashMap<String, List<Integer>>();
761    
762            public ClauseEqualityIdentityConstructor(List<Literal> literals,
763                            LiteralsSorter sorter) {
764    
765                    clauseVarCounts = new int[literals.size()];
766    
767                    for (Literal l : literals) {
768                            if (l.isNegativeLiteral()) {
769                                    identity.append("~");
770                            }
771                            identity.append(l.getAtomicSentence().getSymbolicName());
772                            identity.append("(");
773                            boolean firstTerm = true;
774                            for (Term t : l.getAtomicSentence().getArgs()) {
775                                    if (firstTerm) {
776                                            firstTerm = false;
777                                    } else {
778                                            identity.append(",");
779                                    }
780                                    t.accept(this, null);
781                            }
782                            identity.append(")");
783                            currentLiteral++;
784                    }
785    
786                    int min, max;
787                    min = max = 0;
788                    for (int i = 0; i < literals.size(); i++) {
789                            int incITo = i;
790                            int next = i + 1;
791                            max += clauseVarCounts[i];
792                            while (next < literals.size()) {
793                                    if (0 != sorter.compare(literals.get(i), literals.get(next))) {
794                                            break;
795                                    }
796                                    max += clauseVarCounts[next];
797                                    incITo = next; // Need to skip to the end of the range
798                                    next++;
799                            }
800                            // This indicates two or more literals are identical
801                            // except for variable naming (note: identical
802                            // same name would be removed as are working
803                            // with sets so don't need to worry about this).
804                            if ((next - i) > 1) {
805                                    // Need to check each variable
806                                    // and if it has a position within the
807                                    // current min/max range then need
808                                    // to include its alternative
809                                    // sort order positions as well
810                                    for (String key : varPositions.keySet()) {
811                                            List<Integer> positions = varPositions.get(key);
812                                            List<Integer> additPositions = new ArrayList<Integer>();
813                                            // Add then subtract for all possible
814                                            // positions in range
815                                            for (int pos : positions) {
816                                                    if (pos >= min && pos < max) {
817                                                            int pPos = pos;
818                                                            int nPos = pos;
819                                                            for (int candSlot = i; candSlot < (next - 1); candSlot++) {
820                                                                    pPos += clauseVarCounts[i];
821                                                                    if (pPos >= min && pPos < max) {
822                                                                            if (!positions.contains(pPos)
823                                                                                            && !additPositions.contains(pPos)) {
824                                                                                    additPositions.add(pPos);
825                                                                            }
826                                                                    }
827                                                                    nPos -= clauseVarCounts[i];
828                                                                    if (nPos >= min && nPos < max) {
829                                                                            if (!positions.contains(nPos)
830                                                                                            && !additPositions.contains(nPos)) {
831                                                                                    additPositions.add(nPos);
832                                                                            }
833                                                                    }
834                                                            }
835                                                    }
836                                            }
837                                            positions.addAll(additPositions);
838                                    }
839                            }
840                            min = max;
841                            i = incITo;
842                    }
843    
844                    // Determine the maxWidth
845                    int maxWidth = 1;
846                    while (noVarPositions >= 10) {
847                            noVarPositions = noVarPositions / 10;
848                            maxWidth++;
849                    }
850                    String format = "%0" + maxWidth + "d";
851    
852                    // Sort the individual position lists
853                    // And then add their string representations
854                    // together
855                    List<String> varOffsets = new ArrayList<String>();
856                    for (String key : varPositions.keySet()) {
857                            List<Integer> positions = varPositions.get(key);
858                            Collections.sort(positions);
859                            StringBuilder sb = new StringBuilder();
860                            for (int pos : positions) {
861                                    sb.append(String.format(format, pos));
862                            }
863                            varOffsets.add(sb.toString());
864                    }
865                    Collections.sort(varOffsets);
866                    for (int i = 0; i < varOffsets.size(); i++) {
867                            identity.append(varOffsets.get(i));
868                            if (i < (varOffsets.size() - 1)) {
869                                    identity.append(",");
870                            }
871                    }
872            }
873    
874            public String getIdentity() {
875                    return identity.toString();
876            }
877    
878            //
879            // START-FOLVisitor
880            public Object visitVariable(Variable var, Object arg) {
881                    // All variables will be marked with an *
882                    identity.append("*");
883    
884                    List<Integer> positions = varPositions.get(var.getValue());
885                    if (null == positions) {
886                            positions = new ArrayList<Integer>();
887                            varPositions.put(var.getValue(), positions);
888                    }
889                    positions.add(noVarPositions);
890    
891                    noVarPositions++;
892                    clauseVarCounts[currentLiteral]++;
893                    return var;
894            }
895    
896            public Object visitConstant(Constant constant, Object arg) {
897                    identity.append(constant.getValue());
898                    return constant;
899            }
900    
901            public Object visitFunction(Function function, Object arg) {
902                    boolean firstTerm = true;
903                    identity.append(function.getFunctionName());
904                    identity.append("(");
905                    for (Term t : function.getTerms()) {
906                            if (firstTerm) {
907                                    firstTerm = false;
908                            } else {
909                                    identity.append(",");
910                            }
911                            t.accept(this, arg);
912                    }
913                    identity.append(")");
914    
915                    return function;
916            }
917    
918            public Object visitPredicate(Predicate predicate, Object arg) {
919                    throw new IllegalStateException("Should not be called");
920            }
921    
922            public Object visitTermEquality(TermEquality equality, Object arg) {
923                    throw new IllegalStateException("Should not be called");
924            }
925    
926            public Object visitQuantifiedSentence(QuantifiedSentence sentence,
927                            Object arg) {
928                    throw new IllegalStateException("Should not be called");
929            }
930    
931            public Object visitNotSentence(NotSentence sentence, Object arg) {
932                    throw new IllegalStateException("Should not be called");
933            }
934    
935            public Object visitConnectedSentence(ConnectedSentence sentence, Object arg) {
936                    throw new IllegalStateException("Should not be called");
937            }
938    
939            // END-FOLVisitor
940            //
941    }