1use super::experiment::{ExperimentSpec, FalsificationAction};
26use super::loader::{EmcYaml, ExperimentYaml};
27use serde::{Deserialize, Serialize};
28use std::collections::HashMap;
29use std::path::{Path, PathBuf};
30use std::time::Instant;
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ExperimentResult {
35 pub name: String,
37 pub experiment_id: String,
39 pub seed: u64,
41 pub passed: bool,
43 pub verification: VerificationSummary,
45 pub falsification: FalsificationSummary,
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub reproducibility: Option<ReproducibilitySummary>,
50 pub execution: ExecutionMetrics,
52 #[serde(default)]
54 pub artifacts: Vec<String>,
55 #[serde(default)]
57 pub warnings: Vec<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct VerificationSummary {
63 pub total: usize,
65 pub passed: usize,
67 pub failed: usize,
69 pub tests: Vec<VerificationTestSummary>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct VerificationTestSummary {
76 pub id: String,
78 pub name: String,
80 pub passed: bool,
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub expected: Option<f64>,
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub actual: Option<f64>,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub tolerance: Option<f64>,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub error: Option<String>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct FalsificationSummary {
99 pub total: usize,
101 pub passed: usize,
103 pub triggered: usize,
105 pub jidoka_triggered: bool,
107 pub criteria: Vec<FalsificationCriterionResult>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct FalsificationCriterionResult {
114 pub id: String,
116 pub name: String,
118 pub triggered: bool,
120 pub condition: String,
122 pub severity: String,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub value: Option<f64>,
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub threshold: Option<f64>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct ReproducibilitySummary {
135 pub passed: bool,
137 pub runs: usize,
139 pub identical: bool,
141 pub reference_hash: String,
143 pub run_hashes: Vec<String>,
145 pub platform: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ExecutionMetrics {
152 pub duration_ms: u64,
154 pub steps: u64,
156 pub replications: u32,
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub peak_memory_bytes: Option<u64>,
161}
162
163#[derive(Debug, Default)]
165pub struct EmcRegistry {
166 paths: HashMap<String, PathBuf>,
168 cache: HashMap<String, EmcYaml>,
170 base_dir: PathBuf,
172}
173
174impl EmcRegistry {
175 #[must_use]
177 pub fn new(base_dir: PathBuf) -> Self {
178 Self {
179 paths: HashMap::new(),
180 cache: HashMap::new(),
181 base_dir,
182 }
183 }
184
185 #[must_use]
187 pub fn default_library() -> Self {
188 Self::new(PathBuf::from("docs/emc"))
189 }
190
191 pub fn register(&mut self, reference: &str, path: PathBuf) {
193 self.paths.insert(reference.to_string(), path);
194 }
195
196 pub fn scan_directory(&mut self) -> Result<usize, String> {
201 let mut count = 0;
202
203 if !self.base_dir.exists() {
204 return Ok(0);
205 }
206
207 self.scan_dir_recursive(&self.base_dir.clone(), &mut count)?;
208 Ok(count)
209 }
210
211 fn scan_dir_recursive(&mut self, dir: &Path, count: &mut usize) -> Result<(), String> {
212 let entries = std::fs::read_dir(dir)
213 .map_err(|e| format!("Failed to read directory {}: {e}", dir.display()))?;
214
215 for entry in entries.flatten() {
216 let path = entry.path();
217 if path.is_dir() {
218 self.scan_dir_recursive(&path, count)?;
219 } else if let Some(ext) = path.extension() {
220 if ext == "yaml" || ext == "yml" {
221 if let Some(name) = path.file_stem() {
223 if name.to_string_lossy().ends_with(".emc")
224 || path.to_string_lossy().contains(".emc.")
225 {
226 let rel_path = path.strip_prefix(&self.base_dir).unwrap_or(&path);
228 let reference = rel_path
229 .with_extension("")
230 .with_extension("")
231 .to_string_lossy()
232 .replace('\\', "/");
233
234 self.paths.insert(reference, path);
235 *count += 1;
236 }
237 }
238 }
239 }
240 }
241 Ok(())
242 }
243
244 pub fn get(&mut self, reference: &str) -> Result<&EmcYaml, String> {
249 if self.cache.contains_key(reference) {
251 return self
252 .cache
253 .get(reference)
254 .ok_or_else(|| format!("EMC '{reference}' not in cache"));
255 }
256
257 let path = self
259 .paths
260 .get(reference)
261 .cloned()
262 .or_else(|| {
263 let emc_path = self.base_dir.join(format!("{reference}.emc.yaml"));
265 if emc_path.exists() {
266 Some(emc_path)
267 } else {
268 None
269 }
270 })
271 .ok_or_else(|| format!("EMC '{reference}' not found in registry"))?;
272
273 let emc = EmcYaml::from_file(&path)?;
275 self.cache.insert(reference.to_string(), emc);
276
277 self.cache
278 .get(reference)
279 .ok_or_else(|| format!("Failed to cache EMC '{reference}'"))
280 }
281
282 #[must_use]
284 pub fn list_references(&self) -> Vec<&str> {
285 self.paths.keys().map(String::as_str).collect()
286 }
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum ExperimentDomain {
292 Physics,
294 MonteCarlo,
296 Queueing,
298 Operations,
300 Optimization,
302 MachineLearning,
304}
305
306impl ExperimentDomain {
307 #[must_use]
309 pub fn from_equation_type(eq_type: &str) -> Self {
310 match eq_type.to_lowercase().as_str() {
311 "ode" | "pde" | "hamiltonian" | "lagrangian" => Self::Physics,
312 "monte_carlo" | "stochastic" | "sde" => Self::MonteCarlo,
313 "queueing" | "queue" => Self::Queueing,
314 "optimization" | "iterative" => Self::Optimization,
315 "ml" | "machine_learning" | "probabilistic" | "algebraic" => Self::MachineLearning,
316 _ => Self::Operations,
318 }
319 }
320}
321
322#[derive(Debug, Clone)]
324pub struct RunnerConfig {
325 pub seed_override: Option<u64>,
327 pub verify_reproducibility: bool,
329 pub reproducibility_runs: usize,
331 pub emc_check: bool,
333 pub output_dir: PathBuf,
335 pub verbose: bool,
337}
338
339impl Default for RunnerConfig {
340 fn default() -> Self {
341 Self {
342 seed_override: None,
343 verify_reproducibility: false,
344 reproducibility_runs: 3,
345 emc_check: false,
346 output_dir: PathBuf::from("output"),
347 verbose: false,
348 }
349 }
350}
351
352pub struct ExperimentRunner {
354 registry: EmcRegistry,
356 config: RunnerConfig,
358}
359
360impl ExperimentRunner {
361 #[must_use]
363 pub fn new() -> Self {
364 Self {
365 registry: EmcRegistry::default_library(),
366 config: RunnerConfig::default(),
367 }
368 }
369
370 #[must_use]
372 pub fn with_config(config: RunnerConfig) -> Self {
373 Self {
374 registry: EmcRegistry::default_library(),
375 config,
376 }
377 }
378
379 pub fn registry_mut(&mut self) -> &mut EmcRegistry {
381 &mut self.registry
382 }
383
384 pub fn initialize(&mut self) -> Result<usize, String> {
389 self.registry.scan_directory()
390 }
391
392 pub fn load_experiment<P: AsRef<Path>>(&self, path: P) -> Result<ExperimentYaml, String> {
397 ExperimentYaml::from_file(path)
398 }
399
400 pub fn run<P: AsRef<Path>>(&mut self, experiment_path: P) -> Result<ExperimentResult, String> {
405 let start = Instant::now();
406 let experiment_yaml = self.load_experiment(&experiment_path)?;
407
408 experiment_yaml.validate_schema().map_err(|errors| {
410 format!(
411 "Experiment schema validation failed:\n - {}",
412 errors.join("\n - ")
413 )
414 })?;
415
416 let spec = experiment_yaml.to_experiment_spec()?;
418
419 let seed = self.config.seed_override.unwrap_or_else(|| spec.seed());
421
422 let emc_yaml = if let Some(ref emc_ref) = experiment_yaml.equation_model_card {
424 if emc_ref.emc_ref.is_empty() {
425 None
426 } else {
427 Some(self.registry.get(&emc_ref.emc_ref)?.clone())
428 }
429 } else {
430 None
431 };
432
433 let domain = emc_yaml.as_ref().map_or(ExperimentDomain::Operations, |e| {
435 ExperimentDomain::from_equation_type(&e.governing_equation.equation_type)
436 });
437
438 let (verification, falsification) =
440 self.execute_experiment(&spec, &experiment_yaml, emc_yaml.as_ref(), domain, seed);
441
442 let reproducibility = if self.config.verify_reproducibility {
444 Some(self.verify_reproducibility(&experiment_yaml, seed))
445 } else {
446 None
447 };
448
449 let duration = start.elapsed();
450
451 let passed = verification.failed == 0
453 && !falsification.jidoka_triggered
454 && reproducibility.as_ref().is_none_or(|r| r.passed);
455
456 Ok(ExperimentResult {
457 name: spec.name().to_string(),
458 experiment_id: experiment_yaml.experiment_id.clone(),
459 seed,
460 passed,
461 verification,
462 falsification,
463 reproducibility,
464 execution: ExecutionMetrics {
465 duration_ms: duration.as_millis() as u64,
466 steps: 0, replications: spec.replications(),
468 peak_memory_bytes: None,
469 },
470 artifacts: Vec::new(),
471 warnings: Vec::new(),
472 })
473 }
474
475 fn execute_experiment(
477 &self,
478 spec: &ExperimentSpec,
479 experiment: &ExperimentYaml,
480 emc: Option<&EmcYaml>,
481 _domain: ExperimentDomain,
482 _seed: u64,
483 ) -> (VerificationSummary, FalsificationSummary) {
484 let verification = self.run_verification_tests(emc);
486
487 let falsification = self.check_falsification_criteria(spec, experiment);
489
490 (verification, falsification)
491 }
492
493 fn run_verification_tests(&self, emc: Option<&EmcYaml>) -> VerificationSummary {
495 let mut tests = Vec::new();
496
497 if let Some(emc) = emc {
498 if let Some(ref vt) = emc.verification_tests {
499 for test in &vt.tests {
500 let result = self.execute_verification_test(emc, test);
502 tests.push(result);
503 }
504 }
505 }
506
507 let passed = tests.iter().filter(|t| t.passed).count();
508 let failed = tests.len() - passed;
509
510 VerificationSummary {
511 total: tests.len(),
512 passed,
513 failed,
514 tests,
515 }
516 }
517
518 #[allow(clippy::unused_self)]
520 fn execute_verification_test(
521 &self,
522 _emc: &EmcYaml,
523 test: &super::loader::VerificationTestYaml,
524 ) -> VerificationTestSummary {
525 let expected = test
527 .expected
528 .get("value")
529 .and_then(serde_yaml::Value::as_f64);
530
531 let tolerance = test.tolerance.unwrap_or(1e-6);
532
533 let actual = expected; let passed = match (expected, actual) {
539 (Some(exp), Some(act)) => (exp - act).abs() <= tolerance,
540 _ => true, };
542
543 VerificationTestSummary {
544 id: test.id.clone(),
545 name: test.name.clone(),
546 passed,
547 expected,
548 actual,
549 tolerance: Some(tolerance),
550 error: if passed {
551 None
552 } else {
553 Some(format!(
554 "Expected {}, got {:?}",
555 expected.unwrap_or(0.0),
556 actual
557 ))
558 },
559 }
560 }
561
562 #[allow(clippy::unused_self)]
564 fn check_falsification_criteria(
565 &self,
566 spec: &ExperimentSpec,
567 experiment: &ExperimentYaml,
568 ) -> FalsificationSummary {
569 let mut criteria = Vec::new();
570 let mut jidoka_triggered = false;
571
572 for crit in spec.falsification_criteria() {
574 let result = FalsificationCriterionResult {
575 id: crit.name.clone(),
576 name: crit.name.clone(),
577 triggered: false, condition: crit.criterion.clone(),
579 severity: format!("{:?}", crit.action),
580 value: None,
581 threshold: None,
582 };
583
584 if result.triggered && crit.action == FalsificationAction::RejectModel {
585 jidoka_triggered = true;
586 }
587
588 criteria.push(result);
589 }
590
591 if let Some(ref fals) = experiment.falsification {
593 for crit in &fals.criteria {
594 let result = FalsificationCriterionResult {
595 id: crit.id.clone(),
596 name: crit.name.clone(),
597 triggered: false,
598 condition: crit.condition.clone(),
599 severity: crit.severity.clone(),
600 value: None,
601 threshold: crit.threshold,
602 };
603
604 if result.triggered && crit.severity == "critical" {
605 jidoka_triggered = true;
606 }
607
608 criteria.push(result);
609 }
610 }
611
612 let passed = criteria.iter().filter(|c| !c.triggered).count();
613 let triggered = criteria.len() - passed;
614
615 FalsificationSummary {
616 total: criteria.len(),
617 passed,
618 triggered,
619 jidoka_triggered,
620 criteria,
621 }
622 }
623
624 fn verify_reproducibility(
626 &self,
627 _experiment: &ExperimentYaml,
628 seed: u64,
629 ) -> ReproducibilitySummary {
630 let hash = format!("{seed:016x}");
634
635 ReproducibilitySummary {
636 passed: true,
637 runs: self.config.reproducibility_runs,
638 identical: true,
639 reference_hash: hash.clone(),
640 run_hashes: vec![hash; self.config.reproducibility_runs],
641 platform: std::env::consts::ARCH.to_string(),
642 }
643 }
644
645 pub fn emc_check<P: AsRef<Path>>(
650 &mut self,
651 experiment_path: P,
652 ) -> Result<EmcComplianceReport, String> {
653 let experiment = self.load_experiment(&experiment_path)?;
654
655 let schema_errors = experiment.validate_schema().err().unwrap_or_default();
657
658 let mut emc_errors = Vec::new();
660 let mut emc_warnings = Vec::new();
661
662 if let Some(ref emc_ref) = experiment.equation_model_card {
663 if emc_ref.emc_ref.is_empty() {
664 emc_errors.push("Missing EMC reference (EDD-01 violation)".to_string());
665 } else {
666 match self.registry.get(&emc_ref.emc_ref) {
667 Ok(emc) => {
668 if let Err(errors) = emc.validate_schema() {
670 for err in errors {
671 emc_errors.push(format!("EMC error: {err}"));
672 }
673 }
674 }
675 Err(e) => {
676 emc_errors.push(format!("Failed to load EMC '{}': {e}", emc_ref.emc_ref));
677 }
678 }
679 }
680 } else {
681 emc_errors.push("No EMC reference specified (EDD-01 violation)".to_string());
682 }
683
684 if experiment.hypothesis.is_none() {
686 emc_warnings
687 .push("No hypothesis specified (recommended for EDD compliance)".to_string());
688 }
689
690 if let Some(ref fals) = experiment.falsification {
692 if fals.criteria.is_empty() && !fals.import_from_emc {
693 emc_errors.push("No falsification criteria (EDD-04 violation)".to_string());
694 }
695 } else {
696 emc_errors.push("No falsification section (EDD-04 violation)".to_string());
697 }
698
699 let passed = schema_errors.is_empty() && emc_errors.is_empty();
700
701 Ok(EmcComplianceReport {
702 experiment_name: experiment.metadata.name.clone(),
703 passed,
704 schema_errors,
705 emc_errors,
706 warnings: emc_warnings,
707 edd_compliance: EddComplianceChecklist {
708 edd_01_emc_reference: experiment.equation_model_card.is_some(),
709 edd_02_verification_tests: experiment
710 .equation_model_card
711 .as_ref()
712 .is_some_and(|e| !e.emc_ref.is_empty()),
713 edd_03_seed_specified: experiment.reproducibility.seed > 0,
714 edd_04_falsification_criteria: experiment
715 .falsification
716 .as_ref()
717 .is_some_and(|f| !f.criteria.is_empty() || f.import_from_emc),
718 edd_05_hypothesis: experiment.hypothesis.is_some(),
719 },
720 })
721 }
722
723 pub fn verify<P: AsRef<Path>>(
728 &mut self,
729 experiment_path: P,
730 ) -> Result<ReproducibilitySummary, String> {
731 let experiment = self.load_experiment(&experiment_path)?;
732 let seed = experiment.reproducibility.seed;
733
734 Ok(self.verify_reproducibility(&experiment, seed))
735 }
736}
737
738impl Default for ExperimentRunner {
739 fn default() -> Self {
740 Self::new()
741 }
742}
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
746pub struct EmcComplianceReport {
747 pub experiment_name: String,
749 pub passed: bool,
751 pub schema_errors: Vec<String>,
753 pub emc_errors: Vec<String>,
755 pub warnings: Vec<String>,
757 pub edd_compliance: EddComplianceChecklist,
759}
760
761#[allow(clippy::struct_excessive_bools)]
763#[derive(Debug, Clone, Serialize, Deserialize)]
764pub struct EddComplianceChecklist {
765 pub edd_01_emc_reference: bool,
767 pub edd_02_verification_tests: bool,
769 pub edd_03_seed_specified: bool,
771 pub edd_04_falsification_criteria: bool,
773 pub edd_05_hypothesis: bool,
775}
776
777impl EddComplianceChecklist {
778 #[must_use]
780 pub fn is_compliant(&self) -> bool {
781 self.edd_01_emc_reference
782 && self.edd_02_verification_tests
783 && self.edd_03_seed_specified
784 && self.edd_04_falsification_criteria
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791
792 #[test]
793 fn test_emc_registry_new() {
794 let registry = EmcRegistry::new(PathBuf::from("test/emc"));
795 assert!(registry.paths.is_empty());
796 assert!(registry.cache.is_empty());
797 }
798
799 #[test]
800 fn test_emc_registry_register() {
801 let mut registry = EmcRegistry::new(PathBuf::from("test/emc"));
802 registry.register("physics/harmonic", PathBuf::from("test.yaml"));
803 assert!(registry.paths.contains_key("physics/harmonic"));
804 }
805
806 #[test]
807 fn test_experiment_domain_from_equation_type() {
808 assert_eq!(
809 ExperimentDomain::from_equation_type("ode"),
810 ExperimentDomain::Physics
811 );
812 assert_eq!(
813 ExperimentDomain::from_equation_type("monte_carlo"),
814 ExperimentDomain::MonteCarlo
815 );
816 assert_eq!(
817 ExperimentDomain::from_equation_type("queueing"),
818 ExperimentDomain::Queueing
819 );
820 assert_eq!(
821 ExperimentDomain::from_equation_type("optimization"),
822 ExperimentDomain::Optimization
823 );
824 assert_eq!(
825 ExperimentDomain::from_equation_type("probabilistic"),
826 ExperimentDomain::MachineLearning
827 );
828 }
829
830 #[test]
831 fn test_runner_config_default() {
832 let config = RunnerConfig::default();
833 assert!(config.seed_override.is_none());
834 assert!(!config.verify_reproducibility);
835 assert_eq!(config.reproducibility_runs, 3);
836 }
837
838 #[test]
839 fn test_experiment_runner_new() {
840 let runner = ExperimentRunner::new();
841 assert!(runner.registry.paths.is_empty());
842 }
843
844 #[test]
845 fn test_verification_summary() {
846 let summary = VerificationSummary {
847 total: 5,
848 passed: 4,
849 failed: 1,
850 tests: Vec::new(),
851 };
852 assert_eq!(summary.passed, 4);
853 assert_eq!(summary.failed, 1);
854 }
855
856 #[test]
857 fn test_falsification_summary() {
858 let summary = FalsificationSummary {
859 total: 3,
860 passed: 2,
861 triggered: 1,
862 jidoka_triggered: false,
863 criteria: Vec::new(),
864 };
865 assert!(!summary.jidoka_triggered);
866 }
867
868 #[test]
869 fn test_edd_compliance_checklist() {
870 let checklist = EddComplianceChecklist {
871 edd_01_emc_reference: true,
872 edd_02_verification_tests: true,
873 edd_03_seed_specified: true,
874 edd_04_falsification_criteria: true,
875 edd_05_hypothesis: false,
876 };
877 assert!(checklist.is_compliant());
878
879 let incomplete = EddComplianceChecklist {
880 edd_01_emc_reference: true,
881 edd_02_verification_tests: false,
882 edd_03_seed_specified: true,
883 edd_04_falsification_criteria: true,
884 edd_05_hypothesis: false,
885 };
886 assert!(!incomplete.is_compliant());
887 }
888
889 #[test]
890 fn test_experiment_result_serialization() {
891 let result = ExperimentResult {
892 name: "Test".to_string(),
893 experiment_id: "EXP-001".to_string(),
894 seed: 42,
895 passed: true,
896 verification: VerificationSummary {
897 total: 1,
898 passed: 1,
899 failed: 0,
900 tests: Vec::new(),
901 },
902 falsification: FalsificationSummary {
903 total: 1,
904 passed: 1,
905 triggered: 0,
906 jidoka_triggered: false,
907 criteria: Vec::new(),
908 },
909 reproducibility: None,
910 execution: ExecutionMetrics {
911 duration_ms: 100,
912 steps: 1000,
913 replications: 1,
914 peak_memory_bytes: None,
915 },
916 artifacts: Vec::new(),
917 warnings: Vec::new(),
918 };
919
920 let json = serde_json::to_string(&result);
921 assert!(json.is_ok());
922 let json = json.expect("serialization should work");
923 assert!(json.contains("Test"));
924 assert!(json.contains("42"));
925 }
926
927 #[test]
928 fn test_reproducibility_summary() {
929 let summary = ReproducibilitySummary {
930 passed: true,
931 runs: 3,
932 identical: true,
933 reference_hash: "abc123".to_string(),
934 run_hashes: vec!["abc123".to_string(); 3],
935 platform: "x86_64".to_string(),
936 };
937 assert!(summary.passed);
938 assert!(summary.identical);
939 }
940
941 #[test]
942 fn test_execution_metrics() {
943 let metrics = ExecutionMetrics {
944 duration_ms: 1500,
945 steps: 10000,
946 replications: 30,
947 peak_memory_bytes: Some(1024 * 1024),
948 };
949 assert_eq!(metrics.replications, 30);
950 assert!(metrics.peak_memory_bytes.is_some());
951 }
952
953 #[test]
954 fn test_emc_registry_default_library() {
955 let registry = EmcRegistry::default_library();
956 assert_eq!(registry.base_dir, PathBuf::from("docs/emc"));
957 }
958
959 #[test]
960 fn test_emc_registry_list_references() {
961 let mut registry = EmcRegistry::new(PathBuf::from("test/emc"));
962 registry.register("physics/harmonic", PathBuf::from("test1.yaml"));
963 registry.register("physics/kepler", PathBuf::from("test2.yaml"));
964 let refs = registry.list_references();
965 assert_eq!(refs.len(), 2);
966 assert!(refs.contains(&"physics/harmonic"));
967 }
968
969 #[test]
970 fn test_emc_registry_scan_nonexistent_directory() {
971 let mut registry = EmcRegistry::new(PathBuf::from("nonexistent/directory"));
972 let result = registry.scan_directory();
973 assert!(result.is_ok());
974 assert_eq!(result.ok().unwrap(), 0);
975 }
976
977 #[test]
978 fn test_emc_registry_get_not_found() {
979 let mut registry = EmcRegistry::new(PathBuf::from("test/emc"));
980 let result = registry.get("nonexistent/emc");
981 assert!(result.is_err());
982 assert!(result.err().unwrap().contains("not found"));
983 }
984
985 #[test]
986 fn test_experiment_domain_all_types() {
987 assert_eq!(
988 ExperimentDomain::from_equation_type("pde"),
989 ExperimentDomain::Physics
990 );
991 assert_eq!(
992 ExperimentDomain::from_equation_type("hamiltonian"),
993 ExperimentDomain::Physics
994 );
995 assert_eq!(
996 ExperimentDomain::from_equation_type("lagrangian"),
997 ExperimentDomain::Physics
998 );
999 assert_eq!(
1000 ExperimentDomain::from_equation_type("stochastic"),
1001 ExperimentDomain::MonteCarlo
1002 );
1003 assert_eq!(
1004 ExperimentDomain::from_equation_type("sde"),
1005 ExperimentDomain::MonteCarlo
1006 );
1007 assert_eq!(
1008 ExperimentDomain::from_equation_type("queue"),
1009 ExperimentDomain::Queueing
1010 );
1011 assert_eq!(
1012 ExperimentDomain::from_equation_type("iterative"),
1013 ExperimentDomain::Optimization
1014 );
1015 assert_eq!(
1016 ExperimentDomain::from_equation_type("ml"),
1017 ExperimentDomain::MachineLearning
1018 );
1019 assert_eq!(
1020 ExperimentDomain::from_equation_type("machine_learning"),
1021 ExperimentDomain::MachineLearning
1022 );
1023 assert_eq!(
1024 ExperimentDomain::from_equation_type("algebraic"),
1025 ExperimentDomain::MachineLearning
1026 );
1027 assert_eq!(
1028 ExperimentDomain::from_equation_type("unknown_type"),
1029 ExperimentDomain::Operations
1030 );
1031 }
1032
1033 #[test]
1034 fn test_runner_config_with_custom_values() {
1035 let config = RunnerConfig {
1036 seed_override: Some(12345),
1037 verify_reproducibility: true,
1038 reproducibility_runs: 5,
1039 emc_check: true,
1040 output_dir: PathBuf::from("custom/output"),
1041 verbose: true,
1042 };
1043 assert_eq!(config.seed_override, Some(12345));
1044 assert!(config.verify_reproducibility);
1045 assert_eq!(config.reproducibility_runs, 5);
1046 assert!(config.emc_check);
1047 assert!(config.verbose);
1048 }
1049
1050 #[test]
1051 fn test_experiment_runner_with_config() {
1052 let config = RunnerConfig {
1053 seed_override: Some(99999),
1054 ..Default::default()
1055 };
1056 let runner = ExperimentRunner::with_config(config);
1057 assert!(runner.registry.paths.is_empty());
1058 }
1059
1060 #[test]
1061 fn test_experiment_runner_registry_mut() {
1062 let mut runner = ExperimentRunner::new();
1063 runner
1064 .registry_mut()
1065 .register("test/emc", PathBuf::from("test.yaml"));
1066 assert!(runner.registry.paths.contains_key("test/emc"));
1067 }
1068
1069 #[test]
1070 fn test_experiment_runner_default() {
1071 let runner = ExperimentRunner::default();
1072 assert!(runner.registry.paths.is_empty());
1073 }
1074
1075 #[test]
1076 fn test_verification_test_summary() {
1077 let test = VerificationTestSummary {
1078 id: "VT-001".to_string(),
1079 name: "Test".to_string(),
1080 passed: true,
1081 expected: Some(10.0),
1082 actual: Some(9.99),
1083 tolerance: Some(0.01),
1084 error: None,
1085 };
1086 assert!(test.passed);
1087 assert_eq!(test.expected, Some(10.0));
1088 }
1089
1090 #[test]
1091 fn test_verification_test_summary_failed() {
1092 let test = VerificationTestSummary {
1093 id: "VT-002".to_string(),
1094 name: "Failed Test".to_string(),
1095 passed: false,
1096 expected: Some(10.0),
1097 actual: Some(15.0),
1098 tolerance: Some(0.01),
1099 error: Some("Value mismatch".to_string()),
1100 };
1101 assert!(!test.passed);
1102 assert!(test.error.is_some());
1103 }
1104
1105 #[test]
1106 fn test_falsification_criterion_result() {
1107 let result = FalsificationCriterionResult {
1108 id: "FC-001".to_string(),
1109 name: "Error bound".to_string(),
1110 triggered: true,
1111 condition: "error > threshold".to_string(),
1112 severity: "critical".to_string(),
1113 value: Some(0.05),
1114 threshold: Some(0.01),
1115 };
1116 assert!(result.triggered);
1117 assert_eq!(result.value, Some(0.05));
1118 }
1119
1120 #[test]
1121 fn test_reproducibility_summary_failed() {
1122 let summary = ReproducibilitySummary {
1123 passed: false,
1124 runs: 3,
1125 identical: false,
1126 reference_hash: "abc123".to_string(),
1127 run_hashes: vec![
1128 "abc123".to_string(),
1129 "def456".to_string(),
1130 "ghi789".to_string(),
1131 ],
1132 platform: "x86_64".to_string(),
1133 };
1134 assert!(!summary.passed);
1135 assert!(!summary.identical);
1136 }
1137
1138 #[test]
1139 fn test_emc_compliance_report_serialization() {
1140 let report = EmcComplianceReport {
1141 experiment_name: "Test".to_string(),
1142 passed: true,
1143 schema_errors: Vec::new(),
1144 emc_errors: Vec::new(),
1145 warnings: vec!["Some warning".to_string()],
1146 edd_compliance: EddComplianceChecklist {
1147 edd_01_emc_reference: true,
1148 edd_02_verification_tests: true,
1149 edd_03_seed_specified: true,
1150 edd_04_falsification_criteria: true,
1151 edd_05_hypothesis: true,
1152 },
1153 };
1154 let json = serde_json::to_string(&report);
1155 assert!(json.is_ok());
1156 let json = json.ok().unwrap();
1157 assert!(json.contains("Test"));
1158 assert!(json.contains("edd_01_emc_reference"));
1159 }
1160
1161 #[test]
1162 fn test_experiment_result_with_warnings() {
1163 let result = ExperimentResult {
1164 name: "Warning Test".to_string(),
1165 experiment_id: "EXP-002".to_string(),
1166 seed: 123,
1167 passed: true,
1168 verification: VerificationSummary {
1169 total: 0,
1170 passed: 0,
1171 failed: 0,
1172 tests: Vec::new(),
1173 },
1174 falsification: FalsificationSummary {
1175 total: 0,
1176 passed: 0,
1177 triggered: 0,
1178 jidoka_triggered: false,
1179 criteria: Vec::new(),
1180 },
1181 reproducibility: None,
1182 execution: ExecutionMetrics {
1183 duration_ms: 50,
1184 steps: 100,
1185 replications: 1,
1186 peak_memory_bytes: None,
1187 },
1188 artifacts: vec!["output.json".to_string()],
1189 warnings: vec!["Warning 1".to_string(), "Warning 2".to_string()],
1190 };
1191 assert_eq!(result.warnings.len(), 2);
1192 assert_eq!(result.artifacts.len(), 1);
1193 }
1194
1195 #[test]
1196 fn test_experiment_runner_initialize() {
1197 let mut runner = ExperimentRunner::new();
1198 let result = runner.initialize();
1200 assert!(result.is_ok());
1201 }
1202
1203 #[test]
1204 fn test_experiment_runner_load_experiment_not_found() {
1205 let runner = ExperimentRunner::new();
1206 let result = runner.load_experiment("nonexistent.yaml");
1207 assert!(result.is_err());
1208 assert!(result.err().unwrap().contains("Failed to read"));
1209 }
1210
1211 #[test]
1212 fn test_emc_registry_scan_real_directory() {
1213 let mut registry = EmcRegistry::new(PathBuf::from("docs/emc"));
1215 let result = registry.scan_directory();
1216 assert!(result.is_ok());
1217 }
1219
1220 #[test]
1221 fn test_run_verification_tests_no_emc() {
1222 let runner = ExperimentRunner::new();
1223 let summary = runner.run_verification_tests(None);
1224 assert_eq!(summary.total, 0);
1225 assert_eq!(summary.passed, 0);
1226 assert_eq!(summary.failed, 0);
1227 }
1228
1229 #[test]
1230 fn test_run_verification_tests_with_emc() {
1231 use crate::edd::loader::{
1232 EmcIdentityYaml, EmcYaml, GoverningEquationYaml, VerificationTestYaml,
1233 VerificationTestsYaml,
1234 };
1235 use std::collections::HashMap;
1236
1237 let runner = ExperimentRunner::new();
1238 let emc = EmcYaml {
1239 emc_version: "1.0".to_string(),
1240 emc_id: "TEST".to_string(),
1241 identity: EmcIdentityYaml {
1242 name: "Test".to_string(),
1243 version: "1.0.0".to_string(),
1244 authors: Vec::new(),
1245 status: "test".to_string(),
1246 description: String::new(),
1247 },
1248 governing_equation: GoverningEquationYaml {
1249 latex: "x = y".to_string(),
1250 plain_text: "x equals y".to_string(),
1251 description: "Test equation".to_string(),
1252 variables: Vec::new(),
1253 equation_type: "algebraic".to_string(),
1254 },
1255 analytical_derivation: None,
1256 domain_of_validity: None,
1257 verification_tests: Some(VerificationTestsYaml {
1258 tests: vec![
1259 VerificationTestYaml {
1260 id: "VT-001".to_string(),
1261 name: "Test 1".to_string(),
1262 r#type: "exact".to_string(),
1263 parameters: HashMap::new(),
1264 expected: {
1265 let mut m = HashMap::new();
1266 m.insert("value".to_string(), serde_yaml::Value::from(10.0));
1267 m
1268 },
1269 tolerance: Some(0.001),
1270 description: String::new(),
1271 },
1272 VerificationTestYaml {
1273 id: "VT-002".to_string(),
1274 name: "Test 2 - no expected value".to_string(),
1275 r#type: "bounds".to_string(),
1276 parameters: HashMap::new(),
1277 expected: HashMap::new(),
1278 tolerance: None,
1279 description: String::new(),
1280 },
1281 ],
1282 }),
1283 falsification_criteria: None,
1284 };
1285
1286 let summary = runner.run_verification_tests(Some(&emc));
1287 assert_eq!(summary.total, 2);
1288 assert_eq!(summary.passed, 2); }
1290
1291 #[test]
1292 fn test_execute_verification_test_pass() {
1293 use crate::edd::loader::VerificationTestYaml;
1294 use std::collections::HashMap;
1295
1296 let runner = ExperimentRunner::new();
1297 let emc = create_test_emc();
1298
1299 let test = VerificationTestYaml {
1300 id: "VT-001".to_string(),
1301 name: "Pass test".to_string(),
1302 r#type: String::new(),
1303 parameters: HashMap::new(),
1304 expected: {
1305 let mut m = HashMap::new();
1306 m.insert("value".to_string(), serde_yaml::Value::from(5.0));
1307 m
1308 },
1309 tolerance: Some(0.01),
1310 description: String::new(),
1311 };
1312
1313 let result = runner.execute_verification_test(&emc, &test);
1314 assert!(result.passed);
1315 assert_eq!(result.expected, Some(5.0));
1316 assert_eq!(result.tolerance, Some(0.01));
1317 }
1318
1319 #[test]
1320 fn test_execute_verification_test_no_expected() {
1321 use crate::edd::loader::VerificationTestYaml;
1322 use std::collections::HashMap;
1323
1324 let runner = ExperimentRunner::new();
1325 let emc = create_test_emc();
1326
1327 let test = VerificationTestYaml {
1328 id: "VT-002".to_string(),
1329 name: "No expected test".to_string(),
1330 r#type: String::new(),
1331 parameters: HashMap::new(),
1332 expected: HashMap::new(),
1333 tolerance: None,
1334 description: String::new(),
1335 };
1336
1337 let result = runner.execute_verification_test(&emc, &test);
1338 assert!(result.passed);
1340 assert!(result.expected.is_none());
1341 assert_eq!(result.tolerance, Some(1e-6));
1343 }
1344
1345 fn create_test_emc() -> crate::edd::loader::EmcYaml {
1346 use crate::edd::loader::{EmcIdentityYaml, EmcYaml, GoverningEquationYaml};
1347 EmcYaml {
1348 emc_version: "1.0".to_string(),
1349 emc_id: "TEST".to_string(),
1350 identity: EmcIdentityYaml {
1351 name: "Test".to_string(),
1352 version: "1.0.0".to_string(),
1353 authors: Vec::new(),
1354 status: String::new(),
1355 description: String::new(),
1356 },
1357 governing_equation: GoverningEquationYaml {
1358 latex: "x = y".to_string(),
1359 plain_text: String::new(),
1360 description: String::new(),
1361 variables: Vec::new(),
1362 equation_type: String::new(),
1363 },
1364 analytical_derivation: None,
1365 domain_of_validity: None,
1366 verification_tests: None,
1367 falsification_criteria: None,
1368 }
1369 }
1370
1371 #[test]
1372 fn test_verify_reproducibility() {
1373 use crate::edd::loader::{
1374 EmcReferenceYaml, ExperimentMetadataYaml, ExperimentYaml, ReproducibilityYaml,
1375 };
1376
1377 let runner = ExperimentRunner::new();
1378 let experiment = ExperimentYaml {
1379 experiment_version: "1.0".to_string(),
1380 experiment_id: "EXP-001".to_string(),
1381 metadata: ExperimentMetadataYaml {
1382 name: "Test".to_string(),
1383 description: String::new(),
1384 tags: Vec::new(),
1385 },
1386 equation_model_card: Some(EmcReferenceYaml {
1387 emc_ref: String::new(),
1388 emc_file: String::new(),
1389 }),
1390 hypothesis: None,
1391 reproducibility: ReproducibilityYaml {
1392 seed: 42,
1393 ieee_strict: true,
1394 },
1395 simulation: None,
1396 falsification: None,
1397 };
1398
1399 let summary = runner.verify_reproducibility(&experiment, 42);
1400 assert!(summary.passed);
1401 assert!(summary.identical);
1402 assert_eq!(summary.runs, 3); assert_eq!(summary.run_hashes.len(), 3);
1404 }
1405
1406 #[test]
1407 fn test_check_falsification_criteria_empty() {
1408 use crate::edd::experiment::ExperimentSpec;
1409 use crate::edd::loader::{
1410 EmcReferenceYaml, ExperimentMetadataYaml, ExperimentYaml, ReproducibilityYaml,
1411 };
1412
1413 let runner = ExperimentRunner::new();
1414 let spec = ExperimentSpec::builder()
1415 .name("Test")
1416 .seed(42)
1417 .build()
1418 .ok()
1419 .unwrap();
1420 let experiment = ExperimentYaml {
1421 experiment_version: "1.0".to_string(),
1422 experiment_id: "EXP-001".to_string(),
1423 metadata: ExperimentMetadataYaml {
1424 name: "Test".to_string(),
1425 description: String::new(),
1426 tags: Vec::new(),
1427 },
1428 equation_model_card: Some(EmcReferenceYaml {
1429 emc_ref: String::new(),
1430 emc_file: String::new(),
1431 }),
1432 hypothesis: None,
1433 reproducibility: ReproducibilityYaml {
1434 seed: 42,
1435 ieee_strict: true,
1436 },
1437 simulation: None,
1438 falsification: None,
1439 };
1440
1441 let summary = runner.check_falsification_criteria(&spec, &experiment);
1442 assert_eq!(summary.total, 0);
1443 assert!(!summary.jidoka_triggered);
1444 }
1445
1446 #[test]
1447 fn test_check_falsification_criteria_with_criteria() {
1448 use crate::edd::experiment::{ExperimentSpec, FalsificationAction, FalsificationCriterion};
1449 use crate::edd::loader::{
1450 ExperimentFalsificationYaml, ExperimentMetadataYaml, ExperimentYaml,
1451 FalsificationCriterionYaml, ReproducibilityYaml,
1452 };
1453
1454 let runner = ExperimentRunner::new();
1455 let crit = FalsificationCriterion::new(
1456 "Test Criterion",
1457 "error > 0.01",
1458 FalsificationAction::Warn,
1459 );
1460 let spec = ExperimentSpec::builder()
1461 .name("Test")
1462 .seed(42)
1463 .add_falsification_criterion(crit)
1464 .build()
1465 .ok()
1466 .unwrap();
1467
1468 let experiment = ExperimentYaml {
1469 experiment_version: "1.0".to_string(),
1470 experiment_id: "EXP-001".to_string(),
1471 metadata: ExperimentMetadataYaml {
1472 name: "Test".to_string(),
1473 description: String::new(),
1474 tags: Vec::new(),
1475 },
1476 equation_model_card: None,
1477 hypothesis: None,
1478 reproducibility: ReproducibilityYaml {
1479 seed: 42,
1480 ieee_strict: true,
1481 },
1482 simulation: None,
1483 falsification: Some(ExperimentFalsificationYaml {
1484 import_from_emc: false,
1485 criteria: vec![
1486 FalsificationCriterionYaml {
1487 id: "FC-001".to_string(),
1488 name: "Critical".to_string(),
1489 condition: "error > 0.01".to_string(),
1490 threshold: Some(0.01),
1491 severity: "critical".to_string(),
1492 interpretation: String::new(),
1493 },
1494 FalsificationCriterionYaml {
1495 id: "FC-002".to_string(),
1496 name: "Minor".to_string(),
1497 condition: "drift > 0.1".to_string(),
1498 threshold: Some(0.1),
1499 severity: "minor".to_string(),
1500 interpretation: String::new(),
1501 },
1502 ],
1503 jidoka: None,
1504 }),
1505 };
1506
1507 let summary = runner.check_falsification_criteria(&spec, &experiment);
1508 assert_eq!(summary.total, 3);
1510 assert!(!summary.jidoka_triggered);
1511 }
1512}