Skip to main content

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}