Skip to content

Commit 183299c

Browse files
committed
Add type-hints for the component graph bindings
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 6c64f41 commit 183299c

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
# Type hints for the component graph module.
5+
6+
"""A graph representation of the electrical components in a microgrid."""
7+
8+
from collections.abc import Iterable, Set
9+
from typing import Generic, Protocol, TypeVar
10+
11+
class InvalidGraphError(Exception):
12+
"""Exception type that will be thrown if graph data is not valid."""
13+
14+
class FormulaGenerationError(Exception):
15+
"""Encountered during formula generation from the component graph."""
16+
17+
class ComponentGraphConfig:
18+
"""Configuration for the component graph."""
19+
20+
def __init__(
21+
self,
22+
allow_component_validation_failures: bool = False,
23+
allow_unconnected_components: bool = False,
24+
allow_unspecified_inverters: bool = False,
25+
disable_fallback_components: bool = False,
26+
) -> None:
27+
"""Initialize this instance.
28+
29+
Args:
30+
allow_component_validation_failures: Whether to allow validation errors on
31+
components. When this is `True`, the graph will be built even if there
32+
are validation errors on the components.
33+
allow_unconnected_components: Whether to allow unconnected components in the
34+
graph, that are not reachable from the root.
35+
allow_unspecified_inverters: Whether to allow untyped inverters in the
36+
graph. When this is `True`, inverters that have
37+
`InverterType::Unspecified` will be assumed to be Battery inverters.
38+
disable_fallback_components: Whether to disable fallback components in
39+
generated formulas. When this is `True`, the formulas will not include
40+
fallback components.
41+
"""
42+
43+
class ComponentIdProtocol(Protocol):
44+
def __int__(self) -> int:
45+
"""Get the integer representation of the Component ID."""
46+
47+
class ComponentProtocol(Protocol):
48+
@property
49+
def id(self) -> ComponentIdProtocol:
50+
"""The Component ID"""
51+
52+
class ConnectionProtocol(Protocol):
53+
@property
54+
def source(self) -> ComponentIdProtocol:
55+
"""The ID of the component at the start of the connection."""
56+
57+
@property
58+
def destination(self) -> ComponentIdProtocol:
59+
"""The ID of the component at the end of the connection."""
60+
61+
ComponentT = TypeVar("ComponentT", bound=ComponentProtocol)
62+
ConnectionT = TypeVar("ConnectionT", bound=ConnectionProtocol)
63+
ComponentIdT = TypeVar("ComponentIdT", bound=ComponentIdProtocol)
64+
65+
class ComponentGraph(Generic[ComponentT, ConnectionT, ComponentIdT]):
66+
"""A graph representation of the electrical components in a microgrid."""
67+
68+
def __init__(
69+
self,
70+
components: Iterable[ComponentT],
71+
connections: Iterable[ConnectionT],
72+
config: ComponentGraphConfig = ComponentGraphConfig(),
73+
) -> None:
74+
"""Initialize this instance.
75+
76+
Args:
77+
components: The list of components to build the graph with.
78+
connections: The list of connections between the components.
79+
config: The configuration for the component graph.
80+
81+
Raises:
82+
InvalidGraphError: if a valid graph cannot be constructed from the given
83+
components and connections, based on the given configs.
84+
"""
85+
86+
def component(self, component_id: ComponentIdT) -> ComponentT:
87+
"""Fetch the component with the specified `component_id`.
88+
89+
Args:
90+
component_id: The id of the component to look for.
91+
92+
Returns:
93+
The component with the given id.
94+
95+
Raises:
96+
ValueError: if no component exists with the given ID.
97+
"""
98+
99+
def components(
100+
self,
101+
matching_ids: Iterable[ComponentIdT] | None = None,
102+
matching_types: Iterable[type[ComponentT]] | None = None,
103+
) -> set[ComponentT]:
104+
"""Fetch all components in this graph.
105+
106+
Returns:
107+
A set of all components in this graph.
108+
"""
109+
110+
def connections(self) -> Set[ConnectionT]:
111+
"""Fetch all connections in this graph.
112+
113+
Returns:
114+
A set of all connections in this graph.
115+
"""
116+
117+
def predecessors(self, component_id: ComponentIdT) -> Set[ComponentT]:
118+
"""Fetch all predecessors of the specified component ID.
119+
120+
Args:
121+
component_id: ID of the component whose predecessors should be fetched.
122+
123+
Returns:
124+
A set of components that are predecessors of the given component ID.
125+
126+
Raises:
127+
ValueError: if no component exists with the given ID.
128+
"""
129+
130+
def successors(self, component_id: ComponentIdT) -> Set[ComponentT]:
131+
"""Fetch all successors of the specified component ID.
132+
133+
Args:
134+
component_id: ID of the component whose successors should be fetched.
135+
136+
Returns:
137+
A set of components that are successors of the given component ID.
138+
139+
Raises:
140+
ValueError: if no component exists with the given ID.
141+
"""
142+
143+
def is_pv_meter(self, component_id: ComponentIdT) -> bool:
144+
"""Check if the specified component is a PV meter.
145+
146+
A meter is identified as a PV meter if:
147+
- it has at least one successor,
148+
- all its successors are PV inverters.
149+
150+
Args:
151+
component_id: ID of the component to check.
152+
153+
Returns:
154+
Whether the specified component is a PV meter.
155+
156+
Raises:
157+
ValueError: if no component exists with the given ID.
158+
"""
159+
160+
def is_battery_meter(self, component_id: ComponentIdT) -> bool:
161+
"""Check if the specified component is a battery meter.
162+
163+
A meter is identified as a battery meter if:
164+
- it has at least one successor,
165+
- all its successors are battery inverters.
166+
167+
Args:
168+
component_id: ID of the component to check.
169+
170+
Returns:
171+
Whether the specified component is a battery meter.
172+
173+
Raises:
174+
ValueError: if no component exists with the given ID.
175+
"""
176+
177+
def is_ev_charger_meter(self, component_id: ComponentIdT) -> bool:
178+
"""Check if the specified component is an EV charger meter.
179+
180+
A meter is identified as an EV charger meter if
181+
- it has at least one successor,
182+
- all its successors are EV chargers.
183+
184+
Args:
185+
component_id: ID of the component to check.
186+
187+
Returns:
188+
Whether the specified component is an EV charger meter.
189+
190+
Raises:
191+
ValueError: if no component exists with the given ID.
192+
"""
193+
194+
def is_chp_meter(self, component_id: ComponentIdT) -> bool:
195+
"""Check if the specified component is a CHP meter.
196+
197+
A meter is identified as a CHP meter if
198+
- it has at least one successor,
199+
- all its successors are CHPs.
200+
201+
Args:
202+
component_id: ID of the component to check.
203+
204+
Returns:
205+
Whether the specified component is a CHP meter.
206+
207+
Raises:
208+
ValueError: if no component exists with the given ID.
209+
"""
210+
211+
def consumer_formula(self) -> str:
212+
"""Generate the consumer formula for this component graph.
213+
214+
Returns:
215+
The consumer formula as a string.
216+
"""
217+
218+
def producer_formula(self) -> str:
219+
"""Generate the producer formula for this component graph.
220+
221+
Returns:
222+
The producer formula as a string.
223+
"""
224+
225+
def grid_formula(self) -> str:
226+
"""Generate the grid formula for this component graph.
227+
228+
Returns:
229+
The grid formula as a string.
230+
"""
231+
232+
def pv_formula(self, pv_inverter_ids: Set[ComponentIdT] | None) -> str:
233+
"""Generate the PV formula for this component graph.
234+
235+
Args:
236+
pv_inverter_ids: The set of PV inverter component IDs to include in
237+
the formula. If `None`, all PV inverters in the graph will be
238+
included.
239+
240+
Returns:
241+
The PV formula as a string.
242+
243+
Raises:
244+
FormulaGenerationError: if the given component IDs don't exist or
245+
are not PV inverters.
246+
"""
247+
248+
def battery_formula(self, battery_ids: Set[ComponentIdT] | None) -> str:
249+
"""Generate the battery formula for this component graph.
250+
251+
Args:
252+
battery_ids: The set of battery component IDs to include in the
253+
formula. If `None`, all batteries in the graph will be
254+
included.
255+
256+
Returns:
257+
The battery formula as a string.
258+
259+
Raises:
260+
FormulaGenerationError: if the given component IDs don't exist or
261+
are not batteries.
262+
"""
263+
264+
def chp_formula(self, chp_ids: Set[ComponentIdT] | None) -> str:
265+
"""Generate the CHP formula for this component graph.
266+
267+
Args:
268+
chp_ids: The set of CHP component IDs to include in the formula. If
269+
`None`, all CHPs in the graph will be included.
270+
271+
Returns:
272+
The CHP formula as a string.
273+
274+
Raises:
275+
FormulaGenerationError: if the given component IDs don't exist or
276+
are not CHPs.
277+
"""
278+
279+
def ev_charger_formula(self, ev_charger_ids: Set[ComponentIdT] | None) -> str:
280+
"""Generate the EV charger formula for this component graph.
281+
282+
Args:
283+
ev_charger_ids: The set of EV charger component IDs to include in
284+
the formula. If `None`, all EV chargers in the graph will be
285+
included.
286+
287+
Returns:
288+
The EV charger formula as a string.
289+
290+
Raises:
291+
FormulaGenerationError: if the given component IDs don't exist or
292+
are not EV chargers.
293+
"""
294+
295+
def grid_coalesce_formula(self) -> str:
296+
"""Generate the grid coalesce formula for this component graph.
297+
298+
Returns:
299+
The grid coalesced formula as a string.
300+
"""
301+
302+
def battery_coalesce_formula(self, battery_ids: Set[ComponentIdT] | None) -> str:
303+
"""Generate the battery coalesce formula for this component graph.
304+
305+
Args:
306+
battery_ids: The set of battery inverter component IDs to include in
307+
the formula. If `None`, all battery inverters in the graph will
308+
be included.
309+
310+
Returns:
311+
The battery coalesced formula as a string.
312+
313+
Raises:
314+
FormulaGenerationError: if the given component IDs don't exist or
315+
are not batteries.
316+
"""
317+
318+
def pv_coalesce_formula(self, pv_inverter_ids: Set[ComponentIdT] | None) -> str:
319+
"""Generate the PV coalesce formula for this component graph.
320+
321+
Args:
322+
pv_inverter_ids: The set of PV inverter component IDs to include in
323+
the formula. If `None`, all PV inverters in the graph will be
324+
included.
325+
326+
Returns:
327+
The PV coalesced formula as a string.
328+
329+
Raises:
330+
FormulaGenerationError: if the given component IDs don't exist or
331+
are not PV inverters.
332+
"""

0 commit comments

Comments
 (0)