Skip to content

Commit

Permalink
Merge pull request #113 from NREL/rjr/headings-module
Browse files Browse the repository at this point in the history
a prototype of creating AccessModels
  • Loading branch information
robfitzgerald authored Mar 22, 2024
2 parents ec75432 + 4f8c3c6 commit e853897
Show file tree
Hide file tree
Showing 47 changed files with 705 additions and 326 deletions.
6 changes: 3 additions & 3 deletions python/nrel/routee/compass/io/generate_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ def replace_id(vertex_uuid):
)

headings = e.bearing.fillna(0).apply(lambda x: int(round(x)))
headings_df = headings.to_frame(name="start_heading")
headings_df = headings.to_frame(name="arrival_heading")

# We could get more sophisticated and compute the end heading
# for links that might have some significant curvature, but
# for now we'll just use the start heading.
headings_df["end_heading"] = None
headings_df["destination_heading"] = None
headings_df.to_csv(
output_directory / "edges-headings-enumerated.txt.gz",
output_directory / "edges-headings-enumerated.csv.gz",
index=False,
compression="gzip",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ mod tests {
use super::*;
use crate::algorithm::search::backtrack::vertex_oriented_route;
use crate::algorithm::search::MinSearchTree;
use crate::model::access::default::no_access_model::NoAccessModel;
use crate::model::cost::cost_aggregation::CostAggregation;
use crate::model::cost::cost_model::CostModel;
use crate::model::cost::vehicle::vehicle_cost_rate::VehicleCostRate;
Expand Down Expand Up @@ -460,6 +461,7 @@ mod tests {
directed_graph: Arc::new(build_mock_graph()),
state_model: state_model.clone(),
traversal_model: Arc::new(DistanceTraversalModel::new(DistanceUnit::Meters)),
access_model: Arc::new(NoAccessModel {}),
cost_model,
frontier_model: Arc::new(NoRestriction {}),
termination_model: Arc::new(TerminationModel::IterationsLimit { limit: 20 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,9 @@ impl EdgeTraversal {

let (v2, e2, v3) = traversal_trajectory;
let access_trajectory = (v1, e1, v2, e2, v3);
si.traversal_model.access_edge(
access_trajectory,
&mut result_state,
&si.state_model,
)?;

si.access_model
.access_edge(access_trajectory, &mut result_state, &si.state_model)?;

let ac = si
.cost_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::{
access::access_model_error::AccessModelError,
cost::cost_error::CostError,
frontier::frontier_model_error::FrontierModelError,
road_network::{edge_id::EdgeId, graph_error::GraphError, vertex_id::VertexId},
Expand All @@ -20,6 +21,8 @@ pub enum SearchError {
#[error(transparent)]
TraversalModelFailure(#[from] TraversalModelError),
#[error(transparent)]
AccessModelFailure(#[from] AccessModelError),
#[error(transparent)]
FrontierModelFailure(#[from] FrontierModelError),
#[error(transparent)]
CostError(#[from] CostError),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::search_error::SearchError;
use crate::model::{
access::access_model::AccessModel,
cost::cost_model::CostModel,
frontier::frontier_model::FrontierModel,
road_network::{graph::Graph, vertex_id::VertexId},
Expand All @@ -16,6 +17,7 @@ pub struct SearchInstance {
pub directed_graph: Arc<Graph>,
pub state_model: Arc<StateModel>,
pub traversal_model: Arc<dyn TraversalModel>,
pub access_model: Arc<dyn AccessModel>,
pub cost_model: CostModel,
pub frontier_model: Arc<dyn FrontierModel>,
pub termination_model: Arc<TerminationModel>,
Expand Down
39 changes: 39 additions & 0 deletions rust/routee-compass-core/src/model/access/access_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::access_model_error::AccessModelError;
use crate::model::{
property::{edge::Edge, vertex::Vertex},
state::{state_feature::StateFeature, state_model::StateModel},
traversal::state::state_variable::StateVar,
};

pub trait AccessModel: Send + Sync {
/// lists the state variables expected by this access model that are not
/// defined on the base configuration. for example, if this access model
/// has state variables that differ based on the query, they can be injected
/// into the state model by listing them here.
fn state_features(&self) -> Vec<(String, StateFeature)>;

/// Updates the traversal state by accessing some destination edge
/// when coming from some previous edge.
///
/// The traversal argument represents a set of vertices and
/// edges connected in the network:
/// `(v1) -[prev]-> (v2) -[next]-> (v3)`
/// Where `next` is the edge we want to access.
///
/// # Arguments
///
/// * `traversal` - the vertex/edge traversal
/// * `state` - state of the search at the beginning of the dst edge
/// * `state_variable_indices` - the names and indices of state variables
///
/// # Returns
///
/// Either an optional access result or an error. if there are no
/// state updates due to access, None is returned.
fn access_edge(
&self,
traversal: (&Vertex, &Edge, &Vertex, &Edge, &Vertex),
state: &mut Vec<StateVar>,
state_model: &StateModel,
) -> Result<(), AccessModelError>;
}
23 changes: 23 additions & 0 deletions rust/routee-compass-core/src/model/access/access_model_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{access_model_error::AccessModelError, access_model_service::AccessModelService};
use std::sync::Arc;

/// A [`AccessModelBuilder`] takes a JSON object describing the configuration of a
/// traversal model and builds a [`AccessModelService`].
///
/// A [`AccessModelBuilder`] instance should be an empty struct that implements
/// this trait.
pub trait AccessModelBuilder {
/// Builds a [`AccessModelService`] from configuration.
///
/// # Arguments
///
/// * `parameters` - the contents of the "traversal" TOML config section
///
/// # Returns
///
/// A [`AccessModelService`] designed to persist the duration of the CompassApp.
fn build(
&self,
parameters: &serde_json::Value,
) -> Result<Arc<dyn AccessModelService>, AccessModelError>;
}
13 changes: 13 additions & 0 deletions rust/routee-compass-core/src/model/access/access_model_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use crate::model::state::state_error::StateError;

#[derive(thiserror::Error, Debug)]
pub enum AccessModelError {
#[error("error while executing access model {name}: {error}")]
RuntimeError { name: String, error: String },
#[error(transparent)]
StateError(#[from] StateError),
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
#[error("{0}")]
BuildError(String),
}
21 changes: 21 additions & 0 deletions rust/routee-compass-core/src/model/access/access_model_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::{access_model::AccessModel, access_model_error::AccessModelError};
use std::sync::Arc;

pub trait AccessModelService: Send + Sync {
/// Builds a [AccessModel] for the incoming query, used as parameters for this
/// build operation.
///
/// The query is passed as parameters to this operation so that any query-time
/// coefficients may be applied to the [AccessModel].
///
/// # Arguments
///
/// * `query` - the incoming query which may contain parameters for building the [AccessModel]
///
/// # Returns
///
/// The [AccessModel] instance for this query, or an error
///
/// [AccessModel]: compass_core::model::access::access_model::AccessModel
fn build(&self, query: &serde_json::Value) -> Result<Arc<dyn AccessModel>, AccessModelError>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::model::{
access::{
access_model::AccessModel, access_model_error::AccessModelError,
access_model_service::AccessModelService,
},
property::{edge::Edge, vertex::Vertex},
state::{state_feature::StateFeature, state_model::StateModel},
traversal::state::state_variable::StateVar,
};
use itertools::Itertools;
use std::sync::Arc;

pub struct CombinedAccessModelService {
pub services: Vec<Arc<dyn AccessModelService>>,
}

pub struct CombinedAccessModel {
pub models: Vec<Arc<dyn AccessModel>>,
}

impl AccessModelService for CombinedAccessModelService {
fn build(&self, query: &serde_json::Value) -> Result<Arc<dyn AccessModel>, AccessModelError> {
let models = self
.services
.iter()
.map(|m| m.build(query))
.collect::<Result<_, _>>()?;
Ok(Arc::new(CombinedAccessModel { models }))
}
}

impl AccessModel for CombinedAccessModel {
fn state_features(&self) -> Vec<(String, StateFeature)> {
self.models
.iter()
.flat_map(|m| m.state_features())
.collect_vec()
}

fn access_edge(
&self,
traversal: (&Vertex, &Edge, &Vertex, &Edge, &Vertex),
state: &mut Vec<StateVar>,
state_model: &StateModel,
) -> Result<(), AccessModelError> {
for model in self.models.iter() {
model.access_edge(traversal, state, state_model)?;
}
Ok(())
}
}
3 changes: 3 additions & 0 deletions rust/routee-compass-core/src/model/access/default/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod combined_model;
pub mod no_access_model;
pub mod turn_delays;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::sync::Arc;

use crate::model::access::{
access_model::AccessModel, access_model_builder::AccessModelBuilder,
access_model_service::AccessModelService,
};

#[derive(Clone, Debug)]
pub struct NoAccessModel {}

impl AccessModel for NoAccessModel {
fn state_features(&self) -> Vec<(String, crate::model::state::state_feature::StateFeature)> {
vec![]
}

fn access_edge(
&self,
_traversal: (
&crate::model::property::vertex::Vertex,
&crate::model::property::edge::Edge,
&crate::model::property::vertex::Vertex,
&crate::model::property::edge::Edge,
&crate::model::property::vertex::Vertex,
),
_state: &mut Vec<crate::model::traversal::state::state_variable::StateVar>,
_state_model: &crate::model::state::state_model::StateModel,
) -> Result<(), crate::model::access::access_model_error::AccessModelError> {
Ok(())
}
}

impl AccessModelService for NoAccessModel {
fn build(
&self,
_query: &serde_json::Value,
) -> Result<
std::sync::Arc<dyn AccessModel>,
crate::model::access::access_model_error::AccessModelError,
> {
let model: Arc<dyn AccessModel> = Arc::new(self.clone());
Ok(model)
}
}

impl AccessModelBuilder for NoAccessModel {
fn build(
&self,
_parameters: &serde_json::Value,
) -> Result<
Arc<dyn AccessModelService>,
crate::model::access::access_model_error::AccessModelError,
> {
let service: Arc<dyn AccessModelService> = Arc::new(self.clone());
Ok(service)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use serde::Deserialize;

/// simplifies the representation of directionality for a linestring
/// to just the headings of the start and end points, using cardinal angles [0, 360).
/// if the start and end have the same heading, the edge heading is None.
#[derive(Copy, Clone, Deserialize)]
pub struct EdgeHeading {
arrival_heading: i16,
departure_heading: Option<i16>,
}

impl EdgeHeading {
/// creates an EdgeHeading from a start and end heading
pub fn new(arrival_heading: i16, departure_heading: i16) -> Self {
Self {
arrival_heading,
departure_heading: Some(departure_heading),
}
}

/// retrieve the start
pub fn start_heading(&self) -> i16 {
self.arrival_heading
}

/// If the end heading is not specified, it is assumed to be the same as the start heading
pub fn end_heading(&self) -> i16 {
match self.departure_heading {
Some(end_heading) => end_heading,
None => self.arrival_heading,
}
}
/// Compute the angle between this edge and some destination edge.
pub fn bearing_to_destination(&self, destination: &EdgeHeading) -> i16 {
let angle = destination.start_heading() - self.end_heading();
if angle > 180 {
angle - 360
} else if angle < -180 {
angle + 360
} else {
angle
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_simple() {
let edge_heading = EdgeHeading::new(45, 90);
let next_edge_heading = EdgeHeading::new(90, 135);
assert_eq!(edge_heading.bearing_to_destination(&next_edge_heading), 0);
}

#[test]
fn test_wrap_360() {
let edge_heading = EdgeHeading::new(10, 10);
let next_edge_heading = EdgeHeading::new(350, 350);
assert_eq!(edge_heading.bearing_to_destination(&next_edge_heading), -20);

let edge_heading = EdgeHeading::new(350, 350);
let next_edge_heading = EdgeHeading::new(10, 10);
assert_eq!(edge_heading.bearing_to_destination(&next_edge_heading), 20);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod edge_heading;
pub mod turn;
pub mod turn_delay_access_model;
pub mod turn_delay_access_model_engine;
pub mod turn_delay_access_model_service;
pub mod turn_delay_model;
Loading

0 comments on commit e853897

Please sign in to comment.