Skip to main content

simular/edd/
validation.rs

1//! EDD Validator - Compliance checking for Equation-Driven Development.
2//!
3//! The validator ensures all simulations and experiments comply with the
4//! four pillars of EDD:
5//!
6//! 1. **Prove It**: Simulation must have a governing equation (EMC)
7//! 2. **Fail It**: Simulation must have failing tests derived from equations
8//! 3. **Seed It**: Experiment must have explicit seed
9//! 4. **Falsify It**: Experiment must have falsification criteria
10//!
11//! # EDD Violation Codes
12//!
13//! - EDD-01: Missing Equation Model Card
14//! - EDD-02: Missing governing equation
15//! - EDD-03: Missing citation
16//! - EDD-04: Missing verification tests
17//! - EDD-05: Missing explicit seed
18//! - EDD-06: Missing falsification criteria
19//! - EDD-07: Verification test failed
20//! - EDD-08: Conservation laws violated (runtime monitoring)
21//! - EDD-09: Cross-platform reproducibility failed
22//! - EDD-10: Implementation without failing test (TDD violation)
23
24use super::experiment::ExperimentSpec;
25use super::model_card::EquationModelCard;
26
27/// EDD violation codes and messages.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct EddViolation {
30    /// Violation code (e.g., "EDD-01")
31    pub code: String,
32    /// Human-readable message
33    pub message: String,
34    /// Severity level
35    pub severity: ViolationSeverity,
36    /// Context or additional details
37    pub context: Option<String>,
38}
39
40/// Severity levels for violations.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
42pub enum ViolationSeverity {
43    /// Informational - suggestion for improvement
44    Info,
45    /// Warning - should be addressed
46    Warning,
47    /// Error - must be fixed
48    Error,
49    /// Critical - halts execution (Jidoka)
50    Critical,
51}
52
53impl EddViolation {
54    /// Create a new violation.
55    #[must_use]
56    pub fn new(code: &str, message: &str, severity: ViolationSeverity) -> Self {
57        Self {
58            code: code.to_string(),
59            message: message.to_string(),
60            severity,
61            context: None,
62        }
63    }
64
65    /// Add context to the violation.
66    #[must_use]
67    pub fn with_context(mut self, context: &str) -> Self {
68        self.context = Some(context.to_string());
69        self
70    }
71}
72
73/// Result type for EDD validation.
74pub type EddResult<T> = Result<T, EddViolation>;
75
76/// EDD Validator for checking compliance with the four pillars.
77#[derive(Debug, Default)]
78pub struct EddValidator {
79    /// Collected violations
80    violations: Vec<EddViolation>,
81    /// Whether to halt on first critical error (Jidoka mode)
82    #[allow(dead_code)]
83    jidoka_mode: bool,
84}
85
86impl EddValidator {
87    /// Create a new EDD validator.
88    #[must_use]
89    pub fn new() -> Self {
90        Self {
91            violations: Vec::new(),
92            jidoka_mode: true,
93        }
94    }
95
96    /// Create a validator in lenient mode (collects all violations).
97    #[must_use]
98    pub fn lenient() -> Self {
99        Self {
100            violations: Vec::new(),
101            jidoka_mode: false,
102        }
103    }
104
105    /// Clear all collected violations.
106    pub fn clear(&mut self) {
107        self.violations.clear();
108    }
109
110    /// Get collected violations.
111    #[must_use]
112    pub fn violations(&self) -> &[EddViolation] {
113        &self.violations
114    }
115
116    /// Check if any critical violations exist.
117    #[must_use]
118    pub fn has_critical_violations(&self) -> bool {
119        self.violations
120            .iter()
121            .any(|v| v.severity == ViolationSeverity::Critical)
122    }
123
124    /// Check if any errors exist (including critical).
125    #[must_use]
126    pub fn has_errors(&self) -> bool {
127        self.violations
128            .iter()
129            .any(|v| v.severity >= ViolationSeverity::Error)
130    }
131
132    // =========================================================================
133    // Pillar 1: Prove It - EMC Validation
134    // =========================================================================
135
136    /// Validate that a simulation has an Equation Model Card.
137    ///
138    /// # EDD-01: Missing Equation Model Card
139    ///
140    /// # Errors
141    /// Returns `EDD-01` violation if EMC is `None`.
142    pub fn validate_simulation_has_emc(&self, emc: Option<&EquationModelCard>) -> EddResult<()> {
143        match emc {
144            Some(_) => Ok(()),
145            None => Err(EddViolation::new(
146                "EDD-01",
147                "Simulation must have an Equation Model Card (Pillar 1: Prove It)",
148                ViolationSeverity::Critical,
149            )),
150        }
151    }
152
153    /// Validate that an EMC has a governing equation.
154    ///
155    /// # EDD-02: Missing governing equation
156    ///
157    /// # Errors
158    /// Returns `EDD-02` violation if equation is empty.
159    pub fn validate_emc_has_equation(&self, emc: &EquationModelCard) -> EddResult<()> {
160        if emc.equation.is_empty() {
161            Err(EddViolation::new(
162                "EDD-02",
163                "EMC must have a governing equation in LaTeX format",
164                ViolationSeverity::Critical,
165            ))
166        } else {
167            Ok(())
168        }
169    }
170
171    /// Validate that an EMC has a citation.
172    ///
173    /// # EDD-03: Missing citation
174    ///
175    /// # Errors
176    /// Returns `EDD-03` violation if citation has no authors.
177    pub fn validate_emc_has_citation(&self, emc: &EquationModelCard) -> EddResult<()> {
178        if emc.citation.authors.is_empty() {
179            Err(EddViolation::new(
180                "EDD-03",
181                "EMC must have a peer-reviewed citation",
182                ViolationSeverity::Critical,
183            ))
184        } else {
185            Ok(())
186        }
187    }
188
189    /// Validate that an EMC has verification tests.
190    ///
191    /// # EDD-04: Missing verification tests
192    ///
193    /// # Errors
194    /// Returns `EDD-04` violation if verification tests are empty.
195    pub fn validate_emc_has_tests(&self, emc: &EquationModelCard) -> EddResult<()> {
196        if emc.verification_tests.is_empty() {
197            Err(EddViolation::new(
198                "EDD-04",
199                "EMC must have at least one verification test (Pillar 2: Fail It)",
200                ViolationSeverity::Critical,
201            ))
202        } else {
203            Ok(())
204        }
205    }
206
207    /// Perform full EMC validation.
208    ///
209    /// # Errors
210    /// Returns list of violations if any validation checks fail.
211    pub fn validate_emc(&mut self, emc: &EquationModelCard) -> Result<(), Vec<EddViolation>> {
212        let mut violations = Vec::new();
213
214        if let Err(v) = self.validate_emc_has_equation(emc) {
215            violations.push(v);
216        }
217
218        if let Err(v) = self.validate_emc_has_citation(emc) {
219            violations.push(v);
220        }
221
222        if let Err(v) = self.validate_emc_has_tests(emc) {
223            violations.push(v);
224        }
225
226        if violations.is_empty() {
227            Ok(())
228        } else {
229            self.violations.extend(violations.clone());
230            Err(violations)
231        }
232    }
233
234    // =========================================================================
235    // Pillar 3: Seed It - Reproducibility Validation
236    // =========================================================================
237
238    /// Validate that a seed is explicitly specified.
239    ///
240    /// # EDD-05: Missing explicit seed
241    ///
242    /// # Errors
243    /// Returns `EDD-05` violation if seed is `None`.
244    pub fn validate_seed_specified(&self, seed: Option<u64>) -> EddResult<()> {
245        match seed {
246            Some(_) => Ok(()),
247            None => Err(EddViolation::new(
248                "EDD-05",
249                "Experiment must have an explicit seed (Pillar 3: Seed It)",
250                ViolationSeverity::Critical,
251            )),
252        }
253    }
254
255    // =========================================================================
256    // Pillar 4: Falsify It - Falsification Validation
257    // =========================================================================
258
259    /// Validate that an experiment has falsification criteria.
260    ///
261    /// # EDD-06: Missing falsification criteria
262    ///
263    /// # Errors
264    /// Returns `EDD-06` warning if falsification criteria are empty.
265    pub fn validate_has_falsification_criteria(&self, spec: &ExperimentSpec) -> EddResult<()> {
266        if spec.falsification_criteria().is_empty() {
267            // This is a warning, not critical - experiments can still run
268            Err(EddViolation::new(
269                "EDD-06",
270                "Experiment should have falsification criteria (Pillar 4: Falsify It)",
271                ViolationSeverity::Warning,
272            ))
273        } else {
274            Ok(())
275        }
276    }
277
278    // =========================================================================
279    // Full Experiment Validation
280    // =========================================================================
281
282    /// Validate an experiment specification fully.
283    ///
284    /// # Errors
285    /// Returns list of violations if any validation checks fail (error severity or higher).
286    pub fn validate_experiment(&mut self, spec: &ExperimentSpec) -> Result<(), Vec<EddViolation>> {
287        let mut violations = Vec::new();
288
289        // Pillar 3: Seed It
290        if let Err(v) = self.validate_seed_specified(Some(spec.seed())) {
291            violations.push(v);
292        }
293
294        // Pillar 4: Falsify It (warning only)
295        if let Err(v) = self.validate_has_falsification_criteria(spec) {
296            violations.push(v);
297        }
298
299        // Standard validation
300        if let Err(errs) = spec.validate() {
301            for err in errs {
302                violations.push(EddViolation::new("EDD-00", &err, ViolationSeverity::Error));
303            }
304        }
305
306        if violations
307            .iter()
308            .any(|v| v.severity >= ViolationSeverity::Error)
309        {
310            self.violations.extend(violations.clone());
311            Err(violations)
312        } else {
313            // Add warnings but return Ok
314            self.violations.extend(violations);
315            Ok(())
316        }
317    }
318
319    // =========================================================================
320    // Verification Test Validation
321    // =========================================================================
322
323    /// Validate that verification tests pass.
324    ///
325    /// # EDD-07: Verification test failed
326    ///
327    /// # Errors
328    /// Returns `EDD-07` violations for each verification test that fails.
329    pub fn validate_verification_tests<F>(
330        &mut self,
331        emc: &EquationModelCard,
332        evaluator: F,
333    ) -> Result<(), Vec<EddViolation>>
334    where
335        F: Fn(&std::collections::HashMap<String, f64>) -> f64,
336    {
337        let results = emc.run_verification_tests(evaluator);
338        let failures: Vec<EddViolation> = results
339            .into_iter()
340            .filter(|(_, passed, _)| !passed)
341            .map(|(name, _, msg)| {
342                EddViolation::new(
343                    "EDD-07",
344                    &format!("Verification test failed: {name}"),
345                    ViolationSeverity::Critical,
346                )
347                .with_context(&msg)
348            })
349            .collect();
350
351        if failures.is_empty() {
352            Ok(())
353        } else {
354            self.violations.extend(failures.clone());
355            Err(failures)
356        }
357    }
358
359    // =========================================================================
360    // Conservation Law Validation (EDD-08)
361    // =========================================================================
362
363    /// Validate conservation laws are satisfied.
364    ///
365    /// # EDD-08: Conservation laws violated
366    ///
367    /// Monitors quantities that should be conserved (energy, momentum, etc.)
368    /// and triggers Jidoka (stop-on-error) if drift exceeds tolerance.
369    ///
370    /// # Arguments
371    ///
372    /// * `quantity_name` - Name of the conserved quantity (e.g., "energy")
373    /// * `initial_value` - Value at simulation start
374    /// * `current_value` - Current value during simulation
375    /// * `tolerance` - Maximum allowed relative drift
376    ///
377    /// # Errors
378    ///
379    /// Returns `EDD-08` violation if conservation law is violated.
380    pub fn validate_conservation_law(
381        &self,
382        quantity_name: &str,
383        initial_value: f64,
384        current_value: f64,
385        tolerance: f64,
386    ) -> EddResult<()> {
387        let relative_drift = if initial_value.abs() > f64::EPSILON {
388            (current_value - initial_value).abs() / initial_value.abs()
389        } else {
390            (current_value - initial_value).abs()
391        };
392
393        if relative_drift > tolerance {
394            Err(EddViolation::new(
395                "EDD-08",
396                &format!("Conservation law violated: {quantity_name} drifted beyond tolerance"),
397                ViolationSeverity::Critical,
398            ).with_context(&format!(
399                "initial={initial_value:.6e}, current={current_value:.6e}, drift={relative_drift:.6e}, tolerance={tolerance:.6e}"
400            )))
401        } else {
402            Ok(())
403        }
404    }
405
406    // =========================================================================
407    // Cross-Platform Reproducibility Validation (EDD-09)
408    // =========================================================================
409
410    /// Validate cross-platform reproducibility.
411    ///
412    /// # EDD-09: Cross-platform reproducibility failed
413    ///
414    /// Compares simulation results across different platforms to ensure
415    /// bitwise-identical outputs given the same seed.
416    ///
417    /// # Arguments
418    ///
419    /// * `platform_a` - Name of first platform
420    /// * `platform_b` - Name of second platform
421    /// * `result_a` - Result from platform A
422    /// * `result_b` - Result from platform B
423    /// * `tolerance` - Maximum allowed difference (0.0 for exact match)
424    ///
425    /// # Errors
426    ///
427    /// Returns `EDD-09` violation if results differ beyond tolerance.
428    pub fn validate_cross_platform_reproducibility(
429        &self,
430        platform_a: &str,
431        platform_b: &str,
432        result_a: f64,
433        result_b: f64,
434        tolerance: f64,
435    ) -> EddResult<()> {
436        let diff = (result_a - result_b).abs();
437
438        if diff > tolerance {
439            Err(EddViolation::new(
440                "EDD-09",
441                &format!("Cross-platform reproducibility failed: {platform_a} vs {platform_b}"),
442                ViolationSeverity::Error,
443            )
444            .with_context(&format!(
445                "{platform_a}={result_a:.15e}, {platform_b}={result_b:.15e}, diff={diff:.15e}"
446            )))
447        } else {
448            Ok(())
449        }
450    }
451
452    // =========================================================================
453    // TDD Enforcement (EDD-10)
454    // =========================================================================
455
456    /// Validate that implementation has associated failing tests.
457    ///
458    /// # EDD-10: Implementation without failing test
459    ///
460    /// Enforces the TDD principle: no implementation without a failing test.
461    /// This is verified by checking that test files exist and contain
462    /// appropriate test cases.
463    ///
464    /// # Arguments
465    ///
466    /// * `implementation_name` - Name of the implementation
467    /// * `has_test_file` - Whether a test file exists
468    /// * `test_count` - Number of tests for this implementation
469    ///
470    /// # Errors
471    ///
472    /// Returns `EDD-10` violation if no tests exist.
473    pub fn validate_tdd_compliance(
474        &self,
475        implementation_name: &str,
476        has_test_file: bool,
477        test_count: usize,
478    ) -> EddResult<()> {
479        if !has_test_file {
480            return Err(EddViolation::new(
481                "EDD-10",
482                &format!("Implementation '{implementation_name}' has no test file"),
483                ViolationSeverity::Critical,
484            )
485            .with_context("EDD requires TDD: write failing tests BEFORE implementation"));
486        }
487
488        if test_count == 0 {
489            return Err(EddViolation::new(
490                "EDD-10",
491                &format!("Implementation '{implementation_name}' has no tests"),
492                ViolationSeverity::Critical,
493            )
494            .with_context("Every implementation must have at least one test"));
495        }
496
497        Ok(())
498    }
499
500    // =========================================================================
501    // Three Pillars Quality Gate (EDD-13, EDD-14, EDD-15)
502    // =========================================================================
503
504    /// # EDD-13: YAML-Only Configuration
505    ///
506    /// Validates that no hardcoded parameters exist - all configuration
507    /// must come from YAML files.
508    ///
509    /// # Arguments
510    ///
511    /// * `has_yaml_config` - Whether the simulation has YAML configuration
512    /// * `hardcoded_params` - List of detected hardcoded parameters
513    ///
514    /// # Errors
515    ///
516    /// Returns `EDD-13` violation if:
517    /// - No YAML configuration is provided, or
518    /// - Hardcoded parameters are detected
519    pub fn validate_yaml_only_config(
520        has_yaml_config: bool,
521        hardcoded_params: &[String],
522    ) -> Result<(), EddViolation> {
523        if !has_yaml_config {
524            return Err(EddViolation::new(
525                "EDD-13",
526                "Simulation requires YAML configuration",
527                ViolationSeverity::Critical,
528            )
529            .with_context("Three Pillars: Pillar 2 requires YAML-only configuration"));
530        }
531
532        if !hardcoded_params.is_empty() {
533            return Err(EddViolation::new(
534                "EDD-13",
535                &format!(
536                    "Hardcoded parameters detected: {}",
537                    hardcoded_params.join(", ")
538                ),
539                ViolationSeverity::Critical,
540            )
541            .with_context("All parameters must come from YAML configuration"));
542        }
543
544        Ok(())
545    }
546
547    /// # EDD-14: Probar TUI Verification
548    ///
549    /// Validates that TUI components have been tested with Probar.
550    ///
551    /// # Arguments
552    ///
553    /// * `probar_tests_passed` - Whether Probar TUI tests passed
554    /// * `test_count` - Number of Probar TUI tests
555    ///
556    /// # Errors
557    ///
558    /// Returns `EDD-14` violation if:
559    /// - No Probar tests exist, or
560    /// - Probar tests failed
561    pub fn validate_probar_tui(
562        probar_tests_passed: bool,
563        test_count: usize,
564    ) -> Result<(), EddViolation> {
565        if test_count == 0 {
566            return Err(EddViolation::new(
567                "EDD-14",
568                "No Probar TUI tests found",
569                ViolationSeverity::Critical,
570            )
571            .with_context("Three Pillars: Pillar 3 requires Probar TUI verification"));
572        }
573
574        if !probar_tests_passed {
575            return Err(EddViolation::new(
576                "EDD-14",
577                "Probar TUI tests failed",
578                ViolationSeverity::Critical,
579            )
580            .with_context("All Probar TUI tests must pass before release"));
581        }
582
583        Ok(())
584    }
585
586    /// # EDD-15: Probar WASM Verification
587    ///
588    /// Validates that WASM components have been tested with Probar.
589    ///
590    /// # Arguments
591    ///
592    /// * `probar_wasm_passed` - Whether Probar WASM tests passed
593    /// * `test_count` - Number of Probar WASM tests
594    ///
595    /// # Errors
596    ///
597    /// Returns `EDD-15` violation if:
598    /// - No Probar WASM tests exist, or
599    /// - Probar WASM tests failed
600    pub fn validate_probar_wasm(
601        probar_wasm_passed: bool,
602        test_count: usize,
603    ) -> Result<(), EddViolation> {
604        if test_count == 0 {
605            return Err(EddViolation::new(
606                "EDD-15",
607                "No Probar WASM tests found",
608                ViolationSeverity::Critical,
609            )
610            .with_context("Three Pillars: Pillar 3 requires Probar WASM verification"));
611        }
612
613        if !probar_wasm_passed {
614            return Err(EddViolation::new(
615                "EDD-15",
616                "Probar WASM tests failed",
617                ViolationSeverity::Critical,
618            )
619            .with_context("All Probar WASM tests must pass before release"));
620        }
621
622        Ok(())
623    }
624
625    /// # Three Pillars Quality Gate
626    ///
627    /// Validates all three pillars of provable simulation:
628    /// 1. Z3 Equation Proofs (EDD-11, EDD-12)
629    /// 2. YAML-Only Configuration (EDD-05, EDD-13)
630    /// 3. Probar UX Verification (EDD-14, EDD-15)
631    ///
632    /// # Arguments
633    ///
634    /// * `z3_proofs_passed` - Whether Z3 proofs pass
635    /// * `has_yaml_config` - Whether YAML config exists
636    /// * `seed_specified` - Whether seed is specified
637    /// * `probar_tui_passed` - Whether Probar TUI tests pass
638    /// * `probar_wasm_passed` - Whether Probar WASM tests pass
639    ///
640    /// # Returns
641    ///
642    /// A vector of any violations found across all three pillars.
643    #[must_use]
644    #[allow(clippy::fn_params_excessive_bools)]
645    pub fn validate_three_pillars(
646        z3_proofs_passed: bool,
647        has_yaml_config: bool,
648        seed_specified: bool,
649        probar_tui_passed: bool,
650        probar_tui_test_count: usize,
651        probar_wasm_passed: bool,
652        probar_wasm_test_count: usize,
653    ) -> Vec<EddViolation> {
654        let mut violations = Vec::new();
655
656        // Pillar 1: Z3 Equation Proofs
657        if !z3_proofs_passed {
658            violations.push(
659                EddViolation::new(
660                    "EDD-11",
661                    "Z3 equation proofs did not pass",
662                    ViolationSeverity::Critical,
663                )
664                .with_context("Pillar 1: All equations must be provable with Z3"),
665            );
666        }
667
668        // Pillar 2: YAML-Only Configuration
669        if !has_yaml_config {
670            violations.push(
671                EddViolation::new(
672                    "EDD-13",
673                    "No YAML configuration provided",
674                    ViolationSeverity::Critical,
675                )
676                .with_context("Pillar 2: All configuration must be YAML-based"),
677            );
678        }
679
680        if !seed_specified {
681            violations.push(
682                EddViolation::new(
683                    "EDD-05",
684                    "No seed specified in configuration",
685                    ViolationSeverity::Critical,
686                )
687                .with_context("Pillar 2: Seed must be explicitly specified in YAML"),
688            );
689        }
690
691        // Pillar 3: Probar UX Verification
692        if let Err(e) = Self::validate_probar_tui(probar_tui_passed, probar_tui_test_count) {
693            violations.push(e);
694        }
695
696        if let Err(e) = Self::validate_probar_wasm(probar_wasm_passed, probar_wasm_test_count) {
697            violations.push(e);
698        }
699
700        violations
701    }
702}
703
704/// Summary of EDD compliance status.
705#[derive(Debug)]
706pub struct EddComplianceSummary {
707    /// Total number of violations
708    pub total_violations: usize,
709    /// Number of critical violations
710    pub critical_count: usize,
711    /// Number of error violations
712    pub error_count: usize,
713    /// Number of warning violations
714    pub warning_count: usize,
715    /// Number of info violations
716    pub info_count: usize,
717    /// Overall compliance status
718    pub compliant: bool,
719}
720
721// =============================================================================
722// TPS-Aligned Grades (Section 9.2)
723// =============================================================================
724
725/// TPS-aligned quality grades from Section 9.2 of EDD spec.
726#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
727pub enum TpsGrade {
728    /// 95-100%: Release OK
729    ToyotaStandard,
730    /// 85-94%: Beta with documented limitations
731    KaizenRequired,
732    /// 70-84%: Significant revision required
733    AndonWarning,
734    /// <70% or Critical failure: Block release
735    StopTheLine,
736}
737
738impl TpsGrade {
739    /// Calculate TPS grade from a compliance score (0.0 to 1.0).
740    #[must_use]
741    pub fn from_score(score: f64) -> Self {
742        if score >= 0.95 {
743            Self::ToyotaStandard
744        } else if score >= 0.85 {
745            Self::KaizenRequired
746        } else if score >= 0.70 {
747            Self::AndonWarning
748        } else {
749            Self::StopTheLine
750        }
751    }
752
753    /// Calculate TPS grade from violations.
754    ///
755    /// Any critical violation results in `StopTheLine` regardless of score.
756    #[must_use]
757    pub fn from_violations(violations: &[EddViolation], total_checks: usize) -> Self {
758        // Any critical violation = STOP THE LINE
759        if violations
760            .iter()
761            .any(|v| v.severity == ViolationSeverity::Critical)
762        {
763            return Self::StopTheLine;
764        }
765
766        // Calculate score based on errors (warnings don't affect score)
767        let error_count = violations
768            .iter()
769            .filter(|v| v.severity >= ViolationSeverity::Error)
770            .count();
771
772        if total_checks == 0 {
773            return Self::ToyotaStandard;
774        }
775
776        let score = 1.0 - (error_count as f64 / total_checks as f64);
777        Self::from_score(score)
778    }
779
780    /// Get the decision text for this grade.
781    #[must_use]
782    pub const fn decision(&self) -> &'static str {
783        match self {
784            Self::ToyotaStandard => "Release OK",
785            Self::KaizenRequired => "Beta with documented limitations",
786            Self::AndonWarning => "Significant revision required",
787            Self::StopTheLine => "Block release",
788        }
789    }
790
791    /// Get the score range for this grade.
792    #[must_use]
793    pub const fn score_range(&self) -> &'static str {
794        match self {
795            Self::ToyotaStandard => "95-100%",
796            Self::KaizenRequired => "85-94%",
797            Self::AndonWarning => "70-84%",
798            Self::StopTheLine => "<70% or Critical",
799        }
800    }
801}
802
803impl std::fmt::Display for TpsGrade {
804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805        let name = match self {
806            Self::ToyotaStandard => "Toyota Standard",
807            Self::KaizenRequired => "Kaizen Required",
808            Self::AndonWarning => "Andon Warning",
809            Self::StopTheLine => "STOP THE LINE",
810        };
811        write!(f, "{name}")
812    }
813}
814
815// =============================================================================
816// Richardson Extrapolation for Convergence Order (EDD-07)
817// =============================================================================
818
819/// Result of Richardson extrapolation analysis.
820#[derive(Debug, Clone)]
821pub struct ConvergenceAnalysis {
822    /// Computed convergence order
823    pub order: f64,
824    /// Expected convergence order
825    pub expected_order: f64,
826    /// Whether the order matches within tolerance
827    pub order_matches: bool,
828    /// Tolerance used for comparison
829    pub tolerance: f64,
830    /// Extrapolated value (improved estimate)
831    pub extrapolated_value: f64,
832    /// Error estimates at each refinement level
833    pub error_estimates: Vec<f64>,
834}
835
836/// Compute convergence order using Richardson extrapolation.
837///
838/// Given function evaluations at successively refined step sizes,
839/// estimate the order of convergence p where error ~ h^p.
840///
841/// # Arguments
842///
843/// * `values` - Function values at each refinement level (coarse to fine)
844/// * `refinement_ratio` - Ratio between successive step sizes (default: 2.0)
845/// * `expected_order` - Expected convergence order
846/// * `tolerance` - Tolerance for order comparison
847///
848/// # Returns
849///
850/// `ConvergenceAnalysis` with computed order and extrapolated value.
851///
852/// # Panics
853///
854/// Panics if fewer than 3 values are provided.
855#[must_use]
856pub fn richardson_extrapolation(
857    values: &[f64],
858    refinement_ratio: f64,
859    expected_order: f64,
860    tolerance: f64,
861) -> ConvergenceAnalysis {
862    assert!(
863        values.len() >= 3,
864        "Richardson extrapolation requires at least 3 values"
865    );
866
867    let n = values.len();
868    let r = refinement_ratio;
869
870    // Compute error estimates (differences between successive values)
871    let mut error_estimates = Vec::with_capacity(n - 1);
872    for i in 0..n - 1 {
873        error_estimates.push((values[i] - values[i + 1]).abs());
874    }
875
876    // Compute observed convergence order using three finest values
877    // p = log(|f_{h} - f_{h/r}| / |f_{h/r} - f_{h/r²}|) / log(r)
878    let e1 = (values[n - 3] - values[n - 2]).abs();
879    let e2 = (values[n - 2] - values[n - 1]).abs();
880
881    let order = if e2 > f64::EPSILON && e1 > f64::EPSILON {
882        (e1 / e2).ln() / r.ln()
883    } else {
884        expected_order // Assume expected if errors are tiny
885    };
886
887    // Richardson extrapolation formula for improved estimate
888    // f_exact ≈ f_{h/r} + (f_{h/r} - f_h) / (r^p - 1)
889    let extrapolated_value = if (r.powf(order) - 1.0).abs() > f64::EPSILON {
890        values[n - 1] + (values[n - 1] - values[n - 2]) / (r.powf(order) - 1.0)
891    } else {
892        values[n - 1]
893    };
894
895    let order_matches = (order - expected_order).abs() <= tolerance;
896
897    ConvergenceAnalysis {
898        order,
899        expected_order,
900        order_matches,
901        tolerance,
902        extrapolated_value,
903        error_estimates,
904    }
905}
906
907impl EddComplianceSummary {
908    /// Generate summary from violations.
909    #[must_use]
910    pub fn from_violations(violations: &[EddViolation]) -> Self {
911        let critical_count = violations
912            .iter()
913            .filter(|v| v.severity == ViolationSeverity::Critical)
914            .count();
915        let error_count = violations
916            .iter()
917            .filter(|v| v.severity == ViolationSeverity::Error)
918            .count();
919        let warning_count = violations
920            .iter()
921            .filter(|v| v.severity == ViolationSeverity::Warning)
922            .count();
923        let info_count = violations
924            .iter()
925            .filter(|v| v.severity == ViolationSeverity::Info)
926            .count();
927
928        Self {
929            total_violations: violations.len(),
930            critical_count,
931            error_count,
932            warning_count,
933            info_count,
934            compliant: critical_count == 0 && error_count == 0,
935        }
936    }
937}
938
939#[cfg(test)]
940mod tests {
941    use super::*;
942    use crate::edd::equation::Citation;
943    use crate::edd::model_card::EmcBuilder;
944
945    #[test]
946    fn test_validator_new() {
947        let validator = EddValidator::new();
948        assert!(validator.violations().is_empty());
949    }
950
951    #[test]
952    fn test_validate_simulation_has_emc_fails() {
953        let validator = EddValidator::new();
954        let result = validator.validate_simulation_has_emc(None);
955        assert!(result.is_err());
956        let err = result.err().unwrap();
957        assert_eq!(err.code, "EDD-01");
958        assert_eq!(err.severity, ViolationSeverity::Critical);
959    }
960
961    #[test]
962    fn test_validate_simulation_has_emc_passes() {
963        let validator = EddValidator::new();
964        let emc = EmcBuilder::new()
965            .name("Test")
966            .equation("x = y")
967            .citation(Citation::new(&["Test"], "Test", 2024))
968            .add_verification_test("test", 1.0, 0.1)
969            .build()
970            .ok()
971            .unwrap();
972
973        let result = validator.validate_simulation_has_emc(Some(&emc));
974        assert!(result.is_ok());
975    }
976
977    #[test]
978    fn test_validate_seed_specified_fails() {
979        let validator = EddValidator::new();
980        let result = validator.validate_seed_specified(None);
981        assert!(result.is_err());
982        let err = result.err().unwrap();
983        assert_eq!(err.code, "EDD-05");
984    }
985
986    #[test]
987    fn test_validate_seed_specified_passes() {
988        let validator = EddValidator::new();
989        let result = validator.validate_seed_specified(Some(42));
990        assert!(result.is_ok());
991    }
992
993    #[test]
994    fn test_compliance_summary() {
995        let violations = vec![
996            EddViolation::new("EDD-01", "test", ViolationSeverity::Critical),
997            EddViolation::new("EDD-05", "test", ViolationSeverity::Error),
998            EddViolation::new("EDD-06", "test", ViolationSeverity::Warning),
999        ];
1000
1001        let summary = EddComplianceSummary::from_violations(&violations);
1002        assert_eq!(summary.total_violations, 3);
1003        assert_eq!(summary.critical_count, 1);
1004        assert_eq!(summary.error_count, 1);
1005        assert_eq!(summary.warning_count, 1);
1006        assert!(!summary.compliant);
1007    }
1008
1009    #[test]
1010    fn test_compliance_summary_compliant() {
1011        let violations = vec![
1012            EddViolation::new("EDD-06", "test", ViolationSeverity::Warning),
1013            EddViolation::new("EDD-00", "test", ViolationSeverity::Info),
1014        ];
1015
1016        let summary = EddComplianceSummary::from_violations(&violations);
1017        assert!(summary.compliant);
1018    }
1019
1020    #[test]
1021    fn test_violation_with_context() {
1022        let violation = EddViolation::new("EDD-07", "Test failed", ViolationSeverity::Critical)
1023            .with_context("Expected 10, got 15");
1024
1025        assert!(violation.context.is_some());
1026        assert!(violation.context.unwrap().contains("Expected 10"));
1027    }
1028
1029    #[test]
1030    fn test_validator_collects_violations() {
1031        let mut validator = EddValidator::lenient();
1032
1033        // Create an EMC with missing tests
1034        let emc = EquationModelCard {
1035            name: "Test".to_string(),
1036            version: "1.0".to_string(),
1037            equation: String::new(), // Missing!
1038            class: crate::edd::equation::EquationClass::Queueing,
1039            citation: Citation::new(&[], "Test", 2024), // Missing authors!
1040            references: vec![],
1041            variables: vec![],
1042            verification_tests: vec![], // Missing!
1043            domain_constraints: vec![],
1044            falsification_criteria: vec![],
1045            implementation_notes: vec![],
1046            description: String::new(),
1047            lineage: vec![],
1048        };
1049
1050        let result = validator.validate_emc(&emc);
1051        assert!(result.is_err());
1052
1053        // Should have collected multiple violations
1054        assert!(validator.violations().len() >= 3);
1055    }
1056
1057    #[test]
1058    fn test_has_critical_violations() {
1059        let mut validator = EddValidator::new();
1060        validator.violations.push(EddViolation::new(
1061            "EDD-01",
1062            "test",
1063            ViolationSeverity::Critical,
1064        ));
1065
1066        assert!(validator.has_critical_violations());
1067        assert!(validator.has_errors());
1068    }
1069
1070    #[test]
1071    fn test_clear_violations() {
1072        let mut validator = EddValidator::new();
1073        validator.violations.push(EddViolation::new(
1074            "EDD-01",
1075            "test",
1076            ViolationSeverity::Critical,
1077        ));
1078
1079        validator.clear();
1080        assert!(validator.violations().is_empty());
1081    }
1082
1083    // =========================================================================
1084    // EDD-08: Conservation Law Tests
1085    // =========================================================================
1086
1087    #[test]
1088    fn test_validate_conservation_law_passes() {
1089        let validator = EddValidator::new();
1090        // Energy conserved within tolerance
1091        let result = validator.validate_conservation_law("energy", 100.0, 100.001, 1e-4);
1092        assert!(result.is_ok());
1093    }
1094
1095    #[test]
1096    fn test_validate_conservation_law_fails() {
1097        let validator = EddValidator::new();
1098        // Energy drifted beyond tolerance
1099        let result = validator.validate_conservation_law("energy", 100.0, 110.0, 1e-4);
1100        assert!(result.is_err());
1101        let err = result.err().unwrap();
1102        assert_eq!(err.code, "EDD-08");
1103        assert_eq!(err.severity, ViolationSeverity::Critical);
1104        assert!(err.message.contains("energy"));
1105    }
1106
1107    #[test]
1108    fn test_validate_conservation_law_zero_initial() {
1109        let validator = EddValidator::new();
1110        // Handle case where initial value is zero
1111        let result = validator.validate_conservation_law("momentum", 0.0, 0.0001, 1e-4);
1112        assert!(result.is_ok());
1113
1114        let result_fail = validator.validate_conservation_law("momentum", 0.0, 1.0, 1e-4);
1115        assert!(result_fail.is_err());
1116    }
1117
1118    // =========================================================================
1119    // EDD-09: Cross-Platform Reproducibility Tests
1120    // =========================================================================
1121
1122    #[test]
1123    fn test_validate_cross_platform_reproducibility_passes() {
1124        let validator = EddValidator::new();
1125        let result = validator.validate_cross_platform_reproducibility(
1126            "x86_64-linux",
1127            "aarch64-darwin",
1128            1.234567890123456,
1129            1.234567890123456,
1130            0.0,
1131        );
1132        assert!(result.is_ok());
1133    }
1134
1135    #[test]
1136    fn test_validate_cross_platform_reproducibility_fails() {
1137        let validator = EddValidator::new();
1138        let result = validator.validate_cross_platform_reproducibility(
1139            "x86_64-linux",
1140            "aarch64-darwin",
1141            1.234567890123456,
1142            1.234567890123999,
1143            1e-15,
1144        );
1145        assert!(result.is_err());
1146        let err = result.err().unwrap();
1147        assert_eq!(err.code, "EDD-09");
1148        assert_eq!(err.severity, ViolationSeverity::Error);
1149        assert!(err.context.is_some());
1150    }
1151
1152    #[test]
1153    fn test_validate_cross_platform_with_tolerance() {
1154        let validator = EddValidator::new();
1155        // Allow small differences with relaxed IEEE mode
1156        let result = validator.validate_cross_platform_reproducibility(
1157            "x86_64-linux",
1158            "wasm32",
1159            1.0000000001,
1160            1.0000000002,
1161            1e-9,
1162        );
1163        assert!(result.is_ok());
1164    }
1165
1166    // =========================================================================
1167    // EDD-10: TDD Compliance Tests
1168    // =========================================================================
1169
1170    #[test]
1171    fn test_validate_tdd_compliance_passes() {
1172        let validator = EddValidator::new();
1173        let result = validator.validate_tdd_compliance("harmonic_oscillator", true, 5);
1174        assert!(result.is_ok());
1175    }
1176
1177    #[test]
1178    fn test_validate_tdd_compliance_no_test_file() {
1179        let validator = EddValidator::new();
1180        let result = validator.validate_tdd_compliance("new_simulation", false, 0);
1181        assert!(result.is_err());
1182        let err = result.err().unwrap();
1183        assert_eq!(err.code, "EDD-10");
1184        assert!(err.message.contains("no test file"));
1185    }
1186
1187    #[test]
1188    fn test_validate_tdd_compliance_no_tests() {
1189        let validator = EddValidator::new();
1190        let result = validator.validate_tdd_compliance("empty_simulation", true, 0);
1191        assert!(result.is_err());
1192        let err = result.err().unwrap();
1193        assert_eq!(err.code, "EDD-10");
1194        assert!(err.message.contains("no tests"));
1195    }
1196
1197    // =========================================================================
1198    // TPS Grade Tests (Section 9.2)
1199    // =========================================================================
1200
1201    #[test]
1202    fn test_tps_grade_from_score_toyota_standard() {
1203        assert_eq!(TpsGrade::from_score(1.0), TpsGrade::ToyotaStandard);
1204        assert_eq!(TpsGrade::from_score(0.95), TpsGrade::ToyotaStandard);
1205        assert_eq!(TpsGrade::from_score(0.99), TpsGrade::ToyotaStandard);
1206    }
1207
1208    #[test]
1209    fn test_tps_grade_from_score_kaizen_required() {
1210        assert_eq!(TpsGrade::from_score(0.94), TpsGrade::KaizenRequired);
1211        assert_eq!(TpsGrade::from_score(0.85), TpsGrade::KaizenRequired);
1212        assert_eq!(TpsGrade::from_score(0.90), TpsGrade::KaizenRequired);
1213    }
1214
1215    #[test]
1216    fn test_tps_grade_from_score_andon_warning() {
1217        assert_eq!(TpsGrade::from_score(0.84), TpsGrade::AndonWarning);
1218        assert_eq!(TpsGrade::from_score(0.70), TpsGrade::AndonWarning);
1219        assert_eq!(TpsGrade::from_score(0.75), TpsGrade::AndonWarning);
1220    }
1221
1222    #[test]
1223    fn test_tps_grade_from_score_stop_the_line() {
1224        assert_eq!(TpsGrade::from_score(0.69), TpsGrade::StopTheLine);
1225        assert_eq!(TpsGrade::from_score(0.0), TpsGrade::StopTheLine);
1226        assert_eq!(TpsGrade::from_score(0.50), TpsGrade::StopTheLine);
1227    }
1228
1229    #[test]
1230    fn test_tps_grade_from_violations_critical_always_stops() {
1231        let violations = vec![EddViolation::new(
1232            "EDD-01",
1233            "test",
1234            ViolationSeverity::Critical,
1235        )];
1236        // Even with only 1 violation out of 100 checks, critical = STOP
1237        assert_eq!(
1238            TpsGrade::from_violations(&violations, 100),
1239            TpsGrade::StopTheLine
1240        );
1241    }
1242
1243    #[test]
1244    fn test_tps_grade_from_violations_no_violations() {
1245        let violations: Vec<EddViolation> = vec![];
1246        assert_eq!(
1247            TpsGrade::from_violations(&violations, 10),
1248            TpsGrade::ToyotaStandard
1249        );
1250    }
1251
1252    #[test]
1253    fn test_tps_grade_from_violations_warnings_ignored() {
1254        let violations = vec![
1255            EddViolation::new("EDD-06", "test", ViolationSeverity::Warning),
1256            EddViolation::new("EDD-06", "test", ViolationSeverity::Warning),
1257        ];
1258        // Warnings don't affect score
1259        assert_eq!(
1260            TpsGrade::from_violations(&violations, 10),
1261            TpsGrade::ToyotaStandard
1262        );
1263    }
1264
1265    #[test]
1266    fn test_tps_grade_decision_text() {
1267        assert_eq!(TpsGrade::ToyotaStandard.decision(), "Release OK");
1268        assert_eq!(
1269            TpsGrade::KaizenRequired.decision(),
1270            "Beta with documented limitations"
1271        );
1272        assert_eq!(
1273            TpsGrade::AndonWarning.decision(),
1274            "Significant revision required"
1275        );
1276        assert_eq!(TpsGrade::StopTheLine.decision(), "Block release");
1277    }
1278
1279    #[test]
1280    fn test_tps_grade_display() {
1281        assert_eq!(format!("{}", TpsGrade::ToyotaStandard), "Toyota Standard");
1282        assert_eq!(format!("{}", TpsGrade::StopTheLine), "STOP THE LINE");
1283    }
1284
1285    // =========================================================================
1286    // EDD-13: YAML-Only Configuration Tests
1287    // =========================================================================
1288
1289    #[test]
1290    fn test_validate_yaml_only_config_passes() {
1291        let result = EddValidator::validate_yaml_only_config(true, &[]);
1292        assert!(result.is_ok());
1293    }
1294
1295    #[test]
1296    fn test_validate_yaml_only_config_no_yaml() {
1297        let result = EddValidator::validate_yaml_only_config(false, &[]);
1298        assert!(result.is_err());
1299        let err = result.err().unwrap();
1300        assert_eq!(err.code, "EDD-13");
1301        assert_eq!(err.severity, ViolationSeverity::Critical);
1302    }
1303
1304    #[test]
1305    fn test_validate_yaml_only_config_hardcoded_params() {
1306        let hardcoded = vec!["omega".to_string(), "amplitude".to_string()];
1307        let result = EddValidator::validate_yaml_only_config(true, &hardcoded);
1308        assert!(result.is_err());
1309        let err = result.err().unwrap();
1310        assert_eq!(err.code, "EDD-13");
1311        assert!(err.message.contains("omega"));
1312        assert!(err.message.contains("amplitude"));
1313    }
1314
1315    // =========================================================================
1316    // EDD-14: Probar TUI Verification Tests
1317    // =========================================================================
1318
1319    #[test]
1320    fn test_validate_probar_tui_passes() {
1321        let result = EddValidator::validate_probar_tui(true, 5);
1322        assert!(result.is_ok());
1323    }
1324
1325    #[test]
1326    fn test_validate_probar_tui_no_tests() {
1327        let result = EddValidator::validate_probar_tui(true, 0);
1328        assert!(result.is_err());
1329        let err = result.err().unwrap();
1330        assert_eq!(err.code, "EDD-14");
1331        assert_eq!(err.severity, ViolationSeverity::Critical);
1332    }
1333
1334    #[test]
1335    fn test_validate_probar_tui_failed() {
1336        let result = EddValidator::validate_probar_tui(false, 5);
1337        assert!(result.is_err());
1338        let err = result.err().unwrap();
1339        assert_eq!(err.code, "EDD-14");
1340        assert!(err.message.contains("failed"));
1341    }
1342
1343    // =========================================================================
1344    // EDD-15: Probar WASM Verification Tests
1345    // =========================================================================
1346
1347    #[test]
1348    fn test_validate_probar_wasm_passes() {
1349        let result = EddValidator::validate_probar_wasm(true, 3);
1350        assert!(result.is_ok());
1351    }
1352
1353    #[test]
1354    fn test_validate_probar_wasm_no_tests() {
1355        let result = EddValidator::validate_probar_wasm(true, 0);
1356        assert!(result.is_err());
1357        let err = result.err().unwrap();
1358        assert_eq!(err.code, "EDD-15");
1359        assert_eq!(err.severity, ViolationSeverity::Critical);
1360    }
1361
1362    #[test]
1363    fn test_validate_probar_wasm_failed() {
1364        let result = EddValidator::validate_probar_wasm(false, 3);
1365        assert!(result.is_err());
1366        let err = result.err().unwrap();
1367        assert_eq!(err.code, "EDD-15");
1368        assert!(err.message.contains("failed"));
1369    }
1370
1371    // =========================================================================
1372    // Three Pillars Quality Gate Tests
1373    // =========================================================================
1374
1375    #[test]
1376    fn test_validate_three_pillars_all_pass() {
1377        let violations = EddValidator::validate_three_pillars(
1378            true, // z3_proofs_passed
1379            true, // has_yaml_config
1380            true, // seed_specified
1381            true, // probar_tui_passed
1382            5,    // probar_tui_test_count
1383            true, // probar_wasm_passed
1384            3,    // probar_wasm_test_count
1385        );
1386        assert!(violations.is_empty(), "All pillars should pass");
1387    }
1388
1389    #[test]
1390    fn test_validate_three_pillars_z3_fails() {
1391        let violations = EddValidator::validate_three_pillars(
1392            false, // z3_proofs_passed
1393            true, true, true, 5, true, 3,
1394        );
1395        assert_eq!(violations.len(), 1);
1396        assert_eq!(violations[0].code, "EDD-11");
1397    }
1398
1399    #[test]
1400    fn test_validate_three_pillars_yaml_fails() {
1401        let violations = EddValidator::validate_three_pillars(
1402            true, false, // has_yaml_config
1403            true, true, 5, true, 3,
1404        );
1405        assert_eq!(violations.len(), 1);
1406        assert_eq!(violations[0].code, "EDD-13");
1407    }
1408
1409    #[test]
1410    fn test_validate_three_pillars_seed_missing() {
1411        let violations = EddValidator::validate_three_pillars(
1412            true, true, false, // seed_specified
1413            true, 5, true, 3,
1414        );
1415        assert_eq!(violations.len(), 1);
1416        assert_eq!(violations[0].code, "EDD-05");
1417    }
1418
1419    #[test]
1420    fn test_validate_three_pillars_probar_fails() {
1421        let violations = EddValidator::validate_three_pillars(
1422            true, true, true, false, // probar_tui_passed
1423            5, false, // probar_wasm_passed
1424            3,
1425        );
1426        assert_eq!(violations.len(), 2);
1427        assert!(violations.iter().any(|v| v.code == "EDD-14"));
1428        assert!(violations.iter().any(|v| v.code == "EDD-15"));
1429    }
1430
1431    #[test]
1432    fn test_validate_three_pillars_multiple_failures() {
1433        let violations = EddValidator::validate_three_pillars(
1434            false, // z3_proofs_passed
1435            false, // has_yaml_config
1436            false, // seed_specified
1437            false, // probar_tui_passed
1438            0,     // probar_tui_test_count
1439            false, // probar_wasm_passed
1440            0,     // probar_wasm_test_count
1441        );
1442        // Should have multiple violations
1443        assert!(
1444            violations.len() >= 4,
1445            "Expected multiple violations: {:?}",
1446            violations
1447        );
1448    }
1449
1450    // =========================================================================
1451    // Richardson Extrapolation Tests (EDD-07)
1452    // =========================================================================
1453
1454    #[test]
1455    fn test_richardson_extrapolation_second_order() {
1456        // Simulate second-order convergence: error ~ h^2
1457        // f(h) = exact + C*h^2
1458        // With h = 1, 0.5, 0.25 and exact = 1.0, C = 1.0
1459        let values = vec![
1460            2.0,    // h=1:   1 + 1*1^2 = 2
1461            1.25,   // h=0.5: 1 + 1*0.25 = 1.25
1462            1.0625, // h=0.25: 1 + 1*0.0625 = 1.0625
1463        ];
1464
1465        let result = richardson_extrapolation(&values, 2.0, 2.0, 0.1);
1466
1467        // Should detect second-order convergence
1468        assert!(
1469            (result.order - 2.0).abs() < 0.1,
1470            "Expected order ~2.0, got {}",
1471            result.order
1472        );
1473        assert!(result.order_matches);
1474
1475        // Extrapolated value should be close to exact (1.0)
1476        assert!(
1477            (result.extrapolated_value - 1.0).abs() < 0.1,
1478            "Expected extrapolated ~1.0, got {}",
1479            result.extrapolated_value
1480        );
1481    }
1482
1483    #[test]
1484    fn test_richardson_extrapolation_first_order() {
1485        // Simulate first-order convergence: error ~ h
1486        // f(h) = exact + C*h
1487        // With h = 1, 0.5, 0.25 and exact = 0.0, C = 1.0
1488        let values = vec![
1489            1.0,   // h=1
1490            0.5,   // h=0.5
1491            0.25,  // h=0.25
1492            0.125, // h=0.125
1493        ];
1494
1495        let result = richardson_extrapolation(&values, 2.0, 1.0, 0.1);
1496
1497        // Should detect first-order convergence
1498        assert!(
1499            (result.order - 1.0).abs() < 0.1,
1500            "Expected order ~1.0, got {}",
1501            result.order
1502        );
1503        assert!(result.order_matches);
1504    }
1505
1506    #[test]
1507    fn test_richardson_extrapolation_fourth_order() {
1508        // Simulate fourth-order convergence (RK4-like)
1509        // f(h) = exact + C*h^4
1510        let values = vec![
1511            1.0 + 1.0,            // h=1: 1 + 1^4 = 2
1512            1.0 + 0.0625,         // h=0.5: 1 + 0.5^4 = 1.0625
1513            1.0 + 0.00390625,     // h=0.25: 1 + 0.25^4
1514            1.0 + 0.000244140625, // h=0.125
1515        ];
1516
1517        let result = richardson_extrapolation(&values, 2.0, 4.0, 0.2);
1518
1519        // Should detect fourth-order convergence
1520        assert!(
1521            (result.order - 4.0).abs() < 0.3,
1522            "Expected order ~4.0, got {}",
1523            result.order
1524        );
1525    }
1526
1527    #[test]
1528    fn test_richardson_extrapolation_error_estimates() {
1529        let values = vec![2.0, 1.25, 1.0625];
1530        let result = richardson_extrapolation(&values, 2.0, 2.0, 0.1);
1531
1532        // Should have n-1 error estimates
1533        assert_eq!(result.error_estimates.len(), 2);
1534
1535        // Errors should decrease
1536        assert!(
1537            result.error_estimates[0] > result.error_estimates[1],
1538            "Errors should decrease: {:?}",
1539            result.error_estimates
1540        );
1541    }
1542
1543    #[test]
1544    #[should_panic(expected = "requires at least 3 values")]
1545    fn test_richardson_extrapolation_requires_minimum_values() {
1546        let values = vec![1.0, 0.5];
1547        let _ = richardson_extrapolation(&values, 2.0, 2.0, 0.1);
1548    }
1549
1550    #[test]
1551    fn test_richardson_extrapolation_tolerance() {
1552        let values = vec![2.0, 1.25, 1.0625];
1553
1554        // With tight tolerance, should not match if order slightly off
1555        let result_tight = richardson_extrapolation(&values, 2.0, 2.5, 0.01);
1556        assert!(!result_tight.order_matches);
1557
1558        // With loose tolerance, should match
1559        let result_loose = richardson_extrapolation(&values, 2.0, 2.5, 1.0);
1560        assert!(result_loose.order_matches);
1561    }
1562}