frequenz_microgrid_component_graph/graph/
creation.rs1use petgraph::graph::DiGraph;
8
9use crate::{component_category::CategoryPredicates, ComponentGraphConfig, Edge, Error, Node};
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::graph::test_utils::{ComponentGraphBuilder, ComponentHandle};
124 use crate::ComponentCategory;
125 use crate::InverterType;
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!(builder
145 .build(None)
146 .is_err_and(|e| e == Error::invalid_graph("No grid component found.")),);
147
148 let grid = builder.grid();
149 builder.connect(grid, grid_meter);
150
151 assert!(builder.build(None).is_ok());
152
153 builder.add_component_with_id(2, ComponentCategory::Meter);
154 assert!(builder
155 .build(None)
156 .is_err_and(|e| e == Error::invalid_graph("Duplicate component ID found: 2")));
157
158 builder.pop_component();
159 builder.add_component(ComponentCategory::Unspecified);
160 assert!(builder
161 .build(None)
162 .is_err_and(|e| e
163 == Error::invalid_component("ComponentCategory not specified for component: 8")));
164
165 builder.pop_component();
166 let unspec_inv =
167 builder.add_component(ComponentCategory::Inverter(InverterType::Unspecified));
168 builder.connect(grid_meter, unspec_inv);
169
170 assert!(builder.build(None).is_err_and(
172 |e| e == Error::invalid_component("InverterType not specified for inverter: 9")
173 ));
174 let unspec_inv_config = ComponentGraphConfig {
177 allow_unspecified_inverters: true,
178 ..Default::default()
179 };
180
181 assert!(builder.build(Some(unspec_inv_config.clone())).is_ok());
182
183 assert!(builder
184 .pop_component()
185 .unwrap()
186 .is_battery_inverter(&unspec_inv_config));
187 builder.pop_connection();
188 builder.add_component(ComponentCategory::GridConnectionPoint);
189 assert!(builder
190 .build(None)
191 .is_err_and(|e| e == Error::invalid_graph("Multiple grid components found.")));
192
193 builder.pop_component();
194 assert!(builder.build(None).is_ok());
195 }
196
197 #[test]
198 fn test_connection_validation() {
199 let (mut builder, grid_meter) = nodes_and_edges();
200
201 let grid = builder.grid();
202 builder.connect(grid, grid_meter);
203
204 builder.connect(grid, grid);
205 assert!(builder.build(None).is_err_and(|e| e
206 == Error::invalid_connection(
207 "Connection:(7, 7) Can't connect a component to itself."
208 )));
209 builder.pop_connection();
210
211 builder.connect(grid_meter, ComponentHandle::new(9));
212 assert!(builder.build(None).is_err_and(|e| e
213 == Error::invalid_connection("Connection:(0, 9) Can't find a component with ID 9")));
214
215 builder.pop_connection();
216 assert!(builder.build(None).is_ok());
217 }
218}