1use serde::{de::DeserializeOwned, Deserialize, Serialize};
18use std::fmt::Debug;
19use thiserror::Error;
20
21#[derive(Debug, Error)]
23pub enum DemoError {
24 #[error("YAML parse error: {0}")]
26 YamlParse(#[from] serde_yaml::Error),
27
28 #[error("Validation error: {0}")]
30 Validation(String),
31
32 #[error("Schema validation error: {0}")]
34 Schema(String),
35
36 #[error("Metamorphic relation {id} failed: {message}")]
38 MetamorphicFailure { id: String, message: String },
39
40 #[error("Invariant violation: {0}")]
42 InvariantViolation(String),
43
44 #[error("Serialization error: {0}")]
46 Serialization(String),
47}
48
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "lowercase")]
52pub enum Severity {
53 Critical,
55 #[default]
57 Major,
58 Minor,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct FalsificationCriterion {
65 pub id: String,
67
68 pub name: String,
70
71 pub metric: String,
73
74 pub threshold: f64,
76
77 pub condition: String,
79
80 #[serde(default)]
82 pub tolerance: f64,
83
84 #[serde(default)]
86 pub severity: Severity,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct CriterionResult {
92 pub id: String,
94
95 pub passed: bool,
97
98 pub actual: f64,
100
101 pub expected: f64,
103
104 pub message: String,
106
107 pub severity: Severity,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct MetamorphicRelation {
117 pub id: String,
119
120 pub description: String,
122
123 pub source_transform: String,
125
126 pub expected_relation: String,
128
129 #[serde(default = "default_tolerance")]
131 pub tolerance: f64,
132}
133
134fn default_tolerance() -> f64 {
135 1e-10
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct MrResult {
141 pub id: String,
143
144 pub passed: bool,
146
147 pub message: String,
149
150 pub source_value: Option<f64>,
152
153 pub followup_value: Option<f64>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct DemoMeta {
160 pub id: String,
162
163 pub version: String,
165
166 pub demo_type: String,
168
169 #[serde(default)]
171 pub description: String,
172
173 #[serde(default)]
175 pub author: String,
176
177 #[serde(default)]
179 pub created: String,
180}
181
182pub trait DemoEngine: Sized + Clone {
208 type Config: DeserializeOwned + Debug;
210
211 type State: Clone + Serialize + DeserializeOwned + PartialEq + Debug;
213
214 type StepResult: Debug;
216
217 fn from_yaml(yaml: &str) -> Result<Self, DemoError>;
226
227 fn from_config(config: Self::Config) -> Self;
229
230 fn config(&self) -> &Self::Config;
232
233 fn reset(&mut self);
235
236 fn reset_with_seed(&mut self, seed: u64);
238
239 fn step(&mut self) -> Self::StepResult;
243
244 fn run(&mut self, n: usize) -> Vec<Self::StepResult> {
246 (0..n).map(|_| self.step()).collect()
247 }
248
249 fn is_complete(&self) -> bool;
251
252 fn state(&self) -> Self::State;
256
257 fn restore(&mut self, state: &Self::State);
259
260 fn step_count(&self) -> u64;
262
263 fn seed(&self) -> u64;
265
266 fn meta(&self) -> &DemoMeta;
268
269 fn falsification_criteria(&self) -> Vec<FalsificationCriterion>;
273
274 fn evaluate_criteria(&self) -> Vec<CriterionResult>;
276
277 fn is_verified(&self) -> bool {
279 self.evaluate_criteria()
280 .iter()
281 .filter(|r| r.severity == Severity::Critical)
282 .all(|r| r.passed)
283 }
284
285 fn metamorphic_relations(&self) -> Vec<MetamorphicRelation>;
289
290 fn verify_mr(&self, mr: &MetamorphicRelation) -> MrResult;
292
293 fn verify_all_mrs(&self) -> Vec<MrResult> {
295 self.metamorphic_relations()
296 .iter()
297 .map(|mr| self.verify_mr(mr))
298 .collect()
299 }
300}
301
302pub trait DeterministicReplay: DemoEngine {
307 fn verify_determinism(&self, other: &Self) -> bool {
309 self.state() == other.state()
310 }
311
312 fn state_checksum(&self) -> u64;
314}
315
316pub trait RendererIndependent: DemoEngine {
323 type RenderData: Clone + Serialize;
325
326 fn render_data(&self) -> Self::RenderData;
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_severity_default() {
336 assert_eq!(Severity::default(), Severity::Major);
337 }
338
339 #[test]
340 fn test_severity_serialization() {
341 let critical = Severity::Critical;
342 let json = serde_json::to_string(&critical).expect("serialize");
343 assert_eq!(json, "\"critical\"");
344
345 let deserialized: Severity = serde_json::from_str(&json).expect("deserialize");
346 assert_eq!(deserialized, Severity::Critical);
347 }
348
349 #[test]
350 fn test_demo_meta_serialization() {
351 let meta = DemoMeta {
352 id: "TEST-001".to_string(),
353 version: "1.0.0".to_string(),
354 demo_type: "test".to_string(),
355 description: "Test demo".to_string(),
356 author: "PAIML".to_string(),
357 created: "2025-12-12".to_string(),
358 };
359
360 let json = serde_json::to_string(&meta).expect("serialize");
361 assert!(json.contains("TEST-001"));
362 assert!(json.contains("1.0.0"));
363 }
364
365 #[test]
366 fn test_demo_meta_deserialization() {
367 let yaml = r#"
368id: "DEMO-001"
369version: "2.0.0"
370demo_type: "orbit"
371description: "Orbit demo"
372author: "Test"
373created: "2025-01-01"
374"#;
375 let meta: DemoMeta = serde_yaml::from_str(yaml).expect("deserialize");
376 assert_eq!(meta.id, "DEMO-001");
377 assert_eq!(meta.demo_type, "orbit");
378 }
379
380 #[test]
381 fn test_falsification_criterion_serialization() {
382 let criterion = FalsificationCriterion {
383 id: "GAP-001".to_string(),
384 name: "Optimality gap".to_string(),
385 metric: "gap".to_string(),
386 threshold: 0.20,
387 condition: "gap <= threshold".to_string(),
388 tolerance: 1e-6,
389 severity: Severity::Major,
390 };
391
392 let json = serde_json::to_string(&criterion).expect("serialize");
393 assert!(json.contains("GAP-001"));
394 assert!(json.contains("0.2"));
395 }
396
397 #[test]
398 fn test_criterion_result() {
399 let result = CriterionResult {
400 id: "TEST".to_string(),
401 passed: true,
402 actual: 0.15,
403 expected: 0.20,
404 message: "Passed".to_string(),
405 severity: Severity::Critical,
406 };
407
408 assert!(result.passed);
409 assert!(result.actual < result.expected);
410 }
411
412 #[test]
413 fn test_metamorphic_relation_default_tolerance() {
414 let yaml = r#"
415id: "MR-Test"
416description: "Test relation"
417source_transform: "identity"
418expected_relation: "unchanged"
419"#;
420 let mr: MetamorphicRelation = serde_yaml::from_str(yaml).expect("deserialize");
421 assert!((mr.tolerance - 1e-10).abs() < 1e-15);
422 }
423
424 #[test]
425 fn test_mr_result() {
426 let result = MrResult {
427 id: "MR-Energy".to_string(),
428 passed: true,
429 message: "Energy conserved".to_string(),
430 source_value: Some(1000.0),
431 followup_value: Some(1000.0),
432 };
433
434 assert!(result.passed);
435 assert_eq!(result.source_value, result.followup_value);
436 }
437
438 #[test]
439 fn test_demo_error_display() {
440 let err = DemoError::Validation("invalid config".to_string());
441 let msg = format!("{err}");
442 assert!(msg.contains("Validation error"));
443 assert!(msg.contains("invalid config"));
444 }
445
446 #[test]
447 fn test_demo_error_metamorphic() {
448 let err = DemoError::MetamorphicFailure {
449 id: "MR-001".to_string(),
450 message: "Invariant broken".to_string(),
451 };
452 let msg = format!("{err}");
453 assert!(msg.contains("MR-001"));
454 assert!(msg.contains("Invariant broken"));
455 }
456
457 #[test]
458 fn test_demo_error_from_yaml() {
459 let bad_yaml = "{{{{not valid yaml";
460 let result: Result<DemoMeta, _> = serde_yaml::from_str(bad_yaml);
461 assert!(result.is_err());
462 }
463
464 #[test]
465 fn test_demo_error_validation() {
466 let err = DemoError::Validation("config invalid".to_string());
467 let msg = format!("{err}");
468 assert!(msg.contains("Validation error"));
469 assert!(msg.contains("config invalid"));
470 }
471
472 #[test]
473 fn test_demo_error_schema() {
474 let err = DemoError::Schema("schema mismatch".to_string());
475 let msg = format!("{err}");
476 assert!(msg.contains("Schema validation error"));
477 }
478
479 #[test]
480 fn test_demo_error_invariant_violation() {
481 let err = DemoError::InvariantViolation("state corrupted".to_string());
482 let msg = format!("{err}");
483 assert!(msg.contains("Invariant violation"));
484 }
485
486 #[test]
487 fn test_demo_error_serialization() {
488 let err = DemoError::Serialization("failed to serialize".to_string());
489 let msg = format!("{err}");
490 assert!(msg.contains("Serialization error"));
491 }
492
493 #[test]
494 fn test_severity_minor_serialization() {
495 let minor = Severity::Minor;
496 let json = serde_json::to_string(&minor).expect("serialize");
497 assert_eq!(json, "\"minor\"");
498
499 let deserialized: Severity = serde_json::from_str(&json).expect("deserialize");
500 assert_eq!(deserialized, Severity::Minor);
501 }
502
503 #[test]
504 fn test_severity_all_variants() {
505 let severities = [Severity::Critical, Severity::Major, Severity::Minor];
506 for sev in severities {
507 let json = serde_json::to_string(&sev).expect("serialize");
508 let deserialized: Severity = serde_json::from_str(&json).expect("deserialize");
509 assert_eq!(sev, deserialized);
510 }
511 }
512
513 #[test]
514 fn test_falsification_criterion_deserialization() {
515 let yaml = r#"
516id: "CRIT-001"
517name: "Test Criterion"
518metric: "accuracy"
519threshold: 0.95
520condition: "accuracy >= threshold"
521tolerance: 0.001
522severity: "critical"
523"#;
524 let criterion: FalsificationCriterion = serde_yaml::from_str(yaml).expect("deserialize");
525 assert_eq!(criterion.id, "CRIT-001");
526 assert_eq!(criterion.severity, Severity::Critical);
527 assert!((criterion.tolerance - 0.001).abs() < 1e-10);
528 }
529
530 #[test]
531 fn test_falsification_criterion_defaults() {
532 let yaml = r#"
533id: "CRIT-002"
534name: "Minimal"
535metric: "val"
536threshold: 1.0
537condition: "val > 0"
538"#;
539 let criterion: FalsificationCriterion = serde_yaml::from_str(yaml).expect("deserialize");
540 assert_eq!(criterion.severity, Severity::Major); assert!((criterion.tolerance - 0.0).abs() < 1e-15); }
543
544 #[test]
545 fn test_criterion_result_serialization() {
546 let result = CriterionResult {
547 id: "RES-001".to_string(),
548 passed: false,
549 actual: 0.85,
550 expected: 0.95,
551 message: "Below threshold".to_string(),
552 severity: Severity::Critical,
553 };
554
555 let json = serde_json::to_string(&result).expect("serialize");
556 assert!(json.contains("RES-001"));
557 assert!(json.contains("false"));
558 assert!(json.contains("0.85"));
559 }
560
561 #[test]
562 fn test_metamorphic_relation_with_tolerance() {
563 let yaml = r#"
564id: "MR-Energy"
565description: "Energy conservation"
566source_transform: "time_reverse"
567expected_relation: "energy_unchanged"
568tolerance: 1e-6
569"#;
570 let mr: MetamorphicRelation = serde_yaml::from_str(yaml).expect("deserialize");
571 assert_eq!(mr.id, "MR-Energy");
572 assert!((mr.tolerance - 1e-6).abs() < 1e-15);
573 }
574
575 #[test]
576 fn test_mr_result_with_none_values() {
577 let result = MrResult {
578 id: "MR-None".to_string(),
579 passed: true,
580 message: "No comparison needed".to_string(),
581 source_value: None,
582 followup_value: None,
583 };
584
585 assert!(result.passed);
586 assert!(result.source_value.is_none());
587 assert!(result.followup_value.is_none());
588 }
589
590 #[test]
591 fn test_mr_result_serialization() {
592 let result = MrResult {
593 id: "MR-Serialize".to_string(),
594 passed: false,
595 message: "Values diverged".to_string(),
596 source_value: Some(100.0),
597 followup_value: Some(101.0),
598 };
599
600 let json = serde_json::to_string(&result).expect("serialize");
601 assert!(json.contains("MR-Serialize"));
602 assert!(json.contains("100"));
603 assert!(json.contains("101"));
604 }
605
606 #[test]
607 fn test_demo_meta_with_defaults() {
608 let yaml = r#"
609id: "MIN-001"
610version: "0.1.0"
611demo_type: "test"
612"#;
613 let meta: DemoMeta = serde_yaml::from_str(yaml).expect("deserialize");
614 assert_eq!(meta.id, "MIN-001");
615 assert!(meta.description.is_empty());
616 assert!(meta.author.is_empty());
617 assert!(meta.created.is_empty());
618 }
619
620 #[test]
621 fn test_demo_meta_roundtrip() {
622 let meta = DemoMeta {
623 id: "ROUND-001".to_string(),
624 version: "1.2.3".to_string(),
625 demo_type: "orbit".to_string(),
626 description: "Round trip test".to_string(),
627 author: "Test Author".to_string(),
628 created: "2025-12-12".to_string(),
629 };
630
631 let yaml = serde_yaml::to_string(&meta).expect("serialize");
632 let restored: DemoMeta = serde_yaml::from_str(&yaml).expect("deserialize");
633
634 assert_eq!(meta.id, restored.id);
635 assert_eq!(meta.version, restored.version);
636 assert_eq!(meta.demo_type, restored.demo_type);
637 assert_eq!(meta.description, restored.description);
638 }
639
640 #[test]
641 fn test_criterion_result_deserialization() {
642 let json = r#"{
643 "id": "DES-001",
644 "passed": true,
645 "actual": 0.99,
646 "expected": 0.95,
647 "message": "Exceeded threshold",
648 "severity": "minor"
649 }"#;
650
651 let result: CriterionResult = serde_json::from_str(json).expect("deserialize");
652 assert_eq!(result.id, "DES-001");
653 assert!(result.passed);
654 assert_eq!(result.severity, Severity::Minor);
655 }
656
657 #[test]
658 fn test_metamorphic_relation_serialization() {
659 let mr = MetamorphicRelation {
660 id: "MR-Serial".to_string(),
661 description: "Test MR".to_string(),
662 source_transform: "identity".to_string(),
663 expected_relation: "equal".to_string(),
664 tolerance: 1e-8,
665 };
666
667 let yaml = serde_yaml::to_string(&mr).expect("serialize");
668 assert!(yaml.contains("MR-Serial"));
669 assert!(yaml.contains("identity"));
670 }
671}