/****************************************************************************** * * 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.emotion; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.botlibre.Bot; import org.botlibre.api.emotion.Emotion; import org.botlibre.api.emotion.Mood; import org.botlibre.api.knowledge.Network; import org.botlibre.api.knowledge.Relationship; import org.botlibre.api.knowledge.Vertex; import org.botlibre.knowledge.Primitive; /** * Controls and manages the thought processing. */ public class BasicMood implements Mood { protected Bot bot; protected boolean isEnabled = true; /** * Map of emotions. */ protected Map<String, Emotion> emotions; public BasicMood() { this.emotions = new HashMap<String, Emotion>(); } public void pool() { for (Emotion emotion : this.emotions.values()) { emotion.setState(0.0f); } } public void saveProperties() { Network memory = getBot().memory().newMemory(); memory.saveProperty("Mood.enabled", String.valueOf(isEnabled()), true); memory.save(); } /** * Log the message if the debug level is greater or equal to the level. */ public void log(String message, Level level, Object... arguments) { getBot().log(this, message, level, arguments); } /** * Log the exception. */ public void log(Throwable exception) { getBot().log(this, exception); } /** * Return Bot. */ @Override public Bot getBot() { return bot; } /** * Set Bot. */ @Override public void setBot(Bot bot) { this.bot = bot; } /** * Initialize any configurable settings from the properties. */ @Override public void initialize(Map<String, Object> properties) { return; } @Override public void shutdown() { getBot().log(this, "Shutdown", Bot.FINE); } @Override public void awake() { getBot().log(this, "Awake", Bot.FINE); String enabled = this.bot.memory().getProperty("Mood.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(); } @Override public Map<String, Emotion> getEmotions() { return emotions; } @Override @SuppressWarnings("unchecked") public <T> T getEmotion(Class<T> type) { return (T)getEmotions().get(type.getName()); } @Override public Emotion getEmotion(String name) { return getEmotions().get(name); } @Override public void addEmotion(Emotion emotion) { getEmotions().put(emotion.getName(), emotion); } @Override public void removeEmotion(Emotion emotion) { getEmotions().remove(emotion.getName()); } /** * Evaluate the active memory input for emotional influence. */ @Override public void evaluate() { if (!isEnabled()) { return; } Network network = getBot().memory().getShortTermMemory(); List<Vertex> activeMemory = getBot().memory().getActiveMemory(); Iterator<Vertex> inputs = activeMemory.iterator(); while (inputs.hasNext()) { // Must register into the current memory context. Vertex input = network.createVertex(inputs.next()); Vertex source = input; if (input.instanceOf(Primitive.INPUT)) { // Let emotions simmer. for (Emotion emotion : getEmotions().values()) { emotion.setState(emotion.getState() / 2); } Collection<Relationship> emotions = input.getRelationships(Primitive.EMOTION); Vertex sentence = input.getRelationship(Primitive.INPUT); if ((emotions == null) && (sentence != null)) { // Attempt to determine emotion from sentence. emotions = sentence.getRelationships(Primitive.EMOTION); if (emotions != null) { source = sentence; } else { // Attempt to determine from words. Collection<Relationship> words = sentence.getRelationships(Primitive.WORD); if (words != null) { for (Relationship word : words) { if (word.getTarget().getRelationships(Primitive.EMOTION) != null) { for (Emotion emotion : getEmotions().values()) { Vertex emotionVertex = network.createVertex(emotion.primitive()); Relationship emotionRelation = word.getTarget().getRelationship(Primitive.EMOTION, emotionVertex); if (emotionRelation != null) { Relationship relationship = input.getRelationship(Primitive.EMOTION, emotionVertex); if (relationship == null) { relationship = input.addRelationship(Primitive.EMOTION, emotionVertex); relationship.setCorrectness(emotionRelation.getCorrectness()); } else { relationship.setCorrectness((relationship.getCorrectness() + emotionRelation.getCorrectness()) / 2); } } } } } emotions = input.getRelationships(Primitive.EMOTION); } } } if (emotions != null) { // Increment or decrement each emotional state based on the input. for (Emotion emotion : getEmotions().values()) { Vertex emotionVertex = network.createVertex(emotion.primitive()); Relationship relationship = source.getRelationship(Primitive.EMOTION, emotionVertex); if (relationship != null) { try { float value = relationship.getCorrectness(); emotion.setState(emotion.getState() + value); log("Applying emotion", Level.FINE, emotion, value); } catch (Exception error) { getBot().log(this, error); } } } } } } } /** * Evaluate the input response for emotional influence. */ @Override public void evaluateResponse(Vertex response, Vertex meta) { if (!isEnabled()) { return; } // Let emotions simmer. for (Emotion emotion : getEmotions().values()) { emotion.setState(emotion.getState() / 2); } Collection<Relationship> emotions = null; if (meta != null) { emotions = meta.getRelationships(Primitive.EMOTION); } if (emotions == null) { emotions = response.getRelationships(Primitive.EMOTION); } if (emotions == null) { // Attempt to determine from words. Collection<Relationship> words = response.getRelationships(Primitive.WORD); if (words != null) { emotions = new ArrayList<Relationship>(); for (Relationship word : words) { Collection<Relationship> wordEmotions = word.getTarget().getRelationships(Primitive.EMOTION); if (wordEmotions != null) { emotions.addAll(wordEmotions); } } } } if (emotions != null) { // Increment or decrement each emotional state based on the input. for (Relationship relationship : emotions) { Vertex emotionVertex = relationship.getTarget(); Emotion emotion = getEmotion(emotionVertex.getDataValue()); try { float value = relationship.getCorrectness(); emotion.setState(emotion.getState() + value); log("Applying emotion", Level.FINE, emotion, value); } catch (Exception error) { getBot().log(this, error); } } } } /** * Self API to set mood. */ public Vertex setEmotion(Vertex source, Vertex emotionVertex, Vertex valueVertex) { Emotion emotion = getEmotion(emotionVertex.getDataValue()); if (emotion == null) { log("Invalid emotion", Level.FINE, emotion); } if (valueVertex.getData() instanceof Number) { float value = ((Number)valueVertex.getData()).floatValue(); if (value > 1 || value < -1) { log("Invalid emotion value (must be between -1 and 1)", Level.FINE, value); } else { emotion.setState(emotion.getState() + value); } } return source; } /** * Evaluate the output with emotional expression. */ @Override public void evaluateOutput(Vertex output) { if (!isEnabled()) { return; } for (Emotion emotion : getBot().mood().getEmotions().values()) { if (Math.abs(emotion.getState()) > 0.1) { Relationship relationship = output.addRelationship(Primitive.EMOTION, emotion.primitive()); relationship.setCorrectness(emotion.getState()); } } } /** * Determine the main emotional state of the output. */ @Override public EmotionalState currentEmotionalState() { EmotionalState state = EmotionalState.NONE; float max = 0; float value = 0; Emotion maxEmotion = null; if (emotions != null) { // Find the most relevant emotional state. for (Emotion emotion : getEmotions().values()) { if (Math.abs(emotion.getState()) > max) { value = emotion.getState(); max = Math.abs(value); maxEmotion = emotion; } } } if (max >= 0.3) { state = maxEmotion.evaluate(value); } return state; } /** * Determine the main emotional state of the output. */ @Override public EmotionalState evaluateEmotionalState(Vertex output) { Collection<Relationship> emotions = output.getRelationships(Primitive.EMOTION); EmotionalState state = EmotionalState.NONE; float max = 0; float value = 0; Emotion maxEmotion = null; if (emotions != null) { // Find the most relevant emotional state. for (Emotion emotion : getEmotions().values()) { Relationship relationship = output.getRelationship(Primitive.EMOTION, emotion.primitive()); if ((relationship != null) && (Math.abs(relationship.getCorrectness()) > max)) { value = relationship.getCorrectness(); max = Math.abs(value); maxEmotion = emotion; } } } if (max >= 0.3) { state = maxEmotion.evaluate(value); } return state; } /** * Determine the emotional states of the output. */ @Override public List<EmotionalState> evaluateEmotionalStates(Vertex output) { Collection<Relationship> emotions = output.getRelationships(Primitive.EMOTION); List<EmotionalState> states = new ArrayList<EmotionalState>(); if (emotions != null) { // Find the most relevant emotional state. for (Emotion emotion : getEmotions().values()) { Relationship relationship = output.getRelationship(Primitive.EMOTION, emotion.primitive()); if (relationship != null && Math.abs(relationship.getCorrectness()) >= 0.3) { states.add(emotion.evaluate(relationship.getCorrectness())); } } } return states; } public boolean isEnabled() { return isEnabled; } public void setEnabled(boolean isEnabled) { this.isEnabled = isEnabled; } /** * Print a useful string representation of the mood. */ @Override public String toString() { StringWriter writer = new StringWriter(); writer.write(getClass().getSimpleName()); writer.write("("); boolean first = true; for (Emotion emotion : getEmotions().values()) { if (! first) { writer.write(", "); } else { first = false; } writer.write(emotion.toString()); } writer.write(")"); return writer.toString(); } }