frequenz_microgrid_component_graph/graph/
creation.rs1use petgraph::graph::DiGraph;
8
9use crate::{ComponentGraphConfig, Edge, Error, Node, component_category::CategoryPredicates};
10
11use super::{ComponentGraph, EdgeMap, NodeIndexMap};
12
13impl<N, E> ComponentGraph<N, E>
15where
16 N: Node,
17 E: Edge,
18{
19 pub fn try_new<NodeIterator: IntoIterator<Item = N>, EdgeIterator: IntoIterator<Item = E>>(
23 components: NodeIterator,
24 connections: EdgeIterator,
25 config: ComponentGraphConfig,
26 ) -> Result<Self, Error> {
27 let (graph, indices) = Self::create_graph(components, &config)?;
28 let root_id = Self::find_root(&graph)?.component_id();
29
30 let mut cg = Self {
31 graph,
32 node_indices: indices,
33 root_id,
34 edges: EdgeMap::new(),
35 config,
36 };
37 cg.add_connections(connections)?;
38
39 cg.validate()?;
40
41 for component in cg.components() {
46 if component.category().is_passthrough() {
47 tracing::warn!(
48 "Component {cid} ({category}) is a pass-through category and \
49 will be treated as transparent in validators and formula generators.",
50 cid = component.component_id(),
51 category = component.category(),
52 );
53 }
54 }
55
56 Ok(cg)
57 }
58
59 fn find_root(graph: &DiGraph<N, ()>) -> Result<&N, Error> {
60 let mut roots_iter = graph.raw_nodes().iter().filter(|n| n.weight.is_grid());
61
62 let root = roots_iter
63 .next()
64 .map(|n| &n.weight)
65 .ok_or_else(|| Error::invalid_graph("No grid component found."))?;
66
67 if roots_iter.next().is_some() {
68 return Err(Error::invalid_graph("Multiple grid components found."));
69 }
70
71 Ok(root)
72 }
73
74 fn create_graph(
75 components: impl IntoIterator<Item = N>,
76 config: &ComponentGraphConfig,
77 ) -> Result<(DiGraph<N, ()>, NodeIndexMap), Error> {
78 let mut graph = DiGraph::new();
79 let mut indices = NodeIndexMap::new();
80
81 for component in components {
82 let cid = component.component_id();
83
84 if component.is_unspecified() {
85 return Err(Error::invalid_component(format!(
86 "ComponentCategory not specified for component: {cid}"
87 )));
88 }
89 if component.is_unspecified_inverter(config) {
90 return Err(Error::invalid_component(format!(
91 "InverterType not specified for inverter: {cid}"
92 )));
93 }
94 if indices.contains_key(&cid) {
95 return Err(Error::invalid_graph(format!(
96 "Duplicate component ID found: {cid}"
97 )));
98 }
99
100 let idx = graph.add_node(component);
101 indices.insert(cid, idx);
102 }
103
104 Ok((graph, indices))
105 }
106
107 fn add_connections(&mut self, connections: impl IntoIterator<Item = E>) -> Result<(), Error> {
108 for connection in connections {
109 let sid = connection.source();
110 let did = connection.destination();
111
112 if sid == did {
113 return Err(Error::invalid_connection(format!(
114 "Connection:({sid}, {did}) Can't connect a component to itself."
115 )));
116 }
117 for cid in [sid, did] {
118 if !self.node_indices.contains_key(&cid) {
119 return Err(Error::invalid_connection(format!(
120 "Connection:({sid}, {did}) Can't find a component with ID {cid}"
121 )));
122 }
123 }
124
125 let source_idx = self.node_indices[&connection.source()];
126 let dest_idx = self.node_indices[&connection.destination()];
127 self.edges.insert((source_idx, dest_idx), connection);
128 self.graph.update_edge(source_idx, dest_idx, ());
129 }
130
131 Ok(())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::ComponentCategory;
139 use crate::InverterType;
140 use crate::graph::test_utils::{ComponentGraphBuilder, ComponentHandle};
141
142 fn nodes_and_edges() -> (ComponentGraphBuilder, ComponentHandle) {
143 let mut builder = ComponentGraphBuilder::new();
144
145 let grid_meter = builder.meter();
146 let meter_bat_chain = builder.meter_bat_chain(1, 1);
147 builder.connect(grid_meter, meter_bat_chain);
148
149 let meter_bat_chain = builder.meter_bat_chain(1, 1);
150 builder.connect(grid_meter, meter_bat_chain);
151
152 (builder, grid_meter)
153 }
154
155 #[test]
156 fn test_component_validation() {
157 let (mut builder, grid_meter) = nodes_and_edges();
158
159 assert!(
160 builder
161 .build(None)
162 .is_err_and(|e| e == Error::invalid_graph("No grid component found.")),
163 );
164
165 let grid = builder.grid();
166 builder.connect(grid, grid_meter);
167
168 assert!(builder.build(None).is_ok());
169
170 builder.add_component_with_id(2, ComponentCategory::Meter);
171 assert!(
172 builder
173 .build(None)
174 .is_err_and(|e| e == Error::invalid_graph("Duplicate component ID found: 2"))
175 );
176
177 builder.pop_component();
178 builder.add_component(ComponentCategory::Unspecified);
179 assert!(
180 builder.build(None).is_err_and(|e| e
181 == Error::invalid_component("ComponentCategory not specified for component: 8"))
182 );
183
184 builder.pop_component();
185 let unspec_inv =
186 builder.add_component(ComponentCategory::Inverter(InverterType::Unspecified));
187 builder.connect(grid_meter, unspec_inv);
188
189 assert!(builder.build(None).is_err_and(
191 |e| e == Error::invalid_component("InverterType not specified for inverter: 9")
192 ));
193 let unspec_inv_config = ComponentGraphConfig::builder()
196 .allow_unspecified_inverters(true)
197 .build();
198
199 assert!(builder.build(Some(unspec_inv_config.clone())).is_ok());
200
201 assert!(
202 builder
203 .pop_component()
204 .unwrap()
205 .is_battery_inverter(&unspec_inv_config)
206 );
207 builder.pop_connection();
208 builder.add_component(ComponentCategory::GridConnectionPoint);
209 assert!(
210 builder
211 .build(None)
212 .is_err_and(|e| e == Error::invalid_graph("Multiple grid components found."))
213 );
214
215 builder.pop_component();
216 assert!(builder.build(None).is_ok());
217 }
218
219 #[test]
220 fn test_connection_validation() {
221 let (mut builder, grid_meter) = nodes_and_edges();
222
223 let grid = builder.grid();
224 builder.connect(grid, grid_meter);
225
226 builder.connect(grid, grid);
227 assert!(builder.build(None).is_err_and(|e| e
228 == Error::invalid_connection(
229 "Connection:(7, 7) Can't connect a component to itself."
230 )));
231 builder.pop_connection();
232
233 builder.connect(grid_meter, ComponentHandle::new(9));
234 assert!(builder.build(None).is_err_and(|e| e
235 == Error::invalid_connection("Connection:(0, 9) Can't find a component with ID 9")));
236
237 builder.pop_connection();
238 assert!(builder.build(None).is_ok());
239 }
240}