use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use super::error::AssignmentError;
use super::indexed_graph::IndexedGraph;
use crate::gmns::meso::network::Network;
use crate::gmns::types::ZoneID;
use crate::log_additional;
use crate::log_main;
use crate::od::OdMatrix;
use crate::verbose::{EVENT_ASSIGNMENT, EVENT_ASSIGNMENT_ITERATION, EVENT_CONVERGENCE};
use super::{AssignmentConfig, AssignmentMethod, AssignmentResult, VolumeDelayFunction};
#[derive(Debug, Clone)]
struct Path {
link_indices: Vec<usize>,
fingerprint: u64,
flow: f64,
cost: f64,
}
fn path_fingerprint(link_indices: &[usize]) -> u64 {
let mut hasher = DefaultHasher::new();
link_indices.hash(&mut hasher);
hasher.finish()
}
#[inline]
fn path_cost_indexed(link_indices: &[usize], costs: &[f64]) -> f64 {
link_indices.iter().map(|&li| costs[li]).sum()
}
fn build_path_indexed(
pred: &[Option<usize>],
graph: &IndexedGraph,
dest_idx: usize,
) -> Vec<usize> {
let mut path = Vec::new();
let mut current = dest_idx;
loop {
match pred[current] {
Some(li) => {
path.push(li);
current = graph.link_source(li);
}
None => break,
}
}
path.reverse();
path
}
#[derive(Debug)]
pub struct GradientProjection {
pub step_scale: f64,
}
impl GradientProjection {
pub fn new() -> Self {
GradientProjection { step_scale: 0.1 }
}
pub fn with_step_scale(step_scale: f64) -> Self {
GradientProjection { step_scale }
}
}
impl Default for GradientProjection {
fn default() -> Self {
Self::new()
}
}
impl AssignmentMethod for GradientProjection {
fn assign(
&self,
_network: &Network,
graph: &IndexedGraph,
od_matrix: &dyn OdMatrix,
vdf: &dyn VolumeDelayFunction,
config: &AssignmentConfig,
) -> Result<AssignmentResult, AssignmentError> {
log_main!(EVENT_ASSIGNMENT, "Starting Gradient Projection assignment",);
let zone_ids = od_matrix.zone_ids().to_vec();
let n = graph.num_links;
let zone_node_idxs: Vec<Option<usize>> = zone_ids.iter()
.map(|&z| graph.zone_node_idx(z))
.collect();
let mut costs = vec![0.0; n];
let mut volumes = vec![0.0; n];
let mut dij_dist = vec![f64::INFINITY; graph.num_nodes];
let mut dij_pred: Vec<Option<usize>> = vec![None; graph.num_nodes];
let mut dij_visited = vec![false; graph.num_nodes];
graph.compute_costs(&volumes, vdf, &mut costs);
let mut path_sets: HashMap<(ZoneID, ZoneID), Vec<Path>> = HashMap::new();
for (oi, &origin_zone) in zone_ids.iter().enumerate() {
let origin_idx = match zone_node_idxs[oi] {
Some(i) => i,
None => continue,
};
graph.dijkstra_into(origin_idx, &costs, &mut dij_dist, &mut dij_pred, &mut dij_visited);
for (di, &dest_zone) in zone_ids.iter().enumerate() {
if oi == di {
continue;
}
let demand = od_matrix.get(origin_zone, dest_zone);
if demand <= 0.0 {
continue;
}
let dest_idx = match zone_node_idxs[di] {
Some(i) => i,
None => continue,
};
let link_indices = build_path_indexed(&dij_pred, graph, dest_idx);
if link_indices.is_empty() {
continue;
}
let cost = path_cost_indexed(&link_indices, &costs);
let fp = path_fingerprint(&link_indices);
for &li in &link_indices {
volumes[li] += demand;
}
path_sets.insert(
(origin_zone, dest_zone),
vec![Path {
link_indices,
fingerprint: fp,
flow: demand,
cost,
}],
);
}
}
let mut converged = false;
let mut relative_gap = f64::MAX;
let mut iteration = 0;
for iter in 0..config.max_iterations {
iteration = iter + 1;
graph.compute_costs(&volumes, vdf, &mut costs);
for paths in path_sets.values_mut() {
for path in paths.iter_mut() {
path.cost = path_cost_indexed(&path.link_indices, &costs);
}
}
let mut total_cost = 0.0;
let mut total_shortest_cost = 0.0;
for (oi, &origin_zone) in zone_ids.iter().enumerate() {
let origin_idx = match zone_node_idxs[oi] {
Some(i) => i,
None => continue,
};
graph.dijkstra_into(origin_idx, &costs, &mut dij_dist, &mut dij_pred, &mut dij_visited);
for (di, &dest_zone) in zone_ids.iter().enumerate() {
if oi == di {
continue;
}
let paths = match path_sets.get_mut(&(origin_zone, dest_zone)) {
Some(p) => p,
None => continue,
};
let dest_idx = match zone_node_idxs[di] {
Some(i) => i,
None => continue,
};
let sp_links = build_path_indexed(&dij_pred, graph, dest_idx);
if sp_links.is_empty() {
continue;
}
let sp_cost = path_cost_indexed(&sp_links, &costs);
let sp_fp = path_fingerprint(&sp_links);
let sp_idx = match paths.iter().position(|p| p.fingerprint == sp_fp) {
Some(idx) => idx,
None => {
paths.push(Path {
link_indices: sp_links,
fingerprint: sp_fp,
flow: 0.0,
cost: sp_cost,
});
paths.len() - 1
}
};
let mut total_demand = 0.0;
let mut weighted_cost = 0.0;
for path in paths.iter() {
total_demand += path.flow;
weighted_cost += path.flow * path.cost;
}
total_cost += weighted_cost;
total_shortest_cost += total_demand * sp_cost;
let step = self.step_scale / (iteration as f64).sqrt();
let mut deficit = 0.0;
for (idx, path) in paths.iter_mut().enumerate() {
if idx == sp_idx {
continue;
}
let cost_diff = path.cost - sp_cost;
if cost_diff > 0.0 && path.flow > 0.0 {
let shift = (step * cost_diff * path.flow).min(path.flow);
path.flow -= shift;
deficit += shift;
}
}
if deficit > 0.0 {
paths[sp_idx].flow += deficit;
}
paths.retain(|p| p.flow > 1e-10 || p.fingerprint == sp_fp);
}
}
volumes.fill(0.0);
for paths in path_sets.values() {
for path in paths {
for &li in &path.link_indices {
volumes[li] += path.flow;
}
}
}
if total_cost > 0.0 {
relative_gap = (total_cost - total_shortest_cost) / total_cost;
} else {
relative_gap = 0.0;
}
log_additional!(
EVENT_ASSIGNMENT_ITERATION,
"Gradient Projection iteration",
iteration = iteration,
gap = format!("{:.8}", relative_gap)
);
if relative_gap.abs() < config.convergence_gap {
converged = true;
break;
}
}
graph.compute_costs(&volumes, vdf, &mut costs);
log_main!(
EVENT_CONVERGENCE,
"Gradient Projection complete",
iterations = iteration,
gap = format!("{:.8}", relative_gap),
converged = converged
);
Ok(AssignmentResult {
link_volumes: graph.volumes_to_hashmap(&volumes),
link_costs: graph.costs_to_hashmap(&costs),
iterations: iteration,
relative_gap,
converged,
})
}
}