Skip to content

Commit 8d74e15

Browse files
Feature/add wind turbine (#28)
Fixes #27
2 parents 50b0e72 + 0b51ef0 commit 8d74e15

File tree

7 files changed

+230
-14
lines changed

7 files changed

+230
-14
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,4 @@
22

33
## Summary
44

5-
<!-- Here goes a general summary of what this release is about -->
6-
7-
## Upgrading
8-
9-
<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
10-
11-
## New Features
12-
13-
<!-- Here goes the main new features and examples or instructions on how to use them -->
14-
15-
## Bug Fixes
16-
17-
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
5+
- Added Wind Turbine formulas

src/component_category.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ pub(crate) trait CategoryPredicates: Node {
180180
fn is_chp(&self) -> bool {
181181
self.category() == ComponentCategory::Chp
182182
}
183+
184+
fn is_wind_turbine(&self) -> bool {
185+
self.category() == ComponentCategory::WindTurbine
186+
}
183187
}
184188

185189
/// Implement the `CategoryPredicates` trait for all types that implement the

src/graph/formulas.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ where
6464
generators::pv::PVFormulaBuilder::try_new(self, pv_inverter_ids)?.build()
6565
}
6666

67+
/// Returns the wind_turbine formula for the graph.
68+
pub fn wind_turbine_formula(
69+
&self,
70+
wind_turbine_ids: Option<BTreeSet<u64>>,
71+
) -> Result<AggregationFormula, Error> {
72+
generators::wind_turbine::WindTurbineFormulaBuilder::try_new(self, wind_turbine_ids)?
73+
.build()
74+
}
75+
6776
/// Returns the EV charger formula for the graph.
6877
pub fn ev_charger_formula(
6978
&self,

src/graph/formulas/fallback.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ impl FallbackExpr {
114114
/// - Battery Inverter
115115
/// - PV Inverter
116116
/// - EV Charger
117+
/// - Wind Turbine
117118
fn component_fallback<N: Node, E: Edge>(
118119
&self,
119120
graph: &ComponentGraph<N, E>,
@@ -125,6 +126,7 @@ impl FallbackExpr {
125126
&& !component.is_chp()
126127
&& !component.is_pv_inverter()
127128
&& !component.is_ev_charger()
129+
&& !component.is_wind_turbine()
128130
{
129131
return Ok(None);
130132
}

src/graph/formulas/generators.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ pub(super) mod grid_coalesce;
1313
pub(super) mod producer;
1414
pub(super) mod pv;
1515
pub(super) mod pv_ac_coalesce;
16+
pub(super) mod wind_turbine;
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// License: MIT
2+
// Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
//! This module contains the methods for generating producer formulas.
5+
6+
use std::collections::BTreeSet;
7+
8+
use crate::component_category::CategoryPredicates;
9+
use crate::graph::formulas::AggregationFormula;
10+
use crate::graph::formulas::expr::Expr;
11+
use crate::graph::formulas::fallback::FallbackExpr;
12+
use crate::{ComponentGraph, Edge, Error, Node};
13+
14+
pub(crate) struct WindTurbineFormulaBuilder<'a, N, E>
15+
where
16+
N: Node,
17+
E: Edge,
18+
{
19+
graph: &'a ComponentGraph<N, E>,
20+
wind_turbine_ids: BTreeSet<u64>,
21+
}
22+
23+
impl<'a, N, E> WindTurbineFormulaBuilder<'a, N, E>
24+
where
25+
N: Node,
26+
E: Edge,
27+
{
28+
pub fn try_new(
29+
graph: &'a ComponentGraph<N, E>,
30+
wind_turbine_ids: Option<BTreeSet<u64>>,
31+
) -> Result<Self, Error> {
32+
let wind_turbine_ids = if let Some(wind_turbine_ids) = wind_turbine_ids {
33+
wind_turbine_ids
34+
} else {
35+
graph.find_all(
36+
graph.root_id,
37+
|node| node.is_wind_turbine(),
38+
petgraph::Direction::Outgoing,
39+
false,
40+
)?
41+
};
42+
Ok(Self {
43+
graph,
44+
wind_turbine_ids,
45+
})
46+
}
47+
48+
/// Generates the wind_turbine formula.
49+
///
50+
/// This is the sum of all wind_turbines in the graph. If the wind_turbine_ids are provided,
51+
/// only the wind_turbines with the given ids are included in the formula.
52+
pub fn build(self) -> Result<AggregationFormula, Error> {
53+
if self.wind_turbine_ids.is_empty() {
54+
return Ok(AggregationFormula::new(Expr::number(0.0)));
55+
}
56+
57+
for id in &self.wind_turbine_ids {
58+
if !self.graph.component(*id)?.is_wind_turbine() {
59+
return Err(Error::invalid_component(format!(
60+
"Component with id {id} is not a wind turbine."
61+
)));
62+
}
63+
}
64+
65+
FallbackExpr {
66+
prefer_meters: false,
67+
meter_fallback_for_meters: false,
68+
}
69+
.generate(self.graph, self.wind_turbine_ids.clone())
70+
.map(AggregationFormula::new)
71+
}
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use std::collections::BTreeSet;
77+
78+
use crate::{Error, graph::test_utils::ComponentGraphBuilder};
79+
80+
#[test]
81+
fn test_wind_turbine_formula() -> Result<(), Error> {
82+
let mut builder = ComponentGraphBuilder::new();
83+
let grid = builder.grid();
84+
85+
let grid_meter = builder.meter();
86+
builder.connect(grid, grid_meter);
87+
88+
let graph = builder.build(None)?;
89+
let formula = graph.wind_turbine_formula(None)?.to_string();
90+
assert_eq!(formula, "0.0");
91+
92+
// Add a wind_turbine meter with one wind turbine.
93+
let meter_wind_turbine_chain = builder.meter_wind_turbine_chain(1);
94+
builder.connect(grid_meter, meter_wind_turbine_chain);
95+
96+
assert_eq!(grid_meter.component_id(), 1);
97+
assert_eq!(meter_wind_turbine_chain.component_id(), 2);
98+
let graph = builder.build(None)?;
99+
let formula = graph.wind_turbine_formula(None)?.to_string();
100+
assert_eq!(formula, "COALESCE(#3, #2, 0.0)");
101+
102+
// Add a battery meter with one battery inverter and two batteries.
103+
let meter_bat_chain = builder.meter_bat_chain(1, 2);
104+
builder.connect(grid_meter, meter_bat_chain);
105+
106+
assert_eq!(meter_bat_chain.component_id(), 4);
107+
108+
let graph = builder.build(None)?;
109+
let formula = graph.wind_turbine_formula(None)?.to_string();
110+
assert_eq!(formula, "COALESCE(#3, #2, 0.0)");
111+
112+
// Add a wind_turbine meter with two wind_turbine inverters.
113+
let meter_wind_turbine_chain = builder.meter_wind_turbine_chain(2);
114+
builder.connect(grid_meter, meter_wind_turbine_chain);
115+
116+
assert_eq!(meter_wind_turbine_chain.component_id(), 8);
117+
118+
let graph = builder.build(None)?;
119+
let formula = graph.wind_turbine_formula(None)?.to_string();
120+
assert_eq!(
121+
formula,
122+
concat!(
123+
"COALESCE(#3, #2, 0.0) + ",
124+
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0))"
125+
)
126+
);
127+
128+
let formula = graph
129+
.wind_turbine_formula(Some(BTreeSet::from([10, 3])))?
130+
.to_string();
131+
assert_eq!(formula, "COALESCE(#3, #2, 0.0) + COALESCE(#10, 0.0)");
132+
133+
// add a meter direct to the grid with three wind_turbine inverters
134+
let meter_wind_turbine_chain = builder.meter_wind_turbine_chain(3);
135+
builder.connect(grid, meter_wind_turbine_chain);
136+
137+
assert_eq!(meter_wind_turbine_chain.component_id(), 11);
138+
let graph = builder.build(None)?;
139+
let formula = graph.wind_turbine_formula(None)?.to_string();
140+
assert_eq!(
141+
formula,
142+
concat!(
143+
"COALESCE(#3, #2, 0.0) + ",
144+
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
145+
"COALESCE(",
146+
"#14 + #13 + #12, ",
147+
"#11, ",
148+
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
149+
")"
150+
),
151+
);
152+
153+
let formula = graph
154+
.wind_turbine_formula(Some(BTreeSet::from([3, 9, 10, 12, 13])))?
155+
.to_string();
156+
assert_eq!(
157+
formula,
158+
concat!(
159+
"COALESCE(#3, #2, 0.0) + ",
160+
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
161+
"COALESCE(#12, 0.0) + ",
162+
"COALESCE(#13, 0.0)"
163+
)
164+
);
165+
166+
let formula = graph
167+
.wind_turbine_formula(Some(BTreeSet::from([3, 9, 10, 12, 13, 14])))?
168+
.to_string();
169+
assert_eq!(
170+
formula,
171+
concat!(
172+
"COALESCE(#3, #2, 0.0) + ",
173+
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
174+
"COALESCE(",
175+
"#14 + #13 + #12, ",
176+
"#11, ",
177+
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
178+
")"
179+
)
180+
);
181+
182+
let formula = graph
183+
.wind_turbine_formula(Some(BTreeSet::from([10, 14])))?
184+
.to_string();
185+
assert_eq!(formula, "COALESCE(#10, 0.0) + COALESCE(#14, 0.0)");
186+
187+
// Failure cases:
188+
let formula = graph.wind_turbine_formula(Some(BTreeSet::from([8])));
189+
assert_eq!(
190+
formula.unwrap_err().to_string(),
191+
"InvalidComponent: Component with id 8 is not a wind turbine."
192+
);
193+
194+
Ok(())
195+
}
196+
}

src/graph/test_utils.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Copyright © 2024 Frequenz Energy-as-a-Service GmbH
33

44
//! This module is only compiled when running unit tests and contains features
5-
//! that are shared by all tests of the `graph` modue.
5+
//! that are shared by all tests of the `graph` module.
66
//!
77
//! - the `TestComponent` and `TestConnection` types, which implement the `Node`
88
//! and `Edge` traits respectively.
@@ -150,6 +150,11 @@ impl ComponentGraphBuilder {
150150
self.add_component(ComponentCategory::Chp)
151151
}
152152

153+
/// Adds a wind_turbine to the graph and returns its handle.
154+
pub(super) fn wind_turbine(&mut self) -> ComponentHandle {
155+
self.add_component(ComponentCategory::WindTurbine)
156+
}
157+
153158
/// Connects two components in the graph.
154159
pub(super) fn connect(&mut self, from: ComponentHandle, to: ComponentHandle) -> &mut Self {
155160
self.connections
@@ -226,6 +231,17 @@ impl ComponentGraphBuilder {
226231
meter
227232
}
228233

234+
/// Adds a meter, followed by the given number of wind turbines, and
235+
/// returns a handle to the meter.
236+
pub(super) fn meter_wind_turbine_chain(&mut self, num_wind_turbines: usize) -> ComponentHandle {
237+
let meter = self.meter();
238+
for _ in 0..num_wind_turbines {
239+
let wind_turbine = self.wind_turbine();
240+
self.connect(meter, wind_turbine);
241+
}
242+
meter
243+
}
244+
229245
/// Builds and returns the component graph from the components and
230246
/// connections added to the builder.
231247
pub(super) fn build(

0 commit comments

Comments
 (0)