use log::debug;
use std::collections::HashMap;
use crate::graph::{Edge, NamedNode};
use crate::probleminstance::{ProblemInstance, Solution};
pub(crate) fn star_expand(instance: &ProblemInstance) -> Solution {
debug!(
"Running 'star_expand' for graph: {:?}",
instance.g.to_string()
);
if !instance.is_solvable() {
None
} else {
let mut total_transaction_amount = 0;
let v_max: Option<&NamedNode> = instance.g.vertices.iter().max();
match v_max {
None => None,
Some(v) => {
let edges: HashMap<Edge, f64> = instance
.g
.vertices
.iter()
.filter(|u| u != &v)
.map(|u| {
total_transaction_amount += u.weight.abs();
if u.weight > 0 {
(Edge { u: u.id, v: v.id }, u.weight as f64)
} else {
(Edge { u: v.id, v: u.id }, -u.weight as f64)
}
})
.collect();
debug!(
"Caculated Approximation for graph {:?} with edges {:?}",
instance.g.to_string(),
edges
);
debug!(
"The total amount of transactions is: {:?} VS Optimum: {:?}",
total_transaction_amount,
instance.optimal_transaction_amount()
);
Some(edges)
}
}
}
}
pub(crate) fn greedy_satisfaction(instance: &ProblemInstance) -> Solution {
debug!(
"Running 'greedy_satisfaction' for graph: {:?}",
instance.g.to_string()
);
if !instance.is_solvable() {
None
} else {
let mut sol = HashMap::new();
let (mut neg_vertices, mut pos_vertices): (Vec<&NamedNode>, Vec<&NamedNode>) =
instance.g.vertices.iter().partition(|v| v.weight < 0_i64);
let mut side_capacities = 0;
if let Some(x) = neg_vertices.first() {
side_capacities = x.weight;
}
if let Some(x) = pos_vertices.first() {
if x.weight > side_capacities.abs() {
side_capacities = x.weight;
}
}
while !neg_vertices.is_empty() && !pos_vertices.is_empty() {
let n = neg_vertices.first().unwrap();
let p = pos_vertices.first().unwrap();
match side_capacities.cmp(&0_i64) {
std::cmp::Ordering::Less => {
if p.weight <= -side_capacities {
sol.insert(Edge { u: p.id, v: n.id }, p.weight as f64);
side_capacities += p.weight;
if side_capacities == 0 {
neg_vertices.remove(0);
}
pos_vertices.remove(0);
} else {
sol.insert(Edge { u: p.id, v: n.id }, side_capacities as f64);
side_capacities += p.weight;
neg_vertices.remove(0);
}
}
std::cmp::Ordering::Equal => {
side_capacities = p.weight;
}
std::cmp::Ordering::Greater => {
if -n.weight <= side_capacities {
sol.insert(Edge { u: p.id, v: n.id }, n.weight.abs() as f64);
side_capacities += n.weight;
if side_capacities == 0 {
pos_vertices.remove(0);
}
neg_vertices.remove(0);
} else {
sol.insert(Edge { u: p.id, v: n.id }, side_capacities as f64);
side_capacities += n.weight;
pos_vertices.remove(0);
}
}
}
}
Some(sol)
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::approximation::greedy_satisfaction;
use crate::approximation::star_expand;
use crate::graph::Edge;
use crate::graph::Graph;
use crate::probleminstance::ProblemInstance;
use env_logger::Env;
use log::debug;
fn init() {
let _ = env_logger::Builder::from_env(Env::default().default_filter_or("debug"))
.is_test(true)
.try_init();
}
#[test]
fn test_star_expand() {
init();
debug!("Running 'test_star_expand'");
let graph: Graph = vec![
("A".to_owned(), -2_i64),
("B".to_owned(), 2_i64),
("C".to_owned(), 3_i64),
("D".to_owned(), -4_i64),
]
.into();
let graph_string = graph.to_string();
let instance: ProblemInstance = graph.into();
let sol = star_expand(&instance);
debug!(
"For graph '{:?}' star_expand returns: {:?}",
graph_string, sol
);
assert!(sol.is_none());
let graph: Graph = vec![
("A".to_owned(), -1_i64),
("B".to_owned(), 2_i64),
("C".to_owned(), 3_i64),
("D".to_owned(), -4_i64),
]
.into();
let graph_string = graph.to_string();
let instance: ProblemInstance = graph.clone().into();
let sol_opt = star_expand(&instance);
debug!(
"For graph '{:?}' star_expand returns: {:?}",
graph_string, sol
);
assert!(sol_opt.is_some());
let na = instance.g.get_node_from_name("A".to_owned()).unwrap();
let nb = instance.g.get_node_from_name("B".to_owned()).unwrap();
let nc = instance.g.get_node_from_name("C".to_owned()).unwrap();
let nd = instance.g.get_node_from_name("D".to_owned()).unwrap();
let res: HashMap<Edge, f64> = HashMap::from([
(Edge { u: nc.id, v: na.id }, 1.0_f64),
(Edge { u: nc.id, v: nd.id }, 4.0_f64),
(Edge { u: nb.id, v: nc.id }, 2.0_f64),
]);
let sol = sol_opt.unwrap();
debug!("Solution: {:?}", sol);
debug!("Expected Result: {:?}", res);
for (e, w) in sol {
assert!(
res.contains_key(&e),
"Edge '{:?}' should not be in the solution.",
e
);
let rew = res.get(&e).unwrap().to_owned();
assert!(
rew == w,
"Edge '{:?}' should have a weight of '{:?}' but has '{:?}'",
e,
rew,
w
);
}
}
#[test]
fn test_greedy_satisfaction() {
init();
debug!("Running 'test_greedy_satisfaction'");
let graph: Graph = vec![
("A".to_owned(), -2_i64),
("B".to_owned(), 2_i64),
("C".to_owned(), 3_i64),
("D".to_owned(), -4_i64),
]
.into();
let graph_string = graph.to_string();
let instance: ProblemInstance = graph.into();
let sol = greedy_satisfaction(&instance);
debug!(
"For graph '{:?}' greedy_satisfaction returns: {:?}",
graph_string, sol
);
assert!(sol.is_none());
let graph: Graph = vec![
("A".to_owned(), -1_i64),
("B".to_owned(), 2_i64),
("C".to_owned(), 3_i64),
("D".to_owned(), -4_i64),
]
.into();
let graph_string = graph.to_string();
let instance: ProblemInstance = graph.into();
let sol = greedy_satisfaction(&instance);
debug!(
"For graph '{:?}' greedy_satisfaction returns: {:?}",
graph_string, sol
);
assert!(sol.is_some());
assert_eq!(sol.unwrap().into_iter().map(|(_, v)| v).sum::<f64>(), 5_f64);
}
}