1use indexmap::IndexMap;
30use serde::{Deserialize, Serialize};
31use std::time::Duration;
32
33use crate::engine::SimTime;
34use crate::error::{SimError, SimResult};
35
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
45pub struct EquationEval {
46 pub equation_id: String,
48
49 pub inputs: IndexMap<String, f64>,
51
52 pub result: f64,
54
55 pub expected: Option<f64>,
57
58 pub error: Option<f64>,
60
61 #[serde(default)]
63 pub z3_verified: Option<bool>,
64}
65
66impl EquationEval {
67 #[must_use]
69 pub fn new(equation_id: impl Into<String>, result: f64) -> Self {
70 Self {
71 equation_id: equation_id.into(),
72 inputs: IndexMap::new(),
73 result,
74 expected: None,
75 error: None,
76 z3_verified: None,
77 }
78 }
79
80 #[must_use]
82 pub fn with_input(mut self, name: impl Into<String>, value: f64) -> Self {
83 self.inputs.insert(name.into(), value);
84 self
85 }
86
87 #[must_use]
89 pub fn with_expected(mut self, expected: f64) -> Self {
90 self.expected = Some(expected);
91 self.error = Some((self.result - expected).abs());
92 self
93 }
94
95 #[must_use]
97 pub fn with_z3_verified(mut self, verified: bool) -> Self {
98 self.z3_verified = Some(verified);
99 self
100 }
101
102 #[must_use]
104 pub fn is_correct(&self, tolerance: f64) -> bool {
105 self.error.is_none_or(|err| err <= tolerance)
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114pub struct Decision {
115 pub decision_type: String,
117
118 pub options: Vec<String>,
120
121 pub chosen: String,
123
124 pub rationale: IndexMap<String, f64>,
126
127 pub rng_value: Option<f64>,
129}
130
131impl Decision {
132 #[must_use]
134 pub fn new(decision_type: impl Into<String>, chosen: impl Into<String>) -> Self {
135 Self {
136 decision_type: decision_type.into(),
137 options: Vec::new(),
138 chosen: chosen.into(),
139 rationale: IndexMap::new(),
140 rng_value: None,
141 }
142 }
143
144 #[must_use]
146 pub fn with_options(mut self, options: Vec<String>) -> Self {
147 self.options = options;
148 self
149 }
150
151 #[must_use]
153 pub fn with_rationale(mut self, name: impl Into<String>, value: f64) -> Self {
154 self.rationale.insert(name.into(), value);
155 self
156 }
157
158 #[must_use]
160 pub fn with_rng_value(mut self, value: f64) -> Self {
161 self.rng_value = Some(value);
162 self
163 }
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct StepEntry<S: Clone> {
172 pub step_id: u64,
174
175 pub timestamp: SimTime,
177
178 pub rng_state_before: [u8; 32],
180
181 pub rng_state_after: [u8; 32],
183
184 pub input_state: S,
186
187 pub output_state: S,
189
190 pub equation_evaluations: Vec<EquationEval>,
192
193 pub decisions: Vec<Decision>,
195
196 pub step_type: String,
198
199 pub compute_duration_us: u64,
201}
202
203impl<S: Clone + Serialize> StepEntry<S> {
204 #[must_use]
206 pub fn new(
207 step_id: u64,
208 timestamp: SimTime,
209 step_type: impl Into<String>,
210 input_state: S,
211 output_state: S,
212 ) -> Self {
213 contract_pre_iterator!();
214 contract_post_configuration!(&"ok");
215 Self {
216 step_id,
217 timestamp,
218 rng_state_before: [0; 32],
219 rng_state_after: [0; 32],
220 input_state,
221 output_state,
222 equation_evaluations: Vec::new(),
223 decisions: Vec::new(),
224 step_type: step_type.into(),
225 compute_duration_us: 0,
226 }
227 }
228
229 #[must_use]
231 pub fn with_rng_states(mut self, before: [u8; 32], after: [u8; 32]) -> Self {
232 self.rng_state_before = before;
233 self.rng_state_after = after;
234 self
235 }
236
237 pub fn add_equation_eval(&mut self, eval: EquationEval) {
239 self.equation_evaluations.push(eval);
240 }
241
242 pub fn add_decision(&mut self, decision: Decision) {
244 self.decisions.push(decision);
245 }
246
247 #[must_use]
249 pub fn with_duration(mut self, duration_us: u64) -> Self {
250 self.compute_duration_us = duration_us;
251 self
252 }
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct GeneratedTestCase {
262 pub name: String,
264
265 pub equation_id: String,
267
268 pub inputs: IndexMap<String, f64>,
270
271 pub expected_output: f64,
273
274 pub assertion: String,
276
277 pub source_step_id: u64,
279
280 pub tolerance: f64,
282}
283
284impl GeneratedTestCase {
285 #[must_use]
287 pub fn new(
288 name: impl Into<String>,
289 equation_id: impl Into<String>,
290 expected_output: f64,
291 source_step_id: u64,
292 ) -> Self {
293 Self {
294 name: name.into(),
295 equation_id: equation_id.into(),
296 inputs: IndexMap::new(),
297 expected_output,
298 assertion: String::new(),
299 source_step_id,
300 tolerance: 1e-10,
301 }
302 }
303
304 #[must_use]
306 pub fn with_input(mut self, name: impl Into<String>, value: f64) -> Self {
307 self.inputs.insert(name.into(), value);
308 self
309 }
310
311 #[must_use]
313 pub fn with_assertion(mut self, assertion: impl Into<String>) -> Self {
314 self.assertion = assertion.into();
315 self
316 }
317
318 #[must_use]
320 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
321 self.tolerance = tolerance;
322 self
323 }
324
325 #[must_use]
327 pub fn to_rust_test(&self) -> String {
328 let inputs_str: Vec<String> = self
329 .inputs
330 .iter()
331 .map(|(k, v)| format!(" let {k} = {v}_f64;"))
332 .collect();
333
334 format!(
335 r#"#[test]
336fn {name}() {{
337 // Generated from step {step_id}
338 // Equation: {equation_id}
339{inputs}
340 let result = /* compute {equation_id} */;
341 let expected = {expected}_f64;
342 assert!(
343 (result - expected).abs() < {tolerance},
344 "{assertion}: got {{result}}, expected {{expected}}"
345 );
346}}
347"#,
348 name = self.name,
349 step_id = self.source_step_id,
350 equation_id = self.equation_id,
351 inputs = inputs_str.join("\n"),
352 expected = self.expected_output,
353 tolerance = self.tolerance,
354 assertion = self.assertion,
355 )
356 }
357}
358
359pub trait SimulationAuditLog {
368 type StateSnapshot: Clone + Serialize + for<'de> Deserialize<'de>;
370
371 fn log_step(&mut self, entry: StepEntry<Self::StateSnapshot>);
373
374 fn audit_log(&self) -> &[StepEntry<Self::StateSnapshot>];
376
377 fn audit_log_mut(&mut self) -> &mut Vec<StepEntry<Self::StateSnapshot>>;
379
380 fn clear_audit_log(&mut self);
382
383 fn export_audit_json(&self) -> SimResult<String> {
389 serde_json::to_string_pretty(self.audit_log())
390 .map_err(|e| SimError::serialization(format!("Audit log JSON export: {e}")))
391 }
392
393 fn export_audit_json_compact(&self) -> SimResult<String> {
399 serde_json::to_string(self.audit_log())
400 .map_err(|e| SimError::serialization(format!("Audit log JSON export: {e}")))
401 }
402
403 fn generate_test_cases(&self) -> Vec<GeneratedTestCase> {
405 let mut test_cases = Vec::new();
406
407 for entry in self.audit_log() {
408 for eval in &entry.equation_evaluations {
409 let mut tc = GeneratedTestCase::new(
410 format!("test_{}_{}", eval.equation_id, entry.step_id),
411 &eval.equation_id,
412 eval.result,
413 entry.step_id,
414 );
415
416 for (name, value) in &eval.inputs {
417 tc = tc.with_input(name, *value);
418 }
419
420 if let Some(expected) = eval.expected {
421 tc = tc.with_assertion(format!(
422 "{} should equal {expected} (computed {result})",
423 eval.equation_id,
424 result = eval.result
425 ));
426 } else {
427 tc = tc.with_assertion(format!("{} computation", eval.equation_id));
428 }
429
430 test_cases.push(tc);
431 }
432 }
433
434 test_cases
435 }
436
437 fn total_equation_evals(&self) -> usize {
439 self.audit_log()
440 .iter()
441 .map(|e| e.equation_evaluations.len())
442 .sum()
443 }
444
445 fn total_decisions(&self) -> usize {
447 self.audit_log().iter().map(|e| e.decisions.len()).sum()
448 }
449
450 fn verify_all_equations(&self, tolerance: f64) -> Vec<(u64, String, f64)> {
452 let mut failures = Vec::new();
453
454 for entry in self.audit_log() {
455 for eval in &entry.equation_evaluations {
456 if !eval.is_correct(tolerance) {
457 if let Some(err) = eval.error {
458 failures.push((entry.step_id, eval.equation_id.clone(), err));
459 }
460 }
461 }
462 }
463
464 failures
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
474pub enum ReplaySpeed {
475 #[default]
477 Instant,
478 RealTime(Duration),
480 StepByStep,
482 FastForward(u32),
484}
485
486#[derive(Debug, Clone)]
488pub struct ReplayState<S: Clone> {
489 pub current_index: usize,
491 pub total_steps: usize,
493 pub current_state: Option<S>,
495 pub speed: ReplaySpeed,
497 pub paused: bool,
499}
500
501impl<S: Clone> ReplayState<S> {
502 #[must_use]
504 pub fn new(total_steps: usize) -> Self {
505 Self {
506 current_index: 0,
507 total_steps,
508 current_state: None,
509 speed: ReplaySpeed::default(),
510 paused: false,
511 }
512 }
513
514 #[must_use]
516 pub fn is_complete(&self) -> bool {
517 self.current_index >= self.total_steps
518 }
519
520 #[must_use]
522 pub fn progress_percent(&self) -> f64 {
523 if self.total_steps == 0 {
524 100.0
525 } else {
526 (self.current_index as f64 / self.total_steps as f64) * 100.0
527 }
528 }
529
530 pub fn advance(&mut self) {
532 if self.current_index < self.total_steps {
533 self.current_index += 1;
534 }
535 }
536
537 pub fn seek(&mut self, step_index: usize) {
539 contract_pre_iterator!();
540 self.current_index = step_index.min(self.total_steps);
541 }
542
543 pub fn reset(&mut self) {
545 self.current_index = 0;
546 self.current_state = None;
547 }
548}
549
550pub struct AuditLogReplayer<S: Clone + Serialize + for<'de> Deserialize<'de>> {
552 entries: Vec<StepEntry<S>>,
554 state: ReplayState<S>,
556}
557
558impl<S: Clone + Serialize + for<'de> Deserialize<'de>> AuditLogReplayer<S> {
559 #[must_use]
561 pub fn new(entries: Vec<StepEntry<S>>) -> Self {
562 let total = entries.len();
563 Self {
564 entries,
565 state: ReplayState::new(total),
566 }
567 }
568
569 #[must_use]
571 pub fn state(&self) -> &ReplayState<S> {
572 &self.state
573 }
574
575 pub fn state_mut(&mut self) -> &mut ReplayState<S> {
577 &mut self.state
578 }
579
580 #[must_use]
582 pub fn current_entry(&self) -> Option<&StepEntry<S>> {
583 self.entries.get(self.state.current_index)
584 }
585
586 pub fn step_forward(&mut self) -> Option<&StepEntry<S>> {
588 if self.state.paused || self.state.is_complete() {
589 return None;
590 }
591
592 contract_pre_iterator!();
593 let entry = self.entries.get(self.state.current_index);
594 if entry.is_some() {
595 self.state.current_state = entry.map(|e| e.output_state.clone());
596 self.state.advance();
597 }
598 entry
599 }
600
601 pub fn seek_to(&mut self, index: usize) {
603 self.state.seek(index);
604 if let Some(entry) = self.entries.get(index) {
605 self.state.current_state = Some(entry.output_state.clone());
606 }
607 }
608
609 #[must_use]
611 pub fn entries(&self) -> &[StepEntry<S>] {
612 &self.entries
613 }
614
615 #[must_use]
617 pub fn find_by_step_id(&self, step_id: u64) -> Option<&StepEntry<S>> {
618 contract_pre_iterator!();
619 self.entries.iter().find(|e| e.step_id == step_id)
620 }
621
622 pub fn entries_in_range(&self, start: SimTime, end: SimTime) -> Vec<&StepEntry<S>> {
624 self.entries
625 .iter()
626 .filter(|e| e.timestamp >= start && e.timestamp <= end)
627 .collect()
628 }
629}
630
631pub fn verify_rng_consistency<S: Clone + Serialize + for<'de> Deserialize<'de>>(
639 log: &[StepEntry<S>],
640) -> Vec<u64> {
641 let mut inconsistencies = Vec::new();
642
643 for i in 1..log.len() {
644 if log[i - 1].rng_state_after != log[i].rng_state_before {
646 inconsistencies.push(log[i].step_id);
647 }
648 }
649
650 inconsistencies
651}
652
653pub fn hash_state<S: Serialize>(state: &S) -> SimResult<[u8; 32]> {
659 let bytes = bincode::serialize(state)
660 .map_err(|e| SimError::serialization(format!("Hash state: {e}")))?;
661 Ok(*blake3::hash(&bytes).as_bytes())
662}
663
664#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
670pub enum TspStepType {
671 Construction,
673 TwoOptPass,
675 TwoOptImprove,
677 BestUpdate,
679 GraspIteration,
681}
682
683impl std::fmt::Display for TspStepType {
684 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685 match self {
686 Self::Construction => write!(f, "construction"),
687 Self::TwoOptPass => write!(f, "two_opt_pass"),
688 Self::TwoOptImprove => write!(f, "two_opt_improve"),
689 Self::BestUpdate => write!(f, "best_update"),
690 Self::GraspIteration => write!(f, "grasp_iteration"),
691 }
692 }
693}
694
695#[derive(Debug, Clone, Serialize, Deserialize)]
697pub struct TspStateSnapshot {
698 pub tour: Vec<usize>,
700 pub tour_length: f64,
702 pub best_tour: Vec<usize>,
704 pub best_tour_length: f64,
706 pub restarts: u64,
708 pub two_opt_iterations: u64,
710 pub two_opt_improvements: u64,
712}
713
714impl Default for TspStateSnapshot {
715 fn default() -> Self {
716 Self {
717 tour: Vec::new(),
718 tour_length: 0.0,
719 best_tour: Vec::new(),
720 best_tour_length: 0.0,
721 restarts: 0,
722 two_opt_iterations: 0,
723 two_opt_improvements: 0,
724 }
725 }
726}
727
728#[cfg(test)]
733mod tests {
734 use super::*;
735
736 #[test]
745 fn test_equation_eval_creation() {
746 let eval = EquationEval::new("two_opt_delta", 0.5);
747
748 assert_eq!(eval.equation_id, "two_opt_delta");
749 assert!((eval.result - 0.5).abs() < f64::EPSILON);
750 assert!(eval.inputs.is_empty());
751 assert!(eval.expected.is_none());
752 assert!(eval.error.is_none());
753 }
754
755 #[test]
756 fn test_equation_eval_with_inputs() {
757 let eval = EquationEval::new("two_opt_delta", 0.5)
758 .with_input("d_i_i1", 1.0)
759 .with_input("d_j_j1", 0.8)
760 .with_input("d_i_j", 0.7)
761 .with_input("d_i1_j1", 0.6);
762
763 assert_eq!(eval.inputs.len(), 4);
764 assert!((eval.inputs["d_i_i1"] - 1.0).abs() < f64::EPSILON);
765 assert!((eval.inputs["d_j_j1"] - 0.8).abs() < f64::EPSILON);
766 }
767
768 #[test]
769 fn test_equation_eval_with_expected() {
770 let eval = EquationEval::new("tour_length", 4.5).with_expected(4.48);
771
772 assert!(eval.expected.is_some());
773 assert!((eval.expected.unwrap() - 4.48).abs() < f64::EPSILON);
774 assert!(eval.error.is_some());
775 assert!((eval.error.unwrap() - 0.02).abs() < 1e-10);
777 }
778
779 #[test]
780 fn test_equation_eval_is_correct() {
781 let eval_correct = EquationEval::new("test", 1.0).with_expected(1.001);
782 let eval_incorrect = EquationEval::new("test", 1.0).with_expected(2.0);
783
784 assert!(eval_correct.is_correct(0.01));
785 assert!(!eval_incorrect.is_correct(0.01));
786 }
787
788 #[test]
789 fn test_equation_eval_z3_verified() {
790 let eval = EquationEval::new("test", 1.0).with_z3_verified(true);
791
792 assert_eq!(eval.z3_verified, Some(true));
793 }
794
795 #[test]
800 fn test_decision_creation() {
801 let decision = Decision::new("rcl_selection", "city_7");
802
803 assert_eq!(decision.decision_type, "rcl_selection");
804 assert_eq!(decision.chosen, "city_7");
805 assert!(decision.options.is_empty());
806 assert!(decision.rationale.is_empty());
807 }
808
809 #[test]
810 fn test_decision_with_options() {
811 let decision = Decision::new("rcl_selection", "city_7").with_options(vec![
812 "city_3".into(),
813 "city_7".into(),
814 "city_12".into(),
815 ]);
816
817 assert_eq!(decision.options.len(), 3);
818 assert!(decision.options.contains(&"city_7".to_string()));
819 }
820
821 #[test]
822 fn test_decision_with_rationale() {
823 let decision = Decision::new("two_opt_apply", "apply")
824 .with_rationale("delta", 0.15)
825 .with_rationale("new_length", 4.35);
826
827 assert_eq!(decision.rationale.len(), 2);
828 assert!((decision.rationale["delta"] - 0.15).abs() < f64::EPSILON);
829 }
830
831 #[test]
832 fn test_decision_with_rng_value() {
833 let decision = Decision::new("rcl_selection", "city_7").with_rng_value(0.42);
834
835 assert_eq!(decision.rng_value, Some(0.42));
836 }
837
838 #[test]
843 fn test_step_entry_creation() {
844 let entry: StepEntry<u64> =
845 StepEntry::new(1, SimTime::from_secs(0.1), "construction", 0u64, 1u64);
846
847 assert_eq!(entry.step_id, 1);
848 assert_eq!(entry.step_type, "construction");
849 assert_eq!(entry.input_state, 0);
850 assert_eq!(entry.output_state, 1);
851 }
852
853 #[test]
854 fn test_step_entry_with_rng_states() {
855 let before = [1u8; 32];
856 let after = [2u8; 32];
857
858 let entry: StepEntry<u64> = StepEntry::new(1, SimTime::from_secs(0.1), "test", 0u64, 1u64)
859 .with_rng_states(before, after);
860
861 assert_eq!(entry.rng_state_before, before);
862 assert_eq!(entry.rng_state_after, after);
863 }
864
865 #[test]
866 fn test_step_entry_add_equation_eval() {
867 let mut entry: StepEntry<u64> =
868 StepEntry::new(1, SimTime::from_secs(0.1), "test", 0u64, 1u64);
869
870 entry.add_equation_eval(EquationEval::new("test_eq", 1.5));
871
872 assert_eq!(entry.equation_evaluations.len(), 1);
873 assert_eq!(entry.equation_evaluations[0].equation_id, "test_eq");
874 }
875
876 #[test]
877 fn test_step_entry_add_decision() {
878 let mut entry: StepEntry<u64> =
879 StepEntry::new(1, SimTime::from_secs(0.1), "test", 0u64, 1u64);
880
881 entry.add_decision(Decision::new("test_decision", "option_a"));
882
883 assert_eq!(entry.decisions.len(), 1);
884 assert_eq!(entry.decisions[0].decision_type, "test_decision");
885 }
886
887 #[test]
888 fn test_step_entry_with_duration() {
889 let entry: StepEntry<u64> =
890 StepEntry::new(1, SimTime::from_secs(0.1), "test", 0u64, 1u64).with_duration(1000);
891
892 assert_eq!(entry.compute_duration_us, 1000);
893 }
894
895 #[test]
900 fn test_generated_test_case_creation() {
901 let tc = GeneratedTestCase::new("test_two_opt_1", "two_opt_delta", 0.5, 42);
902
903 assert_eq!(tc.name, "test_two_opt_1");
904 assert_eq!(tc.equation_id, "two_opt_delta");
905 assert!((tc.expected_output - 0.5).abs() < f64::EPSILON);
906 assert_eq!(tc.source_step_id, 42);
907 }
908
909 #[test]
910 fn test_generated_test_case_with_inputs() {
911 let tc = GeneratedTestCase::new("test", "eq", 1.0, 1)
912 .with_input("x", 2.0)
913 .with_input("y", 3.0);
914
915 assert_eq!(tc.inputs.len(), 2);
916 assert!((tc.inputs["x"] - 2.0).abs() < f64::EPSILON);
917 }
918
919 #[test]
920 fn test_generated_test_case_to_rust() {
921 let tc = GeneratedTestCase::new("test_eq_1", "my_equation", 42.0, 5)
922 .with_input("a", 1.0)
923 .with_assertion("equation should compute correctly");
924
925 let rust_code = tc.to_rust_test();
926
927 assert!(rust_code.contains("#[test]"));
928 assert!(rust_code.contains("fn test_eq_1()"));
929 assert!(rust_code.contains("let a = 1"));
930 assert!(rust_code.contains("let expected = 42"));
931 }
932
933 #[test]
938 fn test_replay_state_creation() {
939 let state: ReplayState<u64> = ReplayState::new(100);
940
941 assert_eq!(state.current_index, 0);
942 assert_eq!(state.total_steps, 100);
943 assert!(!state.is_complete());
944 assert!(!state.paused);
945 }
946
947 #[test]
948 fn test_replay_state_advance() {
949 let mut state: ReplayState<u64> = ReplayState::new(10);
950
951 state.advance();
952 assert_eq!(state.current_index, 1);
953
954 state.advance();
955 assert_eq!(state.current_index, 2);
956 }
957
958 #[test]
959 fn test_replay_state_progress() {
960 let mut state: ReplayState<u64> = ReplayState::new(10);
961
962 assert!((state.progress_percent() - 0.0).abs() < f64::EPSILON);
963
964 state.seek(5);
965 assert!((state.progress_percent() - 50.0).abs() < f64::EPSILON);
966
967 state.seek(10);
968 assert!((state.progress_percent() - 100.0).abs() < f64::EPSILON);
969 }
970
971 #[test]
972 fn test_replay_state_is_complete() {
973 let mut state: ReplayState<u64> = ReplayState::new(3);
974
975 assert!(!state.is_complete());
976
977 state.seek(3);
978 assert!(state.is_complete());
979 }
980
981 #[test]
982 fn test_replay_state_reset() {
983 let mut state: ReplayState<u64> = ReplayState::new(10);
984 state.seek(5);
985 state.current_state = Some(42);
986
987 state.reset();
988
989 assert_eq!(state.current_index, 0);
990 assert!(state.current_state.is_none());
991 }
992
993 #[test]
998 fn test_replayer_creation() {
999 let entries: Vec<StepEntry<u64>> = vec![
1000 StepEntry::new(0, SimTime::ZERO, "step_0", 0, 1),
1001 StepEntry::new(1, SimTime::from_secs(0.1), "step_1", 1, 2),
1002 ];
1003
1004 let replayer = AuditLogReplayer::new(entries);
1005
1006 assert_eq!(replayer.entries().len(), 2);
1007 assert_eq!(replayer.state().total_steps, 2);
1008 }
1009
1010 #[test]
1011 fn test_replayer_step_forward() {
1012 let entries: Vec<StepEntry<u64>> = vec![
1013 StepEntry::new(0, SimTime::ZERO, "step_0", 0, 1),
1014 StepEntry::new(1, SimTime::from_secs(0.1), "step_1", 1, 2),
1015 ];
1016
1017 let mut replayer = AuditLogReplayer::new(entries);
1018
1019 let entry = replayer.step_forward();
1020 assert!(entry.is_some());
1021 assert_eq!(entry.unwrap().step_id, 0);
1022
1023 let entry = replayer.step_forward();
1024 assert!(entry.is_some());
1025 assert_eq!(entry.unwrap().step_id, 1);
1026
1027 let entry = replayer.step_forward();
1029 assert!(entry.is_none());
1030 }
1031
1032 #[test]
1033 fn test_replayer_seek() {
1034 let entries: Vec<StepEntry<u64>> = vec![
1035 StepEntry::new(0, SimTime::ZERO, "step_0", 0, 1),
1036 StepEntry::new(1, SimTime::from_secs(0.1), "step_1", 1, 2),
1037 StepEntry::new(2, SimTime::from_secs(0.2), "step_2", 2, 3),
1038 ];
1039
1040 let mut replayer = AuditLogReplayer::new(entries);
1041
1042 replayer.seek_to(2);
1043 assert_eq!(replayer.state().current_index, 2);
1044 assert_eq!(replayer.state().current_state, Some(3));
1045 }
1046
1047 #[test]
1048 fn test_replayer_find_by_step_id() {
1049 let entries: Vec<StepEntry<u64>> = vec![
1050 StepEntry::new(10, SimTime::ZERO, "step_0", 0, 1),
1051 StepEntry::new(20, SimTime::from_secs(0.1), "step_1", 1, 2),
1052 ];
1053
1054 let replayer = AuditLogReplayer::new(entries);
1055
1056 let found = replayer.find_by_step_id(20);
1057 assert!(found.is_some());
1058 assert_eq!(found.unwrap().step_id, 20);
1059
1060 let not_found = replayer.find_by_step_id(99);
1061 assert!(not_found.is_none());
1062 }
1063
1064 #[test]
1069 fn test_verify_rng_consistency_valid() {
1070 let entries: Vec<StepEntry<u64>> = vec![
1071 StepEntry::new(0, SimTime::ZERO, "s0", 0, 1).with_rng_states([1; 32], [2; 32]),
1072 StepEntry::new(1, SimTime::from_secs(0.1), "s1", 1, 2)
1073 .with_rng_states([2; 32], [3; 32]),
1074 StepEntry::new(2, SimTime::from_secs(0.2), "s2", 2, 3)
1075 .with_rng_states([3; 32], [4; 32]),
1076 ];
1077
1078 let inconsistencies = verify_rng_consistency(&entries);
1079 assert!(inconsistencies.is_empty());
1080 }
1081
1082 #[test]
1083 fn test_verify_rng_consistency_invalid() {
1084 let entries: Vec<StepEntry<u64>> = vec![
1085 StepEntry::new(0, SimTime::ZERO, "s0", 0, 1).with_rng_states([1; 32], [2; 32]),
1086 StepEntry::new(1, SimTime::from_secs(0.1), "s1", 1, 2)
1087 .with_rng_states([99; 32], [3; 32]), ];
1089
1090 let inconsistencies = verify_rng_consistency(&entries);
1091 assert_eq!(inconsistencies.len(), 1);
1092 assert_eq!(inconsistencies[0], 1);
1093 }
1094
1095 #[test]
1096 fn test_hash_state() {
1097 let state1 = 42u64;
1098 let state2 = 42u64;
1099 let state3 = 43u64;
1100
1101 let hash1 = hash_state(&state1).expect("hash");
1102 let hash2 = hash_state(&state2).expect("hash");
1103 let hash3 = hash_state(&state3).expect("hash");
1104
1105 assert_eq!(hash1, hash2); assert_ne!(hash1, hash3); }
1108
1109 struct MockSimulation {
1114 log: Vec<StepEntry<u64>>,
1115 }
1116
1117 impl MockSimulation {
1118 fn new() -> Self {
1119 Self { log: Vec::new() }
1120 }
1121 }
1122
1123 impl SimulationAuditLog for MockSimulation {
1124 type StateSnapshot = u64;
1125
1126 fn log_step(&mut self, entry: StepEntry<Self::StateSnapshot>) {
1127 self.log.push(entry);
1128 }
1129
1130 fn audit_log(&self) -> &[StepEntry<Self::StateSnapshot>] {
1131 &self.log
1132 }
1133
1134 fn audit_log_mut(&mut self) -> &mut Vec<StepEntry<Self::StateSnapshot>> {
1135 &mut self.log
1136 }
1137
1138 fn clear_audit_log(&mut self) {
1139 self.log.clear();
1140 }
1141 }
1142
1143 #[test]
1144 fn test_simulation_audit_log_trait() {
1145 let mut sim = MockSimulation::new();
1146
1147 let mut entry = StepEntry::new(0, SimTime::ZERO, "test", 0u64, 1u64);
1148 entry.add_equation_eval(EquationEval::new("eq1", 1.0).with_expected(1.0));
1149 entry.add_decision(Decision::new("decide", "a"));
1150
1151 sim.log_step(entry);
1152
1153 assert_eq!(sim.audit_log().len(), 1);
1154 assert_eq!(sim.total_equation_evals(), 1);
1155 assert_eq!(sim.total_decisions(), 1);
1156 }
1157
1158 #[test]
1159 fn test_simulation_audit_log_export_json() {
1160 let mut sim = MockSimulation::new();
1161 sim.log_step(StepEntry::new(0, SimTime::ZERO, "test", 0u64, 1u64));
1162
1163 let json = sim.export_audit_json().expect("json export");
1164 assert!(json.contains("step_id"));
1165 assert!(json.contains("\"step_id\": 0"));
1166 }
1167
1168 #[test]
1169 fn test_simulation_audit_log_generate_tests() {
1170 let mut sim = MockSimulation::new();
1171
1172 let mut entry = StepEntry::new(0, SimTime::ZERO, "test", 0u64, 1u64);
1173 entry.add_equation_eval(
1174 EquationEval::new("my_equation", 42.0)
1175 .with_input("x", 1.0)
1176 .with_input("y", 2.0),
1177 );
1178 sim.log_step(entry);
1179
1180 let test_cases = sim.generate_test_cases();
1181
1182 assert_eq!(test_cases.len(), 1);
1183 assert_eq!(test_cases[0].equation_id, "my_equation");
1184 assert!((test_cases[0].expected_output - 42.0).abs() < f64::EPSILON);
1185 }
1186
1187 #[test]
1188 fn test_simulation_audit_log_verify_equations() {
1189 let mut sim = MockSimulation::new();
1190
1191 let mut entry1 = StepEntry::new(0, SimTime::ZERO, "test", 0u64, 1u64);
1192 entry1.add_equation_eval(EquationEval::new("eq1", 1.0).with_expected(1.0)); let mut entry2 = StepEntry::new(1, SimTime::from_secs(0.1), "test", 1u64, 2u64);
1195 entry2.add_equation_eval(EquationEval::new("eq2", 1.0).with_expected(2.0)); sim.log_step(entry1);
1198 sim.log_step(entry2);
1199
1200 let failures = sim.verify_all_equations(0.001);
1201
1202 assert_eq!(failures.len(), 1);
1203 assert_eq!(failures[0].0, 1); assert_eq!(failures[0].1, "eq2"); }
1206
1207 #[test]
1208 fn test_simulation_audit_log_clear() {
1209 let mut sim = MockSimulation::new();
1210 sim.log_step(StepEntry::new(0, SimTime::ZERO, "test", 0u64, 1u64));
1211
1212 assert_eq!(sim.audit_log().len(), 1);
1213
1214 sim.clear_audit_log();
1215 assert!(sim.audit_log().is_empty());
1216 }
1217
1218 #[test]
1223 fn test_equation_eval_serialization() {
1224 let eval = EquationEval::new("test", 1.5)
1225 .with_input("x", 1.0)
1226 .with_expected(1.5);
1227
1228 let json = serde_json::to_string(&eval).expect("serialize");
1229 let restored: EquationEval = serde_json::from_str(&json).expect("deserialize");
1230
1231 assert_eq!(restored.equation_id, eval.equation_id);
1232 assert!((restored.result - eval.result).abs() < f64::EPSILON);
1233 }
1234
1235 #[test]
1236 fn test_decision_serialization() {
1237 let decision = Decision::new("test", "a")
1238 .with_options(vec!["a".into(), "b".into()])
1239 .with_rationale("score", 0.5);
1240
1241 let json = serde_json::to_string(&decision).expect("serialize");
1242 let restored: Decision = serde_json::from_str(&json).expect("deserialize");
1243
1244 assert_eq!(restored.decision_type, decision.decision_type);
1245 assert_eq!(restored.chosen, decision.chosen);
1246 }
1247
1248 #[test]
1249 fn test_step_entry_serialization() {
1250 let entry: StepEntry<u64> =
1251 StepEntry::new(1, SimTime::from_secs(0.5), "test", 10u64, 20u64);
1252
1253 let json = serde_json::to_string(&entry).expect("serialize");
1254 let restored: StepEntry<u64> = serde_json::from_str(&json).expect("deserialize");
1255
1256 assert_eq!(restored.step_id, entry.step_id);
1257 assert_eq!(restored.input_state, entry.input_state);
1258 assert_eq!(restored.output_state, entry.output_state);
1259 }
1260}