pub use swarmkit::Evolution;
pub use swarmkit_sailing::Path;
#[derive(serde::Deserialize, serde::Serialize, PartialEq, Eq, Clone, Copy, Default, Debug)]
pub enum WaypointCount {
N5,
N8,
N10,
N15,
N20,
#[default]
N30,
N40,
N50,
N60,
}
impl WaypointCount {
pub const ALL: [Self; 9] = [
Self::N5,
Self::N8,
Self::N10,
Self::N15,
Self::N20,
Self::N30,
Self::N40,
Self::N50,
Self::N60,
];
pub fn as_usize(self) -> usize {
match self {
Self::N5 => 5,
Self::N8 => 8,
Self::N10 => 10,
Self::N15 => 15,
Self::N20 => 20,
Self::N30 => 30,
Self::N40 => 40,
Self::N50 => 50,
Self::N60 => 60,
}
}
pub fn from_usize(n: usize) -> Option<Self> {
match n {
5 => Some(Self::N5),
8 => Some(Self::N8),
10 => Some(Self::N10),
15 => Some(Self::N15),
20 => Some(Self::N20),
30 => Some(Self::N30),
40 => Some(Self::N40),
50 => Some(Self::N50),
60 => Some(Self::N60),
_ => None,
}
}
}
pub enum RouteEvolution {
N5(Evolution<Path<5>>),
N8(Evolution<Path<8>>),
N10(Evolution<Path<10>>),
N15(Evolution<Path<15>>),
N20(Evolution<Path<20>>),
N30(Evolution<Path<30>>),
N40(Evolution<Path<40>>),
N50(Evolution<Path<50>>),
N60(Evolution<Path<60>>),
}
#[macro_export]
macro_rules! route_evolution_match {
($expr:expr, |$evo:ident| $body:expr) => {
match $expr {
$crate::route::RouteEvolution::N5($evo) => $body,
$crate::route::RouteEvolution::N8($evo) => $body,
$crate::route::RouteEvolution::N10($evo) => $body,
$crate::route::RouteEvolution::N15($evo) => $body,
$crate::route::RouteEvolution::N20($evo) => $body,
$crate::route::RouteEvolution::N30($evo) => $body,
$crate::route::RouteEvolution::N40($evo) => $body,
$crate::route::RouteEvolution::N50($evo) => $body,
$crate::route::RouteEvolution::N60($evo) => $body,
}
};
}
pub use route_evolution_match;
#[macro_export]
macro_rules! waypoint_match {
($wc:expr, $n:ident, $wrap:ident, $body:expr) => {
match $wc {
$crate::route::WaypointCount::N5 => {
const $n: usize = 5;
let $wrap = $crate::route::RouteEvolution::N5;
$body
}
$crate::route::WaypointCount::N8 => {
const $n: usize = 8;
let $wrap = $crate::route::RouteEvolution::N8;
$body
}
$crate::route::WaypointCount::N10 => {
const $n: usize = 10;
let $wrap = $crate::route::RouteEvolution::N10;
$body
}
$crate::route::WaypointCount::N15 => {
const $n: usize = 15;
let $wrap = $crate::route::RouteEvolution::N15;
$body
}
$crate::route::WaypointCount::N20 => {
const $n: usize = 20;
let $wrap = $crate::route::RouteEvolution::N20;
$body
}
$crate::route::WaypointCount::N30 => {
const $n: usize = 30;
let $wrap = $crate::route::RouteEvolution::N30;
$body
}
$crate::route::WaypointCount::N40 => {
const $n: usize = 40;
let $wrap = $crate::route::RouteEvolution::N40;
$body
}
$crate::route::WaypointCount::N50 => {
const $n: usize = 50;
let $wrap = $crate::route::RouteEvolution::N50;
$body
}
$crate::route::WaypointCount::N60 => {
const $n: usize = 60;
let $wrap = $crate::route::RouteEvolution::N60;
$body
}
}
};
}
pub use waypoint_match;
#[derive(Clone, Debug)]
pub struct BenchmarkRoute {
pub waypoints: Vec<(f64, f64)>,
pub total_time: f64,
pub total_fuel: f64,
pub total_land_metres: f64,
pub fitness: f64,
}
pub struct GbestView<'a> {
pub xs: &'a [f64],
pub ys: &'a [f64],
pub ts: &'a [f64],
pub best_fit: f64,
}
pub struct GbestViewMut<'a> {
pub xs: &'a mut [f64],
pub ys: &'a mut [f64],
pub ts: &'a mut [f64],
}
impl RouteEvolution {
pub fn iter_count(&self) -> usize {
route_evolution_match!(self, |e| e.frames().len())
}
pub fn gbest_at(&self, iter: usize) -> Option<GbestView<'_>> {
route_evolution_match!(self, |evo| {
let frames = evo.frames();
let iter_idx = iter.min(frames.len().saturating_sub(1));
let particles = frames.get(iter_idx)?;
let best = particles.iter().max_by(|a, b| {
a.best_fit
.partial_cmp(&b.best_fit)
.unwrap_or(std::cmp::Ordering::Equal)
})?;
Some(GbestView {
xs: &best.best_pos.xy.0.0[..],
ys: &best.best_pos.xy.1.0[..],
ts: &best.best_pos.t.0.0[..],
best_fit: best.best_fit,
})
})
}
pub fn gbest_at_mut(&mut self, iter: usize) -> Option<GbestViewMut<'_>> {
route_evolution_match!(self, |evo| {
let frames = evo.frames_mut();
let iter_idx = iter.min(frames.len().saturating_sub(1));
let particles = frames.get_mut(iter_idx)?;
let best_idx = particles
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
a.best_fit
.partial_cmp(&b.best_fit)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)?;
let p = particles.get_mut(best_idx)?;
Some(GbestViewMut {
xs: &mut p.best_pos.xy.0.0[..],
ys: &mut p.best_pos.xy.1.0[..],
ts: &mut p.best_pos.t.0.0[..],
})
})
}
pub fn mutate_waypoint(&mut self, iter: usize, idx: usize, dx: f64, dy: f64) {
let Some(g) = self.gbest_at_mut(iter) else {
return;
};
if let Some(x) = g.xs.get_mut(idx) {
*x += dx;
}
if let Some(y) = g.ys.get_mut(idx) {
*y += dy;
}
}
pub fn apply_reopt_times(&mut self, iter: usize, new_times: &[f64]) {
let Some(g) = self.gbest_at_mut(iter) else {
return;
};
if g.ts.len() != new_times.len() {
return;
}
g.ts.copy_from_slice(new_times);
}
}
pub(crate) fn debug_assert_path_no_nans<const N: usize>(path: &Path<N>, context: &str) {
if !cfg!(debug_assertions) {
return;
}
for (i, &x) in path.xy.0.0.iter().enumerate() {
assert!(!x.is_nan(), "NaN in {context}: x[{i}]");
}
for (i, &y) in path.xy.1.0.iter().enumerate() {
assert!(!y.is_nan(), "NaN in {context}: y[{i}]");
}
for (i, &t) in path.t.0.0.iter().enumerate() {
assert!(!t.is_nan(), "NaN in {context}: t[{i}]");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn waypoint_count_round_trips_through_usize() {
for &wc in &WaypointCount::ALL {
let n = wc.as_usize();
assert_eq!(WaypointCount::from_usize(n), Some(wc));
}
}
#[test]
fn waypoint_count_rejects_unsupported_n() {
for unsupported in [0, 1, 7, 11, 25, 45, 100] {
assert_eq!(
WaypointCount::from_usize(unsupported),
None,
"n={unsupported}",
);
}
}
#[test]
fn waypoint_count_default_is_n30() {
assert_eq!(WaypointCount::default().as_usize(), 30);
}
#[test]
fn waypoint_count_all_is_sorted_and_complete() {
let sizes: Vec<usize> = WaypointCount::ALL.iter().map(|wc| wc.as_usize()).collect();
assert_eq!(sizes, vec![5, 8, 10, 15, 20, 30, 40, 50, 60]);
}
}