use super::error::AssignmentError;
use super::indexed_graph::IndexedGraph;
use crate::gmns::meso::network::Network;
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};
const INV_PHI: f64 = 0.618_033_988_749_895;
const TOL: f64 = 1e-8;
#[derive(Debug)]
pub struct FrankWolfe;
impl FrankWolfe {
pub fn new() -> Self {
FrankWolfe
}
fn line_search(
graph: &IndexedGraph,
current: &[f64],
auxiliary: &[f64],
vdf: &dyn VolumeDelayFunction,
) -> f64 {
let mut a = 0.0_f64;
let mut b = 1.0_f64;
let mut x1 = b - INV_PHI * (b - a);
let mut x2 = a + INV_PHI * (b - a);
let mut f1 = Self::eval_at_step(graph, current, auxiliary, vdf, x1);
let mut f2 = Self::eval_at_step(graph, current, auxiliary, vdf, x2);
for _ in 0..20 {
if (b - a) < TOL {
break;
}
if f1 < f2 {
b = x2;
x2 = x1;
f2 = f1;
x1 = b - INV_PHI * (b - a);
f1 = Self::eval_at_step(graph, current, auxiliary, vdf, x1);
} else {
a = x1;
x1 = x2;
f1 = f2;
x2 = a + INV_PHI * (b - a);
f2 = Self::eval_at_step(graph, current, auxiliary, vdf, x2);
}
}
(a + b) / 2.0
}
fn eval_at_step(
graph: &IndexedGraph,
current: &[f64],
auxiliary: &[f64],
vdf: &dyn VolumeDelayFunction,
lambda: f64,
) -> f64 {
let mut objective = 0.0;
for i in 0..graph.num_links {
let vol = current[i] + lambda * (auxiliary[i] - current[i]);
objective += vdf.integral(graph.link_ff_time[i], vol, graph.link_capacity[i]);
}
objective
}
}
impl Default for FrankWolfe {
fn default() -> Self {
Self::new()
}
}
impl AssignmentMethod for FrankWolfe {
fn assign(
&self,
_network: &Network,
graph: &IndexedGraph,
od_matrix: &dyn OdMatrix,
vdf: &dyn VolumeDelayFunction,
config: &AssignmentConfig,
) -> Result<AssignmentResult, AssignmentError> {
log_main!(EVENT_ASSIGNMENT, "Starting Frank-Wolfe assignment",);
let n = graph.num_links;
let mut costs = vec![0.0; n];
let mut volumes = vec![0.0; n];
let mut aux_volumes = vec![0.0; n];
graph.compute_costs(&volumes, vdf, &mut costs);
graph.all_or_nothing(od_matrix, &costs, &mut volumes);
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);
graph.all_or_nothing(od_matrix, &costs, &mut aux_volumes);
relative_gap = graph.relative_gap(&volumes, &costs, &aux_volumes);
log_additional!(
EVENT_ASSIGNMENT_ITERATION,
"Frank-Wolfe iteration",
iteration = iteration,
gap = format!("{:.8}", relative_gap)
);
if relative_gap.abs() < config.convergence_gap {
converged = true;
break;
}
let lambda = Self::line_search(&graph, &volumes, &aux_volumes, vdf);
for i in 0..n {
volumes[i] += lambda * (aux_volumes[i] - volumes[i]);
}
}
graph.compute_costs(&volumes, vdf, &mut costs);
log_main!(
EVENT_CONVERGENCE,
"Frank-Wolfe 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,
})
}
}