use crate::{
algorithms::field_d_star::FieldDStar,
grid::{Cell, Grid, GridEditError},
point::Point,
replanning::{
InterpolatedGridReplanner, InterpolatedPathOutcomeKind, InterpolatedSearchOutcome,
InterpolatedSearchRequest,
},
};
use condor_core::Point2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CertifiedQuality {
Found,
PartialPath,
Fallback,
NoPath,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CertifiedAdaptiveIntervalResult {
pub quality: CertifiedQuality,
pub lower_bound: f64,
pub goal_cost_upper: Option<f64>,
pub start: Point2,
pub goal: Point2,
pub has_path: bool,
pub goal_feasible: bool,
pub path_cost: Option<f64>,
}
#[must_use]
pub fn check_certificate(result: &CertifiedAdaptiveIntervalResult) -> bool {
if !result.lower_bound.is_finite() {
return false;
}
match result.quality {
CertifiedQuality::Found => {
if !result.goal_feasible || !result.has_path {
return false;
}
let Some(upper) = result.goal_cost_upper else {
return false;
};
let Some(cost) = result.path_cost else {
return false;
};
if !upper.is_finite() || !cost.is_finite() {
return false;
}
if result.lower_bound > upper + 1e-9 {
return false;
}
if cost + 1e-9 < result.lower_bound || cost - 1e-9 > upper {
return false;
}
true
}
CertifiedQuality::PartialPath | CertifiedQuality::Fallback => {
if result.goal_feasible || result.goal_cost_upper.is_some() {
return false;
}
if !result.has_path {
return false;
}
let Some(cost) = result.path_cost else {
return false;
};
cost.is_finite() && cost + 1e-9 >= result.lower_bound
}
CertifiedQuality::NoPath => {
!result.has_path
&& !result.goal_feasible
&& result.path_cost.is_none()
&& result.goal_cost_upper.is_none()
}
}
}
pub struct FieldDStarCertifiedAdaptiveIntervals {
support: FieldDStar,
grid: Option<Grid>,
request: Option<InterpolatedSearchRequest>,
}
impl FieldDStarCertifiedAdaptiveIntervals {
pub const CANDIDATE_ID: &str =
"interpolated-dynamic-replanning/C002-certified-adaptive-intervals";
#[must_use]
pub fn new() -> Self {
Self {
support: FieldDStar::new(),
grid: None,
request: None,
}
}
pub fn search_certified(
&mut self,
grid: &Grid,
request: InterpolatedSearchRequest,
) -> Result<CertifiedAdaptiveIntervalResult, crate::InterpolatedSearchError> {
self.grid = Some(grid.clone());
self.request = Some(request);
let outcome = self.support.initialize(grid, request)?;
Ok(certificate_from_outcome(request, &outcome))
}
pub fn replan_certified(
&mut self,
) -> Result<CertifiedAdaptiveIntervalResult, crate::InterpolatedSearchError> {
let request = self
.request
.ok_or(crate::InterpolatedSearchError::NotInitialized)?;
let outcome = self.support.replan()?;
Ok(certificate_from_outcome(request, &outcome))
}
pub fn update_cell(&mut self, point: Point, cell: Cell) {
if let Some(grid) = &mut self.grid {
let _ = grid.set_cell(point, cell);
}
self.support.update_cell(point, cell);
}
pub fn update_cost(&mut self, point: Point, cost: usize) -> Result<(), GridEditError> {
if let Some(grid) = &mut self.grid {
grid.set_traversal_cost(point, cost)?;
}
self.support.update_cost(point, cost)
}
}
impl Default for FieldDStarCertifiedAdaptiveIntervals {
fn default() -> Self {
Self::new()
}
}
fn certificate_from_outcome(
request: InterpolatedSearchRequest,
outcome: &InterpolatedSearchOutcome,
) -> CertifiedAdaptiveIntervalResult {
let path_cost = outcome.cost();
let kind = outcome.path_outcome().map(|o| o.kind());
match kind {
Some(InterpolatedPathOutcomeKind::Found) => CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::Found,
lower_bound: 0.0,
goal_cost_upper: path_cost,
start: request.start,
goal: request.goal,
has_path: true,
goal_feasible: true,
path_cost,
},
Some(InterpolatedPathOutcomeKind::PartialPath) => CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::PartialPath,
lower_bound: 0.0,
goal_cost_upper: None,
start: request.start,
goal: request.goal,
has_path: true,
goal_feasible: false,
path_cost,
},
Some(InterpolatedPathOutcomeKind::Fallback) => CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::Fallback,
lower_bound: 0.0,
goal_cost_upper: None,
start: request.start,
goal: request.goal,
has_path: true,
goal_feasible: false,
path_cost,
},
None => CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::NoPath,
lower_bound: 0.0,
goal_cost_upper: None,
start: request.start,
goal: request.goal,
has_path: false,
goal_feasible: false,
path_cost: None,
},
}
}
#[cfg(test)]
mod tests {
use super::{
CertifiedAdaptiveIntervalResult, CertifiedQuality, FieldDStarCertifiedAdaptiveIntervals,
check_certificate,
};
use crate::{
algorithms::field_d_star::FieldDStar,
grid::{Cell, Grid},
point::Point,
replanning::{
InterpolatedGridReplanner, InterpolatedPathOutcomeKind, InterpolatedSearchRequest,
},
};
use condor_core::Point2;
#[test]
fn certificate_checker_rejects_overstated_bounds() {
let bad = CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::Found,
lower_bound: 10.0,
goal_cost_upper: Some(5.0),
start: Point2::new(0.0, 0.0),
goal: Point2::new(1.0, 0.0),
has_path: true,
goal_feasible: true,
path_cost: Some(7.0),
};
assert!(!check_certificate(&bad));
let overstated = CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::Found,
lower_bound: 0.0,
goal_cost_upper: Some(1.0),
start: Point2::new(0.0, 0.0),
goal: Point2::new(1.0, 0.0),
has_path: true,
goal_feasible: true,
path_cost: Some(5.0),
};
assert!(!check_certificate(&overstated));
let bad_partial = CertifiedAdaptiveIntervalResult {
quality: CertifiedQuality::PartialPath,
lower_bound: 0.0,
goal_cost_upper: Some(3.0),
start: Point2::new(0.0, 0.0),
goal: Point2::new(1.0, 0.0),
has_path: true,
goal_feasible: false,
path_cost: Some(3.0),
};
assert!(!check_certificate(&bad_partial));
}
#[test]
fn result_not_identity_with_field_d_star_exact() {
let grid = Grid::new(4, 4).expect("grid");
let request = InterpolatedSearchRequest::new(Point2::new(0.5, 0.5), Point2::new(3.5, 3.5));
let mut certified = FieldDStarCertifiedAdaptiveIntervals::new();
let mut field = FieldDStar::new();
let cert = certified.search_certified(&grid, request).expect("valid");
let baseline = field.initialize(&grid, request).expect("valid");
assert!(check_certificate(&cert));
assert!(cert.has_path);
assert!(cert.goal_feasible);
assert_eq!(cert.quality, CertifiedQuality::Found);
assert_eq!(
baseline.path_outcome().map(|o| o.kind()),
Some(InterpolatedPathOutcomeKind::Found)
);
assert!(cert.goal_cost_upper.is_some_and(f64::is_finite));
}
#[test]
fn partial_fallback_no_path_preserved() {
let mut grid = Grid::new(3, 3).expect("grid");
for x in 0..3 {
grid.set_cell(Point::new(x, 1), Cell::Blocked)
.expect("valid");
}
let request = InterpolatedSearchRequest::new(Point2::new(0.5, 0.5), Point2::new(2.5, 2.5));
let mut certified = FieldDStarCertifiedAdaptiveIntervals::new();
let cert = certified
.search_certified(&grid, request)
.expect("valid endpoints");
assert!(
check_certificate(&cert),
"engine-produced certificates must always pass the checker"
);
assert_ne!(cert.quality, CertifiedQuality::Found);
assert!(!cert.goal_feasible);
assert!(cert.goal_cost_upper.is_none());
if !cert.has_path {
assert_eq!(cert.quality, CertifiedQuality::NoPath);
}
}
#[test]
fn no_path_certificate_is_checkable() {
let mut grid = Grid::new(3, 1).expect("grid");
grid.set_cell(Point::new(1, 0), Cell::Blocked)
.expect("valid");
let request = InterpolatedSearchRequest::new(Point2::new(0.5, 0.5), Point2::new(2.5, 0.5));
let mut certified = FieldDStarCertifiedAdaptiveIntervals::new();
let cert = certified
.search_certified(&grid, request)
.expect("valid endpoints");
if cert.quality == CertifiedQuality::NoPath {
assert!(check_certificate(&cert));
assert!(!cert.has_path);
assert!(cert.goal_cost_upper.is_none());
}
}
#[test]
fn retains_candidate_id() {
assert_eq!(
FieldDStarCertifiedAdaptiveIntervals::CANDIDATE_ID,
"interpolated-dynamic-replanning/C002-certified-adaptive-intervals"
);
}
}