use rand::prelude::ThreadRng;
use rand::Rng;
use std::fmt;
const DEFAULT_WALL_BOUNCE: f64 = 0.125;
const DEFAULT_LOCAL: f64 = 0.8;
const DEFAULT_TRIBAL: f64 = 1.2;
const DEFAULT_GLOBAL: f64 = 0.9;
const DEFAULT_COLLAB_RATE: usize = 16;
const DEFAULT_MOMENTUM: f64 = 0.5;
const DEFAULT_NUM_PARTICLES: usize = 128;
#[derive(Clone, Debug)]
pub struct SwarmConfig {
pub global_behavior: GlobalBehavior,
pub transient_behavior: TransientBehavior,
pub wall_bounce_factor: f64,
pub local_motion: f64,
pub tribal_motion: f64,
pub momentum: f64,
pub num_particles: usize,
}
impl SwarmConfig {
pub fn new() -> Self {
Self {
global_behavior: GlobalBehavior::default(),
transient_behavior: TransientBehavior::new(),
wall_bounce_factor: DEFAULT_WALL_BOUNCE,
local_motion: DEFAULT_LOCAL,
tribal_motion: DEFAULT_TRIBAL,
momentum: DEFAULT_MOMENTUM,
num_particles: DEFAULT_NUM_PARTICLES,
}
}
pub fn synergic_behavior(
&mut self,
global_motion_coefficient: f64,
collaboration_periodicity: usize,
) {
self.global_behavior =
GlobalBehavior::Synergic(global_motion_coefficient, collaboration_periodicity);
}
pub fn solitary_behavior(&mut self) {
self.global_behavior = GlobalBehavior::Solitary;
}
pub fn set_transient_behavior(&mut self, transient_behavior: TransientBehavior) {
self.transient_behavior = transient_behavior;
}
pub fn wall_bounce(&mut self, wall_bounce_factor: f64) {
assert!(
wall_bounce_factor > 0.0,
"Wall Bounce Factor must be greater than zero!"
);
self.wall_bounce_factor = wall_bounce_factor * -1.0;
}
pub fn motion_coefficients(&mut self, local_motion: f64, tribal_motion: f64, momentum: f64) {
self.local_motion = local_motion;
self.tribal_motion = tribal_motion;
self.momentum = momentum;
}
pub fn num_particles(&mut self, num_particles: usize) {
self.num_particles = num_particles;
}
}
impl fmt::Display for SwarmConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} Particles: ", self.num_particles)?;
write!(f, "Particle motion coefficients: \n")?;
write!(
f,
"\t [Local: {}, tribal: {}] \t momentum: {} \n \t wall_bounce_factor: {} \n",
self.local_motion, self.tribal_motion, self.momentum, self.wall_bounce_factor,
)?;
write!(f, "{} \n", self.global_behavior)?;
write!(f, "{}", self.transient_behavior)
}
}
#[derive(Clone, Debug)]
pub enum GlobalBehavior {
Synergic(f64, usize),
Solitary,
}
impl GlobalBehavior {
pub fn default() -> Self {
Self::Synergic(DEFAULT_GLOBAL, DEFAULT_COLLAB_RATE)
}
}
impl fmt::Display for GlobalBehavior {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Swarm Behavior: ")?;
match self {
Self::Synergic(global_motion_coeff, collaboration) => {
write!(
f,
"Swarm will collaborate with others every {} iterations \n and weight the global record with a coefficient of {}.",
collaboration, global_motion_coeff
)?;
}
Self::Solitary => {
write!(f, "Swarm will not collaborate with others. \n")?;
}
}
write!(f, "")
}
}
const DEFAULT_STOCHASTIC_MAG: f32 = 2.0;
const DEFAULT_STOCHASTIC_MODE: u8 = 0;
const DEFAULT_MOMENTUM_MODE: u8 = 0;
const DEFAULT_MOTION_MODE: u8 = 0;
#[derive(Clone, Debug)]
pub struct TransientBehavior {
pub stochastic_mag: f32,
pub stochastic_mode: u8,
pub momentum_mode: u8,
pub motion_mode: u8,
}
impl TransientBehavior {
pub fn new() -> Self {
Self {
stochastic_mag: DEFAULT_STOCHASTIC_MAG,
stochastic_mode: DEFAULT_STOCHASTIC_MODE,
momentum_mode: DEFAULT_MOMENTUM_MODE,
motion_mode: DEFAULT_MOTION_MODE,
}
}
pub fn stochastic_behavior(&mut self, mag: f32, mode: u8) {
assert!(mag > 0.0, "Stochastic magnitude must be positive!");
self.stochastic_mag = mag;
self.stochastic_mode = mode;
}
pub fn momentum_mode(&mut self, mode: u8) {
self.momentum_mode = mode;
}
pub fn motion_mode(&mut self, mode: u8) {
self.motion_mode = mode;
}
}
impl fmt::Display for TransientBehavior {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Transient Behavior: \n ")?;
write!(
f,
"Stochastic Mode: {}, \t Momentum Mode: {}, \t Motion Mode: {}",
self.stochastic_mode, self.momentum_mode, self.motion_mode
)
}
}
pub struct SwarmConfigSampler {
wall_bounce_factor: FixedRange<f64>,
local_motion: FixedRange<f64>,
tribal_motion: FixedRange<f64>,
global_motion: Option<FixedRange<f64>>,
global_colab_rate: Option<FixedRange<usize>>,
momentum: FixedRange<f64>,
num_particles: FixedRange<usize>,
}
impl SwarmConfigSampler {
pub fn new() -> Self {
Self {
wall_bounce_factor: FixedRange::Fixed(DEFAULT_WALL_BOUNCE),
local_motion: FixedRange::Fixed(DEFAULT_LOCAL),
tribal_motion: FixedRange::Fixed(DEFAULT_TRIBAL),
global_motion: Some(FixedRange::Fixed(DEFAULT_GLOBAL)),
global_colab_rate: Some(FixedRange::Fixed(DEFAULT_COLLAB_RATE)),
momentum: FixedRange::Fixed(DEFAULT_MOMENTUM),
num_particles: FixedRange::Fixed(DEFAULT_NUM_PARTICLES),
}
}
pub fn sample_swarm_config(&self, rng: &mut ThreadRng) -> SwarmConfig {
let mut sc = SwarmConfig::new();
sc.wall_bounce(self.wall_bounce_factor.value(rng));
let local = self.local_motion.value(rng);
let tribal = self.tribal_motion.value(rng);
let momentum = self.momentum.value(rng);
sc.motion_coefficients(local, tribal, momentum);
if let Some(global_motion_fr) = &self.global_motion {
let global_colab_rate_fr = self
.global_colab_rate
.clone()
.expect("Should not be None if global_motion is not None");
let global_motion = global_motion_fr.value(rng);
let global_colab_rate = global_colab_rate_fr.value(rng);
sc.synergic_behavior(global_motion, global_colab_rate);
} else {
sc.solitary_behavior();
}
sc.num_particles(self.num_particles.value(rng));
sc
}
pub fn wall_bounce_range(&mut self, min: f64, max: f64) {
self.wall_bounce_factor = FixedRange::new_range(min, max)
}
pub fn local_motion_range(&mut self, min: f64, max: f64) {
self.local_motion = FixedRange::new_range(min, max)
}
pub fn tribal_motion_range(&mut self, min: f64, max: f64) {
self.tribal_motion = FixedRange::new_range(min, max)
}
pub fn synergic_range(
&mut self,
min_global_motion: f64,
max_global_motion: f64,
min_colab_rate: usize,
max_colab_rate: usize,
) {
self.global_motion = Some(FixedRange::new_range(min_global_motion, max_global_motion));
self.global_colab_rate = Some(FixedRange::new_range(min_colab_rate, max_colab_rate));
}
pub fn solitary(&mut self) {
self.global_motion = None;
self.global_colab_rate = None;
}
pub fn num_particles_range(&mut self, min: usize, max: usize) {
self.num_particles = FixedRange::new_range(min, max);
}
pub fn wall_bounce_fixed(&mut self, value: f64) {
self.wall_bounce_factor = FixedRange::Fixed(value);
}
pub fn local_fixed(&mut self, value: f64) {
self.local_motion = FixedRange::Fixed(value);
}
pub fn tribal_fixed(&mut self, value: f64) {
self.tribal_motion = FixedRange::Fixed(value);
}
pub fn synergic_fized(&mut self, global_value: f64, global_colab_rate: usize) {
self.global_motion = Some(FixedRange::Fixed(global_value));
self.global_colab_rate = Some(FixedRange::Fixed(global_colab_rate));
}
}
#[derive(Clone)]
enum FixedRange<T> {
Fixed(T),
Range([T; 2]),
}
impl<T> FixedRange<T>
where
T: PartialOrd,
{
pub fn new_range(min: T, max: T) -> Self {
assert!(min < max, "Minimum value must be less than Maximum value!");
Self::Range([min, max])
}
}
trait FRValue<T> {
fn value(&self, rng: &mut ThreadRng) -> T;
}
impl FRValue<f64> for FixedRange<f64> {
fn value(&self, rng: &mut ThreadRng) -> f64 {
match self {
Self::Fixed(val) => *val,
Self::Range([min, max]) => rng.gen_range(min, max),
}
}
}
impl FRValue<usize> for FixedRange<usize> {
fn value(&self, rng: &mut ThreadRng) -> usize {
match self {
Self::Fixed(val) => *val,
Self::Range([min, max]) => rng.gen_range(min, max),
}
}
}