1use super::equation::{
16 Citation, EquationClass, EquationVariable, GoverningEquation, VariableConstraints,
17};
18
19#[derive(Debug, Clone)]
34pub struct LittlesLaw {
35 latex: String,
36 description: String,
37}
38
39impl LittlesLaw {
40 #[must_use]
42 pub fn new() -> Self {
43 Self {
44 latex: r"L = \lambda W".to_string(),
45 description: "Little's Law relates the average number of items in a stable system (L) \
46 to the arrival rate (λ) and the average time spent in the system (W)."
47 .to_string(),
48 }
49 }
50
51 #[must_use]
53 pub fn evaluate(&self, lambda: f64, w: f64) -> f64 {
54 lambda * w
55 }
56
57 pub fn validate(&self, l: f64, lambda: f64, w: f64, tolerance: f64) -> Result<(), String> {
68 let expected = lambda * w;
69 let relative_error = if l.abs() > f64::EPSILON {
70 (l - expected).abs() / l
71 } else if expected.abs() > f64::EPSILON {
72 (l - expected).abs() / expected
73 } else {
74 0.0
75 };
76
77 if relative_error <= tolerance {
78 Ok(())
79 } else {
80 Err(format!(
81 "Little's Law violation: L={l:.4}, λW={expected:.4}, relative_error={relative_error:.4} > tolerance={tolerance:.4}"
82 ))
83 }
84 }
85
86 #[must_use]
88 pub fn solve_l(&self, lambda: f64, w: f64) -> f64 {
89 lambda * w
90 }
91
92 #[must_use]
94 pub fn solve_lambda(&self, l: f64, w: f64) -> f64 {
95 if w.abs() < f64::EPSILON {
96 f64::INFINITY
97 } else {
98 l / w
99 }
100 }
101
102 #[must_use]
104 pub fn solve_w(&self, l: f64, lambda: f64) -> f64 {
105 if lambda.abs() < f64::EPSILON {
106 f64::INFINITY
107 } else {
108 l / lambda
109 }
110 }
111}
112
113impl Default for LittlesLaw {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl GoverningEquation for LittlesLaw {
120 fn latex(&self) -> &str {
121 &self.latex
122 }
123
124 fn class(&self) -> EquationClass {
125 EquationClass::Queueing
126 }
127
128 fn citation(&self) -> Citation {
129 Citation::new(&["Little, J.D.C."], "Operations Research", 1961)
130 .with_title("A Proof for the Queuing Formula: L = λW")
131 .with_doi("10.1287/opre.9.3.383")
132 }
133
134 fn variables(&self) -> Vec<EquationVariable> {
135 vec![
136 EquationVariable::new("L", "wip", "items")
137 .with_description("Average number of items in the system (work in progress)")
138 .with_constraints(VariableConstraints {
139 min: Some(0.0),
140 max: None,
141 positive: false,
142 integer: false,
143 }),
144 EquationVariable::new("λ", "arrival_rate", "items/time")
145 .with_description("Average arrival rate (throughput at steady state)")
146 .with_constraints(VariableConstraints {
147 min: Some(0.0),
148 max: None,
149 positive: true,
150 integer: false,
151 }),
152 EquationVariable::new("W", "cycle_time", "time")
153 .with_description("Average time an item spends in the system")
154 .with_constraints(VariableConstraints {
155 min: Some(0.0),
156 max: None,
157 positive: true,
158 integer: false,
159 }),
160 ]
161 }
162
163 fn description(&self) -> &str {
164 &self.description
165 }
166
167 fn name(&self) -> &'static str {
168 "Little's Law"
169 }
170
171 fn validate_consistency(&self, values: &[(&str, f64)], tolerance: f64) -> Result<(), String> {
172 let mut l = None;
173 let mut lambda = None;
174 let mut w = None;
175
176 for (name, value) in values {
177 match *name {
178 "L" | "l" | "wip" => l = Some(*value),
179 "λ" | "lambda" | "arrival_rate" | "throughput" => lambda = Some(*value),
180 "W" | "w" | "cycle_time" => w = Some(*value),
181 _ => {}
182 }
183 }
184
185 match (l, lambda, w) {
186 (Some(l_val), Some(lambda_val), Some(w_val)) => {
187 self.validate(l_val, lambda_val, w_val, tolerance)
188 }
189 _ => Err("Missing required variables for Little's Law validation".to_string()),
190 }
191 }
192}
193
194#[derive(Debug, Clone)]
212pub struct KingmanFormula {
213 latex: String,
214 description: String,
215}
216
217impl KingmanFormula {
218 #[must_use]
220 pub fn new() -> Self {
221 Self {
222 latex: r"W_q \approx \frac{\rho}{1-\rho} \cdot \frac{c_a^2 + c_s^2}{2} \cdot t_s"
223 .to_string(),
224 description: "Kingman's VUT equation approximates expected queue wait time as a \
225 function of Variability, Utilization, and Time."
226 .to_string(),
227 }
228 }
229
230 #[must_use]
241 pub fn expected_wait_time(&self, rho: f64, c_a: f64, c_s: f64, t_s: f64) -> f64 {
242 if rho >= 1.0 {
243 return f64::INFINITY;
244 }
245 if rho <= 0.0 {
246 return 0.0;
247 }
248
249 let utilization_factor = rho / (1.0 - rho);
250 let variability_factor = (c_a * c_a + c_s * c_s) / 2.0;
251
252 utilization_factor * variability_factor * t_s
253 }
254
255 #[must_use]
257 pub fn utilization_factor(&self, rho: f64) -> f64 {
258 if rho >= 1.0 {
259 f64::INFINITY
260 } else if rho <= 0.0 {
261 0.0
262 } else {
263 rho / (1.0 - rho)
264 }
265 }
266
267 #[must_use]
269 pub fn variability_factor(&self, c_a: f64, c_s: f64) -> f64 {
270 (c_a * c_a + c_s * c_s) / 2.0
271 }
272}
273
274impl Default for KingmanFormula {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280impl GoverningEquation for KingmanFormula {
281 fn latex(&self) -> &str {
282 &self.latex
283 }
284
285 fn class(&self) -> EquationClass {
286 EquationClass::Queueing
287 }
288
289 fn citation(&self) -> Citation {
290 Citation::new(
291 &["Kingman, J.F.C."],
292 "Annals of Mathematical Statistics",
293 1961,
294 )
295 .with_title("The single server queue in heavy traffic")
296 .with_doi("10.1214/aoms/1177704567")
297 }
298
299 fn variables(&self) -> Vec<EquationVariable> {
300 vec![
301 EquationVariable::new("ρ", "utilization", "dimensionless")
302 .with_description("Server utilization ratio (arrival rate / service rate)")
303 .with_constraints(VariableConstraints {
304 min: Some(0.0),
305 max: Some(1.0),
306 positive: false,
307 integer: false,
308 }),
309 EquationVariable::new("c_a", "arrival_cv", "dimensionless")
310 .with_description("Coefficient of variation of inter-arrival times"),
311 EquationVariable::new("c_s", "service_cv", "dimensionless")
312 .with_description("Coefficient of variation of service times"),
313 EquationVariable::new("t_s", "service_time", "time")
314 .with_description("Mean service time"),
315 EquationVariable::new("W_q", "queue_wait", "time")
316 .with_description("Expected wait time in queue"),
317 ]
318 }
319
320 fn description(&self) -> &str {
321 &self.description
322 }
323
324 fn name(&self) -> &'static str {
325 "Kingman's VUT Formula"
326 }
327
328 fn validate_consistency(&self, values: &[(&str, f64)], tolerance: f64) -> Result<(), String> {
329 let mut rho = None;
330 let mut c_a = None;
331 let mut c_s = None;
332 let mut t_s = None;
333 let mut w_q = None;
334
335 for (name, value) in values {
336 match *name {
337 "ρ" | "rho" | "utilization" => rho = Some(*value),
338 "c_a" | "arrival_cv" => c_a = Some(*value),
339 "c_s" | "service_cv" => c_s = Some(*value),
340 "t_s" | "service_time" => t_s = Some(*value),
341 "W_q" | "queue_wait" => w_q = Some(*value),
342 _ => {}
343 }
344 }
345
346 match (rho, c_a, c_s, t_s, w_q) {
347 (Some(r), Some(ca), Some(cs), Some(ts), Some(wq)) => {
348 let expected = self.expected_wait_time(r, ca, cs, ts);
349 let relative_error = if expected.abs() > f64::EPSILON {
350 (wq - expected).abs() / expected
351 } else if wq.abs() > f64::EPSILON {
352 (wq - expected).abs() / wq
353 } else {
354 0.0
355 };
356
357 if relative_error <= tolerance {
358 Ok(())
359 } else {
360 Err(format!(
361 "Kingman violation: W_q={wq:.4}, expected={expected:.4}, error={relative_error:.4}"
362 ))
363 }
364 }
365 _ => Err("Missing required variables for Kingman validation".to_string()),
366 }
367 }
368}
369
370#[derive(Debug, Clone)]
386pub struct SquareRootLaw {
387 latex: String,
388 description: String,
389}
390
391impl SquareRootLaw {
392 #[must_use]
394 pub fn new() -> Self {
395 Self {
396 latex: r"I_{safety} = z \cdot \sigma_D \cdot \sqrt{L}".to_string(),
397 description: "The Square Root Law shows that safety stock requirements scale with \
398 the square root of demand or lead time, not linearly."
399 .to_string(),
400 }
401 }
402
403 #[must_use]
413 pub fn safety_stock(&self, demand_std: f64, lead_time: f64, z_score: f64) -> f64 {
414 z_score * demand_std * lead_time.sqrt()
415 }
416
417 #[must_use]
422 pub fn pooling_reduction(&self, num_locations: usize) -> f64 {
423 if num_locations == 0 {
424 return 0.0;
425 }
426 1.0 / (num_locations as f64).sqrt()
427 }
428
429 #[must_use]
431 pub fn z_score_for_service_level(service_level: f64) -> f64 {
432 match service_level {
433 x if (x - 0.90).abs() < 0.001 => 1.28,
434 x if (x - 0.95).abs() < 0.001 => 1.65,
435 x if (x - 0.99).abs() < 0.001 => 2.33,
436 x if (x - 0.999).abs() < 0.001 => 3.09,
437 _ => {
438 1.96
441 }
442 }
443 }
444}
445
446impl Default for SquareRootLaw {
447 fn default() -> Self {
448 Self::new()
449 }
450}
451
452impl GoverningEquation for SquareRootLaw {
453 fn latex(&self) -> &str {
454 &self.latex
455 }
456
457 fn class(&self) -> EquationClass {
458 EquationClass::Inventory
459 }
460
461 fn citation(&self) -> Citation {
462 Citation::new(&["Eppen, G.D."], "Management Science", 1979)
463 .with_title(
464 "Effects of Centralization on Expected Costs in a Multi-Location Newsboy Problem",
465 )
466 .with_doi("10.1287/mnsc.25.5.498")
467 }
468
469 fn variables(&self) -> Vec<EquationVariable> {
470 vec![
471 EquationVariable::new("I_safety", "safety_stock", "units")
472 .with_description("Required safety stock quantity"),
473 EquationVariable::new("z", "z_score", "dimensionless")
474 .with_description("Service level z-score from normal distribution"),
475 EquationVariable::new("σ_D", "demand_std", "units/period")
476 .with_description("Standard deviation of demand per period"),
477 EquationVariable::new("L", "lead_time", "periods")
478 .with_description("Lead time in periods"),
479 ]
480 }
481
482 fn description(&self) -> &str {
483 &self.description
484 }
485
486 fn name(&self) -> &'static str {
487 "Square Root Law"
488 }
489
490 fn validate_consistency(&self, values: &[(&str, f64)], tolerance: f64) -> Result<(), String> {
491 let mut z = None;
492 let mut sigma_d = None;
493 let mut lead_time = None;
494 let mut i_safety = None;
495
496 for (name, value) in values {
497 match *name {
498 "z" | "z_score" => z = Some(*value),
499 "σ_D" | "demand_std" => sigma_d = Some(*value),
500 "L" | "lead_time" => lead_time = Some(*value),
501 "I_safety" | "safety_stock" => i_safety = Some(*value),
502 _ => {}
503 }
504 }
505
506 match (z, sigma_d, lead_time, i_safety) {
507 (Some(z_val), Some(sd), Some(lt), Some(is)) => {
508 let expected = self.safety_stock(sd, lt, z_val);
509 let relative_error = if expected.abs() > f64::EPSILON {
510 (is - expected).abs() / expected
511 } else {
512 0.0
513 };
514
515 if relative_error <= tolerance {
516 Ok(())
517 } else {
518 Err(format!(
519 "Square Root Law violation: I_safety={is:.4}, expected={expected:.4}"
520 ))
521 }
522 }
523 _ => Err("Missing required variables for Square Root Law validation".to_string()),
524 }
525 }
526}
527
528#[derive(Debug, Clone)]
540pub struct BullwhipEffect {
541 latex: String,
542 description: String,
543}
544
545impl BullwhipEffect {
546 #[must_use]
548 pub fn new() -> Self {
549 Self {
550 latex: r"\frac{Var(Orders)}{Var(Demand)} \geq 1 + \frac{2L}{p} + \frac{2L^2}{p^2}"
551 .to_string(),
552 description: "The Bullwhip Effect describes how demand variance amplifies upstream \
553 in supply chains due to demand signal processing."
554 .to_string(),
555 }
556 }
557
558 #[must_use]
567 pub fn amplification_factor(&self, lead_time: f64, review_period: f64) -> f64 {
568 if review_period <= 0.0 {
569 return f64::INFINITY;
570 }
571 let ratio = lead_time / review_period;
572 1.0 + 2.0 * ratio + 2.0 * ratio * ratio
573 }
574
575 #[must_use]
577 pub fn multi_echelon_amplification(&self, lead_times: &[f64], review_periods: &[f64]) -> f64 {
578 if lead_times.len() != review_periods.len() {
579 return f64::NAN;
580 }
581
582 lead_times
583 .iter()
584 .zip(review_periods.iter())
585 .map(|(lt, rp)| self.amplification_factor(*lt, *rp))
586 .product()
587 }
588}
589
590impl Default for BullwhipEffect {
591 fn default() -> Self {
592 Self::new()
593 }
594}
595
596impl GoverningEquation for BullwhipEffect {
597 fn latex(&self) -> &str {
598 &self.latex
599 }
600
601 fn class(&self) -> EquationClass {
602 EquationClass::Inventory
603 }
604
605 fn citation(&self) -> Citation {
606 Citation::new(
607 &["Lee, H.L.", "Padmanabhan, V.", "Whang, S."],
608 "Management Science",
609 1997,
610 )
611 .with_title("The Bullwhip Effect in Supply Chains")
612 .with_doi("10.1287/mnsc.43.4.546")
613 }
614
615 fn variables(&self) -> Vec<EquationVariable> {
616 vec![
617 EquationVariable::new("L", "lead_time", "periods")
618 .with_description("Lead time between ordering and receiving"),
619 EquationVariable::new("p", "review_period", "periods")
620 .with_description("Time between inventory reviews"),
621 EquationVariable::new("Var(Orders)", "order_variance", "units²")
622 .with_description("Variance of orders placed"),
623 EquationVariable::new("Var(Demand)", "demand_variance", "units²")
624 .with_description("Variance of end-customer demand"),
625 ]
626 }
627
628 fn description(&self) -> &str {
629 &self.description
630 }
631
632 fn name(&self) -> &'static str {
633 "Bullwhip Effect"
634 }
635
636 fn validate_consistency(&self, values: &[(&str, f64)], tolerance: f64) -> Result<(), String> {
637 let mut lead_time = None;
638 let mut review_period = None;
639 let mut order_var = None;
640 let mut demand_var = None;
641
642 for (name, value) in values {
643 match *name {
644 "L" | "lead_time" => lead_time = Some(*value),
645 "p" | "review_period" => review_period = Some(*value),
646 "Var(Orders)" | "order_variance" => order_var = Some(*value),
647 "Var(Demand)" | "demand_variance" => demand_var = Some(*value),
648 _ => {}
649 }
650 }
651
652 match (lead_time, review_period, order_var, demand_var) {
653 (Some(lt), Some(rp), Some(ov), Some(dv)) => {
654 let min_factor = self.amplification_factor(lt, rp);
655 let observed_factor = if dv > f64::EPSILON { ov / dv } else { 0.0 };
656
657 if observed_factor >= min_factor * (1.0 - tolerance) {
659 Ok(())
660 } else {
661 Err(format!(
662 "Bullwhip violation: observed amplification {observed_factor:.4} < minimum {min_factor:.4}"
663 ))
664 }
665 }
666 _ => Err("Missing required variables for Bullwhip validation".to_string()),
667 }
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
680 fn test_littles_law_basic() {
681 let law = LittlesLaw::new();
682
683 let result = law.evaluate(5.0, 2.0);
685 assert!((result - 10.0).abs() < f64::EPSILON);
686 }
687
688 #[test]
689 fn test_littles_law_validation_pass() {
690 let law = LittlesLaw::new();
691 let result = law.validate(10.0, 5.0, 2.0, 0.01);
692 assert!(result.is_ok());
693 }
694
695 #[test]
696 fn test_littles_law_validation_fail() {
697 let law = LittlesLaw::new();
698 let result = law.validate(15.0, 5.0, 2.0, 0.01);
699 assert!(result.is_err());
700 }
701
702 #[test]
703 fn test_littles_law_solvers() {
704 let law = LittlesLaw::new();
705
706 assert!((law.solve_l(5.0, 2.0) - 10.0).abs() < f64::EPSILON);
707 assert!((law.solve_lambda(10.0, 2.0) - 5.0).abs() < f64::EPSILON);
708 assert!((law.solve_w(10.0, 5.0) - 2.0).abs() < f64::EPSILON);
709 }
710
711 #[test]
712 fn test_littles_law_has_citation() {
713 let law = LittlesLaw::new();
714 let citation = law.citation();
715 assert_eq!(citation.year, 1961);
716 assert!(!citation.authors.is_empty());
717 }
718
719 #[test]
720 fn test_littles_law_has_variables() {
721 let law = LittlesLaw::new();
722 let vars = law.variables();
723 assert!(vars.len() >= 3);
724 }
725
726 #[test]
731 fn test_kingman_basic() {
732 let formula = KingmanFormula::new();
733
734 let wait = formula.expected_wait_time(0.5, 1.0, 1.0, 1.0);
736 assert!((wait - 1.0).abs() < f64::EPSILON);
737 }
738
739 #[test]
740 fn test_kingman_hockey_stick() {
741 let formula = KingmanFormula::new();
742
743 let wait_50 = formula.expected_wait_time(0.5, 1.0, 1.0, 1.0);
744 let wait_95 = formula.expected_wait_time(0.95, 1.0, 1.0, 1.0);
745
746 assert!(wait_95 > wait_50 * 10.0);
748 }
749
750 #[test]
751 fn test_kingman_100_percent_utilization() {
752 let formula = KingmanFormula::new();
753 let wait = formula.expected_wait_time(1.0, 1.0, 1.0, 1.0);
754 assert!(wait.is_infinite());
755 }
756
757 #[test]
758 fn test_kingman_has_citation() {
759 let formula = KingmanFormula::new();
760 let citation = formula.citation();
761 assert_eq!(citation.year, 1961);
762 }
763
764 #[test]
769 fn test_square_root_law_basic() {
770 let law = SquareRootLaw::new();
771
772 let stock = law.safety_stock(100.0, 1.0, 1.96);
774 assert!((stock - 196.0).abs() < f64::EPSILON);
775 }
776
777 #[test]
778 fn test_square_root_law_scaling() {
779 let law = SquareRootLaw::new();
780
781 let _stock_100 = law.safety_stock(100.0, 1.0, 1.96);
783 let _stock_400 = law.safety_stock(400.0, 1.0, 1.96);
784
785 let stock_l1 = law.safety_stock(100.0, 1.0, 1.96);
788 let stock_l4 = law.safety_stock(100.0, 4.0, 1.96);
789
790 let lt_ratio = stock_l4 / stock_l1;
791 assert!((lt_ratio - 2.0).abs() < 0.01);
792 }
793
794 #[test]
795 fn test_square_root_law_pooling() {
796 let law = SquareRootLaw::new();
797
798 let reduction = law.pooling_reduction(4);
800 assert!((reduction - 0.5).abs() < f64::EPSILON);
801 }
802
803 #[test]
808 fn test_bullwhip_basic() {
809 let effect = BullwhipEffect::new();
810
811 let factor = effect.amplification_factor(1.0, 1.0);
813 assert!((factor - 5.0).abs() < f64::EPSILON);
814 }
815
816 #[test]
817 fn test_bullwhip_multi_echelon() {
818 let effect = BullwhipEffect::new();
819
820 let factor = effect.multi_echelon_amplification(&[1.0, 1.0], &[1.0, 1.0]);
822 assert!((factor - 25.0).abs() < f64::EPSILON);
823 }
824
825 #[test]
826 fn test_bullwhip_has_citation() {
827 let effect = BullwhipEffect::new();
828 let citation = effect.citation();
829 assert_eq!(citation.year, 1997);
830 assert!(citation.authors.len() >= 3);
831 }
832
833 #[test]
838 fn test_littles_law_trait_methods() {
839 let law = LittlesLaw::new();
840
841 assert!(law.latex().contains("lambda"));
843
844 assert_eq!(law.class(), EquationClass::Queueing);
846
847 assert_eq!(law.name(), "Little's Law");
849
850 assert!(!law.description().is_empty());
852 }
853
854 #[test]
855 fn test_littles_law_validate_consistency() {
856 let law = LittlesLaw::new();
857
858 let values = vec![("L", 10.0), ("lambda", 5.0), ("W", 2.0)];
860 assert!(law.validate_consistency(&values, 0.01).is_ok());
861
862 let values2 = vec![("wip", 10.0), ("arrival_rate", 5.0), ("cycle_time", 2.0)];
864 assert!(law.validate_consistency(&values2, 0.01).is_ok());
865
866 let incomplete = vec![("L", 10.0), ("lambda", 5.0)];
868 assert!(law.validate_consistency(&incomplete, 0.01).is_err());
869 }
870
871 #[test]
872 fn test_littles_law_default() {
873 let law = LittlesLaw::default();
874 assert_eq!(law.name(), "Little's Law");
875 }
876
877 #[test]
878 fn test_littles_law_solve_edge_cases() {
879 let law = LittlesLaw::new();
880
881 assert!(law.solve_lambda(10.0, 0.0).is_infinite());
883 assert!(law.solve_w(10.0, 0.0).is_infinite());
884 }
885
886 #[test]
887 fn test_kingman_trait_methods() {
888 let formula = KingmanFormula::new();
889
890 assert!(formula.latex().contains("rho"));
891 assert_eq!(formula.class(), EquationClass::Queueing);
892 assert_eq!(formula.name(), "Kingman's VUT Formula");
893 assert!(!formula.description().is_empty());
894 }
895
896 #[test]
897 fn test_kingman_default() {
898 let formula = KingmanFormula::default();
899 assert_eq!(formula.name(), "Kingman's VUT Formula");
900 }
901
902 #[test]
903 fn test_kingman_variables() {
904 let formula = KingmanFormula::new();
905 let vars = formula.variables();
906 assert!(vars.len() >= 4);
907 }
908
909 #[test]
910 fn test_kingman_validate_consistency() {
911 let formula = KingmanFormula::new();
912
913 let values = vec![
915 ("rho", 0.5),
916 ("c_a", 1.0),
917 ("c_s", 1.0),
918 ("t_s", 1.0),
919 ("W_q", 1.0),
920 ];
921 assert!(formula.validate_consistency(&values, 0.1).is_ok());
922
923 let incomplete = vec![("rho", 0.5)];
925 assert!(formula.validate_consistency(&incomplete, 0.1).is_err());
926 }
927
928 #[test]
929 fn test_kingman_utilization_and_variability() {
930 let formula = KingmanFormula::new();
931
932 assert_eq!(formula.utilization_factor(0.5), 1.0);
934 assert!(formula.utilization_factor(0.9) > 1.0);
935
936 assert_eq!(formula.variability_factor(1.0, 1.0), 1.0);
938 assert!(formula.variability_factor(2.0, 2.0) > 1.0);
939 }
940
941 #[test]
942 fn test_square_root_law_trait_methods() {
943 let law = SquareRootLaw::new();
944
945 assert!(law.latex().contains("sqrt"));
946 assert_eq!(law.class(), EquationClass::Inventory);
947 assert_eq!(law.name(), "Square Root Law");
948 assert!(!law.description().is_empty());
949 }
950
951 #[test]
952 fn test_square_root_law_default() {
953 let law = SquareRootLaw::default();
954 assert_eq!(law.name(), "Square Root Law");
955 }
956
957 #[test]
958 fn test_square_root_law_variables() {
959 let law = SquareRootLaw::new();
960 let vars = law.variables();
961 assert!(!vars.is_empty());
962 }
963
964 #[test]
965 fn test_square_root_law_citation() {
966 let law = SquareRootLaw::new();
967 let citation = law.citation();
968 assert_eq!(citation.year, 1979); }
970
971 #[test]
972 fn test_square_root_law_validate_consistency() {
973 let law = SquareRootLaw::new();
974
975 let values = vec![
977 ("demand_std", 100.0),
978 ("lead_time", 1.0),
979 ("z_score", 1.96),
980 ("I_safety", 196.0),
981 ];
982 assert!(law.validate_consistency(&values, 0.01).is_ok());
983 }
984
985 #[test]
986 fn test_square_root_law_z_score() {
987 let z_90 = SquareRootLaw::z_score_for_service_level(0.90);
989 assert!((z_90 - 1.28).abs() < 0.01);
990
991 let z_95 = SquareRootLaw::z_score_for_service_level(0.95);
992 assert!((z_95 - 1.65).abs() < 0.01);
993
994 let z_99 = SquareRootLaw::z_score_for_service_level(0.99);
995 assert!((z_99 - 2.33).abs() < 0.01);
996
997 let z_999 = SquareRootLaw::z_score_for_service_level(0.999);
998 assert!((z_999 - 3.09).abs() < 0.01);
999
1000 let z_other = SquareRootLaw::z_score_for_service_level(0.50);
1002 assert!((z_other - 1.96).abs() < 0.01);
1003 }
1004
1005 #[test]
1006 fn test_bullwhip_trait_methods() {
1007 let effect = BullwhipEffect::new();
1008
1009 assert!(effect.latex().contains("L"));
1010 assert_eq!(effect.class(), EquationClass::Inventory);
1011 assert_eq!(effect.name(), "Bullwhip Effect");
1012 assert!(!effect.description().is_empty());
1013 }
1014
1015 #[test]
1016 fn test_bullwhip_default() {
1017 let effect = BullwhipEffect::default();
1018 assert_eq!(effect.name(), "Bullwhip Effect");
1019 }
1020
1021 #[test]
1022 fn test_bullwhip_variables() {
1023 let effect = BullwhipEffect::new();
1024 let vars = effect.variables();
1025 assert!(!vars.is_empty());
1026 }
1027
1028 #[test]
1029 fn test_bullwhip_validate_consistency() {
1030 let effect = BullwhipEffect::new();
1031
1032 let values = vec![
1034 ("lead_time", 1.0),
1035 ("review_period", 1.0),
1036 ("order_variance", 50.0),
1037 ("demand_variance", 10.0),
1038 ];
1039 assert!(effect.validate_consistency(&values, 0.1).is_ok());
1041 }
1042
1043 #[test]
1044 fn test_bullwhip_empty_echelons() {
1045 let effect = BullwhipEffect::new();
1046
1047 let factor = effect.multi_echelon_amplification(&[], &[]);
1049 assert!((factor - 1.0).abs() < f64::EPSILON);
1050 }
1051}