-
Notifications
You must be signed in to change notification settings - Fork 1
Add z-level routing option (Rust) #281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
071450d
ArrayIndex stores z value
ppinchuk b64191d
Update grid shape to be 3D
ppinchuk 72cc16e
Array index update
ppinchuk 0f17a6e
New method
ppinchuk b6079ec
UPdate layout to track options dimension
ppinchuk 8d87163
Derived writer now pulls from 3 dimensions
ppinchuk e8a8384
Pass correct function args
ppinchuk 4e961a6
Reader is now option-aware
ppinchuk 2fe04c8
Rename struct
ppinchuk 044bc74
Rename var
ppinchuk 6b006fc
Add `get_cell_cost` for derived data reader
ppinchuk b1d164d
Add comment
ppinchuk c6ccdf7
Dataset can now get center cell cost
ppinchuk ca9b87d
Allow dataset to get non-cost layer cells
ppinchuk 2ad080c
Add comment
ppinchuk 7274b17
Grid indexer is now 3D
ppinchuk 53ad2fc
Add helper methods
ppinchuk 3e87c43
Add `allowed_states_at`
ppinchuk d9f2368
Successors now determined by driver layer and can include transitions…
ppinchuk b25983d
Return an iter now
ppinchuk 29312de
Drop routes with barriered or driver excluded start points
ppinchuk 844e48c
Tests for new functionality
ppinchuk 37e5864
Add feature
ppinchuk c633d73
Merge remote-tracking branch 'origin/main' into pp/rust_z_level
ppinchuk 9e6051b
Merge remote-tracking branch 'origin/main' into pp/rust_z_level
ppinchuk 0d3360c
minor bug fix
ppinchuk aadc274
Minor docstring
ppinchuk baf434e
Merge remote-tracking branch 'origin/main' into pp/rust_z_level
ppinchuk db43375
Add inputs file
ppinchuk fb01cea
Formatting
ppinchuk b85b143
input now in charge of creating the driver rule set
ppinchuk fb51df1
inputs in charge of creating pieces
ppinchuk 60eb47a
Add components
ppinchuk 17f8bf2
Cost function uses new components
ppinchuk 72f6dc8
Use new components
ppinchuk 2f71281
Pull directly from cost function
ppinchuk 29235d0
Rename module
ppinchuk d376868
Move to new module
ppinchuk 554a685
Privatize components and methods
ppinchuk b043765
Minimal wire up on python side
ppinchuk 7604872
Temp patch for FFI
ppinchuk c80e9bb
disable ruff lint
ppinchuk 6b0196b
Linter
ppinchuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| //! Cost function components | ||
|
|
||
| use core::f32; | ||
| use derive_builder::Builder; | ||
| use std::collections::HashMap; | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| pub(crate) struct TransitionCostTable { | ||
| pub(super) default: f32, | ||
| pub(super) pairwise: HashMap<(u32, u32), f32>, | ||
| } | ||
|
|
||
| impl TransitionCostTable { | ||
| pub(super) fn new(default: f32, pairwise: HashMap<(u32, u32), f32>) -> Self { | ||
| Self { default, pairwise } | ||
| } | ||
|
|
||
| pub(crate) fn cost(&self, from: u32, to: u32) -> f32 { | ||
| self.pairwise | ||
| .get(&(from, to)) | ||
| .copied() | ||
| .unwrap_or(self.default) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| pub(crate) struct DriverRuleSet { | ||
| pub(super) default: Vec<Option<f32>>, | ||
| pub(super) zones: Vec<DriverZoneRule>, | ||
| } | ||
|
|
||
| impl DriverRuleSet { | ||
| pub(super) fn new(default: Vec<Option<f32>>, zones: Vec<DriverZoneRule>) -> Self { | ||
| Self { default, zones } | ||
| } | ||
|
|
||
| pub(crate) fn multiplier<F>(&self, option: u32, mut layer_value: F) -> Option<f32> | ||
| where | ||
| F: FnMut(&str) -> Option<f32>, | ||
| { | ||
| let mut multiplier = self | ||
| .default | ||
| .get(option as usize) | ||
| .copied() | ||
| .unwrap_or(Some(1.0)); | ||
|
|
||
| for zone in &self.zones { | ||
| let Some(value) = layer_value(&zone.layer_name) else { | ||
| continue; | ||
| }; | ||
|
|
||
| if zone.matches(value) | ||
| && let Some(zone_multiplier) = zone.options.get(&option) | ||
| { | ||
| multiplier = *zone_multiplier; // TODO: This is not quite right. We want replacement. Maybe error if two zones overlap? | ||
| } | ||
| } | ||
|
|
||
| multiplier | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| pub(super) struct DriverZoneRule { | ||
| pub(super) layer_name: String, | ||
| pub(super) operator: BarrierOperator, | ||
| pub(super) threshold: f32, | ||
| pub(super) options: HashMap<u32, Option<f32>>, | ||
| } | ||
|
|
||
| impl DriverZoneRule { | ||
| pub(super) fn new( | ||
| layer_name: String, | ||
| operator: BarrierOperator, | ||
| threshold: f32, | ||
| options: HashMap<u32, Option<f32>>, | ||
| ) -> Self { | ||
| Self { | ||
| layer_name, | ||
| operator, | ||
| threshold, | ||
| options, | ||
| } | ||
| } | ||
|
|
||
| fn matches(&self, value: f32) -> bool { | ||
| match self.operator { | ||
| BarrierOperator::NotEqual => value != self.threshold, | ||
| BarrierOperator::GreaterThan => value > self.threshold, | ||
| BarrierOperator::GreaterThanOrEqual => value >= self.threshold, | ||
| BarrierOperator::LessThan => value < self.threshold, | ||
| BarrierOperator::LessThanOrEqual => value <= self.threshold, | ||
| BarrierOperator::Equal => value == self.threshold, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Copy, Debug, serde::Deserialize)] | ||
| pub(super) enum BarrierOperator { | ||
| #[serde(rename = "ne")] | ||
| NotEqual, | ||
| #[serde(rename = "gt")] | ||
| GreaterThan, | ||
| #[serde(rename = "ge")] | ||
| GreaterThanOrEqual, | ||
| #[serde(rename = "lt")] | ||
| LessThan, | ||
| #[serde(rename = "le")] | ||
| LessThanOrEqual, | ||
| #[serde(rename = "eq")] | ||
| Equal, | ||
| } | ||
|
|
||
| #[derive(Builder, Clone, Debug, serde::Deserialize)] | ||
| /// A cost layer | ||
| /// | ||
| /// Each cost layer is a raster dataset, i.e. a regular grid, composed by | ||
| /// operating on input features. Following the original `revX` structure, | ||
| /// the possible compositions are limited to combinations of the relation | ||
| /// `weight * layer_name * multiplier_layer`, where the `weight` and the | ||
| /// `multiplier_layer` are optional. Each layer can also be marked as invariant, | ||
| /// meaning that its value does not get scaled by the distance traveled | ||
| /// through the cell. Instead, the value of the layer is added once, right | ||
| /// when the path enters the cell. | ||
| pub(super) struct CostLayer { | ||
| pub(crate) layer_name: String, | ||
| #[builder(setter(strip_option), default)] | ||
| pub(super) multiplier_scalar: Option<f32>, | ||
| #[builder(setter(strip_option, into), default)] | ||
| pub(super) multiplier_layer: Option<String>, | ||
| #[builder(setter(strip_option), default)] | ||
| pub(super) is_invariant: Option<bool>, | ||
| #[builder(default, setter(skip))] | ||
| #[serde(skip)] | ||
| pub(super) option: u32, | ||
| } | ||
|
|
||
| impl CostLayer { | ||
| pub(super) fn with_option(mut self, option: u32) -> Self { | ||
| self.option = option; | ||
| self | ||
| } | ||
| } | ||
|
|
||
| #[derive(Builder, Clone, Debug, serde::Deserialize)] | ||
| /// A friction layer | ||
| /// | ||
| /// Each friction layer is a raster dataset, i.e. a regular grid, that | ||
| /// represents multipliers that should be applied to the cost routing | ||
| /// layer. These multipliers affect the output route but will not be | ||
| /// reported in the output cost. Each friction layer is defined by a | ||
| /// `multiplier_layer` and an optional `multiplier_scalar`. The friction | ||
| /// value at each cell is computed as `multiplier_layer * multiplier_scalar`. | ||
| /// If the `multiplier_scalar` is not provided, it defaults to 1.0. | ||
| /// Friction layers are summed together to produce the final friction | ||
| /// layer that is applied to the cost layer. A clamp is applied to the | ||
| /// final friction layer to ensure that no values are below -1.0, which | ||
| /// would lead to negative routing costs. | ||
| pub(super) struct FrictionLayer { | ||
| pub(super) multiplier_layer: String, | ||
| #[builder(setter(strip_option), default)] | ||
| pub(super) multiplier_scalar: Option<f32>, | ||
| #[serde(skip)] | ||
| pub(super) option: u32, | ||
| } | ||
|
|
||
| impl FrictionLayer { | ||
| pub(super) fn new( | ||
| multiplier_layer: String, | ||
| multiplier_scalar: Option<f32>, | ||
| option: u32, | ||
| ) -> Self { | ||
| Self { | ||
| multiplier_layer, | ||
| multiplier_scalar, | ||
| option, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, serde::Deserialize)] | ||
| pub(crate) struct BarrierLayer { | ||
| pub(crate) layer_name: String, | ||
| pub(super) barrier_operator: BarrierOperator, | ||
| pub(super) barrier_threshold: f32, | ||
| pub(super) barrier_importance: Option<u32>, | ||
| #[serde(skip)] | ||
| pub(super) option: u32, | ||
| } | ||
|
|
||
| impl BarrierLayer { | ||
| pub(super) fn importance(&self) -> Option<u32> { | ||
| self.barrier_importance | ||
| } | ||
|
|
||
| pub(super) fn with_option(mut self, option: u32) -> Self { | ||
| self.option = option; | ||
| self | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn cost_layer_builder_sets_configured_values() { | ||
| let layer = CostLayerBuilder::default() | ||
| .layer_name("A".to_string()) | ||
| .multiplier_scalar(2.0) | ||
| .multiplier_layer("B") | ||
| .is_invariant(false) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(layer.layer_name, "A".to_string()); | ||
| assert_eq!(layer.multiplier_scalar, Some(2.0)); | ||
| assert_eq!(layer.multiplier_layer, Some("B".to_string())); | ||
| assert_eq!(layer.is_invariant, Some(false)); | ||
| assert_eq!(layer.option, 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn cost_layer_builder_applies_defaults() { | ||
| let layer = CostLayerBuilder::default() | ||
| .layer_name("A".to_string()) | ||
| .build() | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(layer.layer_name, "A".to_string()); | ||
| assert_eq!(layer.multiplier_scalar, None); | ||
| assert_eq!(layer.multiplier_layer, None); | ||
| assert_eq!(layer.is_invariant, None); | ||
| assert_eq!(layer.option, 0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn driver_rule_set_supports_zone_overrides_and_exclusions() { | ||
| let driver_rules = DriverRuleSet::new( | ||
| vec![Some(1.0), None], | ||
| vec![DriverZoneRule::new( | ||
| "zone".to_string(), | ||
| BarrierOperator::Equal, | ||
| 1.0, | ||
| HashMap::from([(0, Some(10.0)), (1, Some(1.0))]), | ||
| )], | ||
| ); | ||
|
|
||
| assert_eq!(driver_rules.multiplier(0, |_| Some(0.0)), Some(1.0)); | ||
| assert_eq!(driver_rules.multiplier(1, |_| Some(0.0)), None); | ||
| assert_eq!(driver_rules.multiplier(0, |_| Some(1.0)), Some(10.0)); | ||
| assert_eq!(driver_rules.multiplier(1, |_| Some(1.0)), Some(1.0)); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.