use super::types::{EdgeId, NodeId};
use crate::resources::Resource;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct GraphTopology {
nodes: HashMap<NodeId, ContagionNode>,
edges: HashMap<EdgeId, PropagationEdge>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContagionNode {
pub id: NodeId,
pub node_type: NodeType,
pub population: usize,
pub resistance: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum NodeType {
City,
Village,
TradingPost,
MilitaryBase,
Custom(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PropagationEdge {
pub id: EdgeId,
pub from: NodeId,
pub to: NodeId,
pub transmission_rate: f32,
pub noise_level: f32,
}
impl Resource for GraphTopology {}
impl GraphTopology {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
edges: HashMap::new(),
}
}
pub fn add_node(&mut self, node: ContagionNode) -> &mut Self {
self.nodes.insert(node.id.clone(), node);
self
}
pub fn add_edge(&mut self, edge: PropagationEdge) -> &mut Self {
self.edges.insert(edge.id.clone(), edge);
self
}
pub fn get_node(&self, id: &NodeId) -> Option<&ContagionNode> {
self.nodes.get(id)
}
pub fn get_edge(&self, id: &EdgeId) -> Option<&PropagationEdge> {
self.edges.get(id)
}
pub fn get_outgoing_edges(&self, node_id: &NodeId) -> Vec<&PropagationEdge> {
self.edges
.values()
.filter(|edge| &edge.from == node_id)
.collect()
}
pub fn get_incoming_edges(&self, node_id: &NodeId) -> Vec<&PropagationEdge> {
self.edges
.values()
.filter(|edge| &edge.to == node_id)
.collect()
}
pub fn get_neighbors(&self, node_id: &NodeId) -> Vec<&ContagionNode> {
self.get_outgoing_edges(node_id)
.iter()
.filter_map(|edge| self.get_node(&edge.to))
.collect()
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn has_node(&self, id: &NodeId) -> bool {
self.nodes.contains_key(id)
}
pub fn has_edge(&self, id: &EdgeId) -> bool {
self.edges.contains_key(id)
}
pub fn all_nodes(&self) -> impl Iterator<Item = &ContagionNode> {
self.nodes.values()
}
pub fn all_edges(&self) -> impl Iterator<Item = &PropagationEdge> {
self.edges.values()
}
pub fn remove_node(&mut self, id: &NodeId) -> Option<ContagionNode> {
self.edges
.retain(|_, edge| edge.from != *id && edge.to != *id);
self.nodes.remove(id)
}
pub fn remove_edge(&mut self, id: &EdgeId) -> Option<PropagationEdge> {
self.edges.remove(id)
}
}
impl ContagionNode {
pub fn new(id: impl Into<String>, node_type: NodeType, population: usize) -> Self {
Self {
id: id.into(),
node_type,
population,
resistance: 0.0,
}
}
pub fn with_resistance(mut self, resistance: f32) -> Self {
self.resistance = resistance.clamp(0.0, 1.0);
self
}
}
impl PropagationEdge {
pub fn new(
id: impl Into<String>,
from: impl Into<String>,
to: impl Into<String>,
transmission_rate: f32,
) -> Self {
Self {
id: id.into(),
from: from.into(),
to: to.into(),
transmission_rate: transmission_rate.clamp(0.0, 1.0),
noise_level: 0.0,
}
}
pub fn with_noise(mut self, noise: f32) -> Self {
self.noise_level = noise.clamp(0.0, 1.0);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_topology_creation() {
let topology = GraphTopology::new();
assert_eq!(topology.node_count(), 0);
assert_eq!(topology.edge_count(), 0);
}
#[test]
fn test_add_node() {
let mut topology = GraphTopology::new();
let node = ContagionNode::new("london", NodeType::City, 100000);
topology.add_node(node);
assert_eq!(topology.node_count(), 1);
assert!(topology.has_node(&"london".to_string()));
}
#[test]
fn test_add_edge() {
let mut topology = GraphTopology::new();
topology.add_node(ContagionNode::new("london", NodeType::City, 100000));
topology.add_node(ContagionNode::new("paris", NodeType::City, 80000));
let edge = PropagationEdge::new("london_paris", "london", "paris", 0.8);
topology.add_edge(edge);
assert_eq!(topology.edge_count(), 1);
assert!(topology.has_edge(&"london_paris".to_string()));
}
#[test]
fn test_get_outgoing_edges() {
let mut topology = GraphTopology::new();
topology.add_node(ContagionNode::new("london", NodeType::City, 100000));
topology.add_node(ContagionNode::new("paris", NodeType::City, 80000));
topology.add_node(ContagionNode::new("berlin", NodeType::City, 70000));
topology.add_edge(PropagationEdge::new("london_paris", "london", "paris", 0.8));
topology.add_edge(PropagationEdge::new(
"london_berlin",
"london",
"berlin",
0.6,
));
topology.add_edge(PropagationEdge::new("paris_berlin", "paris", "berlin", 0.7));
let outgoing = topology.get_outgoing_edges(&"london".to_string());
assert_eq!(outgoing.len(), 2);
}
#[test]
fn test_get_neighbors() {
let mut topology = GraphTopology::new();
topology.add_node(ContagionNode::new("london", NodeType::City, 100000));
topology.add_node(ContagionNode::new("paris", NodeType::City, 80000));
topology.add_node(ContagionNode::new("berlin", NodeType::City, 70000));
topology.add_edge(PropagationEdge::new("london_paris", "london", "paris", 0.8));
topology.add_edge(PropagationEdge::new(
"london_berlin",
"london",
"berlin",
0.6,
));
let neighbors = topology.get_neighbors(&"london".to_string());
assert_eq!(neighbors.len(), 2);
}
#[test]
fn test_node_with_resistance() {
let node =
ContagionNode::new("fortress", NodeType::MilitaryBase, 1000).with_resistance(0.8);
assert_eq!(node.resistance, 0.8);
}
#[test]
fn test_resistance_clamping() {
let node = ContagionNode::new("test", NodeType::City, 1000).with_resistance(1.5);
assert_eq!(node.resistance, 1.0);
}
#[test]
fn test_edge_with_noise() {
let edge = PropagationEdge::new("edge1", "a", "b", 0.5).with_noise(0.3);
assert_eq!(edge.noise_level, 0.3);
}
#[test]
fn test_remove_node() {
let mut topology = GraphTopology::new();
topology.add_node(ContagionNode::new("london", NodeType::City, 100000));
topology.add_node(ContagionNode::new("paris", NodeType::City, 80000));
topology.add_edge(PropagationEdge::new("london_paris", "london", "paris", 0.8));
assert_eq!(topology.node_count(), 2);
assert_eq!(topology.edge_count(), 1);
topology.remove_node(&"london".to_string());
assert_eq!(topology.node_count(), 1);
assert_eq!(topology.edge_count(), 0); }
#[test]
fn test_serialization() {
let mut topology = GraphTopology::new();
topology.add_node(ContagionNode::new("london", NodeType::City, 100000));
topology.add_edge(PropagationEdge::new("edge1", "london", "paris", 0.8));
let json = serde_json::to_string(&topology).unwrap();
let deserialized: GraphTopology = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.node_count(), 1);
assert_eq!(deserialized.edge_count(), 1);
}
#[test]
fn test_custom_node_type() {
let node = ContagionNode::new(
"spacestation",
NodeType::Custom("SpaceStation".to_string()),
5000,
);
assert_eq!(node.node_type, NodeType::Custom("SpaceStation".to_string()));
}
}