Skip to content

Commit b51524a

Browse files
FIX: Add Wind Turbine bindings
Fixes #33 Signed-off-by: Nicolás Hatcher <[email protected]>
1 parent 56d19fa commit b51524a

File tree

7 files changed

+84
-5
lines changed

7 files changed

+84
-5
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "frequenz-microgrid-component-graph-python-bindings"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2024"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -10,4 +10,4 @@ crate-type = ["cdylib"]
1010

1111
[dependencies]
1212
pyo3 = "0.27.1"
13-
frequenz-microgrid-component-graph = "0.2.0"
13+
frequenz-microgrid-component-graph = "0.3.0"

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
## New Features
44

55
- Grid formulas now use single successor meters as fallback components for meters attached to the grid.
6+
- Adds Wind Turbine bindings

python/frequenz/microgrid_component_graph/__init__.pyi

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,22 @@ class ComponentGraph(Generic[ComponentT, ConnectionT, ComponentIdT]):
292292
are not EV chargers.
293293
"""
294294

295+
def wind_turbine_formula(self, wind_turbine_ids: Set[ComponentIdT] | None) -> str:
296+
"""Generate the wind turbine formula for this component graph.
297+
298+
Args:
299+
wind_turbine_ids: The set of wind turbine component IDs to include in
300+
the formula. If `None`, all wind turbines in the graph will be
301+
included.
302+
303+
Returns:
304+
The wind turbine formula as a string.
305+
306+
Raises:
307+
FormulaGenerationError: if the given component IDs don't exist or
308+
are not wind turbines.
309+
"""
310+
295311
def grid_coalesce_formula(self) -> str:
296312
"""Generate the grid coalesce formula for this component graph.
297313

src/category.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct ComponentClasses<'py> {
1010
battery: Bound<'py, PyAny>,
1111
ev_charger: Bound<'py, PyAny>,
1212
chp: Bound<'py, PyAny>,
13+
wind_turbine: Bound<'py, PyAny>,
1314
battery_inverter: Bound<'py, PyAny>,
1415
solar_inverter: Bound<'py, PyAny>,
1516
hybrid_inverter: Bound<'py, PyAny>,
@@ -33,6 +34,7 @@ impl<'py> ComponentClasses<'py> {
3334
battery: module.getattr("Battery")?,
3435
ev_charger: module.getattr("EvCharger")?,
3536
chp: module.getattr("Chp")?,
37+
wind_turbine: module.getattr("WindTurbine")?,
3638
battery_inverter: module.getattr("BatteryInverter")?,
3739
solar_inverter: module.getattr("SolarInverter")?,
3840
hybrid_inverter: module.getattr("HybridInverter")?,
@@ -86,6 +88,10 @@ pub(crate) fn category_from_python_component(
8688
|| object.is(&comp_classes.unspecified_component)
8789
{
8890
Ok(cg::ComponentCategory::Unspecified)
91+
} else if object.is_instance(&comp_classes.wind_turbine)?
92+
|| object.is(&comp_classes.wind_turbine)
93+
{
94+
Ok(cg::ComponentCategory::WindTurbine)
8995
} else {
9096
Err(exceptions::PyValueError::new_err(format!(
9197
"Unsupported component category: {:?}",

src/graph.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,18 @@ impl ComponentGraph {
278278
.map_err(|e| PyErr::new::<FormulaGenerationError, _>(e.to_string()))
279279
}
280280

281+
#[pyo3(signature = (wind_turbine_ids=None))]
282+
fn wind_turbine_formula(
283+
&self,
284+
py: Python<'_>,
285+
wind_turbine_ids: Option<Bound<'_, PyAny>>,
286+
) -> PyResult<String> {
287+
self.graph
288+
.wind_turbine_formula(extract_ids(py, wind_turbine_ids)?)
289+
.map(|f| f.to_string())
290+
.map_err(|e| PyErr::new::<FormulaGenerationError, _>(e.to_string()))
291+
}
292+
281293
fn grid_coalesce_formula(&self) -> PyResult<String> {
282294
self.graph
283295
.grid_coalesce_formula()

tests/test_microgrid_component_graph.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
GridConnectionPoint,
1212
Meter,
1313
SolarInverter,
14+
WindTurbine,
1415
)
1516

1617
from frequenz import microgrid_component_graph
@@ -77,3 +78,46 @@ def test_graph_creation() -> None:
7778
Meter(id=ComponentId(3), microgrid_id=MicrogridId(1)),
7879
SolarInverter(id=ComponentId(4), microgrid_id=MicrogridId(1)),
7980
}
81+
82+
83+
def test_wind_turbine_graph() -> None:
84+
"""Test graph creation and formula generation for Wind Turbines."""
85+
graph: microgrid_component_graph.ComponentGraph[
86+
Component, ComponentConnection, ComponentId
87+
] = microgrid_component_graph.ComponentGraph(
88+
components={
89+
GridConnectionPoint(
90+
id=ComponentId(1),
91+
microgrid_id=MicrogridId(1),
92+
rated_fuse_current=100,
93+
),
94+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1)),
95+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1)),
96+
},
97+
connections={
98+
# Grid -> Meter -> Wind Turbine
99+
ComponentConnection(source=ComponentId(1), destination=ComponentId(2)),
100+
ComponentConnection(source=ComponentId(2), destination=ComponentId(3)),
101+
},
102+
)
103+
104+
# 1. Test Component Retrieval
105+
assert graph.components(matching_types=WindTurbine) == {
106+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1))
107+
}
108+
109+
# 2. Test Combined Retrieval (Meter + Wind)
110+
assert graph.components(matching_types=[Meter, WindTurbine]) == {
111+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1)),
112+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1)),
113+
}
114+
115+
# 3. Test Formula Generation
116+
# The formula usually references the Meter (ID 2) measuring the Turbine (ID 3).
117+
assert graph.wind_turbine_formula(wind_turbine_ids={ComponentId(3)}) == "COALESCE(#3, #2, 0.0)"
118+
119+
# 4. Test Topology (Successors/Predecessors)
120+
# The predecessor of the Wind Turbine (3) should be the Meter (2)
121+
assert graph.predecessors(ComponentId(3)) == {
122+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1))
123+
}

0 commit comments

Comments
 (0)