Skip to main content

simular/edd/
mod.rs

1//! Equation-Driven Development (EDD) module.
2//!
3//! Implements the EDD specification for falsifiable, equation-first simulation.
4//!
5//! # The Four Pillars of EDD
6//!
7//! 1. **Prove It** - Every simulation begins with a mathematically-verified governing equation
8//! 2. **Fail It** - TDD with failing tests derived from analytical solutions
9//! 3. **Seed It** - Deterministic reproducibility via explicit random seeds
10//! 4. **Falsify It** - Active search for conditions that disprove the model
11//!
12//! # Operations Science Equations
13//!
14//! - Little's Law: `L = λW` (WIP = Throughput × Cycle Time)
15//! - Kingman's Formula: VUT equation for queue wait times
16//! - Square Root Law: Safety stock scaling
17//! - Bullwhip Effect: Variance amplification in supply chains
18//!
19//! # References
20//!
21//! - [30] Little, J.D.C. (1961). "A Proof for the Queuing Formula: L = λW"
22//! - [31] Kingman, J.F.C. (1961). "The single server queue in heavy traffic"
23//! - [32] Lee, H.L., et al. (1997). "The Bullwhip Effect in Supply Chains"
24//! - [33] Hopp, W.J. & Spearman, M.L. (2004). "To Pull or Not to Pull"
25
26pub mod audit;
27pub mod equation;
28pub mod experiment;
29pub mod falsifiable;
30pub mod gui_coverage;
31pub mod loader;
32pub mod model_card;
33pub mod operations;
34pub mod prover;
35pub mod report;
36pub mod runner;
37pub mod style;
38pub mod tps;
39pub mod traits;
40pub mod v2;
41pub mod validation;
42
43// Re-exports
44pub use audit::{
45    hash_state, verify_rng_consistency, AuditLogReplayer, Decision, EquationEval,
46    GeneratedTestCase, ReplaySpeed, ReplayState, SimulationAuditLog, StepEntry, TspStateSnapshot,
47    TspStepType,
48};
49pub use equation::Citation;
50pub use equation::{EquationClass, EquationVariable, GoverningEquation};
51pub use experiment::{
52    ExperimentHypothesis, ExperimentSpec, FalsificationAction, FalsificationCriterion,
53};
54pub use falsifiable::{
55    ExperimentSeed, FalsifiableSimulation, FalsificationResult, ParamSpace, Trajectory,
56};
57pub use gui_coverage::GuiCoverage;
58pub use loader::{EmcYaml, ExperimentYaml};
59pub use model_card::{DomainConstraint, EmcBuilder, EquationModelCard};
60pub use operations::{BullwhipEffect, KingmanFormula, LittlesLaw, SquareRootLaw};
61#[cfg(feature = "z3-proofs")]
62pub use prover::z3_impl;
63pub use prover::{ProofError, ProofResult, Z3Provable};
64pub use report::{ReportFormat, ReportGenerator};
65pub use runner::{
66    EddComplianceChecklist, EmcComplianceReport, EmcRegistry, ExecutionMetrics, ExperimentDomain,
67    ExperimentResult, ExperimentRunner, FalsificationCriterionResult, FalsificationSummary,
68    ReproducibilitySummary, RunnerConfig, VerificationSummary, VerificationTestSummary,
69};
70pub use tps::{
71    validate_bullwhip_effect, validate_cell_layout, validate_kanban_vs_dbr,
72    validate_kingmans_curve, validate_littles_law, validate_push_vs_pull, validate_shojinka,
73    validate_smed_setup, validate_square_root_law, TpsMetrics, TpsTestCase, TpsTestResult,
74};
75pub use traits::{
76    ConfigError, EddSimulation, Reproducible, TestResult, ValidationResult, VerificationResult,
77    YamlConfigurable,
78};
79pub use validation::{
80    richardson_extrapolation, ConvergenceAnalysis, EddComplianceSummary, EddResult, EddValidator,
81    EddViolation, TpsGrade, ViolationSeverity,
82};
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    // =========================================================================
89    // RED PHASE: Write failing tests first (Extreme TDD)
90    // =========================================================================
91
92    // -------------------------------------------------------------------------
93    // Pillar 1: Prove It - Governing Equation Tests
94    // -------------------------------------------------------------------------
95
96    #[test]
97    fn test_governing_equation_must_have_latex_representation() {
98        let eq = operations::LittlesLaw::new();
99        assert!(
100            !eq.latex().is_empty(),
101            "Equation must have LaTeX representation"
102        );
103        assert!(
104            eq.latex().contains("\\lambda"),
105            "Little's Law must reference λ"
106        );
107    }
108
109    #[test]
110    fn test_governing_equation_must_have_citation() {
111        let eq = operations::LittlesLaw::new();
112        let citation = eq.citation();
113        assert!(citation.year > 0, "Citation must have valid year");
114        assert!(!citation.authors.is_empty(), "Citation must have authors");
115    }
116
117    #[test]
118    fn test_governing_equation_must_have_variables() {
119        let eq = operations::LittlesLaw::new();
120        let vars = eq.variables();
121        assert!(
122            vars.len() >= 3,
123            "Little's Law must have at least 3 variables (L, λ, W)"
124        );
125    }
126
127    // -------------------------------------------------------------------------
128    // Pillar 2: Fail It - TDD Tests from Equations
129    // -------------------------------------------------------------------------
130
131    #[test]
132    fn test_littles_law_analytical_solution() {
133        // L = λW: If λ=5 items/hr and W=2 hrs, then L=10
134        let eq = operations::LittlesLaw::new();
135        let result = eq.evaluate(5.0, 2.0);
136        assert!((result - 10.0).abs() < 1e-10, "L = λW = 5 * 2 = 10");
137    }
138
139    #[test]
140    fn test_littles_law_validation() {
141        let eq = operations::LittlesLaw::new();
142
143        // Valid case: L ≈ λW
144        let valid = eq.validate(10.0, 5.0, 2.0, 0.01);
145        assert!(valid.is_ok(), "Should pass when L = λW");
146
147        // Invalid case: L ≠ λW
148        let invalid = eq.validate(15.0, 5.0, 2.0, 0.01);
149        assert!(invalid.is_err(), "Should fail when L ≠ λW");
150    }
151
152    #[test]
153    fn test_kingmans_formula_hockey_stick() {
154        let eq = operations::KingmanFormula::new();
155
156        // At low utilization (50%), wait time is manageable
157        let wait_50 = eq.expected_wait_time(0.5, 1.0, 1.0, 1.0);
158
159        // At high utilization (95%), wait time explodes
160        let wait_95 = eq.expected_wait_time(0.95, 1.0, 1.0, 1.0);
161
162        // The "hockey stick" effect: 95% util should be ~19x wait of 50% util
163        assert!(
164            wait_95 > wait_50 * 10.0,
165            "High utilization should cause exponential wait time increase"
166        );
167    }
168
169    // -------------------------------------------------------------------------
170    // Pillar 3: Seed It - Reproducibility Tests
171    // -------------------------------------------------------------------------
172
173    #[test]
174    fn test_experiment_must_have_seed() {
175        let spec = ExperimentSpec::builder().name("Test Experiment").build();
176
177        // Should fail without explicit seed
178        assert!(spec.is_err(), "Experiment must require explicit seed");
179    }
180
181    #[test]
182    fn test_experiment_with_seed_is_reproducible() {
183        let spec = ExperimentSpec::builder()
184            .name("Test Experiment")
185            .seed(42)
186            .build()
187            .expect("Should build with seed");
188
189        assert_eq!(spec.seed(), 42, "Seed must be preserved");
190    }
191
192    // -------------------------------------------------------------------------
193    // Pillar 4: Falsify It - Active Refutation Tests
194    // -------------------------------------------------------------------------
195
196    #[test]
197    fn test_falsification_criteria_required() {
198        let spec = ExperimentSpec::builder().name("Test").seed(42).build();
199
200        // Should fail without falsification criteria
201        assert!(
202            spec.is_err()
203                || spec
204                    .as_ref()
205                    .map(|s| s.falsification_criteria().is_empty())
206                    .unwrap_or(true),
207            "Experiment should require falsification criteria"
208        );
209    }
210
211    // -------------------------------------------------------------------------
212    // Equation Model Card Tests
213    // -------------------------------------------------------------------------
214
215    #[test]
216    fn test_emc_must_have_governing_equation() {
217        let emc = EquationModelCard::builder().name("Test EMC").build();
218
219        assert!(emc.is_err(), "EMC must require governing equation");
220    }
221
222    #[test]
223    fn test_emc_must_have_analytical_derivation() {
224        let emc = EquationModelCard::builder()
225            .name("Test EMC")
226            .equation("L = \\lambda W")
227            .build();
228
229        assert!(
230            emc.is_err(),
231            "EMC must require analytical derivation (citation)"
232        );
233    }
234
235    #[test]
236    fn test_emc_must_have_verification_tests() {
237        let emc = EquationModelCard::builder()
238            .name("Little's Law")
239            .equation("L = \\lambda W")
240            .citation(Citation::new(
241                &["Little, J.D.C."],
242                "Operations Research",
243                1961,
244            ))
245            .build();
246
247        assert!(emc.is_err(), "EMC must require verification tests");
248    }
249
250    #[test]
251    fn test_emc_complete_builds_successfully() {
252        let emc = EquationModelCard::builder()
253            .name("Little's Law")
254            .equation("L = \\lambda W")
255            .citation(Citation::new(
256                &["Little, J.D.C."],
257                "Operations Research",
258                1961,
259            ))
260            .add_variable("L", "Average queue length", "items")
261            .add_variable("lambda", "Arrival rate", "items/time")
262            .add_variable("W", "Average wait time", "time")
263            .add_verification_test("L = λW for λ=5, W=2 => L=10", 10.0, 1e-10)
264            .build();
265
266        assert!(emc.is_ok(), "Complete EMC should build successfully");
267    }
268
269    // -------------------------------------------------------------------------
270    // Operations Science Test Cases (from TPS empirical validation)
271    // -------------------------------------------------------------------------
272
273    #[test]
274    fn test_tc1_push_vs_pull_little_law_holds() {
275        // Test Case 1: Little's Law holds under stochastic conditions
276        let eq = operations::LittlesLaw::new();
277
278        // Various WIP levels should maintain linear relationship
279        let test_cases = [
280            (10.0, 2.0, 5.0),  // WIP=10, TH=5 => CT=2
281            (25.0, 5.0, 5.0),  // WIP=25, TH=5 => CT=5
282            (50.0, 10.0, 5.0), // WIP=50, TH=5 => CT=10
283        ];
284
285        for (wip, ct, th) in test_cases {
286            let result = eq.validate(wip, th, ct, 0.001);
287            assert!(
288                result.is_ok(),
289                "Little's Law should hold: WIP={wip}, TH={th}, CT={ct}"
290            );
291        }
292    }
293
294    #[test]
295    fn test_tc8_kingmans_curve_exponential() {
296        // Test Case 8: Wait times are exponential, not linear
297        let eq = operations::KingmanFormula::new();
298
299        let util_levels = [0.5, 0.7, 0.85, 0.95];
300        let mut wait_times = Vec::new();
301
302        for &rho in &util_levels {
303            let wait = eq.expected_wait_time(rho, 1.0, 1.0, 1.0);
304            wait_times.push(wait);
305        }
306
307        // Check exponential growth (each step should increase by more than previous)
308        for i in 1..wait_times.len() - 1 {
309            let delta_prev = wait_times[i] - wait_times[i - 1];
310            let delta_curr = wait_times[i + 1] - wait_times[i];
311            assert!(delta_curr > delta_prev,
312                "Wait time growth should accelerate (exponential): prev_delta={delta_prev}, curr_delta={delta_curr}");
313        }
314    }
315
316    #[test]
317    fn test_tc9_square_root_law() {
318        // Test Case 9: Inventory scales as √lead_time, not linearly
319        // The Square Root Law: I_safety = z × σ_D × √L
320        let eq = operations::SquareRootLaw::new();
321
322        // If lead time quadruples, safety stock should only double (√4 = 2)
323        let stock_l1 = eq.safety_stock(100.0, 1.0, 1.96);
324        let stock_l4 = eq.safety_stock(100.0, 4.0, 1.96);
325
326        let ratio = stock_l4 / stock_l1;
327        assert!(
328            (ratio - 2.0).abs() < 0.01,
329            "Safety stock should scale as √L: ratio should be 2.0, got {ratio}"
330        );
331    }
332
333    // -------------------------------------------------------------------------
334    // EDD Validator Tests
335    // -------------------------------------------------------------------------
336
337    #[test]
338    fn test_edd_validator_checks_emc_presence() {
339        let validator = EddValidator::new();
340
341        // Simulation without EMC should fail
342        let result = validator.validate_simulation_has_emc(None);
343        assert!(result.is_err(), "Simulation without EMC should fail EDD-01");
344    }
345
346    #[test]
347    fn test_edd_validator_checks_seed_presence() {
348        let validator = EddValidator::new();
349
350        // Experiment without seed should fail
351        let result = validator.validate_seed_specified(None);
352        assert!(
353            result.is_err(),
354            "Experiment without seed should fail EDD-05"
355        );
356    }
357}