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 Ok(cg)
42 }
43
44 fn find_root(graph: &DiGraph<N, ()>) -> Result<&N, Error> {
45 let mut roots_iter = graph.raw_nodes().iter().filter(|n| n.weight.is_grid());
46
47 let root = roots_iter
48 .next()
49 .map(|n| &n.weight)
50 .ok_or_else(|| Error::invalid_graph("No grid component found."))?;
51
52 if roots_iter.next().is_some() {
53 return Err(Error::invalid_graph("Multiple grid components found."));
54 }
55
56 Ok(root)
57 }
58
59 fn create_graph(
60 components: impl IntoIterator<Item = N>,
61 config: &ComponentGraphConfig,
62 ) -> Result<(DiGraph<N, ()>, NodeIndexMap), Error> {
63 let mut graph = DiGraph::new();
64 let mut indices = NodeIndexMap::new();
65
66 for component in components {
67 let cid = component.component_id();
68
69 if component.is_unspecified() {
70 return Err(Error::invalid_component(format!(
71 "ComponentCategory not specified for component: {cid}"
72 )));
73 }
74 if component.is_unspecified_inverter(config) {
75 return Err(Error::invalid_component(format!(
76 "InverterType not specified for inverter: {cid}"
77 )));
78 }
79 if indices.contains_key(&cid) {
80 return Err(Error::invalid_graph(format!(
81 "Duplicate component ID found: {cid}"
82 )));
83 }
84
85 let idx = graph.add_node(component);
86 indices.insert(cid, idx);
87 }
88
89 Ok((graph, indices))
90 }
91
92 fn add_connections(&mut self, connections: impl IntoIterator<Item = E>) -> Result<(), Error> {
93 for connection in connections {
94 let sid = connection.source();
95 let did = connection.destination();
96
97 if sid == did {
98 return Err(Error::invalid_connection(format!(
99 "Connection:({sid}, {did}) Can't connect a component to itself."
100 )));
101 }
102 for cid in [sid, did] {
103 if !self.node_indices.contains_key(&cid) {
104 return Err(Error::invalid_connection(format!(
105 "Connection:({sid}, {did}) Can't find a component with ID {cid}"
106 )));
107 }
108 }
109
110 let source_idx = self.node_indices[&connection.source()];
111 let dest_idx = self.node_indices[&connection.destination()];
112 self.edges.insert((source_idx, dest_idx), connection);
113 self.graph.update_edge(source_idx, dest_idx, ());
114 }
115
116 Ok(())
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::ComponentCategory;
124 use crate::InverterType;
125 use crate::graph::test_utils::{ComponentGraphBuilder, ComponentHandle};
126
127 fn nodes_and_edges() -> (ComponentGraphBuilder, ComponentHandle) {
128 let mut builder = ComponentGraphBuilder::new();
129
130 let grid_meter = builder.meter();
131 let meter_bat_chain = builder.meter_bat_chain(1, 1);
132 builder.connect(grid_meter, meter_bat_chain);
133
134 let meter_bat_chain = builder.meter_bat_chain(1, 1);
135 builder.connect(grid_meter, meter_bat_chain);
136
137 (builder, grid_meter)
138 }
139
140 #[test]
141 fn test_component_validation() {
142 let (mut builder, grid_meter) = nodes_and_edges();
143
144 assert!(
145 builder
146 .build(None)
147 .is_err_and(|e| e == Error::invalid_graph("No grid component found.")),
148 );
149
150 let grid = builder.grid();
151 builder.connect(grid, grid_meter);
152
153 assert!(builder.build(None).is_ok());
154
155 builder.add_component_with_id(2, ComponentCategory::Meter);
156 assert!(
157 builder
158 .build(None)
159 .is_err_and(|e| e == Error::invalid_graph("Duplicate component ID found: 2"))
160 );
161
162 builder.pop_component();
163 builder.add_component(ComponentCategory::Unspecified);
164 assert!(
165 builder.build(None).is_err_and(|e| e
166 == Error::invalid_component("ComponentCategory not specified for component: 8"))
167 );
168
169 builder.pop_component();
170 let unspec_inv =
171 builder.add_component(ComponentCategory::Inverter(InverterType::Unspecified));
172 builder.connect(grid_meter, unspec_inv);
173
174 assert!(builder.build(None).is_err_and(
176 |e| e == Error::invalid_component("InverterType not specified for inverter: 9")
177 ));
178 let unspec_inv_config = ComponentGraphConfig {
181 allow_unspecified_inverters: true,
182 ..Default::default()
183 };
184
185 assert!(builder.build(Some(unspec_inv_config.clone())).is_ok());
186
187 assert!(
188 builder
189 .pop_component()
190 .unwrap()
191 .is_battery_inverter(&unspec_inv_config)
192 );
193 builder.pop_connection();
194 builder.add_component(ComponentCategory::GridConnectionPoint);
195 assert!(
196 builder
197 .build(None)
198 .is_err_and(|e| e == Error::invalid_graph("Multiple grid components found."))
199 );
200
201 builder.pop_component();
202 assert!(builder.build(None).is_ok());
203 }
204
205 #[test]
206 fn test_connection_validation() {
207 let (mut builder, grid_meter) = nodes_and_edges();
208
209 let grid = builder.grid();
210 builder.connect(grid, grid_meter);
211
212 builder.connect(grid, grid);
213 assert!(builder.build(None).is_err_and(|e| e
214 == Error::invalid_connection(
215 "Connection:(7, 7) Can't connect a component to itself."
216 )));
217 builder.pop_connection();
218
219 builder.connect(grid_meter, ComponentHandle::new(9));
220 assert!(builder.build(None).is_err_and(|e| e
221 == Error::invalid_connection("Connection:(0, 9) Can't find a component with ID 9")));
222
223 builder.pop_connection();
224 assert!(builder.build(None).is_ok());
225 }
226}