mod agtsp;
mod cost;
mod error;
mod reqpath;
use deke_types::{DekeError, Planner, SRobotPath, SRobotQ, Validator};
use agtsp::Problem;
use cost::build_matrices;
use reqpath::{DirectedOption, expand};
pub use cost::{TransitionCost, weighted_distance};
pub use error::{MultipathError, MultipathResult};
pub use reqpath::ReqPath;
#[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>(
req_paths: &[ReqPath<N>],
cost: &TransitionCost<N>,
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,
{
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>(
req_paths: &[ReqPath<N>],
cost: &TransitionCost<N>,
settings: &MultiPathSettings<N>,
validator: &V,
ctx: &V::Context<'ctx>,
) -> MultipathResult<Vec<SRobotPath<N, f64>>>
where
V: Validator<N, (), f64>,
V::Context<'ctx>: MaybeSync,
{
run(req_paths, cost, settings, |from, to| {
validator.validate_motion(&[*from, *to], ctx)?;
Ok(SRobotPath::from_two(*from, *to))
})
}
fn run<const N: usize, C>(
req_paths: &[ReqPath<N>],
cost: &TransitionCost<N>,
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 (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()
}
}