package nars; import nars.config.Parameters; import nars.config.RuntimeParameters; import nars.util.Plugin; import nars.storage.Memory; import nars.util.Events; import nars.util.EventEmitter; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; import static com.google.common.collect.Iterators.singletonIterator; import java.io.IOException; import java.io.Serializable; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import java.util.logging.Logger; import nars.util.EventEmitter.EventObserver; import nars.util.Events.FrameEnd; import nars.util.Events.FrameStart; import nars.util.Events.Perceive; import nars.config.Plugins; import nars.control.DerivationContext.DerivationFilter; import nars.entity.BudgetValue; import nars.entity.Concept; import nars.entity.Item; import nars.entity.Sentence; import nars.entity.Stamp; import nars.entity.Task; import nars.io.Answered; import nars.io.InPort; import nars.io.Input; import nars.io.Output; import nars.io.Output.ERR; import nars.io.Output.IN; import nars.io.Symbols; import nars.io.TaskInput; import nars.io.TextInput; import nars.io.Narsese; import nars.io.Narsese.InvalidInputException; import nars.language.Tense; import nars.operator.Operator; import nars.io.Echo; import nars.lab.testutils.ConceptMonitor; import nars.storage.LevelBag; /** * Non-Axiomatic Reasoner * * Instances of this represent a reasoner connected to a Memory, and set of Input and Output channels. * * All state is contained within Memory. A NAR is responsible for managing I/O channels and executing * memory operations. It executesa series sof cycles in two possible modes: * * step mode - controlled by an outside system, such as during debugging or testing * * thread mode - runs in a pausable closed-loop at a specific maximum framerate. */ public class NAR implements Runnable { /** * The information about the version and date of the project. */ public static final String VERSION = "Open-NARS v1.6.5"; /** * The project web sites. */ public static final String WEBSITE = " Open-NARS website: http://code.google.com/p/open-nars/ \n" + " NARS website: http://sites.google.com/site/narswang/ \n" + " Github website: http://github.com/opennars/ \n" + " IRC: http://webchat.freenode.net/?channels=nars \n"; ; private Thread thread = null; long minCyclePeriodMS; /** * The name of the reasoner */ protected String name; /** * The memory of the reasoner */ public Memory memory; public RuntimeParameters param; /** The addInput channels of the reasoner */ public final List<InPort<Object,Item>> inputChannels; /** pending input and output channels to add on the next cycle. */ private final List<InPort<Object,Item>> newInputChannels; public class PluginState implements Serializable { final public Plugin plugin; boolean enabled = false; public PluginState(Plugin plugin) { this(plugin,true); } public PluginState(Plugin plugin, boolean enabled) { this.plugin = plugin; setEnabled(enabled); } public void setEnabled(boolean enabled) { if (this.enabled == enabled) return; plugin.setEnabled(NAR.this, enabled); this.enabled = enabled; emit(Events.PluginsChange.class, plugin, enabled); } public boolean isEnabled() { return enabled; } } protected final List<PluginState> plugins = new CopyOnWriteArrayList<>(); /** Flag for running continuously */ private boolean running = false; /** used by stop() to signal that a running loop should be interrupted */ private boolean stopped = false; private boolean inputting = true; private boolean threadYield; private int inputSelected = 0; //counter for the current selected input channel private boolean ioChanged; private int cyclesPerFrame = 1; //how many memory cycles to execute in one NAR cycle public Memory NewMemory(RuntimeParameters p) { return new Memory(p, new LevelBag(Parameters.CONCEPT_BAG_LEVELS, Parameters.CONCEPT_BAG_SIZE), new LevelBag<>(Parameters.NOVEL_TASK_BAG_LEVELS, Parameters.NOVEL_TASK_BAG_SIZE), new LevelBag<>(Parameters.SEQUENCE_BAG_LEVELS, Parameters.SEQUENCE_BAG_SIZE)); } public NAR() { this(new Plugins()); } /** normal way to construct a NAR, using a particular Build instance */ public NAR(Plugins b) { Memory m = NewMemory(b.param); this.memory = m; this.param = m.param; //needs to be concurrent in case we change this while running inputChannels = new ArrayList(); newInputChannels = new CopyOnWriteArrayList(); b.init(this); } /** * Reset the system with an empty memory and reset clock. Called locally and * from {@link NARControls}. */ public void reset() { int numInputs = inputChannels.size(); for (int i = 0; i < numInputs; i++) { InPort port = inputChannels.get(i); port.input.finished(true); } inputChannels.clear(); newInputChannels.clear(); //newOutputChannels.clear(); //oldOutputChannels.clear(); ioChanged = false; memory.reset(); } /** * Convenience method for creating a TextInput and adding as Input Channel. * Generally the text will consist of Task's to be parsed in Narsese, but * may contain other commands recognized by the system. The creationTime * will be set to the current memory cycle time, but may be processed by * memory later according to the length of the input queue. */ public TextInput addInput(final String text) { return addInput(text, text.contains("\n") ? -1 : time()); } /** add text input at a specific time, which can be set to current time (regardless of when it will reach the memory), backdated, or forward dated */ public TextInput addInput(final String text, long creationTime) { final TextInput i = new TextInput(text); ObjectTaskInPort ip = addInput(i); if (creationTime!=-1) ip.setCreationTimeOverride(creationTime); return i; } public NAR addInput(final String taskText, float frequency, float confidence) throws InvalidInputException { return addInput(-1, -1, taskText, frequency, confidence); } public NAR believe(float pri, float dur, String termString, Tense tense, float freq, float conf) throws InvalidInputException { return addInput(memory.newTask(new Narsese(this).parseTerm(termString), Symbols.JUDGMENT_MARK, freq, conf, pri, dur, tense)); } public NAR believe(String termString, Tense tense, float freq, float conf) throws InvalidInputException { return believe(Parameters.DEFAULT_JUDGMENT_PRIORITY, Parameters.DEFAULT_JUDGMENT_DURABILITY, termString, tense, freq, conf); } /** gets a concept if it exists, or returns null if it does not */ public Concept concept(String concept) throws InvalidInputException { return memory.concept(new Narsese(this).parseTerm(concept)); } public NAR ask(String termString) throws InvalidInputException { return ask(termString, null); } public NAR ask(String termString, Answered answered) throws InvalidInputException { Task t; addInput( t = new Task( new Sentence( new Narsese(this).parseTerm(termString), Symbols.QUESTION_MARK, null, new Stamp(memory, Tense.Eternal)), new BudgetValue( Parameters.DEFAULT_QUESTION_PRIORITY, Parameters.DEFAULT_QUESTION_DURABILITY, 1)) ); if (answered!=null) { answered.start(t, this); } return this; } public NAR askNow(String termString, Answered answered) throws InvalidInputException { Task t; addInput( t = new Task( new Sentence( new Narsese(this).parseTerm(termString), Symbols.QUESTION_MARK, null, new Stamp(memory, Tense.Present)), new BudgetValue( Parameters.DEFAULT_QUESTION_PRIORITY, Parameters.DEFAULT_QUESTION_DURABILITY, 1)) ); if (answered!=null) { answered.start(t, this); } return this; } public NAR addInput(final Sentence sentence) throws InvalidInputException { //TODO use correct default values depending on sentence punctuation float priority = Parameters.DEFAULT_JUDGMENT_PRIORITY; float durability = Parameters.DEFAULT_JUDGMENT_DURABILITY; return addInput( new Task(sentence, new BudgetValue(priority, durability, sentence.truth)) ); } public NAR addInput(float priority, float durability, final String taskText, float frequency, float confidence) throws InvalidInputException { Task t = new Narsese(this).parseTask(taskText); if (frequency!=-1) t.sentence.truth.setFrequency(frequency); if (confidence!=-1) t.sentence.truth.setConfidence(confidence); if (priority!=-1) t.budget.setPriority(priority); if (durability!=-1) t.budget.setDurability(durability); return addInput(t); } public NAR addInput(final Task t) { TaskInput ti = new TaskInput(t); addInput(ti); return this; } /** attach event handler */ public void on(Class c, EventObserver o) { memory.event.on(c, o); } /** remove event handler */ public void off(Class c, EventObserver o) { memory.event.on(c, o); } /** set an event handler. useful for multiple events. */ public void event(EventObserver e, boolean enabled, Class... events) { memory.event.set(e, enabled, events); } public int getCyclesPerFrame() { return cyclesPerFrame; } final class ObjectTaskInPort extends InPort<Object,Item> { private long creationTime = -1; public ObjectTaskInPort(Input input, ArrayDeque buffer, float initialAttention) { super(input, buffer, initialAttention); } @Override public void perceive(final Object x) { memory.emit(Perceive.class, this, x); } @Override public Iterator<Item> postprocess(final Iterator<Item> at) { try { if (creationTime == -1) return at; else { //Process tasks with overrides final int duration = Parameters.DURATION; return Iterators.filter(at, new Predicate<Item>() { @Override public boolean apply(Item at) { if (at instanceof Task) { Task t = (Task)at; if (t.sentence!=null) if (t.sentence.stamp!=null) { t.sentence.stamp.setCreationTime(creationTime, duration); } } return true; } }); } } catch (Throwable e) { if (Parameters.DEBUG) throw e; return singletonIterator(new Echo(ERR.class, e)); } } /** sets the 'creationTime' override for new Tasks. sets the stamp to a particular * value upon its exit from the queue. */ public void setCreationTimeOverride(final long creationTime) { this.creationTime = creationTime; } } /** Adds an input channel. Will remain added until it closes or it is explicitly removed. */ public ObjectTaskInPort addInput(final Input channel) { ObjectTaskInPort i = new ObjectTaskInPort(channel, new ArrayDeque(), 1.0f); try { i.update(); newInputChannels.add(i); } catch (IOException ex) { if (Parameters.DEBUG) throw new RuntimeException(ex.toString()); emit(ERR.class, ex); } ioChanged = true; if (!running) updatePorts(); return i; } // /** Explicitly removes an input channel and notifies it, via Input.finished(true) that is has been removed */ // public Input removeInput(Input channel) { // inputChannels.remove(channel); // channel.finished(true); // return channel; // } public void addPlugin(Plugin p) { if (p instanceof Operator) { memory.addOperator((Operator)p); } if (p instanceof DerivationFilter) { param.defaultDerivationFilters.add((DerivationFilter)p); } PluginState ps = new PluginState(p); plugins.add(ps); emit(Events.PluginsChange.class, p, null); } public void removePlugin(PluginState ps) { if (plugins.remove(ps)) { Plugin p = ps.plugin; if (p instanceof Operator) { memory.removeOperator((Operator)p); } if (p instanceof DerivationFilter) { param.defaultDerivationFilters.remove((DerivationFilter)p); } ps.setEnabled(false); emit(Events.PluginsChange.class, null, p); } } public List<PluginState> getPlugins() { return Collections.unmodifiableList(plugins); } @Deprecated public void start(final long minCyclePeriodMS, int cyclesPerFrame) { this.minCyclePeriodMS = minCyclePeriodMS; this.cyclesPerFrame = cyclesPerFrame; if (thread == null) { thread = new Thread(this, "Inference"); thread.start(); } running = true; } /** * Repeatedly execute NARS working cycle in a new thread with Iterative timing. * * @param minCyclePeriodMS minimum cycle period (milliseconds). */ public void start(final long minCyclePeriodMS) { start(minCyclePeriodMS, 1); } /** Can be used to pause/resume input */ public void setInputting(boolean inputEnabled) { this.inputting = inputEnabled; } public EventEmitter event() { return memory.event; } /** * Stop the inference process, killing its thread. */ public void stop() { if (thread!=null) { thread.interrupt(); thread = null; } stopped = true; running = false; } /** Execute a fixed number of frames. * may execute more than requested cycles if cyclesPerFrame > 1 */ public void step(final int frames) { memory.allowExecution = true; /*if (thread!=null) { memory.stepLater(cycles); return; }*/ final boolean wasRunning = running; running = true; stopped = false; for (int f = 0; (f < frames) && (!stopped); f++) { frame(); } running = wasRunning; } /** Execute a fixed number of cycles, then finish any remaining walking steps. */ public NAR run(int cycles) { if (cycles <= 0) return this; running = true; stopped = false; updatePorts(); //clear existing input long cycleStart = time(); do { step(1); } while ((!inputChannels.isEmpty()) && (!stopped)); long cyclesCompleted = time() - cycleStart; //queue additional cycles, cycles -= cyclesCompleted; if (cycles > 0) memory.stepLater(cycles); //finish all remaining cycles while (!memory.isProcessingInput() && (!stopped)) { step(1); } running = false; return this; } /** Main loop executed by the Thread. Should not be called directly. */ @Override public void run() { stopped = false; while (running && !stopped) { frame(); if (minCyclePeriodMS > 0) { try { Thread.sleep(minCyclePeriodMS); } catch (InterruptedException e) { } } else if (threadYield) { Thread.yield(); } } } private void debugTime() { //if (running || stepsQueued > 0 || !finishedInputs) { System.out.println("// doTick: " //+ "walkingSteps " + stepsQueued + ", clock " + time()); System.out.flush(); //} } protected void resetPorts() { for (InPort<Object, Item> i : getInPorts()) { i.reset(); } } protected void updatePorts() { if (!ioChanged) { return; } ioChanged = false; if (!newInputChannels.isEmpty()) { inputChannels.addAll(newInputChannels); newInputChannels.clear(); } } /** * Processes the next input from each input channel. Removes channels that have finished. * @return whether to finish the reasoner afterward, which is true if any input exists. */ public Item nextTask() { if ((!inputting) || (inputChannels.isEmpty())) return null; int remainingChannels = inputChannels.size(); //remaining # of channels to poll while ((remainingChannels > 0) && (inputChannels.size() > 0)) { inputSelected %= inputChannels.size(); final InPort<Object,Item> i = inputChannels.get(inputSelected++); remainingChannels--; if (i.finished()) { inputChannels.remove(i); continue; } try { i.update(); } catch (IOException ex) { emit(ERR.class, ex); } if (i.hasNext()) { Item task = i.next(); if (task!=null) { return task; } } } /** no available inputs */ return null; } /** count of how many items are buffered */ public int getInputItemsBuffered() { int total = 0; for (final InPort i : inputChannels) total += i.getItemsBuffered(); return total; } public void emit(final Class c, final Object... o) { memory.event.emit(c, o); } protected void frame() { frame(cyclesPerFrame); } /** * A frame, consisting of one or more NAR memory cycles */ public void frame(int cycles) { long timeStart = System.currentTimeMillis(); emit(FrameStart.class); updatePorts(); try { for (int i = 0; i < cycles; i++) memory.cycle(this); } catch (Throwable e) { if(Parameters.SHOW_REASONING_ERRORS) { emit(ERR.class, e); } if (Parameters.DEBUG) { e.printStackTrace(); } } emit(FrameEnd.class); } protected long getSimulationTimeCyclesPerFrame() { return minCyclePeriodMS; } @Override public String toString() { return memory.toString(); } /** * Get the current time from the clock Called in {@link nars.entity.Stamp} * * @return The current time */ public long time() { return memory.time(); } public boolean isRunning() { return running; } public long getMinCyclePeriodMS() { return minCyclePeriodMS; } /** When b is true, NAR will call Thread.yield each run() iteration that minCyclePeriodMS==0 (no delay). * This is for improving program responsiveness when NAR is run with no delay. */ public void setThreadYield(boolean b) { this.threadYield = b; } /** stops ad empties all input channels into a receiver. this results in no pending input. @return total number of items flushed */ public int flushInput(Output receiver) { int total = 0; for (InPort c : inputChannels) { total += flushInput(c, receiver); } return total; } /** stops and empties an input channel into a receiver. * this results in no pending input from this channel. */ public int flushInput(InPort i, EventObserver receiver) { int total = 0; i.finish(); while (i.hasNext()) { receiver.event(IN.class, new Object[] { i.next() }); total++; } return total; } public List<InPort<Object, Item>> getInPorts() { return inputChannels; } }