/*
* Stamp.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 Pbulic 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 com.google.common.collect.Iterators;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import nars.storage.Memory;
import nars.config.Parameters;
import nars.inference.TemporalRules;
import static nars.inference.TemporalRules.ORDER_BACKWARD;
import static nars.inference.TemporalRules.ORDER_FORWARD;
import nars.io.Symbols;
import nars.language.Tense;
import static nars.language.Tense.Future;
import static nars.language.Tense.Past;
import static nars.language.Tense.Present;
import nars.language.Term;
import static nars.inference.TemporalRules.order;
public class Stamp implements Cloneable {
/*serial numbers. not to be modified after Stamp constructor has initialized it*/
public long[] evidentialBase;
/* evidentialBase baseLength*/
public int baseLength;
/*creation time of the stamp*/
private long creationTime;
/* estimated occurrence time of the event*/
private long occurrenceTime;
/*default for atemporal events means "always" in Judgment/Question, but "current" in Goal/Quest*/
public static final long ETERNAL = Integer.MIN_VALUE;
/** caches evidentialBase as a set for comparisons and hashcode, stores the unique Long's in-order for efficiency*/
private long[] evidentialSet = null;
/*Tense of the item*/
private Tense tense;
/*True when its a neg confirmation task that was already checked:*/
public boolean alreadyAnticipatedNegConfirmation = false;
/** caches */
transient CharSequence name = null;
/**
* derivation chain containing the used premises and conclusions which made
* deriving the conclusion c possible
* Uses LinkedHashSet for optimal contains/indexOf performance.
* TODO use thread-safety for this
*/
/** cache of hashcode of evidential base */
transient private int evidentialHash;
public boolean before(Stamp s, int duration) {
if (isEternal() || s.isEternal())
return false;
return order(s.occurrenceTime, occurrenceTime, duration) == TemporalRules.ORDER_BACKWARD;
}
public boolean after(Stamp s, int duration) {
if (isEternal() || s.isEternal())
return false;
return order(s.occurrenceTime, occurrenceTime, duration) == TemporalRules.ORDER_FORWARD; }
public float getOriginality() {
return 1.0f / (evidentialBase.length + 1);
}
public interface DerivationBuilder {
LinkedHashSet<Term> build();
}
/** used for when the ocrrence time will be set later; so should not be called from externally but through another Stamp constructor */
protected Stamp(final Tense tense, final long serial) {
this.baseLength = 1;
this.evidentialBase = new long[baseLength];
this.evidentialBase[0] = serial;
this.tense = tense;
this.creationTime = -1;
}
/**
* Generate a new stamp, with a new serial number, for a new Task
*
* @param time Creation time of the stamp
*/
public Stamp(final long time, final Tense tense, final long serial, final int duration) {
this(tense, serial);
setCreationTime(time, duration);
}
/**
* Generate a new stamp identical with a given one
*
* @param old The stamp to be cloned
*/
private Stamp(final Stamp old) {
this(old, old.creationTime);
}
/**
* Generate a new stamp from an existing one, with the same evidentialBase
* but different creation time
* <p>
* For single-premise rules
*
* @param old The stamp of the single premise
* @param creationTim The current time
*/
public Stamp(final Stamp old, final long creationTime) {
this(old, creationTime, old);
}
public Stamp(final Stamp old, final long creationTime, final Stamp useEvidentialBase) {
this.evidentialBase = useEvidentialBase.evidentialBase;
this.baseLength = useEvidentialBase.baseLength;
this.creationTime = creationTime;
this.occurrenceTime = old.getOccurrenceTime();
}
/**
* Generate a new stamp for derived sentence by merging the two from parents
* the first one is no shorter than the second
*
* @param first The first Stamp
* @param second The second Stamp
*/
public Stamp(final Stamp first, final Stamp second, final long time) {
//TODO use iterators instead of repeated first and second .get's?
int i1, i2, j;
i1 = i2 = j = 0;
this.baseLength = Math.min(first.baseLength + second.baseLength, Parameters.MAXIMUM_EVIDENTAL_BASE_LENGTH);
this.evidentialBase = new long[baseLength];
final long[] firstBase = first.evidentialBase;
final long[] secondBase = second.evidentialBase;
int firstLength = firstBase.length;
int secondLength = secondBase.length;
creationTime = time;
occurrenceTime = first.getOccurrenceTime(); // use the occurrence of task
//https://code.google.com/p/open-nars/source/browse/trunk/nars_core_java/nars/entity/Stamp.java#143
while (i2 < secondLength && j < baseLength) {
evidentialBase[j++] = secondBase[i2++];
}
while (i1 < firstLength && j < baseLength) {
evidentialBase[j++] = firstBase[i1++];
}
}
public Stamp(final Memory memory, final Tense tense) {
this(memory.time(), tense, memory.newStampSerial(), memory.param.duration.get());
}
/** creates a stamp with default Present tense */
public Stamp(final Memory memory) {
this(memory, Tense.Present);
}
public static boolean baseOverlap(long[] base1, long[] base2) {
HashSet<Long> task_base = new HashSet<Long>(base1.length + base2.length);
for(int i=0; i < base1.length; i++) {
if(task_base.contains(Long.valueOf(base1[i]))) { //can have an overlap in itself already
return true;
}
task_base.add(base1[i]);
}
//too restrictive, its checked for non-deductive inference rules in derivedTask
for(int i=0; i < base2.length; i++) {
if(task_base.contains(Long.valueOf(base2[i]))) {
return true;
}
task_base.add(base2[i]); //also add to detect collision with itself
}
return false;
}
public boolean evidenceIsCyclic() {
HashSet<Long> task_base = new HashSet<Long>(this.evidentialBase.length);
boolean cyclic = false;
for(int i=0; i < this.evidentialBase.length; i++) {
if(task_base.contains(Long.valueOf(this.evidentialBase[i]))) { //can have an overlap in itself already
return true;
}
task_base.add(this.evidentialBase[i]);
}
return false;
}
public boolean isEternal() {
boolean eternalOccurrence = occurrenceTime == ETERNAL;
if (Parameters.DEBUG) {
if (eternalOccurrence && tense!=Tense.Eternal) {
throw new RuntimeException("Stamp has inconsistent tense and eternal ocurrenceTime: tense=" + tense);
}
}
return eternalOccurrence;
}
/** sets the creation time; used to set input tasks with the actual time they enter Memory */
public void setCreationTime(long time, int duration) {
creationTime = time;
if (tense == null) {
occurrenceTime = ETERNAL;
} else if (tense == Past) {
occurrenceTime = time - duration;
} else if (tense == Future) {
occurrenceTime = time + duration;
} else if (tense == Present) {
occurrenceTime = time;
} else {
occurrenceTime = time;
}
}
/**
* Clone a stamp
*
* @return The cloned stamp
*/
@Override
public Stamp clone() {
return new Stamp(this);
}
/**
* Get a number from the evidentialBase by index, called in this class only
*
* @param i The index
* @return The number at the index
*/
long get(final int i) {
return evidentialBase[i];
}
public static long[] toSetArray(final long[] x) {
long[] set = x.clone();
if (x.length < 2)
return set;
//1. copy evidentialBse
//2. sort
//3. count duplicates
//4. create new array
Arrays.sort(set);
long lastValue = -1;
int j = 0; //# of unique items
for (int i = 0; i < set.length; i++) {
long v = set[i];
if (lastValue != v)
j++;
lastValue = v;
}
lastValue = -1;
long[] sorted = new long[j];
j = 0;
for (int i = 0; i < set.length; i++) {
long v = set[i];
if (lastValue != v)
sorted[j++] = v;
lastValue = v;
}
return sorted;
}
/**
* Convert the evidentialBase into a set
*
* @return The TreeSet representation of the evidential base
*/
private long[] toSet() {
if (evidentialSet == null) {
evidentialSet = toSetArray(evidentialBase);
evidentialHash = Arrays.hashCode(evidentialSet);
}
return evidentialSet;
}
@Override public boolean equals(final Object that) {
throw new RuntimeException("Use other equals() method");
}
/**
* Check if two stamps contains the same types of content
*
* @param that The Stamp to be compared
* @return Whether the two have contain the same evidential base
*/
public boolean equals(Stamp s, final boolean creationTime, final boolean ocurrenceTime, final boolean evidentialBase, final boolean derivationChain) {
if (this == s) return true;
if (creationTime)
if (getCreationTime()!=s.getCreationTime()) return false;
if (ocurrenceTime)
if (getOccurrenceTime()!=s.getOccurrenceTime()) return false;
if (evidentialBase) {
if (evidentialHash() != s.evidentialHash()) return false;
if (!Arrays.equals(toSet(), s.toSet())) return false;
}
return true;
}
/** necessary because LinkedHashSet.equals does not compare order, only set content */
public static boolean chainEquals(final Collection<Term> a, final Collection<Term> b) {
if (a == b) return true;
if ((a instanceof LinkedHashSet) && (b instanceof LinkedHashSet))
return Iterators.elementsEqual(a.iterator(), b.iterator());
else
return a.equals(b);
}
/**
* The hash code of Stamp
*
* @return The hash code
*/
public final int evidentialHash() {
if (evidentialSet==null)
toSet();
return evidentialHash;
}
public Stamp cloneWithNewOccurrenceTime(final long newOcurrenceTime) {
Stamp s = clone();
if (newOcurrenceTime == ETERNAL)
s.tense = Tense.Eternal;
s.setOccurrenceTime(newOcurrenceTime);
return s;
}
/**
* Get the occurrenceTime of the truth-value
*
* @return The occurrence time
*/
public long getOccurrenceTime() {
return occurrenceTime;
}
/**
*
*/
public void setEternal() {
occurrenceTime=ETERNAL;
}
public StringBuilder appendOcurrenceTime(final StringBuilder sb) {
if (occurrenceTime != ETERNAL) {
int estTimeLength = 8; /* # digits */
sb.ensureCapacity(estTimeLength + 1 + 1);
sb.append('[').append(occurrenceTime).append(']').toString();
}
return sb;
}
/**
* Get the occurrenceTime of the truth-value
*
* @return The occurrence time
*/
public String getOccurrenceTimeString() {
if (isEternal()) {
return "";
} else {
return appendOcurrenceTime(new StringBuilder()).toString();
}
}
public String getTense(final long currentTime, final int duration) {
if (isEternal()) {
return "";
}
switch (TemporalRules.order(currentTime, occurrenceTime, duration)) {
case ORDER_FORWARD:
return Symbols.TENSE_FUTURE;
case ORDER_BACKWARD:
return Symbols.TENSE_PAST;
default:
return Symbols.TENSE_PRESENT;
}
}
public void setOccurrenceTime(final long time) {
if (occurrenceTime!=time) {
occurrenceTime = time;
if (time == ETERNAL)
tense = Tense.Eternal;
name = null;
}
}
public CharSequence name() {
if (name == null) {
final int estimatedInitialSize = 10 * baseLength;
final StringBuilder buffer = new StringBuilder(estimatedInitialSize);
buffer.append(Symbols.STAMP_OPENER).append(getCreationTime());
if (!isEternal()) {
buffer.append('|').append(occurrenceTime);
}
buffer.append(' ').append(Symbols.STAMP_STARTER).append(' ');
for (int i = 0; i < baseLength; i++) {
buffer.append(Long.toString(evidentialBase[i]));
if (i < (baseLength - 1)) {
buffer.append(Symbols.STAMP_SEPARATOR);
} else {
buffer.append(' ').append(Symbols.STAMP_STARTER).append(' ');
}
}
buffer.append(Symbols.STAMP_CLOSER).append(' ');
//this is for estimating an initial size of the stringbuffer
//System.out.println(baseLength + " " + derivationChain.size() + " " + buffer.baseLength());
name = buffer;
}
return name;
}
@Override
public String toString() {
return name().toString();
}
/**
* @return the creationTime
*/
public long getCreationTime() {
return creationTime;
}
}