Skip to main content

behaviorsim_rs/simulation/
simulation_builder.rs

1//! Builder pattern for creating Simulation instances.
2//!
3//! Provides a fluent API for constructing simulations with
4//! entities, events, and relationships.
5
6use crate::entity::Entity;
7use crate::enums::RelationshipSchema;
8use crate::event::Event;
9use crate::simulation::Simulation;
10use crate::types::{EntityId, EventId, RelationshipId, Timestamp};
11use std::fmt;
12
13/// Error type for simulation build failures.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum SimulationBuildError {
16    /// An entity was added with a duplicate ID.
17    DuplicateEntityId(EntityId),
18    /// An event references an entity that doesn't exist in the simulation.
19    /// Contains the event ID and the unknown entity ID.
20    EventReferencesUnknownEntity(EventId, EntityId),
21    /// A relationship references an entity that doesn't exist in the simulation.
22    /// Contains the relationship ID and the unknown entity ID.
23    RelationshipReferencesUnknownEntity(RelationshipId, EntityId),
24    /// A relationship between an entity and itself was attempted.
25    SelfRelationship(EntityId),
26}
27
28impl fmt::Display for SimulationBuildError {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            SimulationBuildError::DuplicateEntityId(id) => {
32                write!(f, "Duplicate entity ID: {}", id.as_str())
33            }
34            SimulationBuildError::EventReferencesUnknownEntity(event_id, entity_id) => {
35                write!(
36                    f,
37                    "Event '{}' references unknown entity: {}",
38                    event_id.as_str(),
39                    entity_id.as_str()
40                )
41            }
42            SimulationBuildError::RelationshipReferencesUnknownEntity(rel_id, entity_id) => {
43                write!(
44                    f,
45                    "Relationship '{}' references unknown entity: {}",
46                    rel_id.as_str(),
47                    entity_id.as_str()
48                )
49            }
50            SimulationBuildError::SelfRelationship(id) => {
51                write!(
52                    f,
53                    "Cannot create relationship between entity '{}' and itself",
54                    id.as_str()
55                )
56            }
57        }
58    }
59}
60
61impl std::error::Error for SimulationBuildError {}
62
63/// Pending entity to be added to the simulation.
64struct PendingEntity {
65    entity: Entity,
66    anchor_timestamp: Timestamp,
67}
68
69/// Pending event to be added to the simulation.
70struct PendingEvent {
71    event: Event,
72    timestamp: Timestamp,
73}
74
75/// Pending relationship to be added to the simulation.
76struct PendingRelationship {
77    id: RelationshipId,
78    entity_a: EntityId,
79    entity_b: EntityId,
80    schema: RelationshipSchema,
81    formed_timestamp: Timestamp,
82}
83
84/// Counter for generating unique relationship IDs during building.
85static PENDING_REL_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
86
87fn generate_pending_relationship_id() -> RelationshipId {
88    let count = PENDING_REL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
89    // Safe to unwrap - format string is never empty
90    RelationshipId::new(format!("rel_{}", count)).unwrap()
91}
92
93/// Builder for creating Simulation instances.
94///
95/// Provides a fluent API for constructing simulations with
96/// entities, events, and relationships.
97///
98pub struct SimulationBuilder {
99    reference_date: Timestamp,
100    entities: Vec<PendingEntity>,
101    events: Vec<PendingEvent>,
102    relationships: Vec<PendingRelationship>,
103}
104
105impl SimulationBuilder {
106    /// Creates a new simulation builder with a reference date.
107    ///
108    /// The reference date is the simulation's baseline time point.
109    ///
110    /// # Arguments
111    ///
112    /// * `reference_date` - The simulation's reference date
113    #[must_use]
114    pub fn new(reference_date: Timestamp) -> Self {
115        SimulationBuilder {
116            reference_date,
117            entities: Vec::new(),
118            events: Vec::new(),
119            relationships: Vec::new(),
120        }
121    }
122
123    /// Adds an entity with its anchor timestamp.
124    ///
125    /// The anchor timestamp represents when the entity's state was observed.
126    #[must_use]
127    pub fn add_entity(mut self, entity: Entity, anchor_timestamp: Timestamp) -> Self {
128        self.entities.push(PendingEntity {
129            entity,
130            anchor_timestamp,
131        });
132        self
133    }
134
135    /// Adds an event with its timestamp.
136    #[must_use]
137    pub fn add_event(mut self, event: Event, timestamp: Timestamp) -> Self {
138        self.events.push(PendingEvent { event, timestamp });
139        self
140    }
141
142    /// Adds a relationship between two entities.
143    #[must_use]
144    pub fn add_relationship(
145        mut self,
146        entity_a: EntityId,
147        entity_b: EntityId,
148        schema: RelationshipSchema,
149        formed_timestamp: Timestamp,
150    ) -> Self {
151        self.relationships.push(PendingRelationship {
152            id: generate_pending_relationship_id(),
153            entity_a,
154            entity_b,
155            schema,
156            formed_timestamp,
157        });
158        self
159    }
160
161    /// Builds the simulation.
162    ///
163    /// # Errors
164    ///
165    /// Returns an error if:
166    /// - A duplicate entity ID was added
167    /// - An event references an entity that doesn't exist
168    /// - A relationship references an entity that doesn't exist
169    /// - A relationship between an entity and itself was attempted
170    pub fn build(self) -> Result<Simulation, SimulationBuildError> {
171        let mut simulation = Simulation::new(self.reference_date);
172
173        // Track entity IDs for duplicate detection and reference validation
174        let mut seen_ids = std::collections::HashSet::new();
175
176        // Add entities
177        for pending in &self.entities {
178            let id = pending.entity.id().clone();
179            if !seen_ids.insert(id.clone()) {
180                return Err(SimulationBuildError::DuplicateEntityId(id));
181            }
182        }
183
184        // Validate event source and target references
185        for pending in &self.events {
186            // Validate source if present
187            if let Some(source) = pending.event.source() {
188                if !seen_ids.contains(source) {
189                    return Err(SimulationBuildError::EventReferencesUnknownEntity(
190                        pending.event.id().clone(),
191                        source.clone(),
192                    ));
193                }
194            }
195            // Validate target if present
196            if let Some(target) = pending.event.target() {
197                if !seen_ids.contains(target) {
198                    return Err(SimulationBuildError::EventReferencesUnknownEntity(
199                        pending.event.id().clone(),
200                        target.clone(),
201                    ));
202                }
203            }
204        }
205
206        // Validate relationship references
207        for pending in &self.relationships {
208            // Check for self-relationship
209            if pending.entity_a == pending.entity_b {
210                return Err(SimulationBuildError::SelfRelationship(
211                    pending.entity_a.clone(),
212                ));
213            }
214
215            // Check entity_a exists
216            if !seen_ids.contains(&pending.entity_a) {
217                return Err(SimulationBuildError::RelationshipReferencesUnknownEntity(
218                    pending.id.clone(),
219                    pending.entity_a.clone(),
220                ));
221            }
222
223            // Check entity_b exists
224            if !seen_ids.contains(&pending.entity_b) {
225                return Err(SimulationBuildError::RelationshipReferencesUnknownEntity(
226                    pending.id.clone(),
227                    pending.entity_b.clone(),
228                ));
229            }
230        }
231
232        // All validations passed - now add everything to the simulation
233        for pending in self.entities {
234            simulation.add_entity(pending.entity, pending.anchor_timestamp);
235        }
236
237        for pending in self.events {
238            simulation.add_event(pending.event, pending.timestamp);
239        }
240
241        for pending in self.relationships {
242            simulation.add_relationship(
243                pending.entity_a,
244                pending.entity_b,
245                pending.schema,
246                pending.formed_timestamp,
247            );
248        }
249
250        Ok(simulation)
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::entity::EntityBuilder;
258    use crate::enums::{EventType, Species};
259    use crate::event::EventBuilder;
260
261    fn create_human(id: &str) -> Entity {
262        EntityBuilder::new()
263            .id(id)
264            .species(Species::Human)
265            .age(crate::types::Duration::years(30))
266            .build()
267            .unwrap()
268    }
269
270    fn reference_date() -> Timestamp {
271        Timestamp::from_ymd_hms(2024, 1, 1, 0, 0, 0)
272    }
273
274    #[test]
275    fn builder_creates_empty_simulation() {
276        let sim = SimulationBuilder::new(reference_date()).build().unwrap();
277
278        assert_eq!(sim.entity_count(), 0);
279        assert_eq!(sim.reference_date(), reference_date());
280    }
281
282    #[test]
283    fn builder_add_entity() {
284        let entity = create_human("person_001");
285
286        let sim = SimulationBuilder::new(reference_date())
287            .add_entity(entity, reference_date())
288            .build()
289            .unwrap();
290
291        assert_eq!(sim.entity_count(), 1);
292    }
293
294    #[test]
295    fn builder_add_multiple_entities() {
296        let sim = SimulationBuilder::new(reference_date())
297            .add_entity(create_human("alice"), reference_date())
298            .add_entity(create_human("bob"), reference_date())
299            .add_entity(create_human("carol"), reference_date())
300            .build()
301            .unwrap();
302
303        assert_eq!(sim.entity_count(), 3);
304    }
305
306    #[test]
307    fn builder_duplicate_entity_id_fails() {
308        let result = SimulationBuilder::new(reference_date())
309            .add_entity(create_human("alice"), reference_date())
310            .add_entity(create_human("alice"), reference_date())
311            .build();
312
313        assert!(result.is_err());
314        let err = result.unwrap_err();
315        assert!(
316            matches!(err, SimulationBuildError::DuplicateEntityId(ref id) if id.as_str() == "alice")
317        );
318    }
319
320    #[test]
321    fn builder_add_event() {
322        let target = EntityId::new("person_001").unwrap();
323        let event = EventBuilder::new(EventType::EndRelationshipRomantic)
324            .target(target.clone())
325            .severity(0.7)
326            .build()
327            .unwrap();
328
329        let sim = SimulationBuilder::new(reference_date())
330            .add_entity(create_human("person_001"), reference_date())
331            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0))
332            .build()
333            .unwrap();
334
335        let events = sim.events_for(&target);
336        assert_eq!(events.len(), 1);
337    }
338
339    #[test]
340    fn builder_event_with_valid_source_succeeds() {
341        let source = EntityId::new("person_001").unwrap();
342        let target = EntityId::new("person_002").unwrap();
343        let event = EventBuilder::new(EventType::EndRelationshipRomantic)
344            .source(source.clone())
345            .target(target.clone())
346            .severity(0.7)
347            .build()
348            .unwrap();
349
350        let sim = SimulationBuilder::new(reference_date())
351            .add_entity(create_human("person_001"), reference_date())
352            .add_entity(create_human("person_002"), reference_date())
353            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0))
354            .build()
355            .unwrap();
356
357        // Both entities exist, event should be added
358        let events = sim.events_for(&target);
359        assert_eq!(events.len(), 1);
360    }
361
362    #[test]
363    fn builder_event_unknown_target_fails() {
364        let target = EntityId::new("unknown").unwrap();
365        let event = EventBuilder::new(EventType::EndRelationshipRomantic)
366            .target(target.clone())
367            .severity(0.7)
368            .build()
369            .unwrap();
370
371        let result = SimulationBuilder::new(reference_date())
372            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0))
373            .build();
374
375        assert!(result.is_err());
376        let err = result.unwrap_err();
377        assert!(matches!(
378            err,
379            SimulationBuildError::EventReferencesUnknownEntity(_, ref entity_id)
380                if entity_id.as_str() == "unknown"
381        ));
382    }
383
384    #[test]
385    fn builder_event_unknown_source_fails() {
386        let source = EntityId::new("unknown_source").unwrap();
387        let target = EntityId::new("person_001").unwrap();
388        let event = EventBuilder::new(EventType::EndRelationshipRomantic)
389            .source(source.clone())
390            .target(target.clone())
391            .severity(0.7)
392            .build()
393            .unwrap();
394
395        let result = SimulationBuilder::new(reference_date())
396            .add_entity(create_human("person_001"), reference_date())
397            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0))
398            .build();
399
400        assert!(result.is_err());
401        let err = result.unwrap_err();
402        assert!(matches!(
403            err,
404            SimulationBuildError::EventReferencesUnknownEntity(_, ref entity_id)
405                if entity_id.as_str() == "unknown_source"
406        ));
407    }
408
409    #[test]
410    fn builder_add_relationship() {
411        let alice_id = EntityId::new("alice").unwrap();
412        let bob_id = EntityId::new("bob").unwrap();
413
414        let sim = SimulationBuilder::new(reference_date())
415            .add_entity(create_human("alice"), reference_date())
416            .add_entity(create_human("bob"), reference_date())
417            .add_relationship(
418                alice_id.clone(),
419                bob_id,
420                RelationshipSchema::Peer,
421                reference_date(),
422            )
423            .build()
424            .unwrap();
425
426        assert_eq!(sim.relationship_count(), 1);
427        assert_eq!(sim.relationships_for(&alice_id).len(), 1);
428    }
429
430    #[test]
431    fn builder_relationship_unknown_entity_a_fails() {
432        let alice_id = EntityId::new("alice").unwrap();
433        let bob_id = EntityId::new("bob").unwrap();
434
435        let result = SimulationBuilder::new(reference_date())
436            .add_entity(create_human("bob"), reference_date())
437            .add_relationship(
438                alice_id.clone(),
439                bob_id,
440                RelationshipSchema::Peer,
441                reference_date(),
442            )
443            .build();
444
445        assert!(result.is_err());
446        let err = result.unwrap_err();
447        assert!(matches!(
448            err,
449            SimulationBuildError::RelationshipReferencesUnknownEntity(_, ref entity_id)
450                if entity_id.as_str() == "alice"
451        ));
452    }
453
454    #[test]
455    fn builder_relationship_unknown_entity_b_fails() {
456        let alice_id = EntityId::new("alice").unwrap();
457        let bob_id = EntityId::new("bob").unwrap();
458
459        let result = SimulationBuilder::new(reference_date())
460            .add_entity(create_human("alice"), reference_date())
461            .add_relationship(
462                alice_id,
463                bob_id.clone(),
464                RelationshipSchema::Peer,
465                reference_date(),
466            )
467            .build();
468
469        assert!(result.is_err());
470        let err = result.unwrap_err();
471        assert!(matches!(
472            err,
473            SimulationBuildError::RelationshipReferencesUnknownEntity(_, ref entity_id)
474                if entity_id.as_str() == "bob"
475        ));
476    }
477
478    #[test]
479    fn builder_self_relationship_fails() {
480        let alice_id = EntityId::new("alice").unwrap();
481
482        let result = SimulationBuilder::new(reference_date())
483            .add_entity(create_human("alice"), reference_date())
484            .add_relationship(
485                alice_id.clone(),
486                alice_id,
487                RelationshipSchema::Peer,
488                reference_date(),
489            )
490            .build();
491
492        assert!(result.is_err());
493        let err = result.unwrap_err();
494        assert!(matches!(
495            err,
496            SimulationBuildError::SelfRelationship(ref id)
497                if id.as_str() == "alice"
498        ));
499    }
500
501    #[test]
502    fn builder_fluent_chain() {
503        let alice = create_human("alice");
504        let bob = create_human("bob");
505        let alice_id = EntityId::new("alice").unwrap();
506        let bob_id = EntityId::new("bob").unwrap();
507
508        let event = EventBuilder::new(EventType::AchieveGoalMajor)
509            .target(alice_id.clone())
510            .build()
511            .unwrap();
512
513        let sim = SimulationBuilder::new(reference_date())
514            .add_entity(alice, reference_date())
515            .add_entity(bob, reference_date())
516            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 10, 0, 0, 0))
517            .add_relationship(
518                alice_id.clone(),
519                bob_id,
520                RelationshipSchema::Peer,
521                Timestamp::from_ymd_hms(2024, 1, 5, 0, 0, 0),
522            )
523            .build()
524            .unwrap();
525
526        assert_eq!(sim.entity_count(), 2);
527        assert_eq!(sim.relationship_count(), 1);
528        assert_eq!(sim.events_for(&alice_id).len(), 1);
529    }
530
531    #[test]
532    fn simulation_build_error_display() {
533        let alice_id = EntityId::new("alice").unwrap();
534        let bob_id = EntityId::new("bob").unwrap();
535        let carol_id = EntityId::new("carol").unwrap();
536        let event_id = EventId::new("test_event").unwrap();
537        let rel_id = RelationshipId::new("test_rel").unwrap();
538
539        let err1 = SimulationBuildError::DuplicateEntityId(alice_id);
540        assert!(format!("{}", err1).contains("alice"));
541        assert!(format!("{}", err1).contains("Duplicate"));
542
543        let err2 = SimulationBuildError::EventReferencesUnknownEntity(event_id, bob_id.clone());
544        assert!(format!("{}", err2).contains("bob"));
545        assert!(format!("{}", err2).contains("unknown"));
546        assert!(format!("{}", err2).contains("test_event"));
547
548        let err3 = SimulationBuildError::RelationshipReferencesUnknownEntity(rel_id, bob_id);
549        assert!(format!("{}", err3).contains("bob"));
550        assert!(format!("{}", err3).contains("unknown"));
551        assert!(format!("{}", err3).contains("test_rel"));
552
553        let err4 = SimulationBuildError::SelfRelationship(carol_id);
554        assert!(format!("{}", err4).contains("carol"));
555        assert!(format!("{}", err4).contains("itself"));
556    }
557
558    #[test]
559    fn simulation_build_error_debug() {
560        let alice_id = EntityId::new("alice").unwrap();
561        let err = SimulationBuildError::DuplicateEntityId(alice_id);
562        let debug = format!("{:?}", err);
563        assert!(debug.contains("DuplicateEntityId"));
564    }
565
566    #[test]
567    fn simulation_build_error_clone() {
568        let alice_id = EntityId::new("alice").unwrap();
569        let err1 = SimulationBuildError::DuplicateEntityId(alice_id);
570        let err2 = err1.clone();
571        assert_eq!(err1, err2);
572    }
573
574    #[test]
575    fn event_without_target_allowed() {
576        // Events without targets (broadcast events) are allowed
577        let event = EventBuilder::new(EventType::AchieveGoalMajor)
578            .severity(0.5)
579            .build()
580            .unwrap();
581
582        let sim = SimulationBuilder::new(reference_date())
583            .add_event(event, Timestamp::from_ymd_hms(2024, 1, 15, 0, 0, 0))
584            .build()
585            .unwrap();
586
587        assert_eq!(sim.entity_count(), 0);
588    }
589
590    #[test]
591    fn simulation_build_error_is_std_error() {
592        let alice_id = EntityId::new("alice").unwrap();
593        let err: Box<dyn std::error::Error> =
594            Box::new(SimulationBuildError::DuplicateEntityId(alice_id));
595        // Verify it can be used as a std::error::Error
596        assert!(err.source().is_none());
597    }
598
599    #[test]
600    fn simulation_build_error_eq() {
601        let alice1 = EntityId::new("alice").unwrap();
602        let alice2 = EntityId::new("alice").unwrap();
603        let bob = EntityId::new("bob").unwrap();
604
605        let err1 = SimulationBuildError::DuplicateEntityId(alice1);
606        let err2 = SimulationBuildError::DuplicateEntityId(alice2);
607        let err3 = SimulationBuildError::DuplicateEntityId(bob);
608
609        assert_eq!(err1, err2);
610        assert_ne!(err1, err3);
611    }
612}