network_analysis/network.rs
1/*!
2Tools for creating a [`Network`].
3
4This module provides the [`Network`] struct, which is used to construct [`MeshAnalysis`](crate::MeshAnalysis) and
5[`NodalAnalysis`](crate::NodalAnalysis) instances. A [`Network`] is essentially a wrapper around a
6[`UnGraph<usize, Excitation>`] which makes sure the network represented by the graph is suitable
7for these network analysis methods. Furthermore, this module also contains the [`BuildError`] struct,
8which covers the different reasons why a graph might not be a valid [`Network`]. See their respective
9docstrings for further information.
10*/
11
12use std::{cmp::PartialEq, collections::VecDeque};
13
14use petgraph::{
15 graph::{EdgeIndex, NodeIndex, UnGraph},
16 stable_graph::StableUnGraph,
17 visit::{EdgeRef, IntoEdgeReferences},
18};
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Serialize};
22
23/**
24An error returned from a failed attempt of creating a [`Network`] from a graph.
25
26The diagram below shows a graph which fails to represent a valid electrical circuit:
27```text
28 ┌──┬─[1]─┬─[5]─
29 │ │ [2]
30 │ [0] │
31 │ │ [3]
32 └──┴─[4]─┘
33```
34- Edge 0 is short-circuited. To fix this issue, it needs to be removed from the network.
35- If both edge 2 and 4 would have a current excitation, the current flowing through the edges 2, 3 and 4
36cannot be defined. To fix this issue, one of the excitations needs to be removed.
37- Edge 5 is a dead end. It can simply be deleted.
38 */
39#[derive(Debug, Clone)]
40pub enum BuildError {
41 /**
42 Two (or more) current excitations are defined without a "junction node" (more than two edges) in between.
43 For example, if a node is used by only two edges and both edges have a current excitation, the current going
44 through the edges is not unambiguous (except for the trivial case of both current excitations being equal,
45 however in this case one of them can be omitted anyway).
46
47 Variant contains the indices of the two offending current sources and the input graph.
48 */
49 TwoCurrentExcitations {
50 first_edge: EdgeIndex,
51 second_edge: EdgeIndex,
52 graph: UnGraph<usize, Type>,
53 },
54 /**
55 A node is a "dead end" (has only one edge using it). Since the circuit is not closed, no current can go through
56 this edge and it can be omitted.
57
58 Variant contains the index of the "dead end" node and the input graph.
59 */
60 DeadEnd {
61 node: NodeIndex,
62 graph: UnGraph<usize, Type>,
63 },
64 /**
65 An edge is short-circuited (source and target node are identical). In this case, the edge can be omitted.
66
67 Variant contains the index of the short-circuited edge and the input graph.
68 */
69 ShortCircuit {
70 edge: EdgeIndex,
71 graph: UnGraph<usize, Type>,
72 },
73 /**
74 Some edges are not connected to other edges, or in other words, the graph represents two or more circuits instead of one.
75 */
76 NotAllEdgesConnected,
77 /**
78 For each edge, at least one of the possible loops containing it must not have more than one current source.
79 Otherwise, the network cannot be solved for the general case, since some current sources are contradicting.
80
81 Example: If 0, 6 and 3 are current sources, the network is overdefined, since there is no possible loop without two current sources.
82 ```text
83 ┌─[1]─┬─[2]─┐
84 [0] [6] [3]
85 └─[5]─┴─[4]─┘
86 ```
87 Variant contains the index of one current source edge which must be changed to make the graph valid as well as the input graph.
88 */
89 OverdefinedByCurrentSources {
90 edge: EdgeIndex,
91 graph: UnGraph<usize, Type>,
92 },
93 /**
94 For each edge, all possible loops containing it must not have only voltage sources.
95 Otherwise, the network cannot be solved for the general case, since some voltage sources are contradicting.
96
97 Example: If 0 and 1 are voltage sources, the network is overdefined, since the loop 0 -> 1 contains only voltage sources.
98 ```text
99 ┌───┬───┐
100 [0] [1] [2]
101 └───┴───┘
102 ```
103 Variant contains the index of one voltage source edge which must be changed to make the graph valid as well as the input graph.
104 */
105 OverdefinedByVoltageSources {
106 edge: EdgeIndex,
107 graph: UnGraph<usize, Type>,
108 },
109}
110
111impl std::fmt::Display for BuildError {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 BuildError::TwoCurrentExcitations {
115 first_edge,
116 second_edge,
117 graph: _,
118 } => write!(
119 f,
120 "the edges {} and {} both have a current source and are connected without a fork
121 (node with at least three edges) inbetween. The resulting network is not solvable.",
122 first_edge.index(),
123 second_edge.index()
124 ),
125 BuildError::DeadEnd { node, graph: _ } => write!(
126 f,
127 "node {} is a dead end (has only one edge using it). The resulting network is not solvable.",
128 node.index()
129 ),
130 BuildError::ShortCircuit { edge, graph } => write!(
131 f,
132 "edge {} has two identical end points (node {}). The resulting network is not solvable.",
133 edge.index(),
134 graph.edge_endpoints(*edge).expect("edge exists").0.index()
135 ),
136 BuildError::NotAllEdgesConnected => write!(f, "not all edges are connected"),
137 BuildError::OverdefinedByCurrentSources { edge, graph: _ } => {
138 write!(
139 f,
140 "network is overdefined by current sources. Remove the source in edge {}",
141 edge.index()
142 )
143 }
144 BuildError::OverdefinedByVoltageSources { edge, graph: _ } => {
145 write!(
146 f,
147 "network is overdefined by voltage sources. Remove the source in edge {}",
148 edge.index()
149 )
150 }
151 }
152 }
153}
154
155impl std::error::Error for BuildError {}
156
157/**
158An enum representing the different physical quantities within the [`Network`].
159
160These types are not necessarily electrical quantities, but can represent any similar
161physical quantities which follow the physical relationship `voltage = current * resistance`.
162
163Examples:
164- Magnetic domain: Magnetic voltage (voltage), magnetic flux (current), reluctance (resistance)
165- Thermal domain: Temperature difference (voltage), power (current), thermal resistance (resistance)
166
167# Features
168
169This enum can be serialized / deserialized via the [serde](https://crates.io/crates/serde)
170crate if the `serde` feature is enabled.
171 */
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
174pub enum Type {
175 Voltage,
176 Current,
177 Resistance,
178}
179
180impl std::fmt::Display for Type {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 let t = match self {
183 Type::Voltage => "voltage",
184 Type::Current => "current",
185 Type::Resistance => "resistance",
186 };
187 write!(f, "{t}")
188 }
189}
190
191/**
192An edge where the nodes are defined implicitly via the other edges using them.
193
194The `edge_type` describes whether the edge is a resistance, a current or a voltage source.
195If the edge has an excitation, it is always oriented from source to target.
196The index of each `EdgeListEdge` in `network` equals the index of the represented edge itself.
197
198# Examples
199```
200/*
201Network structure:
202 ┌──[0]──┐
203 │ │
204[3] [1]
205 │ │
206 └──[2]──┘
207Voltage source oriented from 1 to 3 at edge 0
208*/
209use network_analysis::{EdgeListEdge, Network, Type};
210assert!(Network::from_edge_list_edges(
211 &[
212 EdgeListEdge::new(vec![3], vec![1], Type::Voltage),
213 EdgeListEdge::new(vec![0], vec![2], Type::Resistance),
214 EdgeListEdge::new(vec![1], vec![3], Type::Resistance),
215 EdgeListEdge::new(vec![2], vec![0], Type::Resistance),
216 ]
217 ).is_ok());
218
219/*
220Network structure:
221 ┌──[0]──┬──[1]──┐
222 │ │ │
223[2] [3] [4]
224 │ │ │
225 └──[5]──┴──[6]──┘
226Current source oriented from 5 to 0 at edge 2
227*/
228assert!(Network::from_edge_list_edges(
229 &[
230 EdgeListEdge::new(vec![3, 1], vec![2], Type::Resistance),
231 EdgeListEdge::new(vec![0, 3], vec![4], Type::Resistance),
232 EdgeListEdge::new(vec![5], vec![0], Type::Current),
233 EdgeListEdge::new(vec![0, 1], vec![5, 6], Type::Resistance),
234 EdgeListEdge::new(vec![1], vec![6], Type::Resistance),
235 EdgeListEdge::new(vec![2], vec![3, 6], Type::Resistance),
236 EdgeListEdge::new(vec![3, 5], vec![4], Type::Resistance),
237 ]
238 ).is_ok());
239```
240
241# Features
242
243This struct can be serialized / deserialized via the [serde](https://crates.io/crates/serde)
244crate if the `serde` feature is enabled.
245 */
246#[derive(Debug, Clone)]
247#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
248pub struct EdgeListEdge {
249 pub source: Vec<usize>,
250 pub target: Vec<usize>,
251 pub edge_type: Type,
252}
253
254impl EdgeListEdge {
255 /**
256 Creates a new instance of `Self` from its components.
257 */
258 pub fn new(source: Vec<usize>, target: Vec<usize>, edge_type: Type) -> Self {
259 return Self {
260 source,
261 target,
262 edge_type,
263 };
264 }
265}
266
267/**
268An edge where the node indices are specified explictly.
269
270The `edge_type` describes whether the edge is a resistance, a current or a voltage source.
271If the edge has an excitation, it is always oriented from source to target.
272
273# Examples
274
275```
276use network_analysis::{NodeEdge, Network, Type};
277
278/*
279Valid input: The node indices start at 0 and form a continuous number sequence up to 3
280
281(0)──[0]──(2)
282 │ │
283[3] [1]
284 │ │
285(3)──[2]──(1)
286 */
287assert!(Network::from_node_edges(
288 &[
289 NodeEdge::new(0, 2, Type::Voltage),
290 NodeEdge::new(2, 1, Type::Resistance),
291 NodeEdge::new(1, 3, Type::Resistance),
292 NodeEdge::new(3, 0, Type::Resistance),
293 ]
294 ).is_ok());
295
296
297/*
298Invalid input: The node indices do not start at zero and do not form a continuous sequence.
299
300(1)──[0]──(3)
301 │ │
302[3] [1]
303 │ │
304(6)──[2]──(7)
305 */
306
307assert!(Network::from_node_edges(
308 &[
309 NodeEdge::new(1, 3, Type::Voltage),
310 NodeEdge::new(3, 7, Type::Resistance),
311 NodeEdge::new(7, 6, Type::Resistance),
312 NodeEdge::new(6, 1, Type::Resistance),
313 ]
314).is_err());
315```
316
317# Features
318
319This struct can be serialized / deserialized via the [serde](https://crates.io/crates/serde)
320crate if the `serde` feature is enabled.
321 */
322#[derive(Debug, Clone, Copy)]
323#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
324pub struct NodeEdge {
325 pub source: usize,
326 pub target: usize,
327 pub edge_type: Type,
328}
329
330impl NodeEdge {
331 /**
332 Creates a new instance of `Self` from its components.
333 */
334 pub fn new(source: usize, target: usize, edge_type: Type) -> Self {
335 return Self {
336 source,
337 target,
338 edge_type,
339 };
340 }
341}
342
343/**
344A network which is valid for performing mesh and nodal analysis.
345
346This struct represents a network which can be solved via [`MeshAnalysis`](crate::mesh_analysis::MeshAnalysis) and
347[`NodalAnalysis`](crate::nodal_analysis::NodalAnalysis). It is essentially a
348[`newtype`](https://doc.rust-lang.org/rust-by-example/generics/new_types.html) wrapper around a
349[`UnGraph<usize, Excitation>`] which forms a valid electrical circuit. Please see [`BuildError`] for
350all the possible ways a graph can fail to represent a valid electrical circuit.
351
352# Sign conventions
353
354The sign of an edge is oriented from source to target. If the following network has a current source at edge 0
355and resistances everywhere else and all edges are oriented clockwise (e.g source of edge 0 is at edge 3 and
356target of edge 0 is at edge 1), an input of -5 at the current source results in an edge current of -5 over all edges.
357```text
358 ┌──[1]──┐
359 | │ │
360 5| [0] [2]
361 V │ │
362 └──[3]──┘
363```
364
365Likewise, if edge 2 is "flipped" (source at edge 3, target at edge 1), its edge current would be +5.
366
367If edge 0 is a voltage source with value +3 instead (i.e high potential at the source, low potential at the target)
368and all resistances have the value 1 (resistances must always be positive),
369the current going through all edges would be -1 (since the voltage drop must be -1 over all resistances so the entire
370loop adds up to a voltage of 0).
371```text
372 ┌──[1]──┐
373 ^ │ │
374 3| [0] [2]
375 | │ │
376 └──[3]──┘
377```
378
379# Constructing a [`Network`]
380
381The following constructor methods are available:
382- [`Network::new`] (using a graph)
383- [`Network::from_node_edges`] (using a slice of [`NodeEdge`])
384- [`Network::from_edge_list_edges`] (using a slice of [`EdgeListEdge`])
385
386Please see the docstrings of the methods for examples.
387
388# Serialization and deserialization
389
390Serialization and deserialization requires that the `serde` feature is enabled.
391This struct serializes into a `Vec<NodeEdge>` and can be (fallible) deserialized from the following types:
392- `Vec<NodeEdge>`
393- `Vec<EdgeListEdge>`
394- `UnGraph<usize, Type>`
395 */
396#[repr(transparent)]
397#[derive(Debug, Clone)]
398#[cfg_attr(feature = "serde", derive(Serialize))]
399pub struct Network(UnGraph<usize, Type>);
400
401#[cfg(feature = "serde")]
402impl<'de> Deserialize<'de> for Network {
403 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404 where
405 D: serde::Deserializer<'de>,
406 {
407 let content = serde_value::Value::deserialize(deserializer)?;
408
409 /*
410 Try to deserialize this struct from either a
411 - `UnGraph<usize, Type>`
412 - `Vec<NodeEdge>`
413 - `Vec<EdgeListEdge>`
414 */
415 if let Ok(graph) = UnGraph::<usize, Type>::deserialize(serde_value::ValueDeserializer::<
416 D::Error,
417 >::new(content.clone()))
418 {
419 return Self::new(graph).map_err(serde::de::Error::custom);
420 }
421 if let Ok(coll) = Vec::<NodeEdge>::deserialize(
422 serde_value::ValueDeserializer::<D::Error>::new(content.clone()),
423 ) {
424 return Self::from_node_edges(coll.as_slice()).map_err(serde::de::Error::custom);
425 }
426 if let Ok(coll) = Vec::<EdgeListEdge>::deserialize(
427 serde_value::ValueDeserializer::<D::Error>::new(content),
428 ) {
429 return Self::from_edge_list_edges(coll.as_slice()).map_err(serde::de::Error::custom);
430 }
431
432 return Err(serde::de::Error::custom(
433 "can only deserialize from the following types: Vec<NodeEdge>, Vec<EdgeListEdge>",
434 ));
435 }
436}
437
438impl Network {
439 /**
440 A graph is a valid network if it fulfills the conditions outlined in the docstring of [`Network`].
441 The edge weights define whether the edge is a resistance, a current source or a voltage source.
442 */
443 pub fn new(graph: UnGraph<usize, Type>) -> Result<Self, BuildError> {
444 // Condition 1)
445 if petgraph::algo::connected_components(&graph) != 1 {
446 return Err(BuildError::NotAllEdgesConnected);
447 }
448
449 // Condition 2)
450 for edge in graph.edge_references() {
451 if edge.source() == edge.target() {
452 return Err(BuildError::ShortCircuit {
453 edge: edge.id(),
454 graph,
455 });
456 }
457 }
458
459 // Condition 3)
460 for node_idx in graph.node_indices() {
461 if graph.edges(node_idx).count() < 2 {
462 return Err(BuildError::DeadEnd {
463 node: node_idx,
464 graph,
465 });
466 }
467 }
468
469 // Needed for condition 4) and 5)
470 let spanning_tree = find_spanning_tree(&graph);
471
472 // Condition 4)
473 // It is sufficient to check if each independent branch has at most one current source,
474 // since the network fails condition 5) anyway if two current sources are on the same branch
475 // in the spanning tree
476 for branch in spanning_tree.independent_branches.iter() {
477 let mut first_current_source: Option<EdgeIndex> = None;
478 for edge in branch.edges.iter().cloned() {
479 if let Some(edge_type) = graph.edge_weight(edge) {
480 if *edge_type == Type::Current {
481 /*
482 If the branch already contains another current source, condition 4 is failed
483 */
484 match first_current_source {
485 Some(first_edge) => {
486 return Err(BuildError::TwoCurrentExcitations {
487 first_edge,
488 second_edge: edge,
489 graph,
490 });
491 }
492 None => {
493 first_current_source = Some(edge);
494 }
495 }
496 }
497 }
498 }
499 }
500
501 // Condition 5)
502 for edge_ref in spanning_tree.graph.edge_references() {
503 if *edge_ref.weight() == Type::Current {
504 return Err(BuildError::OverdefinedByCurrentSources {
505 edge: edge_ref.id(),
506 graph,
507 });
508 }
509 }
510
511 // Condition 6)
512 for independent_branch in spanning_tree.independent_branches.iter() {
513 /*
514 If the independent branch contains only voltages, check if the corresponding part
515 of the spanning tree also contains only voltage sources.
516 */
517 if independent_branch
518 .edges
519 .iter()
520 .all(|idx| *graph.edge_weight(*idx).expect("must exist") == Type::Voltage)
521 {
522 /*
523 To see if the connection between source and target of the spanning tree contains only voltage sources,
524 we use Dijkstra's algorithm and assign any edges which are voltage sources the value 0 and all
525 other edges the value 1. If the total cost calculated by Dijkstra's algorithm is zero,
526 the network is overdefined by voltage sources.
527 */
528 let val = petgraph::algo::bidirectional_dijkstra(
529 &spanning_tree.graph,
530 independent_branch.source,
531 independent_branch.target,
532 |edge| (*edge.weight() != Type::Voltage) as u32,
533 );
534 if val == Some(0) {
535 // We can return any of the voltage sources on the independent branch here
536 return Err(BuildError::OverdefinedByVoltageSources {
537 edge: independent_branch.edges[0],
538 graph,
539 });
540 }
541 }
542 }
543
544 return Ok(Self(graph));
545 }
546
547 /**
548 Creates a new instance of `Self` from a slice of [`NodeEdge`].
549 See the docstring of [`NodeEdge`] for an example.
550 */
551 pub fn from_node_edges(edges: &[NodeEdge]) -> Result<Self, BuildError> {
552 // Find the largest node index specified in one of the `NodeEdge`s
553 let mut max_node = 0;
554 for edge in edges.iter() {
555 if edge.source > max_node {
556 max_node = edge.source;
557 }
558 if edge.target > max_node {
559 max_node = edge.target;
560 }
561 }
562
563 // Assumption: Number of nodes approximately equal to the number of edges
564 let mut graph = UnGraph::<usize, Type>::with_capacity(edges.len(), edges.len());
565 for node_tag in 0..(max_node + 1) {
566 graph.add_node(node_tag);
567 }
568 for edge in edges.iter() {
569 let source = NodeIndex::new(edge.source);
570 let target = NodeIndex::new(edge.target);
571 graph.add_edge(source, target, edge.edge_type); // Note that this method allows parallel edges!
572 }
573 return Self::new(graph);
574 }
575
576 /**
577 Creates a new instance of `Self` from a slice of [`EdgeListEdge`].
578
579 See the docstring of [`EdgeListEdge`] for an example.
580 */
581 pub fn from_edge_list_edges(edge_list_edges: &[EdgeListEdge]) -> Result<Self, BuildError> {
582 // Intialize an empty vector to hold the edge definition
583 let num_edges = edge_list_edges.len();
584 let mut node_edges: Vec<NodeEdge> = vec![
585 NodeEdge {
586 source: 0,
587 target: 0,
588 edge_type: Type::Resistance,
589 };
590 num_edges
591 ];
592
593 // Node list describing the edges connected to this node.
594 let mut nodes: Vec<Vec<usize>> = Vec::new();
595
596 // Node tag counter
597 let mut free_node_tag: usize = 0;
598
599 // Populate edges
600 for (edge_tag, (mut edge_by_node, edge_by_edge)) in node_edges
601 .iter_mut()
602 .zip(edge_list_edges.iter())
603 .enumerate()
604 {
605 // Inherit the source properties from `edge_by_edge`
606 edge_by_node.edge_type = edge_by_edge.edge_type;
607
608 for (i_side, side) in [&edge_by_edge.source, &edge_by_edge.target]
609 .iter()
610 .enumerate()
611 {
612 // Create a temporary node from the 'source' or 'target' side of the edge
613 let mut edges_connected_to_this_node = side.to_vec(); // All edges listed as connected to the current edge
614 edges_connected_to_this_node.push(edge_tag.try_into().unwrap()); // add the current edge itself, which has the tag "i"
615
616 // Sort the edge vector, this is necessary for later comparison
617 edges_connected_to_this_node.sort_unstable(); // => https://rust-lang-nursery.github.io/rust-cookbook/algorithms/sorting.html
618
619 // Check if the temporary node is already included in edges_connected_to_this_node.
620 // If not, add it to that list. Add the node index of the current side
621 // to the path dictionary.
622 let mut index = None;
623 for (node_idx, node) in nodes.iter().enumerate() {
624 // If we're searching the target node, skip the source node
625 if i_side == 1 && edge_by_node.source == node_idx {
626 continue;
627 }
628 if slices_are_identical(node.as_slice(), &edges_connected_to_this_node) {
629 index = Some(node_idx);
630 break;
631 }
632 }
633
634 match index {
635 // The node has not been created yet => create it now!
636 None => {
637 update_edge_by_node(&mut edge_by_node, free_node_tag, i_side);
638 nodes.push(edges_connected_to_this_node);
639
640 // Now update the free node tag
641 free_node_tag += 1;
642 }
643 // The node does already exist => update the edge with this node
644 Some(existing_node_tag) => {
645 update_edge_by_node(&mut edge_by_node, existing_node_tag, i_side);
646 }
647 }
648 }
649 }
650
651 return Self::from_node_edges(node_edges.as_slice());
652 }
653
654 /**
655 Accesses the [`UnGraph`](https://docs.rs/petgraph/0.8.3/petgraph/graph/type.UnGraph.html) representation of the network.
656
657 The network analysis structs [`MeshAnalysis`](crate::mesh_analysis::MeshAnalysis) and
658 [`NodalAnalysis`](crate::nodal_analysis::NodalAnalysis) use [`petgraph`](https://crates.io/crates/petgraph)s
659 [`UnGraph`] when they are created from a [`Network`] instance. This method allows accessing
660 the graph directly. Please be aware that [`Network`] holds more information than just the graph
661 (i.e. the edge source type information) and can therefore not be losslessly represented by a [`UnGraph`]
662 (otherwise, the need for this type would not exist in the first place).
663
664 # Examples
665 ```
666 use network_analysis::{EdgeListEdge, Network, Type};
667 use petgraph::algo::dijkstra;
668 use petgraph::visit::NodeIndexable;
669
670 let network = Network::from_edge_list_edges(
671 &[
672 EdgeListEdge::new(vec![3], vec![1], Type::Voltage),
673 EdgeListEdge::new(vec![0], vec![2], Type::Resistance),
674 EdgeListEdge::new(vec![1], vec![3], Type::Resistance),
675 EdgeListEdge::new(vec![2], vec![0], Type::Resistance),
676 ]
677 ).expect("valid network");
678 let g = network.graph();
679
680 // Now use some of the functionality provided by petgraph, e.g. the "dijkstra" path finding algorithm
681 let node_map = dijkstra(&g, 0.into(), Some(2.into()), |_| 1);
682 assert_eq!(&2i32, node_map.get(&g.from_index(2)).unwrap());
683 ```
684 */
685 pub fn graph(&self) -> &UnGraph<usize, Type> {
686 return &self.0;
687 }
688
689 /**
690 Returns the number of voltage sources by counting all edges where `edge.edge_type == Type::Voltage`.
691
692 # Examples
693 ```
694 use network_analysis::{EdgeListEdge, Network, Type};
695
696 let network = Network::from_edge_list_edges(
697 &[
698 EdgeListEdge::new(vec![3], vec![1], Type::Voltage),
699 EdgeListEdge::new(vec![0], vec![2], Type::Resistance),
700 EdgeListEdge::new(vec![1], vec![3], Type::Current),
701 EdgeListEdge::new(vec![2], vec![0], Type::Voltage),
702 ]
703 ).expect("valid network");
704 assert_eq!(network.voltage_source_count(), 2);
705 ```
706 */
707 pub fn voltage_source_count(&self) -> usize {
708 return self
709 .0
710 .edge_weights()
711 .map(|edge_type| (edge_type == &Type::Voltage) as usize)
712 .sum();
713 }
714
715 /**
716 Returns the number of current sources by counting all edges where `edge.edge_type == Type::Current`.
717
718 # Examples
719 ```
720 use network_analysis::{EdgeListEdge, Network, Type};
721
722 let network = Network::from_edge_list_edges(
723 &[
724 EdgeListEdge::new(vec![3], vec![1], Type::Voltage),
725 EdgeListEdge::new(vec![0], vec![2], Type::Resistance),
726 EdgeListEdge::new(vec![1], vec![3], Type::Current),
727 EdgeListEdge::new(vec![2], vec![0], Type::Voltage),
728 ]
729 ).expect("valid network");
730 assert_eq!(network.current_source_count(), 1);
731 ```
732 */
733 pub fn current_source_count(&self) -> usize {
734 return self
735 .0
736 .edge_weights()
737 .map(|edge_type| (edge_type == &Type::Current) as usize)
738 .sum();
739 }
740}
741
742pub(crate) struct IndependentBranch {
743 pub(crate) edges: VecDeque<EdgeIndex>,
744 pub(crate) coupling: VecDeque<bool>,
745 pub(crate) source: NodeIndex,
746 pub(crate) target: NodeIndex,
747}
748
749pub(crate) struct SpanningTree {
750 pub(crate) graph: StableUnGraph<usize, Type>,
751 pub(crate) independent_branches: Vec<IndependentBranch>,
752 pub(crate) number_current_source_meshes: usize,
753}
754
755pub(crate) fn find_spanning_tree(graph: &UnGraph<usize, Type>) -> SpanningTree {
756 fn try_remove_adjacent_edge(
757 graph: &UnGraph<usize, Type>,
758 spanning_tree: &mut StableUnGraph<usize, Type>,
759 independent_branch: &mut IndependentBranch,
760 start_from_source: bool,
761 ) {
762 let previous_edge_index = if start_from_source {
763 independent_branch
764 .edges
765 .back()
766 .expect("This function is only called when independent_branch.edges already has an element populated.")
767 } else {
768 independent_branch
769 .edges
770 .front()
771 .expect("This function is only called when independent_branch.edges already has an element populated.")
772 };
773
774 let branch_end_node = if start_from_source {
775 &mut independent_branch.source
776 } else {
777 &mut independent_branch.target
778 };
779
780 // Check how many edges are connected to the given node. If only one is connected, remove it and repeat the process
781 let mut single_edge: Option<EdgeIndex> = None;
782 for (idx, neighbor) in graph.edges(*branch_end_node).enumerate() {
783 if idx < 2 && &neighbor.id() != previous_edge_index {
784 single_edge = Some(neighbor.id());
785 } else if idx > 1 {
786 single_edge = None;
787 break;
788 }
789 }
790 match single_edge {
791 Some(edge_index) => {
792 // If this function returns None, the entire network is actually a single mesh
793 match spanning_tree.edge_endpoints(edge_index) {
794 Some((a, b)) => {
795 spanning_tree.remove_edge(edge_index);
796
797 // Identify the coupling between previous_edge_index and edge_index
798 let coupling = {
799 let (source, target) =
800 unsafe { graph.edge_endpoints(edge_index).unwrap_unchecked() };
801 let (prev_source, prev_target) = unsafe {
802 graph
803 .edge_endpoints(*previous_edge_index)
804 .unwrap_unchecked()
805 };
806 if start_from_source {
807 prev_source == target // If true, the edges are oriented in the same direction.
808 } else {
809 prev_target == source // If true, the edges are oriented in the same direction.
810 }
811 };
812
813 // Add the edge index and the coupling
814 if start_from_source {
815 independent_branch.edges.push_front(edge_index);
816 independent_branch.coupling.push_front(coupling);
817 } else {
818 independent_branch.edges.push_back(edge_index);
819 independent_branch.coupling.push_back(coupling);
820 }
821
822 if a == *branch_end_node {
823 *branch_end_node = b;
824 return try_remove_adjacent_edge(
825 graph,
826 spanning_tree,
827 independent_branch,
828 start_from_source,
829 );
830 } else {
831 *branch_end_node = a;
832 return try_remove_adjacent_edge(
833 graph,
834 spanning_tree,
835 independent_branch,
836 start_from_source,
837 );
838 }
839 }
840 None => return (),
841 }
842 }
843 None => return (),
844 }
845 }
846
847 fn try_create_new_mesh(
848 graph: &UnGraph<usize, Type>,
849 mut spanning_tree: &mut StableUnGraph<usize, Type>,
850 independent_branches: &mut Vec<IndependentBranch>,
851 edge_index: EdgeIndex,
852 ) {
853 // Find the nodes associated with the edge
854 let (a, b) = graph.edge_endpoints(edge_index).unwrap();
855
856 // Remove the "edge_index" edge from the spanning tree and check if the graph is still connected.
857 if let Some(edge_type) = spanning_tree.remove_edge(edge_index) {
858 // If true, the nodes a and b are still connected, hence the edge is not part of the spanning tree.
859 // If false, the edge is part of the spanning tree and therefore it is added again to the spanning tree.
860 if petgraph::algo::has_path_connecting(&*spanning_tree, a, b, None) {
861 let mut branch = IndependentBranch {
862 edges: VecDeque::new(),
863 coupling: VecDeque::new(),
864 source: a,
865 target: b,
866 };
867 branch.edges.push_back(edge_index);
868 branch.coupling.push_back(true);
869
870 // If only one edge is attached to node a or b, this edge belongs to the independent branch as well.
871 // Repeat this process until a node is encountered which features multiple edges connrected to it.
872 try_remove_adjacent_edge(graph, &mut spanning_tree, &mut branch, true);
873 try_remove_adjacent_edge(graph, &mut spanning_tree, &mut branch, false);
874
875 // Add the edges to the list of independent branches
876 independent_branches.push(branch);
877 } else {
878 // Readd the edge to the spanning tree
879 spanning_tree.add_edge(a, b, edge_type);
880 }
881 }
882 }
883
884 /*
885 Create a spanning tree ('vollstaendiger Baum') by the method described in
886 Mathis, S.: Permanentmagneterregte Line-Start-Antriebe in Ferrittechnik,
887 PhD thesis, TU Kaiserslautern, Shaker-Verlag, 2019, p.61f.
888
889 A deep copy of the graph is created and the algorithmus tries to remove edges while keeping all nodes connected.
890 If a connection would be lost due to the removal of the current edge, keep the edge instead and try removing the next one.
891 The returned result of this operation is a reduced network which connects all nodes w/o having a circular connection.
892
893 A traditional problem of mesh analysis has been the treatment of current sources. This implementation uses the following strategy to deal with those:
894 When building the spanning tree, two consecutive passes are done over all edges. In the first pass, only branches with current sources are tried.
895 If they can be made into an independent branch, they automatically define the mesh current through this branch. If they can't be made into an
896 independent branch, the network is overdefined. They are then ignored during the solving process and only used in a post-processing step.
897 In the second pass, all non-current-source branches are tried again until a spanning tree remains.
898 */
899 let mut spanning_tree: StableUnGraph<usize, Type> = graph.clone().into();
900
901 // Storage for the edge indices of the independent branches. An independent branch may contain more than one edge.
902 let mut independent_branches: Vec<IndependentBranch> = Vec::new();
903
904 // First pass: Only consider current sources
905 for edge in graph.edge_references() {
906 if edge.weight() == &Type::Current {
907 try_create_new_mesh(
908 graph,
909 &mut spanning_tree,
910 &mut independent_branches,
911 edge.id(),
912 );
913 }
914 }
915
916 // All meshes which have been created until now are current source meshes, which have to be treated in a special way.
917 let number_current_source_meshes = independent_branches.len();
918
919 // Second pass: Ignore all current sources
920 for edge in graph.edge_references() {
921 if edge.weight() != &Type::Current {
922 try_create_new_mesh(
923 graph,
924 &mut spanning_tree,
925 &mut independent_branches,
926 edge.id(),
927 );
928 }
929 }
930 return SpanningTree {
931 graph: spanning_tree,
932 independent_branches,
933 number_current_source_meshes,
934 };
935}
936
937fn slices_are_identical<T: PartialEq>(va: &[T], vb: &[T]) -> bool {
938 (va.len() == vb.len()) && // zip stops at the shortest
939 va.iter()
940 .zip(vb)
941 .all(|(a,b)| *a == *b)
942}
943
944fn update_edge_by_node(edge: &mut NodeEdge, node_tag: usize, side_index: usize) {
945 if side_index == 0 {
946 edge.source = node_tag;
947 } else {
948 edge.target = node_tag;
949 }
950}