use super::error::TripDistributionError;
use crate::gmns::types::ZoneID;
use crate::log_main;
use crate::od::OdMatrix;
use crate::od::dense::DenseOdMatrix;
use crate::verbose::EVENT_GRAVITY_MODEL;
use super::furness::{FurnessConfig, furness_balance};
use super::impedance::ImpedanceFunction;
#[derive(Debug, Clone)]
pub struct GravityModel {
pub furness_config: FurnessConfig,
}
impl GravityModel {
pub fn new() -> Self {
GravityModel {
furness_config: FurnessConfig::default(),
}
}
pub fn with_furness_config(config: FurnessConfig) -> Self {
GravityModel {
furness_config: config,
}
}
pub fn distribute(
&self,
productions: &[f64],
attractions: &[f64],
cost_matrix: &dyn OdMatrix,
impedance: &dyn ImpedanceFunction,
zone_ids: &[ZoneID],
) -> Result<DenseOdMatrix, TripDistributionError> {
let n = zone_ids.len();
if productions.len() != n || attractions.len() != n {
return Err(TripDistributionError::DimensionMismatch(
"productions/attractions length mismatch with zone_ids".to_string(),
));
}
let mut imp = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
let cost = cost_matrix.get_by_index(i, j);
if cost > 0.0 && cost.is_finite() {
imp[i * n + j] = impedance.compute(cost);
}
}
}
let mut data = vec![0.0; n * n];
for i in 0..n {
if productions[i] <= 0.0 {
continue;
}
let row = i * n;
let mut denom = 0.0;
for k in 0..n {
if attractions[k] > 0.0 {
denom += attractions[k] * imp[row + k];
}
}
if denom <= 0.0 {
continue;
}
for j in 0..n {
if attractions[j] > 0.0 && imp[row + j] > 0.0 {
data[row + j] = productions[i] * attractions[j] * imp[row + j] / denom;
}
}
}
let iterations =
furness_balance(&mut data, n, productions, attractions, &self.furness_config)?;
log_main!(
EVENT_GRAVITY_MODEL,
"Gravity model distribution complete",
zones = n,
furness_iterations = iterations
);
Ok(DenseOdMatrix::from_data(zone_ids.to_vec(), data))
}
}
impl Default for GravityModel {
fn default() -> Self {
Self::new()
}
}