Skip to content

Commit

Permalink
Output details about failed requests
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Sep 10, 2024
1 parent 3047186 commit e4a4c79
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 50 deletions.
17 changes: 13 additions & 4 deletions od2net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ pub struct OutputMetadata {
pub num_destinations: usize,
pub num_requests: usize,
pub num_succeeded_requests: usize,
pub num_failed_requests: usize,
pub num_failed_requests_same_endpoints: usize,
pub num_failed_requests_no_path: usize,
pub num_edges_with_count: usize,
pub routing_time_seconds: f32,
pub total_meters_not_allowed: f64,
Expand All @@ -55,8 +56,9 @@ impl OutputMetadata {
num_origins: counts.count_per_origin.len(),
num_destinations: counts.count_per_destination.len(),
num_requests,
num_succeeded_requests: num_requests - (counts.errors as usize),
num_failed_requests: counts.errors as usize,
num_succeeded_requests: num_requests - counts.num_errors(),
num_failed_requests_same_endpoints: counts.errors_same_endpoints.len(),
num_failed_requests_no_path: counts.errors_no_path.len(),
num_edges_with_count: counts.count_per_edge.len(),
routing_time_seconds: routing_time.as_secs_f32(),
total_time_seconds: None,
Expand All @@ -76,7 +78,14 @@ impl OutputMetadata {
("Destinations", self.num_destinations),
("Requests", self.num_requests),
("Requests (succeeded)", self.num_succeeded_requests),
("Requests (failed)", self.num_failed_requests),
(
"Requests (failed because same endpoints)",
self.num_failed_requests_same_endpoints,
),
(
"Requests (failed because no path)",
self.num_failed_requests_no_path,
),
("Edges with a count", self.num_edges_with_count),
] {
println!("- {label}: {}", HumanCount(count as u64));
Expand Down
32 changes: 30 additions & 2 deletions od2net/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ struct Args {
/// Don't output a CSV file with each edge's counts.
#[clap(long)]
no_output_csv: bool,
/// Don't output a GeoJSON file with failed requests.
#[clap(long)]
no_output_failed_requests: bool,
/// Don't output origin and destination points in the GeoJSON output, to reduce file size.
#[clap(long)]
no_output_od_points: bool,
Expand Down Expand Up @@ -157,8 +160,8 @@ fn main() -> Result<()> {
);
println!(
"{} succeeded, and {} failed",
HumanCount(num_requests as u64 - counts.errors),
HumanCount(counts.errors),
HumanCount(num_requests as u64 - counts.num_errors() as u64),
HumanCount(counts.num_errors() as u64),
);
let routing_time = Instant::now().duration_since(routing_start);
timer.stop();
Expand All @@ -169,6 +172,15 @@ fn main() -> Result<()> {
timer.stop();
}

if !args.no_output_failed_requests {
timer.start("Writing failed requests GJ");
write_failed_requests(
format!("{directory}/output/failed_requests.geojson"),
&counts,
)?;
timer.stop();
}

let mut output_metadata =
od2net::OutputMetadata::new(config, &counts, num_requests, routing_time);
timer.start("Writing output GJ");
Expand Down Expand Up @@ -224,3 +236,19 @@ fn main() -> Result<()> {

Ok(())
}

fn write_failed_requests(path: String, counts: &od2net::network::Counts) -> Result<()> {
let mut writer =
geojson::FeatureWriter::from_writer(std::io::BufWriter::new(fs_err::File::create(path)?));
for req in &counts.errors_same_endpoints {
let mut f = req.as_feature();
f.set_property("reason", "same endpoints");
writer.write_feature(&f)?;
}
for req in &counts.errors_no_path {
let mut f = req.as_feature();
f.set_property("reason", "no path");
writer.write_feature(&f)?;
}
Ok(writer.finish()?)
}
20 changes: 17 additions & 3 deletions od2net/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use serde::{Deserialize, Serialize};

use lts::{Tags, LTS};

use super::requests::Request;

#[derive(Serialize, Deserialize)]
pub struct Network {
// TODO Doesn't handle multiple edges between the same node pair
Expand All @@ -27,7 +29,11 @@ pub struct Network {
pub struct Counts {
// TODO Don't use f64 -- we'll end up rounding somewhere anyway, so pick a precision upfront.
pub count_per_edge: HashMap<(NodeID, NodeID), f64>,
pub errors: u64,

/// These requests failed because the start and end snapped to the same intersection
pub errors_same_endpoints: Vec<Request>,
/// These requests failed because there's no path
pub errors_no_path: Vec<Request>,

// Count how many times a point is used successfully as an origin or destination
pub count_per_origin: HashMap<Position, f64>,
Expand All @@ -41,7 +47,8 @@ impl Counts {
pub fn new() -> Self {
Self {
count_per_edge: HashMap::new(),
errors: 0,
errors_same_endpoints: Vec::new(),
errors_no_path: Vec::new(),

count_per_origin: HashMap::new(),
count_per_destination: HashMap::new(),
Expand All @@ -52,7 +59,10 @@ impl Counts {

/// Adds other to this one
pub fn combine(&mut self, other: Counts) {
self.errors += other.errors;
self.errors_same_endpoints
.extend(other.errors_same_endpoints);
self.errors_no_path.extend(other.errors_no_path);

for (key, count) in other.count_per_edge {
*self.count_per_edge.entry(key).or_insert(0.0) += count;
}
Expand All @@ -66,6 +76,10 @@ impl Counts {
self.total_distance_by_lts[i] += other.total_distance_by_lts[i];
}
}

pub fn num_errors(&self) -> usize {
self.errors_same_endpoints.len() + self.errors_no_path.len()
}
}

impl Network {
Expand Down
9 changes: 8 additions & 1 deletion od2net/src/requests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::Result;
use fs_err::File;

use geojson::{FeatureReader, Geometry, Value};
use geojson::{Feature, FeatureReader, Geometry, Value};

#[derive(Debug)]
pub struct Request {
Expand All @@ -20,6 +20,13 @@ impl Request {
serde_json::to_string(&geometry).unwrap()
}

pub fn as_feature(&self) -> Feature {
Feature::from(Geometry::new(Value::LineString(vec![
vec![self.x1, self.y1],
vec![self.x2, self.y2],
])))
}

pub fn load_from_geojson(path: String) -> Result<Vec<Self>> {
let reader = FeatureReader::from_reader(std::io::BufReader::new(File::open(path)?));
let mut requests = Vec::new();
Expand Down
80 changes: 40 additions & 40 deletions od2net/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pub fn handle_request(
.unwrap()
.data;
if start == end {
counts.errors += 1;
counts.errors_same_endpoints.push(req);
return;
}

Expand All @@ -112,48 +112,48 @@ pub fn handle_request(
);
}

if let Some(path) = path_calc.calc_path(&prepared_ch.ch, start, end) {
// fast_paths returns the total cost, but it's not necessarily the right unit.
// Calculate how long this route is.
let mut total_distance = 0.0;
for pair in path.get_nodes().windows(2) {
let i1 = prepared_ch.node_map.translate_id(pair[0]);
let i2 = prepared_ch.node_map.translate_id(pair[1]);
let edge = network
.edges
.get(&(i1, i2))
.or_else(|| network.edges.get(&(i2, i1)))
.unwrap();
total_distance += edge.length_meters;

counts.total_distance_by_lts[edge.lts as u8 as usize] += edge.length_meters;
}

let count = uptake::calculate_uptake(uptake, total_distance);
// TODO Pick an epsilon based on the final rounding we do... though it's possible 1e6 trips
// cross a segment each with probability 1e-6?
if count == 0.0 {
return;
}
let Some(path) = path_calc.calc_path(&prepared_ch.ch, start, end) else {
counts.errors_no_path.push(req);
return;
};
// fast_paths returns the total cost, but it's not necessarily the right unit. Calculate how
// long this route is.
let mut total_distance = 0.0;
for pair in path.get_nodes().windows(2) {
let i1 = prepared_ch.node_map.translate_id(pair[0]);
let i2 = prepared_ch.node_map.translate_id(pair[1]);
let edge = network
.edges
.get(&(i1, i2))
.or_else(|| network.edges.get(&(i2, i1)))
.unwrap();
total_distance += edge.length_meters;

counts.total_distance_by_lts[edge.lts as u8 as usize] += edge.length_meters;
}

for pair in path.get_nodes().windows(2) {
// TODO Actually, don't do this translation until the very end
let i1 = prepared_ch.node_map.translate_id(pair[0]);
let i2 = prepared_ch.node_map.translate_id(pair[1]);
*counts.count_per_edge.entry((i1, i2)).or_insert(0.0) += count;
}
let count = uptake::calculate_uptake(uptake, total_distance);
// TODO Pick an epsilon based on the final rounding we do... though it's possible 1e6 trips
// cross a segment each with probability 1e-6?
if count == 0.0 {
return;
}

*counts
.count_per_origin
.entry(Position::from_degrees(req.x1, req.y1))
.or_insert(0.0) += count;
*counts
.count_per_destination
.entry(Position::from_degrees(req.x2, req.y2))
.or_insert(0.0) += count;
} else {
counts.errors += 1;
for pair in path.get_nodes().windows(2) {
// TODO Actually, don't do this translation until the very end
let i1 = prepared_ch.node_map.translate_id(pair[0]);
let i2 = prepared_ch.node_map.translate_id(pair[1]);
*counts.count_per_edge.entry((i1, i2)).or_insert(0.0) += count;
}

*counts
.count_per_origin
.entry(Position::from_degrees(req.x1, req.y1))
.or_insert(0.0) += count;
*counts
.count_per_destination
.entry(Position::from_degrees(req.x2, req.y2))
.or_insert(0.0) += count;
}

#[derive(Serialize, Deserialize)]
Expand Down

0 comments on commit e4a4c79

Please sign in to comment.