package nars.language; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import nars.storage.Memory; import nars.io.Symbols; /** * Static utility class for static methods related to Variables */ public class Variables { public static boolean findSubstitute(final char type, final Term term1, final Term term2, final Map<Term, Term> map1, final Map<Term, Term> map2) { return findSubstitute(type, term1, term2, new Map[] { map1, map2 }); } public static boolean allowUnification(final char type, final char uniType) { //it is valid to allow dependent var unification in case that a independent var unification is happening, //as shown in the // <(&&,<$1 --> [ENGLISH]>, <$2 --> [CHINESE]>, <(*, $1, #3) --> REPRESENT>, <(*, $2, #3) --> REPRESENT>) ==> <(*, $1, $2) --> TRANSLATE>>. //example by Kai Liu. //1.7.0 and 2.0.1 also already allowed this, so this is for v1.6.x now. if(uniType == type) { //the usual case return true; } if(uniType == Symbols.VAR_INDEPENDENT) { //the now allowed case if(type == Symbols.VAR_DEPENDENT) { return true; } } return false; } /** map is a 2-element array of HashMap<Term,Term>. it may be null, in which case * the maps will be instantiated as necessary. * this is to delay the instantiation of the 2 HashMap until necessary to avoid * wasting them if they are not used. */ public static boolean findSubstitute(final char type, final Term term1, final Term term2, final Map<Term, Term>[] map) { final boolean term1HasVar = term1.hasVar(type); final boolean term2HasVar = term2.hasVar(type); final boolean term1Var = term1 instanceof Variable; final boolean term2Var = term2 instanceof Variable; final boolean termsEqual = term1.equals(term2); if (!term1Var && !term2Var && termsEqual) { return true; } /*if (termsEqual) { return true; }*/ Term t; if (term1Var && allowUnification(((Variable) term1).getType(), type)) { final Variable var1 = (Variable) term1; t = map[0]!=null ? map[0].get(var1) : null; if (t != null) { return findSubstitute(type, t, term2, map); } else { if (map[0] == null) { map[0] = new HashMap(); map[1] = new HashMap(); } if ((term2 instanceof Variable) && allowUnification(((Variable) term2).getType(), type)) { Variable CommonVar = makeCommonVariable(term1, term2); map[0].put(var1, CommonVar); map[1].put(term2, CommonVar); } else { if(term2 instanceof Variable && ((((Variable)term2).getType()==Symbols.VAR_QUERY && ((Variable)term1).getType()!=Symbols.VAR_QUERY) || (((Variable)term2).getType()!=Symbols.VAR_QUERY && ((Variable)term1).getType()==Symbols.VAR_QUERY))) { return false; } map[0].put(var1, term2); if (var1.isCommon()) { map[1].put(var1, term2); } } return true; } } else if (term2Var && allowUnification(((Variable) term2).getType(), type)) { final Variable var2 = (Variable) term2; t = map[1]!=null ? map[1].get(var2) : null; if (t != null) { return findSubstitute(type, term1, t, map); } else { if (map[0] == null) { map[0] = new HashMap(); map[1] = new HashMap(); } map[1].put(var2, term1); if (var2.isCommon()) { map[0].put(var2, term1); } return true; } } else if ((term1HasVar || term2HasVar) && (term1 instanceof CompoundTerm) && term1.getClass().equals(term2.getClass())) { final CompoundTerm cTerm1 = (CompoundTerm) term1; final CompoundTerm cTerm2 = (CompoundTerm) term2; //consider temporal order on term matching if(term1 instanceof Conjunction && term2 instanceof Conjunction) { if(((Conjunction)term1).getTemporalOrder() != ((Conjunction)term2).getTemporalOrder()) return false; } if(term1 instanceof Implication && term2 instanceof Implication) { if(((Implication)term1).getTemporalOrder() != ((Implication)term2).getTemporalOrder()) return false; } if(term1 instanceof Equivalence && term2 instanceof Equivalence) { if(((Equivalence)term1).getTemporalOrder() != ((Equivalence)term2).getTemporalOrder()) return false; } if (cTerm1.size() != cTerm2.size()) { return false; } if ((cTerm1 instanceof ImageExt) && (((ImageExt) cTerm1).relationIndex != ((ImageExt) cTerm2).relationIndex) || (cTerm1 instanceof ImageInt) && (((ImageInt) cTerm1).relationIndex != ((ImageInt) cTerm2).relationIndex)) { return false; } Term[] list = cTerm1.cloneTerms(); if (cTerm1.isCommutative()) { CompoundTerm.shuffle(list, Memory.randomNumber); HashSet<Integer> alreadyMatched = new HashSet<Integer>(); //ok attempt unification if(cTerm2 == null || list == null || cTerm2.term == null || list.length != cTerm2.term.length) { return false; } HashSet<Integer> matchedJ = new HashSet<Integer>(list.length*2); for(int i = 0; i < list.length; i++) { boolean succeeded = false; for(int j = 0; j < list.length; j++) { if(matchedJ.contains(j)) { //this one already was used to match one of the i's continue; } Term ti = list[i].clone(); //clone map also: Map<Term, Term>[] mapNew = (Map<Term, Term>[]) new HashMap<?,?>[2]; mapNew[0] = new HashMap<Term,Term>(); mapNew[1] = new HashMap<Term,Term>(); if(map[0] == null) { map[0] = new HashMap<Term,Term>(); } if(map[1] == null) { map[1] = new HashMap<Term,Term>(); } for(Term c : map[0].keySet()) { mapNew[0].put(c, map[0].get(c)); } for(Term c : map[1].keySet()) { mapNew[1].put(c, map[1].get(c)); } //attempt unification: if(findSubstitute(type,ti,cTerm2.term[i],mapNew)) { for(Term c : mapNew[0].keySet()) { //ok put back the unifications that were necessary map[0].put(c, mapNew[0].get(c)); } for(Term c : mapNew[1].keySet()) { map[1].put(c, mapNew[1].get(c)); } succeeded = true; matchedJ.add(j); break; } } if(!succeeded) { return false; } } return true; } for (int i = 0; i < cTerm1.size(); i++) { Term t1 = list[i]; Term t2 = cTerm2.term[i]; if (!findSubstitute(type, t1, t2, map)) { return false; } } return true; } return termsEqual; } /** * Check whether a string represent a name of a term that contains a * variable * * @param n The string name to be checked * @return Whether the name contains a variable */ public static boolean containVar(final CharSequence n) { if (n == null) return false; final int l = n.length(); for (int i = 0; i < l; i++) { switch (n.charAt(i)) { case Symbols.VAR_INDEPENDENT: case Symbols.VAR_DEPENDENT: case Symbols.VAR_QUERY: return true; } } return false; } public static final boolean containVar(final Term[] t) { for (final Term x : t) if (x instanceof Variable) return true; return false; } /** * To unify two terms * * @param type The type of variable that can be substituted * @param t The first and second term as an array, which will have been modified upon returning true * @return Whether the unification is possible. 't' will refer to the unified terms */ public static boolean unify(final char type, final Term[] t) { return unify(type, t[0], t[1], t); } /** * To unify two terms * * @param type The type of variable that can be substituted * @param compound1 The compound containing the first term, possibly modified * @param compound2 The compound containing the second term, possibly modified * @param t The first and second term as an array, which will have been modified upon returning true * @return Whether the unification is possible. 't' will refer to the unified terms */ public static boolean unify(final char type, final Term t1, final Term t2, final Term[] compound) { final Map<Term, Term> map[] = new Map[2]; //begins empty: null,null final boolean hasSubs = findSubstitute(type, t1, t2, map); if (hasSubs) { final Term a = applySubstituteAndRenameVariables(((CompoundTerm)compound[0]), map[0]); if (a == null) return false; final Term b = applySubstituteAndRenameVariables(((CompoundTerm)compound[1]), map[1]); if (b == null) return false; //only set the values if it will return true, otherwise if it returns false the callee can expect its original values untouched if(compound[0] instanceof Variable && ((Variable)compound[0]).hasVarQuery() && (((Variable)a).hasVarIndep() || ((Variable)a).hasVarIndep()) ) { return false; } if(compound[1] instanceof Variable && ((Variable)compound[1]).hasVarQuery() && (((Variable)b).hasVarIndep() || ((Variable)b).hasVarIndep()) ) { return false; } compound[0] = a; compound[1] = b; return true; } return false; } /** appliesSubstitute and renameVariables, resulting in a cloned object, * will not change this instance */ private static Term applySubstituteAndRenameVariables(final CompoundTerm t, final Map<Term, Term> subs) { if ((subs == null) || (subs.isEmpty())) { //no change needed return t; } Term r = t.applySubstitute(subs); if (r == null) return null; if (r.equals(t)) return t; return r; } // /** // * Check whether a string represent a name of a term that contains a query // * variable // * // * @param n The string name to be checked // * @return Whether the name contains a query variable // */ // public static boolean containVarQuery(final CharSequence n) { // return Texts.containsChar(n, Symbols.VAR_QUERY); // } // /** // * Check whether a string represent a name of a term that contains a // * dependent variable // * // * @param n The string name to be checked // * @return Whether the name contains a dependent variable // */ // public static boolean containVarDep(final CharSequence n) { // return Texts.containsChar(n, Symbols.VAR_DEPENDENT); // } public static Variable makeCommonVariable(final Term v1, final Term v2) { //TODO use more efficient string construction return new Variable(v1.toString() + v2.toString() + '$'); } // public static boolean containVarDepOrIndep(final CharSequence n) { // final int l = n.length(); // for (int i = 0; i < l; i++) { // char c = n.charAt(i); // if ((c == Symbols.VAR_INDEPENDENT) || (c == Symbols.VAR_DEPENDENT)) { // return true; // } // } // return false; // } // /** // * Check whether a string represent a name of a term that contains an // * independent variable // * // * @param n The string name to be checked // * @return Whether the name contains an independent variable // */ // public static boolean containVarIndep(final CharSequence n) { // return Texts.containsChar(n, Symbols.VAR_INDEPENDENT); // } /** * Check whether a term is using an * independent variable in an invalid way * * @param n The string name to be checked * @return Whether the name contains an independent variable */ public static boolean indepVarUsedInvalid(Term T) { //if its a conjunction/disjunction, this is invalid: (&&,<$1 --> test>,<$1 --> test2>), while this isnt: (&&,<$1 --> test ==> <$1 --> test2>,others) //this means we have to go through the conjunction, and check if the component is a indepVarUsedInvalid instance, if yes, return true // if(T instanceof Conjunction || T instanceof Disjunction) { Term[] part=((CompoundTerm)T).term; for(Term t : part) { if(indepVarUsedInvalid(t)) { return true; } } } if(!(T instanceof Inheritance) && !(T instanceof Similarity)) { return false; } return T.hasVarIndep(); } /** * Check if two terms can be unified * * @param type The type of variable that can be substituted * @param term1 The first term to be unified * @param term2 The second term to be unified * @return Whether there is a substitution */ public static boolean hasSubstitute(final char type, final Term term1, final Term term2) { return findSubstitute(type, term1, term2, new HashMap<>(), new HashMap<>()); } }