1use crate::iterators::{Components, Connections, Neighbors, RawNeighbors, Siblings};
7use crate::{ComponentGraph, Edge, Error, Node};
8use petgraph::graph::NodeIndex;
9use std::collections::{BTreeSet, HashSet, VecDeque};
10
11impl<N, E> ComponentGraph<N, E>
13where
14 N: Node,
15 E: Edge,
16{
17 pub fn component(&self, component_id: u64) -> Result<&N, Error> {
19 self.node_indices
20 .get(&component_id)
21 .map(|i| &self.graph[*i])
22 .ok_or_else(|| {
23 Error::component_not_found(format!("Component with id {component_id} not found."))
24 })
25 }
26
27 pub fn components(&self) -> Components<'_, N> {
29 Components {
30 iter: self.graph.raw_nodes().iter(),
31 }
32 }
33
34 pub fn connections(&self) -> Connections<'_, N, E> {
36 Connections {
37 cg: self,
38 iter: self.graph.raw_edges().iter(),
39 }
40 }
41
42 pub fn raw_predecessors(&self, component_id: u64) -> Result<RawNeighbors<'_, N>, Error> {
52 self.raw_neighbors(component_id, petgraph::Direction::Incoming)
53 }
54
55 pub fn raw_successors(&self, component_id: u64) -> Result<RawNeighbors<'_, N>, Error> {
65 self.raw_neighbors(component_id, petgraph::Direction::Outgoing)
66 }
67
68 fn raw_neighbors(
71 &self,
72 component_id: u64,
73 direction: petgraph::Direction,
74 ) -> Result<RawNeighbors<'_, N>, Error> {
75 self.node_indices
76 .get(&component_id)
77 .map(|&index| RawNeighbors {
78 graph: &self.graph,
79 iter: self.graph.neighbors_directed(index, direction),
80 })
81 .ok_or_else(|| {
82 Error::component_not_found(format!("Component with id {component_id} not found."))
83 })
84 }
85
86 pub fn predecessors(&self, component_id: u64) -> Result<Neighbors<'_, N>, Error> {
97 self.collect_effective_neighbors(component_id, petgraph::Direction::Incoming)
98 }
99
100 pub fn successors(&self, component_id: u64) -> Result<Neighbors<'_, N>, Error> {
111 self.collect_effective_neighbors(component_id, petgraph::Direction::Outgoing)
112 }
113
114 fn collect_effective_neighbors(
117 &self,
118 component_id: u64,
119 direction: petgraph::Direction,
120 ) -> Result<Neighbors<'_, N>, Error> {
121 let start = *self.node_indices.get(&component_id).ok_or_else(|| {
122 Error::component_not_found(format!("Component with id {component_id} not found."))
123 })?;
124
125 let mut queue: VecDeque<NodeIndex> =
126 self.graph.neighbors_directed(start, direction).collect();
127 let mut visited: HashSet<NodeIndex> = HashSet::new();
128 let mut result: Vec<&N> = Vec::new();
129
130 while let Some(idx) = queue.pop_front() {
131 if !visited.insert(idx) {
132 continue;
133 }
134 let node = &self.graph[idx];
135 if node.category().is_passthrough() {
136 queue.extend(self.graph.neighbors_directed(idx, direction));
137 } else {
138 result.push(node);
139 }
140 }
141
142 Ok(Neighbors {
143 iter: result.into_iter(),
144 })
145 }
146
147 pub(crate) fn siblings_from_predecessors(
152 &self,
153 component_id: u64,
154 ) -> Result<Siblings<'_, N>, Error> {
155 Ok(Siblings::new(
156 component_id,
157 self.predecessors(component_id)?
158 .map(|x| self.successors(x.component_id()))
159 .collect::<Result<Vec<_>, _>>()?
160 .into_iter()
161 .flatten(),
162 ))
163 }
164
165 pub(crate) fn siblings_from_successors(
170 &self,
171 component_id: u64,
172 ) -> Result<Siblings<'_, N>, Error> {
173 Ok(Siblings::new(
174 component_id,
175 self.successors(component_id)?
176 .map(|x| self.predecessors(x.component_id()))
177 .collect::<Result<Vec<_>, _>>()?
178 .into_iter()
179 .flatten(),
180 ))
181 }
182
183 pub(crate) fn find_all(
189 &self,
190 from: u64,
191 pred: impl Fn(&N) -> bool,
192 direction: petgraph::Direction,
193 follow_after_match: bool,
194 ) -> Result<BTreeSet<u64>, Error> {
195 let index = self.node_indices.get(&from).ok_or_else(|| {
196 Error::component_not_found(format!("Component with id {from} not found."))
197 })?;
198 let mut stack = vec![*index];
199 let mut visited = HashSet::new();
200 let mut found = BTreeSet::new();
201
202 while let Some(index) = stack.pop() {
203 if !visited.insert(index) {
207 continue;
208 }
209 let node = &self.graph[index];
210 if !node.category().is_passthrough() && pred(node) {
213 found.insert(node.component_id());
214 if !follow_after_match {
215 continue;
216 }
217 }
218
219 let neighbors = self.graph.neighbors_directed(index, direction);
220 stack.extend(neighbors);
221 }
222
223 Ok(found)
224 }
225
226 pub(crate) fn reaches_any(
232 &self,
233 from: u64,
234 pred: impl Fn(&N) -> bool,
235 direction: petgraph::Direction,
236 ) -> Result<bool, Error> {
237 let index = self.node_indices.get(&from).ok_or_else(|| {
238 Error::component_not_found(format!("Component with id {from} not found."))
239 })?;
240 let mut stack = vec![*index];
241 let mut visited = HashSet::new();
242
243 while let Some(index) = stack.pop() {
244 if !visited.insert(index) {
248 continue;
249 }
250 let node = &self.graph[index];
251 if !node.category().is_passthrough() && pred(node) {
252 return Ok(true);
253 }
254 stack.extend(self.graph.neighbors_directed(index, direction));
255 }
256
257 Ok(false)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::ComponentCategory;
265 use crate::ComponentGraphConfig;
266 use crate::InverterType;
267 use crate::component_category::BatteryType;
268 use crate::component_category::CategoryPredicates;
269 use crate::error::Error;
270 use crate::graph::test_utils::ComponentGraphBuilder;
271 use crate::graph::test_utils::{TestComponent, TestConnection};
272
273 fn nodes_and_edges() -> (Vec<TestComponent>, Vec<TestConnection>) {
274 let components = vec![
275 TestComponent::new(6, ComponentCategory::Meter),
276 TestComponent::new(1, ComponentCategory::GridConnectionPoint),
277 TestComponent::new(7, ComponentCategory::Inverter(InverterType::Battery)),
278 TestComponent::new(3, ComponentCategory::Meter),
279 TestComponent::new(5, ComponentCategory::Battery(BatteryType::Unspecified)),
280 TestComponent::new(8, ComponentCategory::Battery(BatteryType::LiIon)),
281 TestComponent::new(4, ComponentCategory::Inverter(InverterType::Battery)),
282 TestComponent::new(2, ComponentCategory::Meter),
283 ];
284 let connections = vec![
285 TestConnection::new(3, 4),
286 TestConnection::new(1, 2),
287 TestConnection::new(7, 8),
288 TestConnection::new(4, 5),
289 TestConnection::new(2, 3),
290 TestConnection::new(6, 7),
291 TestConnection::new(2, 6),
292 ];
293
294 (components, connections)
295 }
296
297 #[test]
298 fn test_component() -> Result<(), Error> {
299 let config = ComponentGraphConfig::default();
300 let (components, connections) = nodes_and_edges();
301 let graph = ComponentGraph::try_new(components.clone(), connections.clone(), config)?;
302
303 assert_eq!(
304 graph.component(1),
305 Ok(&TestComponent::new(
306 1,
307 ComponentCategory::GridConnectionPoint
308 ))
309 );
310 assert_eq!(
311 graph.component(5),
312 Ok(&TestComponent::new(
313 5,
314 ComponentCategory::Battery(BatteryType::Unspecified)
315 ))
316 );
317 assert_eq!(
318 graph.component(9),
319 Err(Error::component_not_found("Component with id 9 not found."))
320 );
321
322 Ok(())
323 }
324
325 #[test]
326 fn test_components() -> Result<(), Error> {
327 let config = ComponentGraphConfig::default();
328 let (components, connections) = nodes_and_edges();
329 let graph = ComponentGraph::try_new(components.clone(), connections.clone(), config)?;
330
331 assert!(graph.components().eq(&components));
332 assert!(graph.components().filter(|x| x.is_battery()).eq(&[
333 TestComponent::new(5, ComponentCategory::Battery(BatteryType::Unspecified)),
334 TestComponent::new(8, ComponentCategory::Battery(BatteryType::LiIon))
335 ]));
336
337 Ok(())
338 }
339
340 #[test]
341 fn test_connections() -> Result<(), Error> {
342 let config = ComponentGraphConfig::default();
343 let (components, connections) = nodes_and_edges();
344 let graph = ComponentGraph::try_new(components.clone(), connections.clone(), config)?;
345
346 assert!(graph.connections().eq(&connections));
347
348 assert!(
349 graph
350 .connections()
351 .filter(|x| x.source() == 2)
352 .eq(&[TestConnection::new(2, 3), TestConnection::new(2, 6)])
353 );
354
355 Ok(())
356 }
357
358 #[test]
359 fn test_neighbors() -> Result<(), Error> {
360 let config = ComponentGraphConfig::default();
361 let (components, connections) = nodes_and_edges();
362 let graph = ComponentGraph::try_new(components.clone(), connections.clone(), config)?;
363
364 assert!(graph.predecessors(1).is_ok_and(|x| x.eq(&[])));
365
366 assert!(
367 graph
368 .predecessors(3)
369 .is_ok_and(|x| x.eq(&[TestComponent::new(2, ComponentCategory::Meter)]))
370 );
371
372 assert!(
373 graph
374 .successors(1)
375 .is_ok_and(|x| x.eq(&[TestComponent::new(2, ComponentCategory::Meter)]))
376 );
377
378 assert!(graph.successors(2).is_ok_and(|x| {
379 x.eq(&[
380 TestComponent::new(6, ComponentCategory::Meter),
381 TestComponent::new(3, ComponentCategory::Meter),
382 ])
383 }));
384
385 assert!(graph.successors(5).is_ok_and(|x| x.eq(&[])));
386
387 assert!(
388 graph
389 .predecessors(32)
390 .is_err_and(|e| e == Error::component_not_found("Component with id 32 not found."))
391 );
392 assert!(
393 graph
394 .successors(32)
395 .is_err_and(|e| e == Error::component_not_found("Component with id 32 not found."))
396 );
397
398 Ok(())
399 }
400
401 #[test]
402 fn test_siblings() -> Result<(), Error> {
403 let mut builder = ComponentGraphBuilder::new();
404 let grid = builder.grid();
405
406 let grid_meter = builder.meter();
408 builder.connect(grid, grid_meter);
409
410 assert_eq!(grid_meter.component_id(), 1);
411
412 let meter_bat_chain = builder.meter_bat_chain(3, 2);
414 builder.connect(grid_meter, meter_bat_chain);
415
416 assert_eq!(meter_bat_chain.component_id(), 2);
417
418 let graph = builder.build(None)?;
419 assert_eq!(
420 graph
421 .siblings_from_predecessors(3)
422 .unwrap()
423 .collect::<Vec<_>>(),
424 [
425 &TestComponent::new(5, ComponentCategory::Inverter(InverterType::Battery)),
426 &TestComponent::new(4, ComponentCategory::Inverter(InverterType::Battery))
427 ]
428 );
429
430 assert_eq!(
431 graph
432 .siblings_from_successors(3)
433 .unwrap()
434 .collect::<Vec<_>>(),
435 [
436 &TestComponent::new(5, ComponentCategory::Inverter(InverterType::Battery)),
437 &TestComponent::new(4, ComponentCategory::Inverter(InverterType::Battery))
438 ]
439 );
440
441 assert_eq!(
442 graph
443 .siblings_from_successors(6)
444 .unwrap()
445 .collect::<Vec<_>>(),
446 Vec::<&TestComponent>::new()
447 );
448
449 assert_eq!(
450 graph
451 .siblings_from_predecessors(6)
452 .unwrap()
453 .collect::<Vec<_>>(),
454 [&TestComponent::new(
455 7,
456 ComponentCategory::Battery(BatteryType::LiIon)
457 )]
458 );
459
460 let dangling_meter = builder.meter();
462 builder.connect(grid_meter, dangling_meter);
463 assert_eq!(dangling_meter.component_id(), 8);
464
465 let dangling_meter = builder.meter();
466 builder.connect(grid_meter, dangling_meter);
467 assert_eq!(dangling_meter.component_id(), 9);
468
469 let graph = builder.build(None)?;
470 assert_eq!(
471 graph
472 .siblings_from_predecessors(8)
473 .unwrap()
474 .collect::<Vec<_>>(),
475 [
476 &TestComponent::new(9, ComponentCategory::Meter),
477 &TestComponent::new(2, ComponentCategory::Meter),
478 ]
479 );
480
481 Ok(())
482 }
483
484 #[test]
490 fn test_raw_neighbors_includes_passthroughs() -> Result<(), Error> {
491 let mut builder = ComponentGraphBuilder::new();
492 let grid = builder.grid();
493 let pt = builder.power_transformer();
494 let meter = builder.meter();
495 let inverter = builder.battery_inverter();
496 let battery = builder.battery();
497
498 builder.connect(grid, pt);
499 builder.connect(pt, meter);
500 builder.connect(meter, inverter);
501 builder.connect(inverter, battery);
502
503 let graph = builder.build(None)?;
504
505 let raw_preds: Vec<u64> = graph
507 .raw_predecessors(meter.component_id())?
508 .map(|n| n.component_id())
509 .collect();
510 assert_eq!(raw_preds, vec![pt.component_id()]);
511
512 let raw_succs: Vec<u64> = graph
513 .raw_successors(grid.component_id())?
514 .map(|n| n.component_id())
515 .collect();
516 assert_eq!(raw_succs, vec![pt.component_id()]);
517
518 let preds: Vec<u64> = graph
520 .predecessors(meter.component_id())?
521 .map(|n| n.component_id())
522 .collect();
523 assert_eq!(preds, vec![grid.component_id()]);
524
525 let succs: Vec<u64> = graph
526 .successors(grid.component_id())?
527 .map(|n| n.component_id())
528 .collect();
529 assert_eq!(succs, vec![meter.component_id()]);
530
531 assert!(graph.raw_predecessors(999).is_err());
533 assert!(graph.raw_successors(999).is_err());
534
535 let _ = (battery, inverter);
538 Ok(())
539 }
540
541 #[test]
547 fn test_find_all_skips_passthroughs() -> Result<(), Error> {
548 let mut builder = ComponentGraphBuilder::new();
549 let grid = builder.grid();
550 let pt = builder.power_transformer();
551 let meter = builder.meter();
552
553 builder.connect(grid, pt);
554 builder.connect(pt, meter);
555
556 let graph = builder.build(None)?;
557
558 let found = graph.find_all(
560 grid.component_id(),
561 |_| true,
562 petgraph::Direction::Outgoing,
563 true,
564 )?;
565 assert_eq!(
566 found,
567 BTreeSet::from([grid.component_id(), meter.component_id()])
568 );
569
570 let found = graph.find_all(
572 grid.component_id(),
573 |n| n.category() == ComponentCategory::PowerTransformer,
574 petgraph::Direction::Outgoing,
575 true,
576 )?;
577 assert!(found.is_empty());
578 Ok(())
579 }
580
581 #[test]
582 fn test_find_all() -> Result<(), Error> {
583 let (components, connections) = nodes_and_edges();
584 let graph = ComponentGraph::try_new(
585 components.clone(),
586 connections.clone(),
587 ComponentGraphConfig::default(),
588 )?;
589
590 let found = graph.find_all(
591 graph.root_id,
592 |x| x.is_meter(),
593 petgraph::Direction::Outgoing,
594 false,
595 )?;
596 assert_eq!(found, [2].iter().cloned().collect());
597
598 let found = graph.find_all(
599 graph.root_id,
600 |x| x.is_meter(),
601 petgraph::Direction::Outgoing,
602 true,
603 )?;
604 assert_eq!(found, [2, 3, 6].iter().cloned().collect());
605
606 let found = graph.find_all(
607 graph.root_id,
608 |x| !x.is_grid() && !graph.is_component_meter(x.component_id()).unwrap_or(false),
609 petgraph::Direction::Outgoing,
610 true,
611 )?;
612 assert_eq!(found, [2, 4, 5, 7, 8].iter().cloned().collect());
613
614 let found = graph.find_all(
615 6,
616 |x| !x.is_grid() && !graph.is_component_meter(x.component_id()).unwrap_or(false),
617 petgraph::Direction::Outgoing,
618 true,
619 )?;
620 assert_eq!(found, [7, 8].iter().cloned().collect());
621
622 let found = graph.find_all(
623 graph.root_id,
624 |x| !x.is_grid() && !graph.is_component_meter(x.component_id()).unwrap_or(false),
625 petgraph::Direction::Outgoing,
626 false,
627 )?;
628 assert_eq!(found, [2].iter().cloned().collect());
629
630 let found = graph.find_all(
631 graph.root_id,
632 |_| true,
633 petgraph::Direction::Outgoing,
634 false,
635 )?;
636 assert_eq!(found, [1].iter().cloned().collect());
637
638 let found = graph.find_all(3, |_| true, petgraph::Direction::Outgoing, true)?;
639 assert_eq!(found, [3, 4, 5].iter().cloned().collect());
640
641 Ok(())
642 }
643
644 #[test]
653 fn test_find_all_dedups_on_diamond() -> Result<(), Error> {
654 let mut builder = ComponentGraphBuilder::new();
655 let grid = builder.grid();
656 let meter_a = builder.meter();
657 let meter_b = builder.meter();
658 let inverter = builder.battery_inverter();
659 let battery = builder.battery();
660
661 builder.connect(grid, meter_a);
662 builder.connect(grid, meter_b);
663 builder.connect(meter_a, inverter);
665 builder.connect(meter_b, inverter);
666 builder.connect(inverter, battery);
667
668 let graph = builder.build(None)?;
669
670 let found = graph.find_all(
673 grid.component_id(),
674 |n| !n.is_grid(),
675 petgraph::Direction::Outgoing,
676 true,
677 )?;
678 assert_eq!(
679 found,
680 BTreeSet::from([
681 meter_a.component_id(),
682 meter_b.component_id(),
683 inverter.component_id(),
684 battery.component_id(),
685 ])
686 );
687
688 let found = graph.find_all(
691 grid.component_id(),
692 |n| n.is_battery(),
693 petgraph::Direction::Outgoing,
694 true,
695 )?;
696 assert_eq!(found, BTreeSet::from([battery.component_id()]));
697
698 Ok(())
699 }
700}