use crate::spherical::{
LatLon, POLE_LATITUDE_LIMIT_DEG, TangentMetres, delta_to_tangent_metres,
tangent_metres_to_delta, wrap_lon_deg,
};
use crate::units::PathXY;
use rand::distr::StandardUniform;
use rand::{Rng, RngExt as _};
use swarmkit::{Best, Contextful, PSOCoeffs, ParticleMover, ParticleRefMut};
pub struct SphericalPSOMover<const N: usize, TContext = ()> {
config: PSOCoeffs,
_ctx: std::marker::PhantomData<fn() -> TContext>,
}
impl<const N: usize, TContext> SphericalPSOMover<N, TContext> {
pub fn new(config: PSOCoeffs) -> Self {
Self {
config,
_ctx: std::marker::PhantomData,
}
}
}
impl<const N: usize, TContext> Contextful for SphericalPSOMover<N, TContext>
where
TContext: Copy,
{
type TContext = TContext;
}
impl<const N: usize, TContext> ParticleMover for SphericalPSOMover<N, TContext>
where
TContext: Copy,
{
type TUnit = PathXY<N>;
type TCommon = Best<PathXY<N>>;
fn update<R: Rng>(
&self,
common: &Self::TCommon,
rng: &mut R,
_idx: usize,
p: &mut ParticleRefMut<'_, Self::TUnit>,
) {
let r1: f64 = rng.sample::<f64, StandardUniform>(StandardUniform);
let r2: f64 = rng.sample::<f64, StandardUniform>(StandardUniform);
for i in 0..N {
let pos = p.pos.lat_lon(i);
let pbest = p.best_pos.lat_lon(i);
let social = common.best_pos.lat_lon(i);
let vel = TangentMetres::new(p.vel.0[i], p.vel.1[i]);
let (new_vel, new_pos) =
spherical_waypoint_update(pos, pbest, social, vel, &self.config, r1, r2);
p.vel.0[i] = new_vel.east;
p.vel.1[i] = new_vel.north;
p.pos.set_lat_lon(i, new_pos);
}
}
}
pub fn spherical_waypoint_update(
pos: LatLon,
pbest: LatLon,
social: LatLon,
vel: TangentMetres,
config: &PSOCoeffs,
r1: f64,
r2: f64,
) -> (TangentMetres, LatLon) {
let cog_metres = delta_to_tangent_metres(pos, pos.delta_to(pbest));
let soc_metres = delta_to_tangent_metres(pos, pos.delta_to(social));
let new_vel = vel * config.inertia
+ cog_metres * (config.cognitive_coeff * r1)
+ soc_metres * (config.social_coeff * r2);
let dlonlat = tangent_metres_to_delta(pos, new_vel);
let new_pos = LatLon::new(
wrap_lon_deg(pos.lon + dlonlat.lon),
(pos.lat + dlonlat.lat).clamp(-POLE_LATITUDE_LIMIT_DEG, POLE_LATITUDE_LIMIT_DEG),
);
(new_vel, new_pos)
}