Skip to content

Commit

Permalink
Merge pull request #87 from NREL/rjf/cost-abstractions
Browse files Browse the repository at this point in the history
Rjf/cost abstractions
  • Loading branch information
robfitzgerald authored Dec 22, 2023
2 parents 0bb79f8 + 0f940a4 commit 73ed0ad
Show file tree
Hide file tree
Showing 92 changed files with 1,787 additions and 541 deletions.
243 changes: 175 additions & 68 deletions docs/notebooks/open_street_maps_example.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ verbose = true
type = "distance"
distance_unit = "miles"

# based on 65.5 cents per mile 2023 IRS mileage rate, $/mile
[cost.vehicle_state_variable_rates.distance]
type = "factor"
factor = 0.655

[plugin]
input_plugins = [
{ type = "vertex_rtree", distance_tolerance = 0.2, distance_unit = "kilometers", vertices_input_file = "vertices-compass.csv.gz" },
Expand Down
20 changes: 20 additions & 0 deletions python/nrel/routee/compass/resources/osm_default_energy.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ speed_table_speed_unit = "kilometers_per_hour"
output_time_unit = "minutes"
output_distance_unit = "miles"

# based on 65.5 cents per mile 2023 IRS mileage rate, $/mile
[cost.vehicle_state_variable_rates.distance]
type = "factor"
factor = 0.655

# based on $20/hr approximation of 2023 median hourly wages, $/second
[cost.vehicle_state_variable_rates.time]
type = "factor"
factor = 0.333336

# based on AAA regular unleaded gas prices sampled 12/21/2023
[cost.vehicle_state_variable_rates.energy_liquid]
type = "factor"
factor = 3.120

# based on $0.50/kWh approximation of DCFC charge rates, $/kWhtype = "factor"
[cost.vehicle_state_variable_rates.energy_electric]
type = "factor"
factor = 0.05

[[traversal.vehicles]]
name = "2012_Ford_Focus"
type = "ice"
Expand Down
10 changes: 10 additions & 0 deletions python/nrel/routee/compass/resources/osm_default_speed.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ speed_unit = "kilometers_per_hour"
output_distance_unit = "miles"
output_time_unit = "minutes"

# based on 65.5 cents per mile 2023 IRS mileage rate, $/mile
[cost.vehicle_state_variable_rates.distance]
type = "factor"
factor = 0.655

# based on $20/hr approximation of 2023 median hourly wages, $/second
[cost.vehicle_state_variable_rates.time]
type = "factor"
factor = 0.333336

[plugin]
input_plugins = [
{ type = "vertex_rtree", distance_tolerance = 0.2, distance_unit = "kilometers", vertices_input_file = "vertices-compass.csv.gz" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ use crate::algorithm::search::edge_traversal::EdgeTraversal;
use crate::algorithm::search::search_error::SearchError;
use crate::algorithm::search::search_tree_branch::SearchTreeBranch;
use crate::algorithm::search::MinSearchTree;
use crate::model::cost::Cost;
use crate::model::cost::cost_model::CostModel;
use crate::model::frontier::frontier_model::FrontierModel;
use crate::model::road_network::edge_id::EdgeId;
use crate::model::road_network::graph::Graph;
use crate::model::termination::termination_model::TerminationModel;
use crate::model::traversal::state::traversal_state::TraversalState;
use crate::model::traversal::traversal_model::TraversalModel;
use crate::model::unit::Cost;
use crate::util::read_only_lock::ExecutorReadOnlyLock;
use crate::{algorithm::search::direction::Direction, model::road_network::vertex_id::VertexId};
use priority_queue::PriorityQueue;
Expand All @@ -28,6 +29,7 @@ pub fn run_a_star(
target: Option<VertexId>,
directed_graph: Arc<ExecutorReadOnlyLock<Graph>>,
m: Arc<dyn TraversalModel>,
u: CostModel,
f: Arc<dyn FrontierModel>,
termination_model: Arc<ExecutorReadOnlyLock<TerminationModel>>,
) -> Result<MinSearchTree, SearchError> {
Expand Down Expand Up @@ -60,7 +62,7 @@ pub fn run_a_star(

let origin_cost = match target {
None => Cost::ZERO,
Some(target_vertex_id) => h_cost(source, target_vertex_id, &initial_state, &g, &m)?,
Some(target_vertex_id) => h_cost(source, target_vertex_id, &initial_state, &g, &m, &u)?,
};
costs.push(source, std::cmp::Reverse(origin_cost));
frontier.insert(source, origin);
Expand Down Expand Up @@ -105,13 +107,6 @@ pub fn run_a_star(
))
})?;

// test for search termination according to the traversal model (Ok/Err)
if m.terminate_search(&current.state)
.map_err(SearchError::TraversalModelFailure)?
{
break;
};

// visit all neighbors of this source vertex
let neighbor_triplets = g
.incident_triplets(current.vertex_id, Direction::Forward)
Expand All @@ -122,7 +117,14 @@ pub fn run_a_star(
if !f.valid_frontier(e, &current.state)? {
continue;
}
let et = EdgeTraversal::new(edge_id, current.prev_edge_id, &current.state, &g, &m)?;
let et = EdgeTraversal::perform_traversal(
edge_id,
current.prev_edge_id,
&current.state,
&g,
&m,
&u,
)?;
let current_gscore = traversal_costs
.get(&src_id)
.unwrap_or(&Cost::INFINITY)
Expand Down Expand Up @@ -152,7 +154,7 @@ pub fn run_a_star(
};
let dst_h_cost = match target {
None => Cost::ZERO,
Some(target_v) => h_cost(dst_id, target_v, &current.state, &g, &m)?,
Some(target_v) => h_cost(dst_id, target_v, &current.state, &g, &m, &u)?,
};
let f_score_value = tentative_gscore + dst_h_cost;
costs.push_increase(f.vertex_id, std::cmp::Reverse(f_score_value));
Expand Down Expand Up @@ -194,6 +196,7 @@ pub fn run_a_star_edge_oriented(
target: Option<EdgeId>,
directed_graph: Arc<ExecutorReadOnlyLock<Graph>>,
m: Arc<dyn TraversalModel>,
u: CostModel,
f: Arc<dyn FrontierModel>,
termination_model: Arc<ExecutorReadOnlyLock<TerminationModel>>,
) -> Result<MinSearchTree, SearchError> {
Expand Down Expand Up @@ -221,6 +224,7 @@ pub fn run_a_star_edge_oriented(
None,
directed_graph.clone(),
m.clone(),
u,
f.clone(),
termination_model,
)?;
Expand All @@ -239,9 +243,16 @@ pub fn run_a_star_edge_oriented(
} else if source_edge_dst_vertex_id == target_edge_src_vertex_id {
// route is simply source -> target
let init_state = m.initial_state();
let src_et = EdgeTraversal::new(source, None, &init_state, &g, &m)?;
let dst_et =
EdgeTraversal::new(target_edge, Some(source), &src_et.result_state, &g, &m)?;
let src_et =
EdgeTraversal::perform_traversal(source, None, &init_state, &g, &m, &u)?;
let dst_et = EdgeTraversal::perform_traversal(
target_edge,
Some(source),
&src_et.result_state,
&g,
&m,
&u,
)?;
let src_traversal = SearchTreeBranch {
terminal_vertex: target_edge_src_vertex_id,
edge_traversal: dst_et,
Expand All @@ -262,6 +273,7 @@ pub fn run_a_star_edge_oriented(
Some(target_edge_src_vertex_id),
directed_graph.clone(),
m.clone(),
u,
f.clone(),
termination_model,
)?;
Expand Down Expand Up @@ -312,10 +324,13 @@ pub fn h_cost(
state: &TraversalState,
g: &RwLockReadGuard<Graph>,
m: &Arc<dyn TraversalModel>,
u: &CostModel,
) -> Result<Cost, SearchError> {
let src_vertex = g.get_vertex(src)?;
let dst_vertex = g.get_vertex(dst)?;
let cost_estimate = m.cost_estimate(src_vertex, dst_vertex, state)?;

let state_estimate = m.estimate_traversal(src_vertex, dst_vertex, state)?;
let cost_estimate = u.cost_estimate(state, &state_estimate)?;
Ok(cost_estimate)
}

Expand All @@ -324,13 +339,15 @@ mod tests {

use super::*;
use crate::algorithm::search::backtrack::vertex_oriented_route;
use crate::model::cost::cost_aggregation::CostAggregation;
use crate::model::cost::vehicle::vehicle_cost_rate::VehicleCostRate;
use crate::model::frontier::default::no_restriction::NoRestriction;
use crate::model::property::edge::Edge;
use crate::model::property::vertex::Vertex;
use crate::model::road_network::graph::Graph;
use crate::model::traversal::default::distance::DistanceModel;
use crate::model::traversal::default::distance_traversal_model::DistanceTraversalModel;
use crate::model::traversal::traversal_model::TraversalModel;
use crate::util::unit::DistanceUnit;
use crate::model::unit::DistanceUnit;
use crate::{model::road_network::edge_id::EdgeId, util::read_only_lock::DriverReadOnlyLock};
use rayon::prelude::*;

Expand Down Expand Up @@ -433,10 +450,20 @@ mod tests {
.map(|(o, d, _expected)| {
let dg_inner = Arc::new(driver_dg.read_only());
let dist_tm: Arc<dyn TraversalModel> =
Arc::new(DistanceModel::new(DistanceUnit::Meters));
Arc::new(DistanceTraversalModel::new(DistanceUnit::Meters));
let dist_um: CostModel = CostModel::new(
vec![(String::from("distance"), 0usize)],
Arc::new(HashMap::from([(String::from("distance"), 1.0)])),
Arc::new(HashMap::from([(
String::from("distance"),
VehicleCostRate::Raw,
)])),
Arc::new(HashMap::new()),
CostAggregation::Sum,
);
let fm_inner = Arc::new(NoRestriction {});
let rm_inner = Arc::new(driver_rm.read_only());
run_a_star(o, Some(d), dg_inner, dist_tm, fm_inner, rm_inner)
run_a_star(o, Some(d), dg_inner, dist_tm, dist_um, fm_inner, rm_inner)
})
.collect();

Expand Down
67 changes: 38 additions & 29 deletions rust/routee-compass-core/src/algorithm/search/edge_traversal.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use serde::Serialize;

use super::search_error::SearchError;
use crate::model::cost::cost_model::CostModel;
use crate::model::road_network::edge_id::EdgeId;
use crate::model::road_network::graph::Graph;
use crate::model::traversal::access_result::AccessResult;
use crate::model::traversal::traversal_model::TraversalModel;

use super::search_error::SearchError;
use crate::model::cost::Cost;
use crate::model::traversal::state::traversal_state::TraversalState;
use crate::model::traversal::traversal_model::TraversalModel;
use crate::model::unit::Cost;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::{fmt::Display, sync::RwLockReadGuard};

#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EdgeTraversal {
pub edge_id: EdgeId,
pub access_cost: Cost,
Expand All @@ -36,46 +34,57 @@ impl Display for EdgeTraversal {
}

impl EdgeTraversal {
///
/// traverses an edge, possibly after traversing some previous edge,
/// collecting the access and traversal costs. returns the
/// accumulated cost and updated search state.
pub fn new(
pub fn perform_traversal(
edge_id: EdgeId,
prev_edge_id: Option<EdgeId>,
prev_state: &TraversalState,
g: &RwLockReadGuard<Graph>,
m: &Arc<dyn TraversalModel>,
tm: &Arc<dyn TraversalModel>,
um: &CostModel,
) -> Result<EdgeTraversal, SearchError> {
let (src, edge, dst) = g
.edge_triplet_attrs(edge_id)
.map_err(SearchError::GraphError)?;

// let (access_cost, access_state);
let access_result = match prev_edge_id {
Some(prev_e) => {
let (access_state, access_cost) = prev_edge_id
.map(|prev_e| {
let prev_edge = g.get_edge(prev_e).map_err(SearchError::GraphError)?;
let prev_src_v = g.get_vertex(prev_edge.src_vertex_id)?;
m.access_cost(prev_src_v, prev_edge, src, edge, dst, prev_state)
}
None => Ok(AccessResult::no_cost()),
}
.map_err(SearchError::TraversalModelFailure)?;
let prev_src_v = g
.get_vertex(prev_edge.src_vertex_id)
.map_err(SearchError::GraphError)?;

let updated_state = match &access_result.updated_state {
Some(s) => s,
None => prev_state,
};
// we are coming from some previous edge and need to access the next edge first to proceed
// with traversal. if there is an access state, compute the access cost.
tm.access_edge(prev_src_v, prev_edge, src, edge, dst, prev_state)
.map_err(SearchError::TraversalModelFailure)
.and_then(|next_state_opt| match next_state_opt {
Some(next_state) => {
let cost = um
.access_cost(prev_edge, edge, prev_state, &next_state)
.map_err(SearchError::CostError)?;
Ok((next_state, cost))
}
None => Ok((prev_state.to_owned(), Cost::ZERO)),
})
})
.unwrap_or(Ok((prev_state.to_owned(), Cost::ZERO)))?;

let traversal_result = m
.traversal_cost(src, edge, dst, updated_state)
let result_state = tm
.traverse_edge(src, edge, dst, &access_state)
.map_err(SearchError::TraversalModelFailure)?;

let traversal_cost = um
.traversal_cost(edge, prev_state, &result_state)
.map_err(SearchError::CostError)?;

let result = EdgeTraversal {
edge_id,
access_cost: access_result.cost,
traversal_cost: traversal_result.total_cost,
result_state: traversal_result.updated_state,
access_cost,
traversal_cost,
result_state,
};

Ok(result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::a_star::a_star_algorithm;
use super::search_error::SearchError;
use super::MinSearchTree;
use crate::algorithm::search::search_algorithm_type::SearchAlgorithmType;
use crate::model::cost::cost_model::CostModel;
use crate::model::frontier::frontier_model::FrontierModel;
use crate::model::road_network::graph::Graph;
use crate::model::road_network::{edge_id::EdgeId, vertex_id::VertexId};
Expand Down Expand Up @@ -32,6 +33,7 @@ impl SearchAlgorithm {
destination: Option<VertexId>,
graph: Arc<ExecutorReadOnlyLock<Graph>>,
traversal_model: Arc<dyn TraversalModel>,
cost_model: CostModel,
frontier_model: Arc<dyn FrontierModel>,
termination_model: Arc<ExecutorReadOnlyLock<TerminationModel>>,
) -> Result<MinSearchTree, SearchError> {
Expand All @@ -41,6 +43,7 @@ impl SearchAlgorithm {
destination,
graph,
traversal_model,
cost_model,
frontier_model,
termination_model,
),
Expand All @@ -52,6 +55,7 @@ impl SearchAlgorithm {
destination: Option<EdgeId>,
graph: Arc<ExecutorReadOnlyLock<Graph>>,
traversal_model: Arc<dyn TraversalModel>,
cost_model: CostModel,
frontier_model: Arc<dyn FrontierModel>,
termination_model: Arc<ExecutorReadOnlyLock<TerminationModel>>,
) -> Result<MinSearchTree, SearchError> {
Expand All @@ -61,6 +65,7 @@ impl SearchAlgorithm {
destination,
graph,
traversal_model,
cost_model,
frontier_model,
termination_model,
),
Expand Down
3 changes: 3 additions & 0 deletions rust/routee-compass-core/src/algorithm/search/search_error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::model::{
cost::cost_error::CostError,
frontier::frontier_model_error::FrontierModelError,
road_network::graph_error::GraphError,
road_network::{edge_id::EdgeId, vertex_id::VertexId},
Expand All @@ -18,6 +19,8 @@ pub enum SearchError {
TraversalModelFailure(#[from] TraversalModelError),
#[error(transparent)]
FrontierModelFailure(#[from] FrontierModelError),
#[error(transparent)]
CostError(#[from] CostError),
#[error("loop in search result revisits edge {0}")]
LoopInSearchResult(EdgeId),
#[error("query terminated due to {0}")]
Expand Down
Loading

0 comments on commit 73ed0ad

Please sign in to comment.