/* * Sentence.java * * Copyright (C) 2008 Pei Wang * * This file is part of Open-NARS. * * Open-NARS is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * Open-NARS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Open-NARS. If not, see <http://www.gnu.org/licenses/>. */ package nars.entity; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import nars.NAR; import nars.config.Parameters; import nars.inference.TemporalRules; import nars.inference.TruthFunctions; import nars.inference.TruthFunctions.EternalizedTruthValue; import nars.io.Symbols; import nars.io.Texts; import nars.language.CompoundTerm; import nars.language.Conjunction; import nars.language.Equivalence; import nars.language.Implication; import nars.language.Interval; import nars.language.Interval.AtomicDuration; import nars.language.Statement; import nars.language.Term; import nars.language.Variable; import nars.operator.Operation; import nars.operator.Operator; /** * A Sentence is an abstract class, mainly containing a Term, a TruthValue, and * a Stamp. * <p> * It is used as the premises and conclusions of all inference rules. */ public class Sentence<T extends Term> implements Cloneable { public boolean producedByTemporalInduction=false; /** * The content of a Sentence is a Term */ public final T term; /** * The punctuation also indicates the type of the Sentence: * Judgment, Question, Goal, or Quest. * Represented by characters: '.', '?', '!', or '@' */ public final char punctuation; /** * The truth value of Judgment, or desire value of Goal */ public final TruthValue truth; /** * Partial record of the derivation path */ public final Stamp stamp; /** * Whether the sentence can be revised */ private boolean revisible; /** caches the 'getKey()' result */ private transient CharSequence key; private final int hash; public Sentence(T term, char punctuation, TruthValue newTruth, Stamp newStamp) { this(term, punctuation, newTruth, newStamp, true); } /** * Create a Sentence with the given fields * * @param content The Term that forms the content of the sentence * @param punctuation The punctuation indicating the type of the sentence * @param truth The truth value of the sentence, null for question * @param stamp The stamp of the sentence indicating its derivation time and * base */ private Sentence(T _content, final char punctuation, final TruthValue truth, final Stamp stamp, boolean normalize) { //cut interval at end for sentence in serial conjunction, and inbetween for parallel if(punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK) { if(_content instanceof Conjunction) { Conjunction c=(Conjunction)_content; if(c.getTemporalOrder()==TemporalRules.ORDER_FORWARD) { if(c.term[c.term.length-1] instanceof Interval) { Term[] term2=new Term[c.term.length-1]; for(int i=0;i<c.term.length-1;i++) { term2[i]=c.term[i]; } _content=(T) Conjunction.make(term2, c.getTemporalOrder()); //ok we removed a part of the interval, we have to transform the occurence time of the sentence back //accordingly long time=Interval.magnitudeToTime(((Interval)c.term[c.term.length-1]).magnitude,new AtomicDuration(Parameters.DURATION)); if(stamp!=null) stamp.setOccurrenceTime(stamp.getOccurrenceTime()-time); } } } } this.punctuation = punctuation; if(this.isJudgment() && _content.hasVarQuery()) { truth.setConfidence(0.0f); } else if(_content instanceof Implication || _content instanceof Equivalence) { if(((Statement) _content).getSubject().hasVarIndep() && !((Statement) _content).getPredicate().hasVarIndep()) truth.setConfidence(0.0f); if(((Statement) _content).getPredicate().hasVarIndep() && !((Statement) _content).getSubject().hasVarIndep()) truth.setConfidence(0.0f); //TODO: if(_content.getTemporalOrder() == TemporalRules.ORDER_FORWARD && truth != null) { //do not allow =/> statements without conjunction on left if(!(((Statement) _content).getSubject() instanceof Conjunction)) { //because at least a time measurement has to be givem // truth.setConfidence(0.0f); //not necessary and consider disjunction case!! } else { Conjunction conj = (Conjunction) ((Statement) _content).getSubject(); if(conj.getTemporalOrder() != TemporalRules.ORDER_FORWARD && conj.getTemporalOrder() != TemporalRules.ORDER_BACKWARD) { truth.setConfidence(0.0f); } else { //when the last two are intervals, its not valid if(conj.term[conj.term.length-1] instanceof Interval && conj.term[conj.term.length-2] instanceof Interval) { truth.setConfidence(0.0f); } } } } } else if (_content instanceof Interval && punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK) { truth.setConfidence(0.0f); //do it that way for now, because else further inference is interrupted. if(Parameters.DEBUG) throw new RuntimeException("Sentence content must not be Interval: " + _content + punctuation + " " + stamp); } if ( (!isQuestion() && !isQuest()) && (truth == null) && punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK) { throw new RuntimeException("Judgment and Goal sentences require non-null truth value"); } if(_content.subjectOrPredicateIsIndependentVar() && punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK) { truth.setConfidence(0.0f); //do it that way for now, because else further inference is interrupted. if(Parameters.DEBUG) throw new RuntimeException("A statement sentence is not allowed to have a independent variable as subj or pred"); } if (Parameters.DEBUG && Parameters.DEBUG_INVALID_SENTENCES && punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK) { if (!Term.valid(_content)) { truth.setConfidence(0.0f); if(Parameters.DEBUG) { CompoundTerm.UnableToCloneException ntc = new CompoundTerm.UnableToCloneException("Invalid Sentence term: " + _content); ntc.printStackTrace(); throw ntc; } } } if ((isQuestion() || isQuest()) && punctuation!=Symbols.TERM_NORMALIZING_WORKAROUND_MARK && !stamp.isEternal()) { stamp.setEternal(); //throw new RuntimeException("Questions and Quests require eternal tense"); } this.truth = truth; this.stamp = stamp; this.revisible = !(_content.hasVarDep()); //Variable name normalization //TODO move this to Concept method, like cloneNormalized() if (normalize && _content.hasVar() && (_content instanceof CompoundTerm) && (!((CompoundTerm)_content).isNormalized() ) ) { this.term = (T)((CompoundTerm)_content).cloneDeepVariables(); final CompoundTerm c = (CompoundTerm)term; List<Variable> vars = new ArrayList(); //may contain duplicates, list for efficiency c.recurseSubtermsContainingVariables(new Term.TermVisitor() { @Override public void visit(final Term t, final Term parent) { if (t instanceof Variable) { Variable v = ((Variable)t); vars.add(v); } } }); Map<CharSequence,CharSequence> rename = new HashMap(); boolean renamed = false; for (final Variable v : vars) { CharSequence vname = v.name(); if (!v.hasVarIndep()) vname = vname + " " + v.getScope().name(); CharSequence n = rename.get(vname); if (n==null) { //type + id rename.put(vname, n = Variable.getName(v.getType(), rename.size()+1)); if (!n.equals(vname)) renamed = true; } v.setScope(c, n); } if (renamed) { c.invalidateName(); if (Parameters.DEBUG && Parameters.DEBUG_INVALID_SENTENCES) { if (!Term.valid(c)) { CompoundTerm.UnableToCloneException ntc = new CompoundTerm.UnableToCloneException("Invalid term discovered after normalization: " + c + " ; prior to normalization: " + _content); ntc.printStackTrace(); throw ntc; } } } c.setNormalized(true); } else { this.term = _content; } if (isUniqueByOcurrenceTime()) this.hash = Objects.hash(term, punctuation, truth, stamp.getOccurrenceTime()); else this.hash = Objects.hash(term, punctuation, truth ); } protected boolean isUniqueByOcurrenceTime() { return ((punctuation == Symbols.JUDGMENT_MARK) || (punctuation == Symbols.QUESTION_MARK)); } /** * To check whether two sentences are equal * * @param that The other sentence * @return Whether the two sentences have the same content */ @Override public boolean equals(final Object that) { if (this == that) return true; if (that instanceof Sentence) { final Sentence t = (Sentence) that; //return getKey().equals(t.getKey()); if (hash!=t.hash) return false; if (punctuation!=t.punctuation) return false; if (isUniqueByOcurrenceTime()) { if (stamp.getOccurrenceTime()!=t.stamp.getOccurrenceTime()) return false; } if (truth==null) { if (t.truth!=null) return false; } else if (t.truth==null) { return false; } else if (!truth.equals(t.truth)) return false; if (!term.equals(t.term)) return false; return true; } return false; } /** * To produce the hashcode of a sentence * * @return A hashcode */ @Override public int hashCode() { return hash; } /** * Check whether the judgment is equivalent to another one * <p> * The two may have different keys * * @param that The other judgment * @return Whether the two are equivalent */ public boolean equivalentTo(final Sentence that) { if (Parameters.DEBUG) { if ((!term.equals(term)) || (punctuation != that.punctuation)) { throw new RuntimeException("invalid comparison for Sentence.equivalentTo"); } } return (truth.equals(that.truth) && stamp.equals(that.stamp,false,true,true,true)); } /** * Clone the Sentence * * @return The clone */ @Override public Sentence clone() { return clone(term); } public Sentence clone(boolean makeEternal) { Sentence clon = clone(term); if(clon.stamp.getOccurrenceTime()!=Stamp.ETERNAL && makeEternal) { //change occurence time of clone clon.stamp.setEternal(); } return clon; } /** Clone with a different Term */ public final Sentence clone(final Term t) { return new Sentence(t, punctuation, truth!=null ? new TruthValue(truth) : null, stamp.clone()); } /** * project a judgment to a difference occurrence time * * @param targetTime The time to be projected into * @param currentTime The current time as a reference * @return The projected belief */ public Sentence projection(final long targetTime, final long currentTime) { TruthValue newTruth = projectionTruth(targetTime, currentTime); boolean eternalizing = (newTruth instanceof EternalizedTruthValue); Stamp newStamp = eternalizing ? stamp.cloneWithNewOccurrenceTime(Stamp.ETERNAL) : stamp.cloneWithNewOccurrenceTime(targetTime); return new Sentence(term, punctuation, newTruth, newStamp, false); } public TruthValue projectionTruth(final long targetTime, final long currentTime) { TruthValue newTruth = null; if (!stamp.isEternal()) { newTruth = TruthFunctions.eternalize(truth); if (targetTime != Stamp.ETERNAL) { long occurrenceTime = stamp.getOccurrenceTime(); float factor = TruthFunctions.temporalProjection(occurrenceTime, targetTime, currentTime); float projectedConfidence = factor * truth.getConfidence(); if (projectedConfidence > newTruth.getConfidence()) { newTruth = new TruthValue(truth.getFrequency(), projectedConfidence); } } } if (newTruth == null) newTruth = truth.clone(); return newTruth; } // /** // * Clone the content of the sentence // * // * @return A clone of the content Term // */ // public Term cloneContent() { // return content.clone(); // } // /** * Recognize a Judgment * * @return Whether the object is a Judgment */ public boolean isJudgment() { return (punctuation == Symbols.JUDGMENT_MARK); } /** * Recognize a Question * * @return Whether the object is a Question */ public boolean isQuestion() { return (punctuation == Symbols.QUESTION_MARK); } public boolean isGoal() { return (punctuation == Symbols.GOAL_MARK); } public boolean isQuest() { return (punctuation == Symbols.QUEST_MARK); } public boolean containQueryVar() { return term.hasVarQuery(); } public boolean getRevisible() { return revisible; } public void setRevisible(final boolean b) { revisible = b; } public int getTemporalOrder() { return term.getTemporalOrder(); } public long getOccurenceTime() { return stamp.getOccurrenceTime(); } public Operator getOperator() { if (term instanceof Operation) { return (Operator) ((Statement) term).getPredicate(); } else { return null; } } /** * Get a String representation of the sentence * * @return The String */ @Override public String toString() { return getKey().toString(); } /** * Get a String representation of the sentence for key of Task and TaskLink * * @return The String */ public CharSequence getKey() { //key must be invalidated if content or truth change if (key == null) { final CharSequence contentName = term.name(); final boolean showOcurrenceTime = ((punctuation == Symbols.JUDGMENT_MARK) || (punctuation == Symbols.QUESTION_MARK)); //final String occurrenceTimeString = ? stamp.getOccurrenceTimeString() : ""; //final CharSequence truthString = truth != null ? truth.name() : null; int stringLength = 0; //contentToString.length() + 1 + 1/* + stampString.baseLength()*/; if (truth != null) { stringLength += (showOcurrenceTime ? 8 : 0) + 11 /*truthString.length()*/; } //suffix = [punctuation][ ][truthString][ ][occurenceTimeString] final StringBuilder suffix = new StringBuilder(stringLength).append(punctuation); if (truth != null) { suffix.append(' '); truth.appendString(suffix, false); } if ((showOcurrenceTime) && (stamp!=null)) { suffix.append(' '); stamp.appendOcurrenceTime(suffix); } key = Texts.yarn(Parameters.ROPE_TERMLINK_TERM_SIZE_THRESHOLD, contentName,//.toString(), suffix); //.toString()); //key = new FlatCharArrayRope(StringUtil.getCharArray(k)); } return key; } /** * Get a String representation of the sentence for display purpose * * @return The String */ public CharSequence toString(NAR nar, boolean showStamp) { CharSequence contentName = term.name(); final long t = nar.memory.time(); long diff=stamp.getOccurrenceTime()-nar.memory.time(); long diffabs = Math.abs(diff); String timediff = ""; if(diffabs < nar.memory.param.duration.get()) { timediff = "|"; } else { int Int = Interval.timeToMagnitude(diffabs, nar.param.duration); timediff = diff>0 ? "+"+String.valueOf(Int) : "-"+String.valueOf(Int); } String tenseString = ":"+timediff+":"; //stamp.getTense(t, nar.memory.getDuration()); if(stamp.getOccurrenceTime() == Stamp.ETERNAL) tenseString=""; CharSequence stampString = showStamp ? stamp.name() : null; int stringLength = contentName.length() + tenseString.length() + 1 + 1; if (truth != null) stringLength += 11; if (showStamp) stringLength += stampString.length()+1; final StringBuilder buffer = new StringBuilder(stringLength). append(contentName).append(punctuation); if (tenseString.length() > 0) buffer.append(' ').append(tenseString); if (truth != null) { buffer.append(' '); truth.appendString(buffer, true); } if (showStamp) buffer.append(' ').append(stampString); return buffer; } /** * Get the truth value (or desire value) of the sentence * * @return Truth value, null for question */ public void discountConfidence() { truth.setConfidence(truth.getConfidence() * Parameters.DISCOUNT_RATE).setAnalytic(false); } final public boolean equalsContent(final Sentence s2) { return term.equals(s2.term); } public boolean isEternal() { return stamp.isEternal(); } public boolean after(Sentence s, int duration) { return stamp.after(s.stamp, duration); } public boolean before(Sentence s, int duration) { return stamp.before(s.stamp, duration); } public long getCreationTime() { return stamp.getCreationTime(); } public static final class ExpectationComparator implements Comparator<Sentence> { final static ExpectationComparator the = new ExpectationComparator(); @Override public int compare(final Sentence b, final Sentence a) { return Float.compare(a.truth.getExpectation(), b.truth.getExpectation()); } } public static final class ConfidenceComparator implements Comparator<Sentence> { final static ExpectationComparator the = new ExpectationComparator(); @Override public int compare(final Sentence b, final Sentence a) { return Float.compare(a.truth.getConfidence(), b.truth.getConfidence()); } } public static List<Sentence> sortExpectation(Collection<Sentence> s) { List<Sentence> l = new ArrayList(s); Collections.sort(l, ExpectationComparator.the); return l; } public static List<Sentence> sortConfidence(Collection<Sentence> s) { List<Sentence> l = new ArrayList(s); Collections.sort(l, ConfidenceComparator.the); return l; } /** performs some (but not exhaustive) tests on a term to determine some cases where it is invalid as a sentence content */ public static final boolean invalidSentenceTerm(final Term T) { if (!(T instanceof CompoundTerm)) { return true; } if (T instanceof Statement) { Statement st = (Statement) T; if (Statement.invalidStatement(st.getSubject(), st.getPredicate())) return true; if (st.getSubject().equals(st.getPredicate())) { return true; } } return false; } public T getTerm() { return term; } public TruthValue getTruth() { return truth; } }