Skip to content

Commit fe57a12

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

File tree

7 files changed

+82
-5
lines changed

7 files changed

+82
-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: 42 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,44 @@ 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+
components={
87+
GridConnectionPoint(
88+
id=ComponentId(1),
89+
microgrid_id=MicrogridId(1),
90+
rated_fuse_current=100,
91+
),
92+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1)),
93+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1)),
94+
},
95+
connections={
96+
# Grid -> Meter -> Wind Turbine
97+
ComponentConnection(source=ComponentId(1), destination=ComponentId(2)),
98+
ComponentConnection(source=ComponentId(2), destination=ComponentId(3)),
99+
},
100+
)
101+
102+
# 1. Test Component Retrieval
103+
assert graph.components(matching_types=WindTurbine) == {
104+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1))
105+
}
106+
107+
# 2. Test Combined Retrieval (Meter + Wind)
108+
assert graph.components(matching_types=[Meter, WindTurbine]) == {
109+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1)),
110+
WindTurbine(id=ComponentId(3), microgrid_id=MicrogridId(1)),
111+
}
112+
113+
# 3. Test Formula Generation
114+
# The formula usually references the Meter (ID 2) measuring the Turbine (ID 3).
115+
assert graph.wind_turbine_formula() == "COALESCE(#3, #2, 0.0)"
116+
117+
# 4. Test Topology (Successors/Predecessors)
118+
# The predecessor of the Wind Turbine (3) should be the Meter (2)
119+
assert graph.predecessors(ComponentId(3)) == {
120+
Meter(id=ComponentId(2), microgrid_id=MicrogridId(1))
121+
}

0 commit comments

Comments
 (0)