use bambam_core::model::{
destination::DestinationPredicate, state::multimodal_state_ops as state_ops,
};
use routee_compass_core::model::{
constraint::ConstraintModelError,
state::{StateModel, StateVariable},
unit::*,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::num::NonZeroU64;
use uom::si::f64::{Energy, Length, Time};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConstraintConfig {
AllowedModes { values: Vec<String> },
ModeCounts { values: HashMap<String, NonZeroU64> },
ExactSequences { values: Vec<Vec<String>> },
#[serde(rename = "distance_limit")]
DistanceConstraint(DistanceConstraint),
#[serde(rename = "time_limit")]
TimeConstraint(TimeConstraint),
ModeDistanceLimit {
values: HashMap<String, DistanceConstraint>,
},
ModeTimeLimit {
values: HashMap<String, TimeConstraint>,
},
ModeLegDistanceLimit {
values: HashMap<String, ModeLegDistanceConstraint>,
},
ModeLegTimeLimit {
values: HashMap<String, ModeLegTimeConstraint>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModeLegDistanceConstraint {
pub leg: TripLegConstraint,
pub constraint: DistanceConstraint,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModeLegTimeConstraint {
pub leg: TripLegConstraint,
pub constraint: TimeConstraint,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModeLegEnergyConstraint {
pub leg: TripLegConstraint,
pub constraint: EnergyConstraint,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LimitOperation {
MinInclusive,
MinExclusive,
#[default]
MaxInclusive,
MaxExclusive,
}
#[derive(Serialize, Debug, Clone)]
pub struct DistanceConstraint {
pub limit: Length,
pub unit: DistanceUnit,
#[serde(default)]
pub op: LimitOperation,
}
#[derive(Serialize, Debug, Clone)]
pub struct TimeConstraint {
pub limit: Time,
pub unit: TimeUnit,
#[serde(default)]
pub op: LimitOperation,
}
#[derive(Serialize, Debug, Clone)]
pub struct EnergyConstraint {
pub limit: Energy,
pub unit: EnergyUnit,
pub variable: EnergyStateVariable,
#[serde(default)]
pub op: LimitOperation,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EnergyStateVariable {
Liquid,
Electric,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TripLegConstraint {
First,
LegIndex { index: usize },
Last,
Arrival {
destination_predicate: DestinationPredicate,
},
Any,
}
impl LimitOperation {
pub fn test<D, U, V>(
&self,
value: uom::si::Quantity<D, U, V>,
limit: uom::si::Quantity<D, U, V>,
mode_switch: bool,
) -> bool
where
D: uom::si::Dimension + ?Sized,
U: uom::si::Units<V> + ?Sized,
V: uom::num_traits::Num + uom::Conversion<V> + PartialOrd,
{
match self {
LimitOperation::MinInclusive => !mode_switch || value >= limit,
LimitOperation::MinExclusive => !mode_switch || value > limit,
LimitOperation::MaxInclusive => value <= limit,
LimitOperation::MaxExclusive => value < limit,
}
}
}
impl<'de> Deserialize<'de> for DistanceConstraint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawConstraint {
limit: f64,
unit: DistanceUnit,
#[serde(default)]
op: LimitOperation,
}
let raw = RawConstraint::deserialize(deserializer)?;
let limit = raw.unit.to_uom(raw.limit);
Ok(DistanceConstraint {
limit,
unit: raw.unit,
op: raw.op,
})
}
}
impl<'de> Deserialize<'de> for TimeConstraint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawConstraint {
limit: f64,
unit: TimeUnit,
#[serde(default)]
op: LimitOperation,
}
let raw = RawConstraint::deserialize(deserializer)?;
let limit = raw.unit.to_uom(raw.limit);
Ok(TimeConstraint {
limit,
unit: raw.unit,
op: raw.op,
})
}
}
impl<'de> Deserialize<'de> for EnergyConstraint {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct RawConstraint {
limit: f64,
unit: EnergyUnit,
variable: EnergyStateVariable,
#[serde(default)]
op: LimitOperation,
}
let raw = RawConstraint::deserialize(deserializer)?;
let limit = raw.unit.to_uom(raw.limit);
Ok(EnergyConstraint {
limit,
unit: raw.unit,
variable: raw.variable,
op: raw.op,
})
}
}
impl DistanceConstraint {
pub fn test(&self, value: Length, mode_switch: bool) -> bool {
self.op.test(value, self.limit, mode_switch)
}
}
impl TimeConstraint {
pub fn test(&self, value: Time, mode_switch: bool) -> bool {
self.op.test(value, self.limit, mode_switch)
}
}
impl EnergyConstraint {
pub fn test(&self, value: Energy, mode_switch: bool) -> bool {
self.op.test(value, self.limit, mode_switch)
}
}
impl TripLegConstraint {
pub fn matches(
&self,
state: &[StateVariable],
state_model: &StateModel,
max_trip_legs: NonZeroU64,
) -> Result<bool, ConstraintModelError> {
match self {
TripLegConstraint::First => matches_leg(state, state_model, 0),
TripLegConstraint::LegIndex { index } => matches_leg(state, state_model, *index as u64),
TripLegConstraint::Last => {
let max_trip_idx = max_trip_legs.get() - 1;
matches_leg(state, state_model, max_trip_idx)
}
TripLegConstraint::Arrival {
destination_predicate,
} => destination_predicate
.valid_destination(state, state_model)
.map_err(|e| {
let msg = format!("while checking trip leg constraint: {e}");
ConstraintModelError::ConstraintModelError(msg)
}),
TripLegConstraint::Any => Ok(true),
}
}
}
fn matches_leg(
state: &[StateVariable],
state_model: &StateModel,
leg_idx: u64,
) -> Result<bool, ConstraintModelError> {
match state_ops::get_active_leg_idx(state, state_model) {
Ok(None) => Ok(false),
Ok(Some(idx)) => Ok(idx == leg_idx),
Err(e) => {
let msg = format!("while checking trip leg constraint: {e}");
Err(ConstraintModelError::ConstraintModelError(msg))
}
}
}