1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! This module contains methods for `Network` related to creating and removing links as well as
//! disconnecting entire nodes from the network.

use super::Network;
use super::enums::*;
use super::algorithms::bfs;
use rand::{prelude::ThreadRng, Rng};
use std::collections::HashMap;

impl Network {

    /// Gets the weight of the link between nodes i and j.  Returns `None` if such link does not
    /// exist and `Err` if either i or j does not exist in the network.
    pub fn get_link(&self, i: usize, j: usize) -> Result<Option<f64>, String> {
        match self.nodes.get(i) {
            Some(node_i) => match self.nodes.get(j) {
                Some(node_j) => Ok(node_i.links.get(&node_j.index).cloned()),
                None => Err(format!("Node {} does not exist.", j)),
            },
            None => Err(format!("Node {} does not exist.", i)),
        }
    }

    /// Private function used to link two nodes i and j. This does not check wether i and j exist
    /// and it will panic if they do not. It is expected to use the guarded `link` in scenarios
    /// where there is such possibility.
    pub(super) fn _link(&mut self, i: usize, j: usize, rng: &mut ThreadRng) {
        let w: f64;
        match self.weight {
            LinkWeight::Constant { c } => w = c,
            LinkWeight::Uniform => w = rng.gen(),
        }
        self.nodes[i].links.insert(j, w);
        self.nodes[j].links.insert(i, w);
    }

    /// Establish a link between nodes i and j with current network's weight. Returns `Err` if
    /// either i or j does not exist in the network. Does nothing if the requested link already
    /// exist.
    ///
    /// This is a more user-friendly interface to the public-in-super `_link`.
    pub fn link(&mut self, i: usize, j: usize) -> Result<(), String> {
        match self.get_link(i, j)? {
            Some(..) => Ok(()),
            None => Ok(self._link(i, j, &mut rand::thread_rng())),
        }
    }

    /// Establish a link between nodes i and j with sepcified `weight`. Updates the link weight and
    /// returns the old value if it already exist, `None` otherwise. Returns `Err` if either i or j
    /// does not exist in the network.
    pub fn link_exact(&mut self, i: usize, j: usize, w: f64) -> Result<Option<f64>, String> {
        if w <= 0. || w > 1. {
            return Err(format!("Link weight cannot be {}", w));
        }
        match self.get_link(i, j)? {
            Some(weight) => {
                self.nodes[i].links.insert(j, w);
                self.nodes[j].links.insert(i, w);
                Ok(Some(weight))
            }
            None => {
                self.nodes[i].links.insert(j, w);
                self.nodes[j].links.insert(i, w);
                Ok(None)
            }
        }
    }

    /// Unsafely remove a link between two nodes. Does not perform a check whether the integrity of
    /// the network is preserved.
    ///
    /// Returns the weight of the removed connection or `None` if the connection does not exist and
    /// `Err` if either i or j does not exist in the network.
    pub fn unlink(&mut self, i: usize, j: usize) -> Result<Option<f64>, String> {
        match self.get_link(i, j)? {
            Some(weight) => {
                self.nodes[i].links.remove(&j);
                self.nodes[j].links.remove(&i);
                Ok(Some(weight))
            }
            None => Ok(None),
        }
    }

    /// Removes a link between two nodes ensuring the network does not split. Performs a BFS search
    /// to check whether the integrity of the network is preserved.
    ///
    /// Returns the weight of removed link or `None` if the link does not exist or cannot be safely
    /// removed. Returns `Err` if either i or j does not exist in the network.
    pub fn unlink_safe(&mut self, i: usize, j: usize) -> Result<Option<f64>, String> {
        let w: f64;
        match self.unlink(i, j)? {
            Some(weight) => w = weight,
            None => return Ok(None),
        }
        match bfs(self, i, j)? {
            Some(..) => Ok(Some(w)),
            None => {
                // There is no longer a path from i to j - revert the unlink
                self.link_exact(i, j, w)?;
                Ok(None)
            }
        }
    }

    /// Unsafely disconnect a node from the network by removing all of its links. Does not
    /// check if the network integrity is perserved. Returns the removed links as a `HashMap`
    /// of (target, weight) pairs. Returns `Err` if node does not exist in the network.
    pub fn disconnect(&mut self, node: usize) -> Result<HashMap<usize, f64>, String> {
        match self.nodes.get(node) {
            Some(..) => {
                let links = self.nodes[node].links.clone();
                for (&other, _) in &links {
                    self.nodes[node].links.remove(&other);
                    self.nodes[other].links.remove(&node);
                }
                Ok(links)
            }
            None => Err(format!("No such node: {}", node)),
        }
    }

    /// Safely disconnect a node from the network by removing all of its links. Performs a bfs
    /// search to ensure that the other nodes can stil all reach each other, ie. the integrity of
    /// the network is perserved.
    ///
    /// Returns removed links as `HashMap` of (target, weight) pairs or `None` if `node` cannot be
    /// safely disconnected. Returns `Err` if `node` does not exist in the network.
    pub fn disconnect_safe(&mut self, node: usize) -> Result<Option<HashMap<usize, f64>>, String> {
        // Blindly disconnect the requested node
        let links = self.disconnect(node)?;
        // Get a vector of used-to-be-connected nodes
        let used_to: Vec<usize> = links.keys().cloned().into_iter().collect();
        let used_to_len = used_to.len();
        // i and j are indexes in the keys vector and the values of keys[i] and keys[j] are
        // the used-to-be-connected nodes in self.nodes
        for i in 0..used_to_len {
            // For every node that used to connect to the removed...
            let node_i = used_to[i];
            for j in i..used_to_len {
                // ... for all nodes after it in the used_to vector...
                let node_j = used_to[j];
                // ... check if path between them still exists
                if let None = bfs(self, node_i, node_j).expect("Network corrupted") {
                    // There is no longer a path from node_i to node_j - revert the disconnect
                    for (key, value) in links {
                        self.link_exact(key, node, value).expect("Network corrupted");
                    }
                    return Ok(None);
                }
            }
        }
        Ok(Some(links))
    }

    /// Removes **ALL** links in the network and sets the `model` to `NetworkModel::None` such that
    /// the initial linking process can be conducted again, eg. using a different model in
    /// combination with `Network::init_*`.
    pub fn disconnect_all(&mut self) {
        for node in &mut self.nodes {
            node.links.clear();
        }
        self.model = NetworkModel::None;
    }
}