mod agtsp;
mod cost;
mod error;
mod reqpath;
use std::cmp::Ordering;
use std::collections::HashMap;
use deke_types::{DekeError, Planner, SRobotPath, SRobotQ, Validator};
use agtsp::Problem;
use cost::build_matrices;
use reqpath::{DirectedOption, expand};
pub use cost::{
planned_path_length, planned_trajectory_time, weighted_distance, weighted_euclidean,
};
pub use error::{MultipathError, MultipathResult};
pub use reqpath::ReqPath;
pub const DEFAULT_CELL_BUDGET: usize = agtsp::DEFAULT_CELL_BUDGET;
#[doc(hidden)]
#[cfg(feature = "rayon")]
pub trait MaybeSync: Sync {}
#[cfg(feature = "rayon")]
impl<T: Sync> MaybeSync for T {}
#[doc(hidden)]
#[cfg(not(feature = "rayon"))]
pub trait MaybeSync {}
#[cfg(not(feature = "rayon"))]
impl<T> MaybeSync for T {}
pub struct MultiPathSettings<const N: usize> {
pub start: SRobotQ<N, f64>,
pub end: Option<SRobotQ<N, f64>>,
pub dedup_tol: f64,
pub exact_cell_budget: usize,
}
impl<const N: usize> MultiPathSettings<N> {
pub fn new(start: SRobotQ<N, f64>) -> Self {
Self {
start,
end: None,
dedup_tol: 1e-6,
exact_cell_budget: agtsp::DEFAULT_CELL_BUDGET,
}
}
pub fn with_end(mut self, end: SRobotQ<N, f64>) -> Self {
self.end = Some(end);
self
}
}
pub struct TransitionPlanner<'a, const N: usize, P, MW>
where
P: Planner<N, f64>,
{
pub planner: &'a P,
pub config: &'a P::Config,
pub make_waypoints: MW,
}
pub fn plan_multipath<'ctx, const N: usize, P, V, MW, C>(
req_paths: &[ReqPath<N>],
cost: &C,
settings: &MultiPathSettings<N>,
transition: &TransitionPlanner<'_, N, P, MW>,
validator: &V,
ctx: &V::Context<'ctx>,
) -> MultipathResult<Vec<SRobotPath<N, f64>>>
where
P: Planner<N, f64> + MaybeSync,
P::Config: MaybeSync,
V: Validator<N, (), f64>,
V::Context<'ctx>: MaybeSync,
MW: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> P::Waypoints + MaybeSync,
C: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64,
{
run(req_paths, cost, settings, |from, to| {
let waypoints = (transition.make_waypoints)(*from, *to);
let (path, _diag) =
transition
.planner
.plan::<DekeError, _>(transition.config, &waypoints, validator, ctx);
Ok(path?)
})
}
pub fn plan_multipath_straight<'ctx, const N: usize, V, C>(
req_paths: &[ReqPath<N>],
cost: &C,
settings: &MultiPathSettings<N>,
validator: &V,
ctx: &V::Context<'ctx>,
) -> MultipathResult<Vec<SRobotPath<N, f64>>>
where
V: Validator<N, (), f64>,
V::Context<'ctx>: MaybeSync,
C: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64,
{
run(req_paths, cost, settings, |from, to| {
validator.validate_motion(&[*from, *to], ctx)?;
Ok(SRobotPath::from_two(*from, *to))
})
}
pub fn solve_matrix(
cluster_ids: &[usize],
transition: &[Vec<f64>],
start: Option<&[f64]>,
end: Option<&[f64]>,
cell_budget: usize,
) -> Option<(Vec<usize>, f64)> {
let prepared = MatrixProblem::new(cluster_ids, transition, start, end);
let solution = agtsp::solve(&prepared.problem(&prepared.start), cell_budget)?;
Some((solution.order, solution.cost))
}
pub fn solve_matrix_multi_start(
cluster_ids: &[usize],
transition: &[Vec<f64>],
start: Option<&[f64]>,
end: Option<&[f64]>,
cell_budget: usize,
k: usize,
) -> Vec<(Vec<usize>, f64)> {
if k == 0 {
return Vec::new();
}
let prepared = MatrixProblem::new(cluster_ids, transition, start, end);
let solve_from = |c: usize| -> Option<(Vec<usize>, f64)> {
let start: Vec<f64> = prepared
.cluster_ids
.iter()
.zip(&prepared.start)
.map(|(&ci, &s)| if ci == c { s } else { f64::INFINITY })
.collect();
let solution = agtsp::solve(&prepared.problem(&start), cell_budget)?;
Some((solution.order, solution.cost))
};
let mut tours: Vec<(Vec<usize>, f64)> = {
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
(0..prepared.n_clusters)
.into_par_iter()
.filter_map(solve_from)
.collect()
}
#[cfg(not(feature = "rayon"))]
{
(0..prepared.n_clusters).filter_map(solve_from).collect()
}
};
tours.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
tours.truncate(k);
tours
}
struct MatrixProblem {
cluster_ids: Vec<usize>,
n_clusters: usize,
options: usize,
transition: Vec<f64>,
start: Vec<f64>,
end: Vec<f64>,
}
impl MatrixProblem {
fn new(
cluster_ids: &[usize],
transition: &[Vec<f64>],
start: Option<&[f64]>,
end: Option<&[f64]>,
) -> Self {
let options = cluster_ids.len();
debug_assert_eq!(
transition.len(),
options,
"transition must be square with side cluster_ids.len()"
);
debug_assert!(
transition.iter().all(|row| row.len() == options),
"transition rows must each have length cluster_ids.len()"
);
let (dense, n_clusters) = densify_clusters(cluster_ids);
let flat = transition
.iter()
.flat_map(|row| row.iter().copied())
.collect();
let resolve = |v: Option<&[f64]>| match v {
Some(s) => {
debug_assert_eq!(s.len(), options, "start/end length must equal option count");
s.to_vec()
}
None => vec![0.0; options],
};
MatrixProblem {
cluster_ids: dense,
n_clusters,
options,
transition: flat,
start: resolve(start),
end: resolve(end),
}
}
fn problem<'a>(&'a self, start: &'a [f64]) -> Problem<'a> {
Problem {
cluster_ids: &self.cluster_ids,
n_clusters: self.n_clusters,
transition: &self.transition,
options: self.options,
start,
end: &self.end,
}
}
}
fn densify_clusters(cluster_ids: &[usize]) -> (Vec<usize>, usize) {
let mut map: HashMap<usize, usize> = HashMap::new();
let dense = cluster_ids
.iter()
.map(|&c| {
let next = map.len();
*map.entry(c).or_insert(next)
})
.collect();
(dense, map.len())
}
fn run<const N: usize, Cost, Conn>(
req_paths: &[ReqPath<N>],
cost: &Cost,
settings: &MultiPathSettings<N>,
connect: Conn,
) -> MultipathResult<Vec<SRobotPath<N, f64>>>
where
Cost: Fn(SRobotQ<N, f64>, SRobotQ<N, f64>) -> f64,
Conn: Fn(&SRobotQ<N, f64>, &SRobotQ<N, f64>) -> MultipathResult<SRobotPath<N, f64>> + MaybeSync,
{
let (options, n_clusters) = expand(req_paths)?;
let matrices = build_matrices(&options, cost, &settings.start, settings.end.as_ref());
let cluster_ids: Vec<usize> = options.iter().map(|o| o.cluster).collect();
let problem = Problem {
cluster_ids: &cluster_ids,
n_clusters,
transition: &matrices.transition,
options: options.len(),
start: &matrices.start,
end: &matrices.end,
};
let solution =
agtsp::solve(&problem, settings.exact_cell_budget).ok_or(MultipathError::NoFeasibleTour)?;
tracing::debug!(
n_required = req_paths.len(),
n_options = options.len(),
cost = solution.cost,
"deke-multipath: ordering solved"
);
assemble(options, &solution.order, settings, connect)
}
enum Segment<const N: usize> {
Connector(SRobotQ<N, f64>, SRobotQ<N, f64>),
Required(SRobotPath<N, f64>),
}
fn assemble<const N: usize, C>(
options: Vec<DirectedOption<N>>,
order: &[usize],
settings: &MultiPathSettings<N>,
connect: C,
) -> MultipathResult<Vec<SRobotPath<N, f64>>>
where
C: Fn(&SRobotQ<N, f64>, &SRobotQ<N, f64>) -> MultipathResult<SRobotPath<N, f64>> + MaybeSync,
{
let mut options: Vec<Option<DirectedOption<N>>> = options.into_iter().map(Some).collect();
let mut segments: Vec<Segment<N>> = Vec::new();
let mut cursor = settings.start;
for &oi in order {
let path = options[oi]
.take()
.expect("each option appears once in the tour")
.path;
let first = *path.first();
if cursor.distance(&first) > settings.dedup_tol {
segments.push(Segment::Connector(cursor, first));
}
cursor = *path.last();
segments.push(Segment::Required(path));
}
if let Some(end) = settings.end
&& cursor.distance(&end) > settings.dedup_tol
{
segments.push(Segment::Connector(cursor, end));
}
let realize = |segment: Segment<N>| -> MultipathResult<SRobotPath<N, f64>> {
match segment {
Segment::Connector(from, to) => connect(&from, &to),
Segment::Required(path) => Ok(path),
}
};
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
segments.into_par_iter().map(realize).collect()
}
#[cfg(not(feature = "rayon"))]
{
segments.into_iter().map(realize).collect()
}
}