/* * Memory.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.storage; import com.google.common.util.concurrent.AtomicDouble; import nars.util.Events; import nars.util.EventEmitter; import java.io.Serializable; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Random; import nars.NAR; import nars.config.RuntimeParameters; import nars.config.Parameters; import nars.util.Events.ResetEnd; import nars.util.Events.ResetStart; import nars.util.Events.TaskRemove; import nars.control.DerivationContext; import nars.control.GeneralInferenceControl; import nars.control.TemporalInferenceControl; import nars.plugin.mental.Emotions; import nars.entity.BudgetValue; import nars.entity.Concept; import static nars.entity.Concept.successfulOperationHandler; import nars.entity.Item; import nars.entity.Sentence; import nars.entity.Stamp; import nars.entity.Task; import nars.entity.TruthValue; import nars.inference.BudgetFunctions; import static nars.inference.BudgetFunctions.truthToQuality; import nars.io.Output.IN; import nars.io.Output.OUT; import nars.io.Symbols; import nars.language.Tense; import nars.language.Term; import nars.operator.Operation; import nars.operator.Operator; import nars.io.Echo; import nars.io.PauseInput; import nars.io.Reset; import nars.io.SetDecisionThreshold; import nars.io.SetVolume; import nars.language.CompoundTerm; import nars.language.Interval; /** * Memory consists of the run-time state of a NAR, including: * * term and concept memory * * clock * * reasoner state * * etc. * * Excluding input/output channels which are managed by a NAR. * * A memory is controlled by zero or one NAR's at a given time. * * Memory is serializable so it can be persisted and transported. */ public class Memory implements Serializable, Iterable<Concept> { //emotion meter keeping track of global emotion public final Emotions emotion = new Emotions(); public long decisionBlock = 0; public Task lastDecision = null; public boolean allowExecution = true; public static long randomSeed = 1; public static Random randomNumber = new Random(randomSeed); public static void resetStatic() { randomNumber.setSeed(randomSeed); } //todo make sense of this class and de-obfuscate public final Bag<Concept,Term> concepts; public final EventEmitter event; /* InnateOperator registry. Containing all registered operators of the system */ public final HashMap<CharSequence, Operator> operators; /* New tasks with novel composed terms, for delayed and selective processing*/ public final Bag<Task<Term>,Sentence<Term>> novelTasks; /* Input event tasks that were either input events or derived sequences*/ public Bag<Task<Term>,Sentence<Term>> sequenceTasks; /* List of new tasks accumulated in one cycle, to be processed in the next cycle */ public final Deque<Task> newTasks; /* The remaining number of steps to be carried out (stepLater mode)*/ private int inputPausedUntil; /* System clock, relatively defined to guarantee the repeatability of behaviors */ private long cycle; /* System parameters that can be changed at runtime */ public final RuntimeParameters param; /* ---------- Constructor ---------- */ /** * Create a new memory * * @param initialOperators - initial set of available operators; more may be added during runtime */ public Memory(RuntimeParameters param, Bag<Concept,Term> concepts, Bag<Task<Term>,Sentence<Term>> novelTasks, Bag<Task<Term>,Sentence<Term>> sequenceTasks) { this.param = param; this.event = new EventEmitter(); this.concepts = concepts; this.novelTasks = novelTasks; this.newTasks = new ArrayDeque<>(); this.sequenceTasks = sequenceTasks; this.operators = new HashMap<>(); reset(); } public void reset() { event.emit(ResetStart.class); decisionBlock = 0; concepts.clear(); novelTasks.clear(); newTasks.clear(); sequenceTasks.clear(); cycle = 0; inputPausedUntil = 0; emotion.set(0.5f, 0.5f); resetStatic(); event.emit(ResetEnd.class); } public long time() { return cycle; } /* ---------- conversion utilities ---------- */ /** * Get an existing Concept for a given name * <p> * called from Term and ConceptWindow. * * @param t the name of a concept * @return a Concept or null */ public Concept concept(final Term t) { return concepts.get(CompoundTerm.cloneDeepReplaceIntervals(t)); } /** * Get the Concept associated to a Term, or create it. * * Existing concept: apply tasklink activation (remove from bag, adjust budget, reinsert) * New concept: set initial activation, insert * Subconcept: extract from cache, apply activation, insert * * If failed to insert as a result of null bag, returns null * * A displaced Concept resulting from insert is forgotten (but may be stored in optional subconcept memory * * @param term indicating the concept * @return an existing Concept, or a new one, or null */ public Concept conceptualize(final BudgetValue budget, Term term) { if(term instanceof Interval) { return null; } term = CompoundTerm.cloneDeepReplaceIntervals(term); //see if concept is active Concept concept = concepts.take(term); if (concept == null) { //create new concept, with the applied budget concept = new Concept(budget, term, this); //if (memory.logic!=null) // memory.logic.CONCEPT_NEW.commit(term.getComplexity()); emit(Events.ConceptNew.class, concept); } else if (concept!=null) { //apply budget to existing concept //memory.logic.CONCEPT_ACTIVATE.commit(term.getComplexity()); BudgetFunctions.activate(concept.budget, budget, BudgetFunctions.Activating.TaskLink); } else { //unable to create, ex: has variables return null; } Concept displaced = concepts.putBack(concept, cycles(param.conceptForgetDurations), this); if (displaced == null) { //added without replacing anything return concept; } else if (displaced == concept) { //not able to insert conceptRemoved(displaced); return null; } else { conceptRemoved(displaced); return concept; } } /* ---------- new task entries ---------- */ /** * add new task that waits to be processed in the next cycleMemory */ public void addNewTask(final Task t, final String reason) { newTasks.add(t); // logic.TASK_ADD_NEW.commit(t.getPriority()); emit(Events.TaskAdd.class, t, reason); output(t); } /* There are several types of new tasks, all added into the newTasks list, to be processed in the next cycleMemory. Some of them are reported and/or logged. */ /** * Input task processing. Invoked by the outside or inside environment. Outside: StringParser (addInput); Inside: InnateOperator (feedback). Input tasks with low priority are ignored, and the others are put into task buffer. * * @param t The addInput task */ boolean checked=false; boolean isjUnit=false; public static boolean isJUnitTest() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); List<StackTraceElement> list = Arrays.asList(stackTrace); for (StackTraceElement element : list) { if (element.getClassName().startsWith("org.junit.")) { return true; } } return false; } public void inputTask(final Item t, boolean emitIn) { if(!checked) { checked=true; isjUnit=isJUnitTest(); } if (t instanceof Task) { Task task = (Task)t; Stamp s = task.sentence.stamp; if (s.getCreationTime()==-1) s.setCreationTime(time(), param.duration.get()); if(emitIn) { emit(IN.class, task); } if (task.budget.aboveThreshold()) { addNewTask(task, "Perceived"); if(task.sentence.isJudgment() && !task.sentence.isEternal() && task.sentence.term instanceof Operation) { this.lastDecision = task; //depriorize everything related to the previous decisions: successfulOperationHandler(this); } } else { removeTask(task, "Neglected"); } } else if (t instanceof PauseInput) { stepLater(((PauseInput)t).cycles); emit(IN.class, t); } else if (t instanceof Reset) { reset(); emit(OUT.class,((Reset) t).input); emit(IN.class, t); } else if (t instanceof Echo) { Echo e = (Echo)t; if(!isjUnit) { emit(OUT.class,((Echo) t).signal); } emit(e.channel, e.signal); } else if (t instanceof SetVolume) { param.noiseLevel.set(((SetVolume)t).volume); emit(IN.class, t); } else if (t instanceof SetDecisionThreshold) { param.decisionThreshold.set(((SetDecisionThreshold)t).volume); emit(IN.class, t); } else { emit(IN.class, "Unrecognized Input Task: " + t); } } public void inputTask(final Item t) { inputTask(t, true); } public void removeTask(final Task task, final String reason) { emit(TaskRemove.class, task, reason); task.end(); } /** * ExecutedTask called in Operator.call * * @param operation The operation just executed */ public void executedTask(final Operation operation, TruthValue truth) { Task opTask = operation.getTask(); // logic.TASK_EXECUTED.commit(opTask.budget.getPriority()); Stamp stamp = new Stamp(this, Tense.Present); Sentence sentence = new Sentence(operation, Symbols.JUDGMENT_MARK, truth, stamp); Task task = new Task(sentence, new BudgetValue(Parameters.DEFAULT_FEEDBACK_PRIORITY, Parameters.DEFAULT_FEEDBACK_DURABILITY, truthToQuality(sentence.getTruth())), operation.getTask()); task.setElemOfSequenceBuffer(true); addNewTask(task, "Executed"); } public void output(final Task t) { final float budget = t.budget.summary(); final float noiseLevel = 1.0f - (param.noiseLevel.get() / 100.0f); if (budget >= noiseLevel) { // only report significant derived Tasks emit(OUT.class, t); } } final public void emit(final Class c, final Object... signal) { event.emit(c, signal); } final public boolean emitting(final Class channel) { return event.isActive(channel); } public void conceptRemoved(Concept c) { emit(Events.ConceptForget.class, c); } public void cycle(final NAR inputs) { event.emit(Events.CycleStart.class); /** adds input tasks to newTasks */ for(int i=0; i<1 && isProcessingInput(); i++) { Item t = inputs.nextTask(); if (t!=null) inputTask(t); } this.processNewTasks(); //if(noResult()) //newTasks empty this.processNovelTask(); //if(noResult()) //newTasks empty GeneralInferenceControl.selectConceptForInference(this); event.emit(Events.CycleEnd.class); event.synch(); cycle++; } public void localInference(Task task) { DerivationContext cont = new DerivationContext(this); cont.setCurrentTask(task); cont.setCurrentTerm(task.getTerm()); cont.setCurrentConcept(conceptualize(task.budget, cont.getCurrentTerm())); if (cont.getCurrentConcept() != null) { boolean processed = cont.getCurrentConcept().directProcess(cont, task); if (processed) { event.emit(Events.ConceptDirectProcessedTask.class, task); } } if (!task.sentence.isEternal()) { TemporalInferenceControl.eventInference(task, cont); } //memory.logic.TASK_IMMEDIATE_PROCESS.commit(); emit(Events.TaskImmediateProcess.class, task, cont); } /** * Process the newTasks accumulated in the previous workCycle, accept input * ones and those that corresponding to existing concepts, plus one from the * buffer. */ public void processNewTasks() { Task task; int counter = newTasks.size(); // don't include new tasks produced in the current workCycle while (counter-- > 0) { task = newTasks.removeFirst(); boolean enterDirect = true; if (/*task.isElemOfSequenceBuffer() || task.isObservablePrediction() || */ enterDirect || task.isInput() || task.sentence.isQuest() || task.sentence.isQuestion() || concept(task.sentence.term)!=null) { // new input or existing concept localInference(task); } else { Sentence s = task.sentence; if (s.isJudgment() || s.isGoal()) { double d = s.getTruth().getExpectation(); if (s.isJudgment() && d > Parameters.DEFAULT_CREATION_EXPECTATION) { novelTasks.putIn(task); // new concept formation } else if(s.isGoal() && d > Parameters.DEFAULT_CREATION_EXPECTATION_GOAL) { novelTasks.putIn(task); // new concept formation } else { removeTask(task, "Neglected"); } } } } } /** * Select a novel task to process. * @return whether a task was processed */ public void processNovelTask() { final Task task = novelTasks.takeNext(); if (task != null) { localInference(task); } } public Operator getOperator(final String op) { return operators.get(op); } public Operator addOperator(final Operator op) { operators.put(op.name(), op); return op; } public Operator removeOperator(final Operator op) { return operators.remove(op.name()); } private long currentStampSerial = 0; public long newStampSerial() { return currentStampSerial++; } public boolean isProcessingInput() { return time() >= inputPausedUntil; } /** * Queue additional cycle()'s to the inference process. * * @param cycles The number of inference steps */ public void stepLater(final int cycles) { inputPausedUntil = (int) (time() + cycles); } public Task newTask(Term content, char sentenceType, float freq, float conf, float priority, float durability) { return newTask(content, sentenceType, freq, conf, priority, durability, (Task)null); } public Task newTask(Term content, char sentenceType, float freq, float conf, float priority, float durability, final Task parentTask) { return newTask(content, sentenceType, freq, conf, priority, durability, parentTask, Tense.Present); } /** convenience method for forming a new Task from a term */ public Task newTask(Term content, char sentenceType, float freq, float conf, float priority, float durability, Tense tense) { return newTask(content, sentenceType, freq, conf, priority, durability, null, tense); } /** convenience method for forming a new Task from a term */ public Task newTask(Term content, char sentenceType, float freq, float conf, float priority, float durability, Task parentTask, Tense tense) { TruthValue truth = new TruthValue(freq, conf); Sentence sentence = new Sentence( content, sentenceType, truth, new Stamp(this, tense)); BudgetValue budget = new BudgetValue(Parameters.DEFAULT_JUDGMENT_PRIORITY, Parameters.DEFAULT_JUDGMENT_DURABILITY, truth); Task task = new Task(sentence, budget, parentTask); return task; } /** converts durations to cycles */ public final float cycles(AtomicDouble durations) { return param.duration.floatValue() * durations.floatValue(); } @Override public Iterator<Concept> iterator() { return concepts.iterator(); } }