vpp_sim/sim/
feeder.rs

1/// A simple feeder model that aggregates device power into net load.
2///
3/// Net load convention:
4/// - Positive values increase feeder load (consumption)
5/// - Negative values reduce feeder load (generation)
6#[derive(Debug, Clone)]
7pub struct Feeder {
8    name: &'static str,
9    net_kw: f32,
10    max_import_kw: f32,
11    max_export_kw: f32,
12}
13
14impl Feeder {
15    #[cfg(test)]
16    pub fn new(name: &'static str) -> Self {
17        Self {
18            name,
19            net_kw: 0.0,
20            max_import_kw: f32::INFINITY,
21            max_export_kw: f32::INFINITY,
22        }
23    }
24
25    pub fn with_limits(name: &'static str, max_import_kw: f32, max_export_kw: f32) -> Self {
26        assert!(max_import_kw >= 0.0);
27        assert!(max_export_kw >= 0.0);
28
29        Self {
30            name,
31            net_kw: 0.0,
32            max_import_kw,
33            max_export_kw,
34        }
35    }
36
37    pub fn reset(&mut self) {
38        self.net_kw = 0.0;
39    }
40
41    /// Adds a signed contribution to feeder net load.
42    pub fn add_net_kw(&mut self, kw: f32) {
43        self.net_kw += kw;
44    }
45
46    pub fn net_kw(&self) -> f32 {
47        self.net_kw
48    }
49
50    pub fn max_import_kw(&self) -> f32 {
51        self.max_import_kw
52    }
53
54    pub fn max_export_kw(&self) -> f32 {
55        self.max_export_kw
56    }
57
58    pub fn min_net_kw(&self) -> f32 {
59        -self.max_export_kw
60    }
61
62    pub fn max_net_kw(&self) -> f32 {
63        self.max_import_kw
64    }
65
66    pub fn within_limits(&self) -> bool {
67        self.net_kw >= self.min_net_kw() && self.net_kw <= self.max_net_kw()
68    }
69
70    pub fn name(&self) -> &'static str {
71        self.name
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_new_feeder_defaults() {
81        let feeder = Feeder::new("FeederA");
82        assert_eq!(feeder.name(), "FeederA");
83        assert_eq!(feeder.net_kw(), 0.0);
84        assert_eq!(feeder.max_import_kw(), f32::INFINITY);
85        assert_eq!(feeder.max_export_kw(), f32::INFINITY);
86    }
87
88    #[test]
89    fn test_with_limits() {
90        let feeder = Feeder::with_limits("FeederA", 5.0, 3.0);
91        assert_eq!(feeder.max_import_kw(), 5.0);
92        assert_eq!(feeder.max_export_kw(), 3.0);
93        assert_eq!(feeder.min_net_kw(), -3.0);
94        assert_eq!(feeder.max_net_kw(), 5.0);
95    }
96
97    #[test]
98    fn test_aggregate_net_kw() {
99        let mut feeder = Feeder::new("FeederA");
100        feeder.add_net_kw(3.5); // load
101        feeder.add_net_kw(-1.0); // generation
102        feeder.add_net_kw(0.5);
103        assert!((feeder.net_kw() - 3.0).abs() < 1e-6);
104    }
105
106    #[test]
107    fn test_reset_clears_net_kw() {
108        let mut feeder = Feeder::new("FeederA");
109        feeder.add_net_kw(2.0);
110        feeder.reset();
111        assert_eq!(feeder.net_kw(), 0.0);
112    }
113
114    #[test]
115    fn test_within_limits() {
116        let mut feeder = Feeder::with_limits("FeederA", 4.0, 2.0);
117        feeder.add_net_kw(3.5);
118        assert!(feeder.within_limits());
119
120        feeder.add_net_kw(1.0);
121        assert!(!feeder.within_limits());
122
123        feeder.reset();
124        feeder.add_net_kw(-2.5);
125        assert!(!feeder.within_limits());
126    }
127}