Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,5 @@
| EV Chargers | Maximum power (aka max consumption power) |

- PV Pool instances can now be created in sites without any PV. This allows for writing generic code that works for all locations, that depends on the PV power formula, for example.

- The `find_first_descendant_component` method in the component graph was allowing non-root components to be used as the root component during traversal. This was leading to confusing behaviour when the root component couldn't be identified deterministically. For example, if the root category was specified as a meter, it could start traversing from a different meter each time. It is no-longer possible to specify a root category anymore and it always traverses from the `GRID` component.
34 changes: 13 additions & 21 deletions src/frequenz/sdk/microgrid/component_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,30 +311,24 @@ def dfs(
def find_first_descendant_component(
self,
*,
root_category: ComponentCategory,
descendant_categories: Iterable[ComponentCategory],
) -> Component:
"""Find the first descendant component given root and descendant categories.

This method searches for the root component within the provided root
category. If multiple components share the same root category, the
first found one is considered as the root component.

Subsequently, it looks for the first descendant component from the root
This method looks for the first descendant component from the GRID
component, considering only the immediate descendants.

The priority of the component to search for is determined by the order
of the descendant categories, with the first category having the
highest priority.

Args:
root_category: The class of the root component to search for.
descendant_categories: The descendant classes to search for the first
descendant component in.

Returns:
The first descendant component found in the component graph,
considering the specified `root` and `descendants` categories.
considering the specified `descendants` categories.
"""


Expand Down Expand Up @@ -824,42 +818,40 @@ def dfs(
def find_first_descendant_component(
self,
*,
root_category: ComponentCategory,
descendant_categories: Iterable[ComponentCategory],
) -> Component:
"""Find the first descendant component given root and descendant categories.

This method searches for the root component within the provided root
category. If multiple components share the same root category, the
first found one is considered as the root component.

Subsequently, it looks for the first descendant component from the root
This method looks for the first descendant component from the GRID
component, considering only the immediate descendants.

The priority of the component to search for is determined by the order
of the descendant categories, with the first category having the
highest priority.

Args:
root_category: The class of the root component to search for.
descendant_categories: The descendant classes to search for the first
descendant component in.

Returns:
The first descendant component found in the component graph,
considering the specified `root` and `descendants` categories.
considering the specified `descendants` categories.

Raises:
ValueError: When the root component is not found in the component
graph or when no component is found in the given categories.
InvalidGraphError: When no GRID component is found in the graph.
ValueError: When no component is found in the given categories.
"""
root_component = next(
(comp for comp in self.components(component_categories={root_category})),
(
comp
for comp in self.components(
component_categories={ComponentCategory.GRID}
)
),
None,
)

if root_component is None:
raise ValueError(f"Root component not found for {root_category.name}")
raise InvalidGraphError("No GRID component found in the component graph!")

# Sort by component ID to ensure consistent results.
successors = sorted(
Expand Down
1 change: 0 additions & 1 deletion src/frequenz/sdk/timeseries/_grid_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def __init__(
if not source:
component_graph = connection_manager.get().component_graph
source = component_graph.find_first_descendant_component(
root_category=ComponentCategory.GRID,
descendant_categories=(
ComponentCategory.METER,
ComponentCategory.INVERTER,
Expand Down
1 change: 0 additions & 1 deletion src/frequenz/sdk/timeseries/_voltage_streamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ def __init__(
if not source_component:
component_graph = connection_manager.get().component_graph
source_component = component_graph.find_first_descendant_component(
root_category=ComponentCategory.GRID,
descendant_categories=[
ComponentCategory.METER,
ComponentCategory.INVERTER,
Expand Down
25 changes: 0 additions & 25 deletions tests/microgrid/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,45 +590,21 @@ def test_find_first_descendant_component(self) -> None:

# Find the first descendant component of the grid endpoint.
result = graph.find_first_descendant_component(
root_category=ComponentCategory.GRID,
descendant_categories=(ComponentCategory.METER,),
)
assert result == Component(2, ComponentCategory.METER)

# Find the first descendant component of the first meter found.
result = graph.find_first_descendant_component(
root_category=ComponentCategory.METER,
descendant_categories=(ComponentCategory.INVERTER,),
)
assert result == Component(4, ComponentCategory.INVERTER, InverterType.BATTERY)

# Find the first descendant component of the grid,
# considering meter or inverter categories.
result = graph.find_first_descendant_component(
root_category=ComponentCategory.GRID,
descendant_categories=(ComponentCategory.METER, ComponentCategory.INVERTER),
)
assert result == Component(2, ComponentCategory.METER)

# Find the first descendant component of the first meter with nested meters.
result = graph.find_first_descendant_component(
root_category=ComponentCategory.METER,
descendant_categories=(ComponentCategory.METER,),
)
assert result == Component(3, ComponentCategory.METER)

# Verify behavior when root component is not found.
with pytest.raises(ValueError):
graph.find_first_descendant_component(
root_category=ComponentCategory.CHP,
descendant_categories=(ComponentCategory.INVERTER,),
)

# Verify behavior when component is not found in immediate descendant
# categories for the first meter.
with pytest.raises(ValueError):
graph.find_first_descendant_component(
root_category=ComponentCategory.METER,
descendant_categories=(
ComponentCategory.EV_CHARGER,
ComponentCategory.BATTERY,
Expand All @@ -639,7 +615,6 @@ def test_find_first_descendant_component(self) -> None:
# categories from the grid component as root.
with pytest.raises(ValueError):
graph.find_first_descendant_component(
root_category=ComponentCategory.GRID,
descendant_categories=(ComponentCategory.INVERTER,),
)

Expand Down
Loading