pub(crate) mod implementations;
pub(crate) mod traits;
use std::fmt::Display;
use std::str::FromStr;
pub use implementations::{
binary_solution::BinarySolutionBuilder,
pareto_crowding_solution::{
MultiObjectiveRealSolutionBuilder, MultiObjectiveVectorRealSolutionBuilder,
},
permutation_solution::PermutationSolutionBuilder,
real_solution::RealSolutionBuilder,
string_solution::StringSolutionBuilder,
};
pub use traits::ParetoCrowdingDistanceQuality;
#[derive(Clone, Debug)]
pub struct Solution<T, Q = f64> {
variables: Vec<T>,
real_lower_bounds: Option<Vec<f64>>,
real_upper_bounds: Option<Vec<f64>>,
value: Option<Q>,
}
impl<T: Display, Q: Display> Solution<T, Q> {
pub fn new(variables: Vec<T>) -> Self {
Self {
variables,
real_lower_bounds: None,
real_upper_bounds: None,
value: None,
}
}
pub fn variables(&self) -> &[T] {
&self.variables
}
pub fn variables_mut(&mut self) -> &mut [T] {
self.invalidate();
&mut self.variables
}
pub fn set_variables(&mut self, variables: Vec<T>) {
if self
.real_lower_bounds
.as_ref()
.is_some_and(|bounds| bounds.len() != variables.len())
|| self
.real_upper_bounds
.as_ref()
.is_some_and(|bounds| bounds.len() != variables.len())
{
self.real_lower_bounds = None;
self.real_upper_bounds = None;
}
self.variables = variables;
self.invalidate();
}
pub fn num_variables(&self) -> usize {
self.variables.len()
}
pub fn copy(&self) -> Self
where
T: Clone,
Q: Clone,
{
self.clone()
}
pub fn get_variable(&self, index: usize) -> Option<&T> {
self.variables.get(index)
}
pub fn get_variable_mut(&mut self, index: usize) -> Option<&mut T> {
self.invalidate();
self.variables.get_mut(index)
}
pub fn set_variable(&mut self, index: usize, value: T) -> bool {
if let Some(variable) = self.variables.get_mut(index) {
*variable = value;
self.invalidate();
true
} else {
false
}
}
pub fn swap_variables(&mut self, i: usize, j: usize) -> bool {
if i < self.variables.len() && j < self.variables.len() {
self.variables.swap(i, j);
self.invalidate();
true
} else {
false
}
}
pub fn quality(&self) -> Option<&Q> {
self.value.as_ref()
}
pub fn quality_mut(&mut self) -> Option<&mut Q> {
self.value.as_mut()
}
pub fn set_quality(&mut self, quality: Q) {
self.value = Some(quality);
}
pub fn has_quality(&self) -> bool {
self.value.is_some()
}
pub fn invalidate(&mut self) {
self.value = None;
}
pub fn encode(&self) -> String {
let quality_string = match &self.value {
Some(q) => q.to_string(),
None => "None".to_string(),
};
let genes: String = self
.variables
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(",");
format!("{}|{}", genes, quality_string)
}
pub fn decode(data: &str) -> Result<Self, String>
where
T: FromStr,
Q: FromStr,
{
let parts: Vec<&str> = data.split('|').collect();
if parts.len() != 2 {
return Err("Invalid format: 'genes|fitness' expected".to_string());
}
let variables: Vec<T> = parts[0]
.split(',')
.filter(|s| !s.is_empty())
.map(|s| {
s.parse::<T>()
.map_err(|_| "Error parsing variable (T)".to_string())
})
.collect::<Result<Vec<T>, String>>()?;
let quality = if parts[1] == "None" || parts[1].is_empty() {
None
} else {
Some(
parts[1]
.parse::<Q>()
.map_err(|_| "Error parsing quality (Q)".to_string())?,
)
};
Ok(Self {
variables,
real_lower_bounds: None,
real_upper_bounds: None,
value: quality,
})
}
}
impl<Q> Solution<f64, Q> {
pub fn lower_bounds(&self) -> Option<&[f64]> {
self.real_lower_bounds.as_deref()
}
pub fn upper_bounds(&self) -> Option<&[f64]> {
self.real_upper_bounds.as_deref()
}
pub fn bounds_at(&self, index: usize) -> Option<(f64, f64)> {
let lower = self.real_lower_bounds.as_ref()?.get(index).copied()?;
let upper = self.real_upper_bounds.as_ref()?.get(index).copied()?;
Some((lower, upper))
}
pub fn set_bounds(&mut self, lower_bounds: Vec<f64>, upper_bounds: Vec<f64>) {
debug_assert_eq!(
lower_bounds.len(),
self.variables.len(),
"lower_bounds length should match variables length"
);
debug_assert_eq!(
upper_bounds.len(),
self.variables.len(),
"upper_bounds length should match variables length"
);
self.real_lower_bounds = Some(lower_bounds);
self.real_upper_bounds = Some(upper_bounds);
}
pub fn set_uniform_bounds(&mut self, lower: f64, upper: f64) {
let size = self.variables.len();
self.set_bounds(vec![lower; size], vec![upper; size]);
}
}
impl<T> Solution<T, f64> {
pub fn try_quality_value(&self) -> Option<f64> {
self.value
}
pub fn quality_value(&self) -> f64 {
self.value
.expect("quality_value() called on a solution without evaluated quality")
}
pub fn has_bigger_quality(&self, other: &Self) -> bool {
match (self.value, other.value) {
(Some(a), Some(b)) => a > b,
_ => false,
}
}
}
fn finalize_scalar_solution<T: Display>(variables: Vec<T>, quality: Option<f64>) -> Solution<T> {
let mut solution = Solution::new(variables);
if let Some(quality) = quality {
solution.set_quality(quality);
}
solution
}
fn apply_bounds(
mut variables: Vec<f64>,
lower_bounds: &Option<Vec<f64>>,
upper_bounds: &Option<Vec<f64>>,
) -> Vec<f64> {
if let (Some(lower), Some(upper)) = (lower_bounds, upper_bounds) {
debug_assert_eq!(
lower.len(),
variables.len(),
"lower_bounds length should match variables length"
);
debug_assert_eq!(
upper.len(),
variables.len(),
"upper_bounds length should match variables length"
);
for ((value, &lo), &up) in variables.iter_mut().zip(lower.iter()).zip(upper.iter()) {
*value = value.clamp(lo, up);
}
}
variables
}
#[cfg(test)]
mod tests {
use super::Solution;
#[test]
fn quality_helpers_work_as_expected() {
let mut s: Solution<i32> = Solution::new(vec![1, 2, 3]);
assert!(!s.has_quality());
assert_eq!(s.quality(), None);
s.set_quality(4.0);
assert!(s.has_quality());
assert_eq!(s.quality().copied(), Some(4.0));
s.invalidate();
assert!(!s.has_quality());
assert_eq!(s.quality(), None);
}
#[test]
fn invalidate_clears_quality() {
let mut s: Solution<bool> = Solution::new(vec![true, false]);
s.set_quality(10.0);
assert_eq!(s.quality().copied(), Some(10.0));
s.invalidate();
assert_eq!(s.quality(), None);
}
#[test]
fn try_quality_value_reflects_presence() {
let mut s: Solution<i32> = Solution::new(vec![1, 2, 3]);
assert_eq!(s.try_quality_value(), None);
s.set_quality(1.25);
assert_eq!(s.try_quality_value(), Some(1.25));
}
#[test]
#[should_panic(expected = "quality_value() called on a solution without evaluated quality")]
fn quality_value_panics_when_missing() {
let s: Solution<i32> = Solution::new(vec![1, 2, 3]);
let _ = s.quality_value();
}
}