/******************************************************************************
*
* Copyright 2014 Paphus Solutions Inc.
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.botlibre.thought.consciousness;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import org.botlibre.api.knowledge.Network;
import org.botlibre.api.knowledge.Relationship;
import org.botlibre.api.knowledge.Vertex;
import org.botlibre.api.thought.Mind.MentalState;
import org.botlibre.knowledge.Primitive;
import org.botlibre.thought.BasicThought;
/**
* Consciousness monitors active memory and decides what to do.
*/
public class Consciousness extends BasicThought {
public static int IGNORE_RELATIONS_MAX = 50;
public static int TRAVERSAL_LIMIT = 100;
public static int MAX_PROCESS_TIME = 100;
/** Defines the flow of time. */
protected Long lastContext;
/**
* Create a new consciousness.
*/
public Consciousness() {
}
/**
* Initialize any configurable settings from the properties.
*/
@Override
public void initialize(Map<String, Object> properties) {
if (properties.containsKey("traversal-limit")) {
TRAVERSAL_LIMIT = Integer.parseInt((String)properties.get("traversal-limit"));
log("Init property:", Level.FINE, "traversal-limit", TRAVERSAL_LIMIT);
}
}
/**
* Return the last conscious point in time.
*/
public Vertex getLastContext(Network network) {
if (this.lastContext == null) {
return null;
}
return network.findById(this.lastContext);
}
/**
* Set the last conscious point in time.
*/
public void setLastContext(Vertex lastContext) {
if (lastContext == null) {
this.lastContext = null;
} else {
this.lastContext = lastContext.getId();
}
}
/**
* Return the number of levels to traverse a vertex for the current state.
*/
public int getTraversalLevel() {
MentalState state = this.bot.mind().getState();
if (state == MentalState.PANIC) {
return 1;
} else if (state == MentalState.ALERT) {
return 3;
} else if (state == MentalState.ACTIVE) {
return 5;
} else if (state == MentalState.BORED) {
return 10;
} else if (state == MentalState.ASLEEP) {
return 20;
}
return 1;
}
/**
* Age the network, decrease consciousness level by 10%.
*/
public void age(Network network) {
for (Vertex vertex : network.findAll()) {
int level = vertex.getConsciousnessLevel();
if (level > 0) {
vertex.decrementConsciousnessLevel(level / 2);
}
}
}
/**
* Return the current allowed processing time.
* This decreases when stressed.
*/
public long getProcessingTime() {
long processTime = MAX_PROCESS_TIME;
int state = this.bot.mind().getState().ordinal();
if (state > MentalState.ALERT.ordinal()) {
processTime = MAX_PROCESS_TIME / 5;
} else if (state > MentalState.ACTIVE.ordinal()) {
processTime = MAX_PROCESS_TIME / 2;
} else if (state < MentalState.ACTIVE.ordinal()) {
processTime = MAX_PROCESS_TIME * 2;
}
return processTime;
}
/**
* Analyse the active memory.
* Output the active article to the senses.
*/
@Override
public void think() {
Network network = getShortTermMemory();
if (!isEnabled()) {
// Still need to record context processing.
List<Vertex> activeMemory = this.bot.memory().getActiveMemory();
Iterator<Vertex> vertices = activeMemory.iterator();
// Create a context for this point in time, associate everything with it.
Vertex context = network.createVertex(Primitive.NULL);
while (vertices.hasNext()) {
// Must register into the current memory context.
Vertex vertex = network.createVertex(vertices.next());
if (!vertex.isPrimitive() && !vertex.hasRelationship(Primitive.INSTANTIATION, Primitive.CONTEXT)) {
vertex.addRelationship(Primitive.CONTEXT, context);
}
}
return;
}
long startTime = System.currentTimeMillis();
long processTime = getProcessingTime();
// First age short term memory.
age(network);
List<Vertex> activeMemory = this.bot.memory().getActiveMemory();
Iterator<Vertex> vertices = activeMemory.iterator();
// Create a context for this point in time, associate everything with it.
Vertex context = network.createTimestamp();
context.addRelationship(Primitive.INSTANTIATION, Primitive.CONTEXT);
while (vertices.hasNext()) {
// Must register into the current memory context.
Vertex vertex = network.createVertex(vertices.next());
if (!vertex.isPrimitive() && !vertex.hasRelationship(Primitive.INSTANTIATION, Primitive.CONTEXT)) {
vertex.addRelationship(Primitive.CONTEXT, context);
context.addRelationship(Primitive.CONTEXT, vertex);
}
}
// Associate this point in time with the previous to create a flow.
Vertex lastContext = getLastContext(network);
if (lastContext != null) {
lastContext.addRelationship(network.createVertex(Primitive.NEXT), context);
context.addRelationship(network.createVertex(Primitive.PREVIOUS), lastContext);
}
setLastContext(context);
vertices = activeMemory.iterator();
// Process active vertices n levels deep.
Set<Vertex> recursiveSet = new HashSet<Vertex>();
// Use a breath first search, and abort at 1000 vertices.
Set<Vertex> breadthSet = new HashSet<Vertex>();
while (vertices.hasNext()) {
// Must register into the current memory context.
breadthSet.add(network.createVertex(vertices.next()));
}
int levels = getTraversalLevel();
while ((levels > 0) && (recursiveSet.size() < TRAVERSAL_LIMIT)) {
Set<Vertex> nextLevelBreadthSet = new HashSet<Vertex>();
Iterator<Vertex> iterator = breadthSet.iterator();
boolean maxTime = false;
while (iterator.hasNext() && (recursiveSet.size() < TRAVERSAL_LIMIT)) {
think(iterator.next(), levels, recursiveSet, nextLevelBreadthSet);
long currentTime = System.currentTimeMillis();
if ((currentTime - startTime) > processTime) {
maxTime = true;
log("Process time limit reached", Level.INFO, processTime, recursiveSet.size());
break;
}
}
if (maxTime) {
break;
}
breadthSet = nextLevelBreadthSet;
levels--;
}
if (recursiveSet.size() >= TRAVERSAL_LIMIT) {
log("Traversal limit reached", Level.FINE, recursiveSet.size());
}
}
@Override
public void awake() {
String enabled = this.bot.memory().getProperty("Consciousness.enabled");
if (enabled != null) {
setEnabled(Boolean.valueOf(enabled));
}
}
/**
* Migrate to new properties system.
*/
public void migrateProperties() {
Network memory = getBot().memory().newMemory();
Vertex mood = memory.createVertex(getClass());
Vertex property = mood.getRelationship(Primitive.ENABLED);
if (property != null) {
setEnabled((Boolean)property.getData());
}
// Remove old properties.
mood.internalRemoveRelationships(Primitive.ENABLED);
memory.save();
saveProperties();
}
public void saveProperties() {
Network memory = this.bot.memory().newMemory();
memory.saveProperty("Consciousness.enabled", String.valueOf(isEnabled()), true);
memory.save();
}
/**
* Analyse vertex and traverse its relationships.
* Swap the active vertex if interesting.
*/
public void think(Vertex vertex, int levels, Set<Vertex> recursiveSet, Set<Vertex> breadtheSet) {
if (vertex == null || recursiveSet.contains(vertex)) {
return;
}
recursiveSet.add(vertex);
int size = recursiveSet.size();
vertex.incrementConsciousnessLevel(levels);
log("Increment:" + levels, Level.FINEST, vertex);
for (Entry<Vertex, Map<Relationship, Relationship>> entry : vertex.getRelationships().entrySet()) {
if ((size + breadtheSet.size()) >= TRAVERSAL_LIMIT) {
break;
}
// Only traverse rare relationships.
if (entry.getValue().size() > IGNORE_RELATIONS_MAX) {
continue;
}
for (Relationship relationship : entry.getValue().values()) {
breadtheSet.add(relationship.getType());
breadtheSet.add(relationship.getTarget());
}
}
for (Iterator<Relationship> relationships = vertex.allRelationships(); relationships.hasNext();) {
if ((size + breadtheSet.size()) >= TRAVERSAL_LIMIT) {
break;
}
Relationship relationship = relationships.next();
breadtheSet.add(relationship.getType());
breadtheSet.add(relationship.getTarget());
}
}
}