1use rand::prelude::*;
12use rand_chacha::ChaCha8Rng;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
17#[serde(rename_all = "snake_case")]
18pub enum DriftType {
19 #[default]
21 Gradual,
22 Sudden,
24 Recurring,
26 Mixed,
28 Regime,
30 EconomicCycle,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37#[derive(Default)]
38pub enum RegimeChangeType {
39 Acquisition,
41 Divestiture,
43 PriceIncrease,
45 PriceDecrease,
47 ProductLaunch,
49 ProductDiscontinuation,
51 #[default]
53 PolicyChange,
54 CompetitorEntry,
56 Custom,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct RegimeEffect {
63 pub field: String,
65 pub multiplier: f64,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct RegimeChange {
72 pub period: u32,
74 pub change_type: RegimeChangeType,
76 #[serde(default)]
78 pub description: Option<String>,
79 #[serde(default)]
81 pub effects: Vec<RegimeEffect>,
82 #[serde(default)]
84 pub transition_periods: u32,
85}
86
87impl RegimeChange {
88 pub fn new(period: u32, change_type: RegimeChangeType) -> Self {
90 Self {
91 period,
92 change_type,
93 description: None,
94 effects: Vec::new(),
95 transition_periods: 0,
96 }
97 }
98
99 pub fn volume_multiplier(&self) -> f64 {
101 match self.change_type {
102 RegimeChangeType::Acquisition => 1.35,
103 RegimeChangeType::Divestiture => 0.70,
104 RegimeChangeType::PriceIncrease => 0.95,
105 RegimeChangeType::PriceDecrease => 1.10,
106 RegimeChangeType::ProductLaunch => 1.20,
107 RegimeChangeType::ProductDiscontinuation => 0.85,
108 RegimeChangeType::PolicyChange => 1.0,
109 RegimeChangeType::CompetitorEntry => 0.90,
110 RegimeChangeType::Custom => self
111 .effects
112 .iter()
113 .find(|e| e.field == "transaction_volume")
114 .map(|e| e.multiplier)
115 .unwrap_or(1.0),
116 }
117 }
118
119 pub fn amount_mean_multiplier(&self) -> f64 {
121 match self.change_type {
122 RegimeChangeType::Acquisition => 1.15,
123 RegimeChangeType::Divestiture => 0.90,
124 RegimeChangeType::PriceIncrease => 1.25,
125 RegimeChangeType::PriceDecrease => 0.80,
126 RegimeChangeType::ProductLaunch => 0.90, RegimeChangeType::ProductDiscontinuation => 1.10, RegimeChangeType::PolicyChange => 1.0,
129 RegimeChangeType::CompetitorEntry => 0.95,
130 RegimeChangeType::Custom => self
131 .effects
132 .iter()
133 .find(|e| e.field == "amount_mean")
134 .map(|e| e.multiplier)
135 .unwrap_or(1.0),
136 }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct EconomicCycleConfig {
143 pub enabled: bool,
145 #[serde(default = "default_cycle_length")]
147 pub cycle_length: u32,
148 #[serde(default = "default_amplitude")]
150 pub amplitude: f64,
151 #[serde(default)]
153 pub phase_offset: u32,
154 #[serde(default)]
156 pub recession_periods: Vec<u32>,
157 #[serde(default = "default_recession_severity")]
159 pub recession_severity: f64,
160}
161
162fn default_cycle_length() -> u32 {
163 48 }
165
166fn default_amplitude() -> f64 {
167 0.15 }
169
170fn default_recession_severity() -> f64 {
171 0.75 }
173
174impl Default for EconomicCycleConfig {
175 fn default() -> Self {
176 Self {
177 enabled: false,
178 cycle_length: 48,
179 amplitude: 0.15,
180 phase_offset: 0,
181 recession_periods: Vec::new(),
182 recession_severity: 0.75,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
189#[serde(rename_all = "snake_case")]
190pub enum ParameterDriftType {
191 #[default]
193 Linear,
194 Exponential,
196 Logistic,
198 Step,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ParameterDrift {
205 pub parameter: String,
207 pub drift_type: ParameterDriftType,
209 pub initial_value: f64,
211 pub target_or_rate: f64,
213 #[serde(default)]
215 pub start_period: u32,
216 #[serde(default)]
218 pub end_period: Option<u32>,
219 #[serde(default = "default_steepness")]
221 pub steepness: f64,
222}
223
224fn default_steepness() -> f64 {
225 0.1
226}
227
228impl Default for ParameterDrift {
229 fn default() -> Self {
230 Self {
231 parameter: String::new(),
232 drift_type: ParameterDriftType::Linear,
233 initial_value: 1.0,
234 target_or_rate: 0.01,
235 start_period: 0,
236 end_period: None,
237 steepness: 0.1,
238 }
239 }
240}
241
242impl ParameterDrift {
243 pub fn value_at(&self, period: u32) -> f64 {
245 if period < self.start_period {
246 return self.initial_value;
247 }
248
249 let effective_period = period - self.start_period;
250
251 match self.drift_type {
252 ParameterDriftType::Linear => {
253 self.initial_value + self.target_or_rate * (effective_period as f64)
254 }
255 ParameterDriftType::Exponential => {
256 self.initial_value * (1.0 + self.target_or_rate).powi(effective_period as i32)
257 }
258 ParameterDriftType::Logistic => {
259 let end_period = self.end_period.unwrap_or(self.start_period + 24);
260 let midpoint = (self.start_period + end_period) as f64 / 2.0;
261 let t = period as f64;
262 let range = self.target_or_rate - self.initial_value;
263 self.initial_value + range / (1.0 + (-self.steepness * (t - midpoint)).exp())
264 }
265 ParameterDriftType::Step => {
266 if let Some(end) = self.end_period {
267 if period >= end {
268 return self.target_or_rate;
269 }
270 }
271 self.initial_value
272 }
273 }
274 }
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct DriftConfig {
280 pub enabled: bool,
282 pub amount_mean_drift: f64,
284 pub amount_variance_drift: f64,
286 pub anomaly_rate_drift: f64,
288 pub concept_drift_rate: f64,
290 pub sudden_drift_probability: f64,
292 pub sudden_drift_magnitude: f64,
294 pub seasonal_drift: bool,
296 pub drift_start_period: u32,
298 pub drift_type: DriftType,
300 #[serde(default)]
302 pub regime_changes: Vec<RegimeChange>,
303 #[serde(default)]
305 pub economic_cycle: EconomicCycleConfig,
306 #[serde(default)]
308 pub parameter_drifts: Vec<ParameterDrift>,
309}
310
311impl Default for DriftConfig {
312 fn default() -> Self {
313 Self {
314 enabled: false,
315 amount_mean_drift: 0.02,
316 amount_variance_drift: 0.0,
317 anomaly_rate_drift: 0.0,
318 concept_drift_rate: 0.01,
319 sudden_drift_probability: 0.0,
320 sudden_drift_magnitude: 2.0,
321 seasonal_drift: false,
322 drift_start_period: 0,
323 drift_type: DriftType::Gradual,
324 regime_changes: Vec::new(),
325 economic_cycle: EconomicCycleConfig::default(),
326 parameter_drifts: Vec::new(),
327 }
328 }
329}
330
331impl DriftConfig {
332 pub fn with_regime_changes(regime_changes: Vec<RegimeChange>) -> Self {
334 Self {
335 enabled: true,
336 drift_type: DriftType::Regime,
337 regime_changes,
338 ..Default::default()
339 }
340 }
341
342 pub fn with_economic_cycle(cycle_config: EconomicCycleConfig) -> Self {
344 Self {
345 enabled: true,
346 drift_type: DriftType::EconomicCycle,
347 economic_cycle: cycle_config,
348 ..Default::default()
349 }
350 }
351}
352
353#[derive(Debug, Clone, Default)]
355pub struct DriftAdjustments {
356 pub amount_mean_multiplier: f64,
358 pub amount_variance_multiplier: f64,
360 pub anomaly_rate_adjustment: f64,
362 pub concept_drift_factor: f64,
364 pub sudden_drift_occurred: bool,
366 pub seasonal_factor: f64,
368 pub volume_multiplier: f64,
370 pub economic_cycle_factor: f64,
372 pub in_recession: bool,
374 pub active_regime_changes: Vec<RegimeChangeType>,
376 pub parameter_values: std::collections::HashMap<String, f64>,
378}
379
380impl DriftAdjustments {
381 pub fn none() -> Self {
383 Self {
384 amount_mean_multiplier: 1.0,
385 amount_variance_multiplier: 1.0,
386 anomaly_rate_adjustment: 0.0,
387 concept_drift_factor: 0.0,
388 sudden_drift_occurred: false,
389 seasonal_factor: 1.0,
390 volume_multiplier: 1.0,
391 economic_cycle_factor: 1.0,
392 in_recession: false,
393 active_regime_changes: Vec::new(),
394 parameter_values: std::collections::HashMap::new(),
395 }
396 }
397
398 pub fn combined_amount_multiplier(&self) -> f64 {
400 self.amount_mean_multiplier * self.seasonal_factor * self.economic_cycle_factor
401 }
402
403 pub fn combined_volume_multiplier(&self) -> f64 {
405 self.volume_multiplier * self.seasonal_factor * self.economic_cycle_factor
406 }
407}
408
409#[derive(Clone)]
411pub struct DriftController {
412 config: DriftConfig,
413 rng: ChaCha8Rng,
414 sudden_drift_periods: Vec<u32>,
416 total_periods: u32,
418}
419
420impl DriftController {
421 pub fn new(config: DriftConfig, seed: u64, total_periods: u32) -> Self {
423 let mut controller = Self {
424 config,
425 rng: ChaCha8Rng::seed_from_u64(seed),
426 sudden_drift_periods: Vec::new(),
427 total_periods,
428 };
429
430 if controller.config.enabled
432 && (controller.config.drift_type == DriftType::Sudden
433 || controller.config.drift_type == DriftType::Mixed)
434 {
435 controller.precompute_sudden_drifts();
436 }
437
438 controller
439 }
440
441 fn precompute_sudden_drifts(&mut self) {
443 for period in 0..self.total_periods {
444 if period >= self.config.drift_start_period
445 && self.rng.random::<f64>() < self.config.sudden_drift_probability
446 {
447 self.sudden_drift_periods.push(period);
448 }
449 }
450 }
451
452 pub fn is_enabled(&self) -> bool {
454 self.config.enabled
455 }
456
457 pub fn compute_adjustments(&self, period: u32) -> DriftAdjustments {
459 if !self.config.enabled {
460 return DriftAdjustments::none();
461 }
462
463 if period < self.config.drift_start_period {
465 return DriftAdjustments::none();
466 }
467
468 let effective_period = period - self.config.drift_start_period;
469 let mut adjustments = DriftAdjustments::none();
470
471 match self.config.drift_type {
472 DriftType::Gradual => {
473 self.apply_gradual_drift(&mut adjustments, effective_period);
474 }
475 DriftType::Sudden => {
476 self.apply_sudden_drift(&mut adjustments, period);
477 }
478 DriftType::Recurring => {
479 self.apply_recurring_drift(&mut adjustments, effective_period);
480 }
481 DriftType::Mixed => {
482 self.apply_gradual_drift(&mut adjustments, effective_period);
484 self.apply_sudden_drift(&mut adjustments, period);
485 }
486 DriftType::Regime => {
487 self.apply_regime_drift(&mut adjustments, period);
488 }
489 DriftType::EconomicCycle => {
490 self.apply_economic_cycle(&mut adjustments, period);
491 }
492 }
493
494 if self.config.seasonal_drift {
496 adjustments.seasonal_factor = self.compute_seasonal_factor(period);
497 }
498
499 self.apply_parameter_drifts(&mut adjustments, period);
501
502 adjustments
503 }
504
505 fn apply_gradual_drift(&self, adjustments: &mut DriftAdjustments, effective_period: u32) {
507 let p = effective_period as f64;
508
509 adjustments.amount_mean_multiplier = (1.0 + self.config.amount_mean_drift).powf(p);
511
512 adjustments.amount_variance_multiplier = (1.0 + self.config.amount_variance_drift).powf(p);
513
514 adjustments.anomaly_rate_adjustment = self.config.anomaly_rate_drift * p;
516
517 adjustments.concept_drift_factor = (self.config.concept_drift_rate * p).min(1.0);
519 }
520
521 fn apply_sudden_drift(&self, adjustments: &mut DriftAdjustments, period: u32) {
523 let events_occurred: usize = self
525 .sudden_drift_periods
526 .iter()
527 .filter(|&&p| p <= period)
528 .count();
529
530 if events_occurred > 0 {
531 adjustments.sudden_drift_occurred = self.sudden_drift_periods.contains(&period);
532
533 let cumulative_magnitude = self
535 .config
536 .sudden_drift_magnitude
537 .powi(events_occurred as i32);
538
539 adjustments.amount_mean_multiplier *= cumulative_magnitude;
540 adjustments.amount_variance_multiplier *= cumulative_magnitude.sqrt();
541 }
543 }
544
545 fn apply_recurring_drift(&self, adjustments: &mut DriftAdjustments, effective_period: u32) {
547 let cycle_position = (effective_period % 12) as f64;
549 let cycle_radians = (cycle_position / 12.0) * 2.0 * std::f64::consts::PI;
550
551 let seasonal_amplitude = self.config.concept_drift_rate;
553 adjustments.amount_mean_multiplier = 1.0 + seasonal_amplitude * cycle_radians.sin();
554
555 adjustments.amount_variance_multiplier =
557 1.0 + (seasonal_amplitude * 0.5) * (cycle_radians + std::f64::consts::FRAC_PI_2).sin();
558 }
559
560 fn compute_seasonal_factor(&self, period: u32) -> f64 {
562 let month = period % 12;
564
565 match month {
567 0 | 1 => 0.85, 2 => 0.90, 3 | 4 => 0.95, 5 => 1.0, 6 | 7 => 0.95, 8 => 1.0, 9 => 1.10, 10 => 1.20, 11 => 1.30, _ => 1.0,
577 }
578 }
579
580 pub fn sudden_drift_periods(&self) -> &[u32] {
582 &self.sudden_drift_periods
583 }
584
585 pub fn config(&self) -> &DriftConfig {
587 &self.config
588 }
589
590 fn apply_regime_drift(&self, adjustments: &mut DriftAdjustments, period: u32) {
592 let mut volume_mult = 1.0;
593 let mut amount_mult = 1.0;
594
595 for regime_change in &self.config.regime_changes {
596 if period >= regime_change.period {
597 let periods_since = period - regime_change.period;
599 let transition_factor = if regime_change.transition_periods == 0 {
600 1.0
601 } else {
602 (periods_since as f64 / regime_change.transition_periods as f64).min(1.0)
603 };
604
605 let vol_change = regime_change.volume_multiplier() - 1.0;
607 let amt_change = regime_change.amount_mean_multiplier() - 1.0;
608
609 volume_mult *= 1.0 + vol_change * transition_factor;
610 amount_mult *= 1.0 + amt_change * transition_factor;
611
612 adjustments
613 .active_regime_changes
614 .push(regime_change.change_type);
615 }
616 }
617
618 adjustments.volume_multiplier = volume_mult;
619 adjustments.amount_mean_multiplier *= amount_mult;
620 }
621
622 fn apply_economic_cycle(&self, adjustments: &mut DriftAdjustments, period: u32) {
624 let cycle = &self.config.economic_cycle;
625 if !cycle.enabled {
626 return;
627 }
628
629 let adjusted_period = period + cycle.phase_offset;
631 let cycle_position =
632 (adjusted_period % cycle.cycle_length) as f64 / cycle.cycle_length as f64;
633
634 let cycle_radians = cycle_position * 2.0 * std::f64::consts::PI;
636 let cycle_factor = 1.0 + cycle.amplitude * cycle_radians.sin();
637
638 let in_recession = cycle.recession_periods.contains(&period);
640 adjustments.in_recession = in_recession;
641
642 let final_factor = if in_recession {
644 cycle_factor * cycle.recession_severity
645 } else {
646 cycle_factor
647 };
648
649 adjustments.economic_cycle_factor = final_factor;
650 adjustments.amount_mean_multiplier *= final_factor;
651 adjustments.volume_multiplier = final_factor;
652 }
653
654 fn apply_parameter_drifts(&self, adjustments: &mut DriftAdjustments, period: u32) {
656 for param_drift in &self.config.parameter_drifts {
657 let value = param_drift.value_at(period);
658 adjustments
659 .parameter_values
660 .insert(param_drift.parameter.clone(), value);
661 }
662 }
663
664 pub fn regime_changes_until(&self, period: u32) -> Vec<&RegimeChange> {
666 self.config
667 .regime_changes
668 .iter()
669 .filter(|rc| rc.period <= period)
670 .collect()
671 }
672}
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677
678 #[test]
679 fn test_no_drift_when_disabled() {
680 let config = DriftConfig::default();
681 let controller = DriftController::new(config, 42, 12);
682
683 let adjustments = controller.compute_adjustments(6);
684 assert!(!controller.is_enabled());
685 assert!((adjustments.amount_mean_multiplier - 1.0).abs() < 0.001);
686 assert!((adjustments.anomaly_rate_adjustment).abs() < 0.001);
687 }
688
689 #[test]
690 fn test_gradual_drift() {
691 let config = DriftConfig {
692 enabled: true,
693 amount_mean_drift: 0.02,
694 anomaly_rate_drift: 0.001,
695 drift_type: DriftType::Gradual,
696 ..Default::default()
697 };
698 let controller = DriftController::new(config, 42, 12);
699
700 let adj0 = controller.compute_adjustments(0);
702 assert!((adj0.amount_mean_multiplier - 1.0).abs() < 0.001);
703
704 let adj6 = controller.compute_adjustments(6);
706 assert!(adj6.amount_mean_multiplier > 1.10);
707 assert!(adj6.amount_mean_multiplier < 1.15);
708
709 let adj12 = controller.compute_adjustments(12);
711 assert!(adj12.amount_mean_multiplier > 1.20);
712 assert!(adj12.amount_mean_multiplier < 1.30);
713 }
714
715 #[test]
716 fn test_drift_start_period() {
717 let config = DriftConfig {
718 enabled: true,
719 amount_mean_drift: 0.02,
720 drift_start_period: 3,
721 drift_type: DriftType::Gradual,
722 ..Default::default()
723 };
724 let controller = DriftController::new(config, 42, 12);
725
726 let adj2 = controller.compute_adjustments(2);
728 assert!((adj2.amount_mean_multiplier - 1.0).abs() < 0.001);
729
730 let adj3 = controller.compute_adjustments(3);
732 assert!((adj3.amount_mean_multiplier - 1.0).abs() < 0.001);
733
734 let adj6 = controller.compute_adjustments(6);
736 assert!(adj6.amount_mean_multiplier > 1.0);
737 }
738
739 #[test]
740 fn test_seasonal_factor() {
741 let config = DriftConfig {
742 enabled: true,
743 seasonal_drift: true,
744 drift_type: DriftType::Gradual,
745 ..Default::default()
746 };
747 let controller = DriftController::new(config, 42, 12);
748
749 let adj_dec = controller.compute_adjustments(11);
751 assert!(adj_dec.seasonal_factor > 1.2);
752
753 let adj_jan = controller.compute_adjustments(0);
755 assert!(adj_jan.seasonal_factor < 0.9);
756 }
757
758 #[test]
759 fn test_sudden_drift_reproducibility() {
760 let config = DriftConfig {
761 enabled: true,
762 sudden_drift_probability: 0.5,
763 sudden_drift_magnitude: 1.5,
764 drift_type: DriftType::Sudden,
765 ..Default::default()
766 };
767
768 let controller1 = DriftController::new(config.clone(), 42, 12);
770 let controller2 = DriftController::new(config, 42, 12);
771
772 assert_eq!(
773 controller1.sudden_drift_periods(),
774 controller2.sudden_drift_periods()
775 );
776 }
777
778 #[test]
779 fn test_regime_change() {
780 let config = DriftConfig {
781 enabled: true,
782 drift_type: DriftType::Regime,
783 regime_changes: vec![RegimeChange::new(6, RegimeChangeType::Acquisition)],
784 ..Default::default()
785 };
786 let controller = DriftController::new(config, 42, 12);
787
788 let adj_before = controller.compute_adjustments(5);
790 assert!((adj_before.volume_multiplier - 1.0).abs() < 0.001);
791
792 let adj_after = controller.compute_adjustments(6);
794 assert!(adj_after.volume_multiplier > 1.3); assert!(adj_after.amount_mean_multiplier > 1.1); assert!(adj_after
797 .active_regime_changes
798 .contains(&RegimeChangeType::Acquisition));
799 }
800
801 #[test]
802 fn test_regime_change_gradual_transition() {
803 let config = DriftConfig {
804 enabled: true,
805 drift_type: DriftType::Regime,
806 regime_changes: vec![RegimeChange {
807 period: 6,
808 change_type: RegimeChangeType::PriceIncrease,
809 description: None,
810 effects: vec![],
811 transition_periods: 4, }],
813 ..Default::default()
814 };
815 let controller = DriftController::new(config, 42, 12);
816
817 let adj_start = controller.compute_adjustments(6);
819 let adj_mid = controller.compute_adjustments(8);
821 let adj_end = controller.compute_adjustments(10);
823
824 assert!(adj_start.amount_mean_multiplier < adj_mid.amount_mean_multiplier);
826 assert!(adj_mid.amount_mean_multiplier < adj_end.amount_mean_multiplier);
827 }
828
829 #[test]
830 fn test_economic_cycle() {
831 let config = DriftConfig {
832 enabled: true,
833 drift_type: DriftType::EconomicCycle,
834 economic_cycle: EconomicCycleConfig {
835 enabled: true,
836 cycle_length: 12, amplitude: 0.20,
838 phase_offset: 0,
839 recession_periods: vec![],
840 recession_severity: 0.75,
841 },
842 ..Default::default()
843 };
844 let controller = DriftController::new(config, 42, 24);
845
846 let adj_0 = controller.compute_adjustments(0);
848 let adj_3 = controller.compute_adjustments(3);
850 let adj_9 = controller.compute_adjustments(9);
852
853 assert!(adj_3.economic_cycle_factor > adj_0.economic_cycle_factor);
855 assert!(adj_9.economic_cycle_factor < adj_0.economic_cycle_factor);
857 }
858
859 #[test]
860 fn test_economic_cycle_recession() {
861 let config = DriftConfig {
862 enabled: true,
863 drift_type: DriftType::EconomicCycle,
864 economic_cycle: EconomicCycleConfig {
865 enabled: true,
866 cycle_length: 12,
867 amplitude: 0.10,
868 phase_offset: 0,
869 recession_periods: vec![6, 7, 8],
870 recession_severity: 0.70,
871 },
872 ..Default::default()
873 };
874 let controller = DriftController::new(config, 42, 12);
875
876 let adj_5 = controller.compute_adjustments(5);
878 assert!(!adj_5.in_recession);
879
880 let adj_7 = controller.compute_adjustments(7);
882 assert!(adj_7.in_recession);
883 assert!(adj_7.economic_cycle_factor < adj_5.economic_cycle_factor);
884 }
885
886 #[test]
887 fn test_parameter_drift_linear() {
888 let config = DriftConfig {
889 enabled: true,
890 drift_type: DriftType::Gradual,
891 parameter_drifts: vec![ParameterDrift {
892 parameter: "discount_rate".to_string(),
893 drift_type: ParameterDriftType::Linear,
894 initial_value: 0.02,
895 target_or_rate: 0.001, start_period: 0,
897 end_period: None,
898 steepness: 0.1,
899 }],
900 ..Default::default()
901 };
902 let controller = DriftController::new(config, 42, 12);
903
904 let adj_0 = controller.compute_adjustments(0);
905 let adj_6 = controller.compute_adjustments(6);
906
907 let rate_0 = adj_0.parameter_values.get("discount_rate").unwrap();
908 let rate_6 = adj_6.parameter_values.get("discount_rate").unwrap();
909
910 assert!((rate_0 - 0.02).abs() < 0.0001);
912 assert!((rate_6 - 0.026).abs() < 0.0001);
913 }
914
915 #[test]
916 fn test_parameter_drift_logistic() {
917 let config = DriftConfig {
918 enabled: true,
919 drift_type: DriftType::Gradual,
920 parameter_drifts: vec![ParameterDrift {
921 parameter: "market_share".to_string(),
922 drift_type: ParameterDriftType::Logistic,
923 initial_value: 0.10, target_or_rate: 0.40, start_period: 0,
926 end_period: Some(24), steepness: 0.3,
928 }],
929 ..Default::default()
930 };
931 let controller = DriftController::new(config, 42, 36);
932
933 let adj_0 = controller.compute_adjustments(0);
934 let adj_12 = controller.compute_adjustments(12);
935 let adj_24 = controller.compute_adjustments(24);
936
937 let share_0 = *adj_0.parameter_values.get("market_share").unwrap();
938 let share_12 = *adj_12.parameter_values.get("market_share").unwrap();
939 let share_24 = *adj_24.parameter_values.get("market_share").unwrap();
940
941 assert!(share_0 < 0.15); assert!(share_12 > 0.20 && share_12 < 0.30); assert!(share_24 > 0.35); }
946
947 #[test]
948 fn test_combined_drift_adjustments() {
949 let adj = DriftAdjustments {
950 amount_mean_multiplier: 1.2,
951 seasonal_factor: 1.1,
952 economic_cycle_factor: 0.9,
953 volume_multiplier: 1.3,
954 ..DriftAdjustments::none()
955 };
956
957 assert!((adj.combined_amount_multiplier() - 1.188).abs() < 0.001);
959
960 assert!((adj.combined_volume_multiplier() - 1.287).abs() < 0.001);
962 }
963
964 #[test]
965 fn test_regime_change_volume_multipliers() {
966 assert!(RegimeChange::new(0, RegimeChangeType::Acquisition).volume_multiplier() > 1.0);
967 assert!(RegimeChange::new(0, RegimeChangeType::Divestiture).volume_multiplier() < 1.0);
968 assert!(
969 (RegimeChange::new(0, RegimeChangeType::PolicyChange).volume_multiplier() - 1.0).abs()
970 < 0.001
971 );
972 }
973}