use deke_types::{DekeError, Planner, Retimer, SRobotPath, SRobotQ, Validator};
use crate::reqpath::DirectedOption;
pub fn weighted_distance<const N: usize>(
a: &SRobotQ<N, f64>,
b: &SRobotQ<N, f64>,
w: &SRobotQ<N, f64>,
) -> f64 {
w.0.iter()
.zip(a.0.iter())
.zip(b.0.iter())
.map(|((&wi, &ai), &bi)| {
let d = wi * (ai - bi);
d * d
})
.sum::<f64>()
.sqrt()
}
pub fn weighted_euclidean<const N: usize>(
weights: SRobotQ<N, f64>,
) -> impl Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64 {
move |from, to| weighted_distance(&from, &to, &weights)
}
pub fn planned_path_length<'a, 'ctx, const N: usize, P, V, MW>(
planner: &'a P,
config: &'a P::Config,
make_waypoints: MW,
validator: &'a V,
ctx: &'a V::Context<'ctx>,
) -> impl Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64 + 'a
where
'ctx: 'a,
P: Planner<N, f64>,
V: Validator<N, (), f64>,
MW: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> P::Waypoints + 'a,
{
move |from, to| {
let waypoints = make_waypoints(from, to);
match planner
.plan::<DekeError, _>(config, &waypoints, validator, ctx)
.0
{
Ok(path) => path.arc_length(),
Err(_) => f64::INFINITY,
}
}
}
pub fn planned_trajectory_time<'a, 'ctx, const N: usize, P, R, V, MW>(
planner: &'a P,
config: &'a P::Config,
retimer: &'a R,
constraints: &'a R::Constraints,
make_waypoints: MW,
validator: &'a V,
ctx: &'a V::Context<'ctx>,
) -> impl Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64 + 'a
where
'ctx: 'a,
P: Planner<N, f64>,
R: Retimer<N, f64>,
V: Validator<N, (), f64>,
MW: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> P::Waypoints + 'a,
{
move |from, to| {
let waypoints = make_waypoints(from, to);
let path = match planner
.plan::<DekeError, _>(config, &waypoints, validator, ctx)
.0
{
Ok(path) => path,
Err(_) => return f64::INFINITY,
};
match retimer.retime(constraints, &path, validator, ctx).0 {
Ok(traj) => traj.duration().as_secs_f64(),
Err(_) => f64::INFINITY,
}
}
}
fn traversal_cost<const N: usize, C>(path: &SRobotPath<N, f64>, cost: &C) -> f64
where
C: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64,
{
path.segments().map(|(a, b)| cost(*a, *b)).sum()
}
pub(crate) struct CostMatrices {
pub transition: Vec<f64>,
pub start: Vec<f64>,
pub end: Vec<f64>,
}
pub(crate) fn build_matrices<const N: usize, C>(
options: &[DirectedOption<N>],
cost: &C,
start_q: &SRobotQ<N, f64>,
end_q: Option<&SRobotQ<N, f64>>,
) -> CostMatrices
where
C: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64,
{
let m = options.len();
let traversal: Vec<f64> = options
.iter()
.map(|o| traversal_cost(&o.path, cost))
.collect();
let mut transition = vec![0.0_f64; m * m];
for (i, oi) in options.iter().enumerate() {
let from = *oi.path.last();
let row = &mut transition[i * m..(i + 1) * m];
for (j, cell) in row.iter_mut().enumerate() {
*cell = cost(from, *options[j].path.first()) + traversal[j];
}
}
let start = options
.iter()
.enumerate()
.map(|(i, o)| cost(*start_q, *o.path.first()) + traversal[i])
.collect();
let end = options
.iter()
.map(|o| end_q.map_or(0.0, |e| cost(*o.path.last(), *e)))
.collect();
CostMatrices {
transition,
start,
end,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn q<const N: usize>(a: [f64; N]) -> SRobotQ<N, f64> {
SRobotQ(a)
}
#[test]
fn weighted_distance_applies_weights() {
let origin = q([0.0, 0.0]);
let w = q([2.0, 1.0]);
assert!((weighted_distance(&origin, &q([1.0, 0.0]), &w) - 2.0).abs() < 1e-12);
assert!((weighted_distance(&origin, &q([0.0, 1.0]), &w) - 1.0).abs() < 1e-12);
}
#[test]
fn weighted_euclidean_matches_distance() {
let w = q([2.0, 1.0]);
let cost = weighted_euclidean(w);
assert!((cost(q([0.0, 0.0]), q([1.0, 0.0])) - 2.0).abs() < 1e-12);
}
#[test]
fn traversal_sums_weighted_segments() {
let path = SRobotPath::<2, f64>::try_new(vec![q([0.0, 0.0]), q([1.0, 0.0]), q([1.0, 1.0])])
.unwrap();
let cost = weighted_euclidean(q([2.0, 1.0]));
assert!((traversal_cost(&path, &cost) - 3.0).abs() < 1e-12);
}
}