oxyde_emotion/
lib.rs

1//! Emotion system for Oxyde agents
2//!
3//! This module implements Plutchik's wheel of emotions with 8 primary emotions
4//! and derived dimensions (valence and arousal). Emotions decay over time and
5//! influence agent behavior and memory consolidation.
6
7use serde::{Deserialize, Serialize};
8
9/// Emotional state based on Plutchik's wheel of emotions
10///
11/// Each emotion is represented as a value between -1.0 and 1.0, where:
12/// - Positive values indicate presence of the emotion
13/// - Negative values indicate presence of the opposite emotion
14/// - 0.0 indicates neutral state
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct EmotionalState {
17    /// Joy (opposite: sadness)
18    /// Positive: happiness, elation
19    /// Negative: sorrow, grief
20    pub joy: f32,
21
22    /// Trust (opposite: disgust)
23    /// Positive: acceptance, admiration
24    /// Negative: loathing, aversion
25    pub trust: f32,
26
27    /// Fear (opposite: anger)
28    /// Positive: apprehension, terror
29    /// Negative: rage, annoyance
30    pub fear: f32,
31
32    /// Surprise (opposite: anticipation)
33    /// Positive: amazement, distraction
34    /// Negative: vigilance, interest
35    pub surprise: f32,
36
37    /// Sadness (derived from negative joy)
38    /// Positive: pensiveness, grief
39    /// Negative: serenity, joy
40    pub sadness: f32,
41
42    /// Disgust (derived from negative trust)
43    /// Positive: boredom, loathing
44    /// Negative: acceptance, trust
45    pub disgust: f32,
46
47    /// Anger (derived from negative fear)
48    /// Positive: annoyance, rage
49    /// Negative: apprehension, fear
50    pub anger: f32,
51
52    /// Anticipation (derived from negative surprise)
53    /// Positive: interest, vigilance
54    /// Negative: distraction, amazement
55    pub anticipation: f32,
56
57    /// Decay rate for emotions (0.0 - 1.0)
58    /// Higher values mean emotions fade faster
59    decay_rate: f32,
60}
61
62impl EmotionalState {
63    /// Create a new emotional state with neutral emotions
64    pub fn new() -> Self {
65        Self {
66            joy: 0.0,
67            trust: 0.0,
68            fear: 0.0,
69            surprise: 0.0,
70            sadness: 0.0,
71            disgust: 0.0,
72            anger: 0.0,
73            anticipation: 0.0,
74            decay_rate: 0.1, // 10% decay per update
75        }
76    }
77
78    /// Create an emotional state with custom decay rate
79    ///
80    /// # Arguments
81    ///
82    /// * `decay_rate` - Rate at which emotions decay (0.0 - 1.0)
83    pub fn with_decay_rate(decay_rate: f32) -> Self {
84        let mut state = Self::new();
85        state.decay_rate = decay_rate.clamp(0.0, 1.0);
86        state
87    }
88
89    /// Calculate overall emotional valence (positive/negative)
90    ///
91    /// Returns a value between -1.0 (very negative) and 1.0 (very positive)
92    pub fn valence(&self) -> f32 {
93        let positive = self.joy + self.trust + self.anticipation;
94        let negative = self.sadness + self.disgust + self.anger + self.fear;
95        ((positive - negative) / 7.0).clamp(-1.0, 1.0)
96    }
97
98    /// Calculate emotional arousal (intensity/activation level)
99    ///
100    /// Returns a value between 0.0 (calm) and 1.0 (highly aroused)
101    pub fn arousal(&self) -> f32 {
102        let total = self.joy.abs()
103            + self.trust.abs()
104            + self.fear.abs()
105            + self.surprise.abs()
106            + self.sadness.abs()
107            + self.disgust.abs()
108            + self.anger.abs()
109            + self.anticipation.abs();
110        (total / 8.0).clamp(0.0, 1.0)
111    }
112
113    /// Get the dominant emotion
114    ///
115    /// Returns the name of the strongest emotion and its value
116    pub fn dominant_emotion(&self) -> (&'static str, f32) {
117        let emotions = [
118            ("joy", self.joy),
119            ("trust", self.trust),
120            ("fear", self.fear),
121            ("surprise", self.surprise),
122            ("sadness", self.sadness),
123            ("disgust", self.disgust),
124            ("anger", self.anger),
125            ("anticipation", self.anticipation),
126        ];
127
128        emotions
129            .iter()
130            .max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap())
131            .map(|(name, value)| (*name, *value))
132            .unwrap_or(("neutral", 0.0))
133    }
134
135    /// Apply time-based decay to all emotions
136    ///
137    /// Emotions gradually return to neutral state over time
138    pub fn decay(&mut self) {
139        self.joy *= 1.0 - self.decay_rate;
140        self.trust *= 1.0 - self.decay_rate;
141        self.fear *= 1.0 - self.decay_rate;
142        self.surprise *= 1.0 - self.decay_rate;
143        self.sadness *= 1.0 - self.decay_rate;
144        self.disgust *= 1.0 - self.decay_rate;
145        self.anger *= 1.0 - self.decay_rate;
146        self.anticipation *= 1.0 - self.decay_rate;
147    }
148
149    /// Update a specific emotion
150    ///
151    /// # Arguments
152    ///
153    /// * `emotion` - Name of the emotion to update
154    /// * `delta` - Amount to change the emotion by
155    pub fn update_emotion(&mut self, emotion: &str, delta: f32) {
156        let value = match emotion {
157            "joy" => &mut self.joy,
158            "trust" => &mut self.trust,
159            "fear" => &mut self.fear,
160            "surprise" => &mut self.surprise,
161            "sadness" => &mut self.sadness,
162            "disgust" => &mut self.disgust,
163            "anger" => &mut self.anger,
164            "anticipation" => &mut self.anticipation,
165            _ => return,
166        };
167
168        *value = (*value + delta).clamp(-1.0, 1.0);
169
170        // Update opposite emotions (Plutchik's wheel opposites)
171        match emotion {
172            "joy" => self.sadness = -self.joy,
173            "sadness" => self.joy = -self.sadness,
174            "trust" => self.disgust = -self.trust,
175            "disgust" => self.trust = -self.disgust,
176            "fear" => self.anger = -self.fear,
177            "anger" => self.fear = -self.anger,
178            "surprise" => self.anticipation = -self.surprise,
179            "anticipation" => self.surprise = -self.anticipation,
180            _ => {}
181        }
182    }
183
184    /// Set multiple emotions at once
185    ///
186    /// # Arguments
187    ///
188    /// * `emotions` - Vector of (emotion_name, value) tuples
189    pub fn set_emotions(&mut self, emotions: Vec<(&str, f32)>) {
190        for (emotion, value) in emotions {
191            self.update_emotion(emotion, value);
192        }
193    }
194
195    /// Check if the agent is in a generally positive emotional state
196    pub fn is_positive(&self) -> bool {
197        self.valence() > 0.2
198    }
199
200    /// Check if the agent is in a generally negative emotional state
201    pub fn is_negative(&self) -> bool {
202        self.valence() < -0.2
203    }
204
205    /// Check if the agent is emotionally aroused (experiencing strong emotions)
206    pub fn is_aroused(&self) -> bool {
207        self.arousal() > 0.5
208    }
209
210    /// Reset all emotions to neutral
211    pub fn reset(&mut self) {
212        self.joy = 0.0;
213        self.trust = 0.0;
214        self.fear = 0.0;
215        self.surprise = 0.0;
216        self.sadness = 0.0;
217        self.disgust = 0.0;
218        self.anger = 0.0;
219        self.anticipation = 0.0;
220    }
221}
222
223impl Default for EmotionalState {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_new_emotional_state() {
235        let state = EmotionalState::new();
236        assert_eq!(state.joy, 0.0);
237        assert_eq!(state.trust, 0.0);
238        assert_eq!(state.fear, 0.0);
239        assert_eq!(state.valence(), 0.0);
240        assert_eq!(state.arousal(), 0.0);
241    }
242
243    #[test]
244    fn test_valence_calculation() {
245        let mut state = EmotionalState::new();
246        // Directly set emotions for testing (bypassing update logic)
247        state.joy = 0.8;
248        state.trust = 0.6;
249        state.sadness = 0.0;
250        state.disgust = 0.0;
251        state.anger = 0.0;
252        state.fear = 0.0;
253        assert!(state.valence() > 0.0);
254        assert!(state.is_positive());
255
256        // Set to negative emotions
257        state.joy = 0.0;
258        state.trust = 0.0;
259        state.sadness = 0.9;
260        state.anger = 0.7;
261        assert!(state.valence() < 0.0);
262        assert!(state.is_negative());
263    }
264
265    #[test]
266    fn test_arousal_calculation() {
267        let mut state = EmotionalState::new();
268        // Directly set emotions for testing - need high values for is_aroused() which needs > 0.5
269        // arousal = sum / 8, so we need sum > 4.0
270        state.joy = 0.9;
271        state.fear = 0.8;
272        state.anger = 0.7;
273        state.surprise = 0.8;
274        state.trust = 0.9;
275        assert!(state.arousal() > 0.0);
276        assert!(state.is_aroused());
277    }
278
279    #[test]
280    fn test_dominant_emotion() {
281        let mut state = EmotionalState::new();
282        state.joy = 0.9;
283        state.fear = 0.3;
284
285        let (emotion, value) = state.dominant_emotion();
286        assert_eq!(emotion, "joy");
287        assert_eq!(value, 0.9);
288    }
289
290    #[test]
291    fn test_emotion_decay() {
292        let mut state = EmotionalState::with_decay_rate(0.5);
293        state.joy = 1.0;
294
295        state.decay();
296        assert_eq!(state.joy, 0.5);
297
298        state.decay();
299        assert_eq!(state.joy, 0.25);
300    }
301
302    #[test]
303    fn test_update_emotion() {
304        let mut state = EmotionalState::new();
305        state.update_emotion("joy", 0.5);
306
307        assert_eq!(state.joy, 0.5);
308        assert_eq!(state.sadness, -0.5); // Opposite emotion
309
310        state.update_emotion("joy", 0.8);
311        assert_eq!(state.joy, 1.0); // Clamped to 1.0
312    }
313
314    #[test]
315    fn test_set_emotions() {
316        let mut state = EmotionalState::new();
317        state.set_emotions(vec![("joy", 0.7), ("trust", 0.5), ("fear", 0.3)]);
318
319        assert_eq!(state.joy, 0.7);
320        assert_eq!(state.trust, 0.5);
321        assert_eq!(state.fear, 0.3);
322    }
323
324    #[test]
325    fn test_reset() {
326        let mut state = EmotionalState::new();
327        state.set_emotions(vec![("joy", 0.7), ("anger", 0.5)]);
328        state.reset();
329
330        assert_eq!(state.joy, 0.0);
331        assert_eq!(state.anger, 0.0);
332        assert_eq!(state.valence(), 0.0);
333    }
334}