use std::error::Error;
use std::fmt::{Debug, Display};
use std::sync::Arc;
use crate::evolution::Challenge;
use crate::phenotype::Phenotype;
use crate::rng::RandomNumberGenerator;
pub mod combinatorial;
pub use combinatorial::{
CapacityConstraint, CompleteAssignmentConstraint, DependencyConstraint,
UniqueElementsConstraint,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct ConstraintViolation {
constraint_name: String,
description: String,
severity: Option<f64>,
}
impl ConstraintViolation {
pub fn new<S: Into<String>, D: Into<String>>(constraint_name: S, description: D) -> Self {
Self {
constraint_name: constraint_name.into(),
description: description.into(),
severity: None,
}
}
pub fn with_severity<S: Into<String>, D: Into<String>>(
constraint_name: S,
description: D,
severity: f64,
) -> Self {
Self {
constraint_name: constraint_name.into(),
description: description.into(),
severity: Some(severity),
}
}
pub fn constraint_name(&self) -> &str {
&self.constraint_name
}
pub fn description(&self) -> &str {
&self.description
}
pub fn severity(&self) -> Option<f64> {
self.severity
}
}
impl Display for ConstraintViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Constraint '{}' violated: {}{}",
self.constraint_name(),
self.description(),
self.severity()
.map(|s| format!(" (severity: {})", s))
.unwrap_or_default()
)
}
}
pub trait Constraint<P>: Debug + Send + Sync
where
P: Phenotype,
{
fn check(&self, phenotype: &P) -> Vec<ConstraintViolation>;
fn repair(&self, _phenotype: &mut P) -> bool {
false
}
fn repair_with_rng(&self, _phenotype: &mut P, _rng: &mut RandomNumberGenerator) -> bool {
false
}
fn penalty_score(&self, violations: &[ConstraintViolation]) -> f64 {
violations.iter().map(|v| v.severity.unwrap_or(1.0)).sum()
}
}
#[derive(Debug, Clone)]
pub struct ConstraintManager<P>
where
P: Phenotype,
{
constraints: Vec<Arc<dyn Constraint<P>>>,
}
impl<P> ConstraintManager<P>
where
P: Phenotype,
{
pub fn new() -> Self {
Self {
constraints: Vec::new(),
}
}
pub fn builder() -> ConstraintManagerBuilder<P> {
ConstraintManagerBuilder::new()
}
pub fn add_constraint<C>(&mut self, constraint: C) -> &mut Self
where
C: Constraint<P> + 'static,
{
self.constraints.push(Arc::new(constraint));
self
}
pub fn check_all(&self, phenotype: &P) -> Vec<ConstraintViolation> {
self.constraints
.iter()
.flat_map(|c| c.check(phenotype))
.collect()
}
pub fn repair_all(&self, phenotype: &mut P) -> bool {
let mut all_repaired = true;
for constraint in &self.constraints {
if !constraint.repair(phenotype) {
all_repaired = false;
}
}
all_repaired
}
pub fn repair_all_with_rng(&self, phenotype: &mut P, rng: &mut RandomNumberGenerator) -> bool {
let mut all_repaired = true;
for constraint in &self.constraints {
if !constraint.repair_with_rng(phenotype, rng) {
all_repaired = false;
}
}
all_repaired
}
pub fn total_penalty_score(&self, phenotype: &P) -> f64 {
let mut total_score = 0.0;
for constraint in &self.constraints {
let violations = constraint.check(phenotype);
if !violations.is_empty() {
total_score += constraint.penalty_score(&violations);
}
}
total_score
}
pub fn is_valid(&self, phenotype: &P) -> bool {
self.check_all(phenotype).is_empty()
}
pub fn len(&self) -> usize {
self.constraints.len()
}
pub fn is_empty(&self) -> bool {
self.constraints.is_empty()
}
}
impl<P> Default for ConstraintManager<P>
where
P: Phenotype,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ConstraintManagerBuilder<P>
where
P: Phenotype,
{
constraints: Vec<Arc<dyn Constraint<P>>>,
}
impl<P> ConstraintManagerBuilder<P>
where
P: Phenotype,
{
pub fn new() -> Self {
Self {
constraints: Vec::new(),
}
}
pub fn with_constraint<C>(mut self, constraint: C) -> Self
where
C: Constraint<P> + 'static,
{
self.constraints.push(Arc::new(constraint));
self
}
pub fn build(self) -> ConstraintManager<P> {
ConstraintManager {
constraints: self.constraints,
}
}
}
impl<P> Default for ConstraintManagerBuilder<P>
where
P: Phenotype,
{
fn default() -> Self {
Self::new()
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstraintError {
EmptyName,
EmptyCollection(String),
Other(String),
}
impl Display for ConstraintError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConstraintError::EmptyName => write!(f, "Constraint name cannot be empty"),
ConstraintError::EmptyCollection(name) => write!(f, "{} cannot be empty", name),
ConstraintError::Other(msg) => write!(f, "{}", msg),
}
}
}
impl Error for ConstraintError {}
#[derive(Debug, Clone)]
pub struct PenaltyAdjustedChallenge<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
challenge: C,
constraint_manager: ConstraintManager<P>,
penalty_weight: f64,
}
impl<P, C> PenaltyAdjustedChallenge<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
pub fn new(
challenge: C,
constraint_manager: ConstraintManager<P>,
penalty_weight: f64,
) -> Self {
Self {
challenge,
constraint_manager,
penalty_weight,
}
}
pub fn inner(&self) -> &C {
&self.challenge
}
pub fn constraint_manager(&self) -> &ConstraintManager<P> {
&self.constraint_manager
}
pub fn penalty_weight(&self) -> f64 {
self.penalty_weight
}
}
impl<P, C> Challenge<P> for PenaltyAdjustedChallenge<P, C>
where
P: Phenotype,
C: Challenge<P>,
{
fn score(&self, phenotype: &P) -> f64 {
let base_score = self.challenge.score(phenotype);
let penalty = self.constraint_manager.total_penalty_score(phenotype) * self.penalty_weight;
base_score - penalty
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rng::RandomNumberGenerator;
use std::collections::HashSet;
#[derive(Clone, Debug)]
struct TestPhenotype {
values: Vec<usize>,
}
impl Phenotype for TestPhenotype {
fn crossover(&mut self, other: &Self) {
if !other.values.is_empty() && !self.values.is_empty() {
self.values[0] = other.values[0];
}
}
fn mutate(&mut self, _rng: &mut RandomNumberGenerator) {
if !self.values.is_empty() {
self.values[0] += 1;
}
}
}
impl AsRef<Vec<usize>> for TestPhenotype {
fn as_ref(&self) -> &Vec<usize> {
&self.values
}
}
impl AsMut<Vec<usize>> for TestPhenotype {
fn as_mut(&mut self) -> &mut Vec<usize> {
&mut self.values
}
}
#[derive(Debug)]
struct UniqueValuesConstraint;
impl Constraint<TestPhenotype> for UniqueValuesConstraint {
fn check(&self, phenotype: &TestPhenotype) -> Vec<ConstraintViolation> {
let values = phenotype.as_ref();
let mut seen = HashSet::new();
let mut violations = Vec::new();
for (idx, &value) in values.iter().enumerate() {
if !seen.insert(value) {
violations.push(ConstraintViolation::new(
"UniqueValues",
format!("Duplicate value {} at position {}", value, idx),
));
}
}
violations
}
fn repair(&self, phenotype: &mut TestPhenotype) -> bool {
let values = phenotype.as_mut();
let mut seen = HashSet::new();
let mut modified = false;
for i in 0..values.len() {
let mut value = values[i];
while !seen.insert(value) {
value += 1;
modified = true;
}
values[i] = value;
}
modified
}
}
#[test]
fn test_constraint_violation() {
let violation = ConstraintViolation::new("Test", "Test violation");
assert_eq!(violation.constraint_name(), "Test");
assert_eq!(violation.description(), "Test violation");
assert!(violation.severity().is_none());
let violation_with_severity =
ConstraintViolation::with_severity("Test", "Test violation", 2.0);
assert_eq!(violation_with_severity.constraint_name(), "Test");
assert_eq!(violation_with_severity.description(), "Test violation");
assert_eq!(violation_with_severity.severity(), Some(2.0));
}
#[test]
fn test_constraint_check() {
let constraint = UniqueValuesConstraint;
let valid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 4, 5],
};
let violations = constraint.check(&valid_phenotype);
assert!(violations.is_empty());
let invalid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 2, 5],
};
let violations = constraint.check(&invalid_phenotype);
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].constraint_name(), "UniqueValues");
}
#[test]
fn test_constraint_repair() {
let constraint = UniqueValuesConstraint;
let mut invalid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 2, 5],
};
let repaired = constraint.repair(&mut invalid_phenotype);
assert!(repaired);
assert_eq!(invalid_phenotype.values, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_constraint_manager() {
let mut manager = ConstraintManager::new();
manager.add_constraint(UniqueValuesConstraint);
let valid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 4, 5],
};
assert!(manager.is_valid(&valid_phenotype));
let invalid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 2, 5],
};
assert!(!manager.is_valid(&invalid_phenotype));
let violations = manager.check_all(&invalid_phenotype);
assert_eq!(violations.len(), 1);
let mut repairable_phenotype = TestPhenotype {
values: vec![1, 2, 3, 2, 5],
};
let repaired = manager.repair_all(&mut repairable_phenotype);
assert!(repaired);
assert!(manager.is_valid(&repairable_phenotype));
}
#[test]
fn test_penalty_score() {
let constraint = UniqueValuesConstraint;
let violations = vec![
ConstraintViolation::with_severity("Test", "Violation 1", 1.0),
ConstraintViolation::with_severity("Test", "Violation 2", 2.0),
];
let score = constraint.penalty_score(&violations);
assert_eq!(score, 3.0);
let violations = vec![
ConstraintViolation::new("Test", "Violation 1"),
ConstraintViolation::new("Test", "Violation 2"),
];
let score = constraint.penalty_score(&violations);
assert_eq!(score, 2.0);
}
#[test]
fn test_constraint_manager_builder() {
let manager = ConstraintManager::<TestPhenotype>::builder()
.with_constraint(UniqueValuesConstraint)
.build();
let valid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 4, 5],
};
assert!(manager.is_valid(&valid_phenotype));
let invalid_phenotype = TestPhenotype {
values: vec![1, 2, 3, 2, 5],
};
assert!(!manager.is_valid(&invalid_phenotype));
let manager = ConstraintManager::<TestPhenotype>::builder()
.with_constraint(UniqueValuesConstraint)
.with_constraint(UniqueValuesConstraint) .build();
assert_eq!(manager.len(), 2);
assert!(!manager.is_empty());
}
#[test]
fn test_constraint_manager_builder_default() {
let builder = ConstraintManagerBuilder::<TestPhenotype>::default();
let manager = builder.build();
assert_eq!(manager.len(), 0);
assert!(manager.is_empty());
}
#[test]
fn test_penalty_adjusted_challenge() {
let phenotype = PenaltyTestPhenotype {
values: vec![1, 2, 3, 2, 5], };
let mut constraint_manager = ConstraintManager::new();
constraint_manager.add_constraint(PenaltyUniqueValuesConstraint);
let challenge = PenaltyTestChallenge::default();
let penalty_challenge = PenaltyAdjustedChallenge::new(challenge, constraint_manager, 10.0);
let base_score = phenotype.values.iter().sum::<usize>() as f64; let penalty = 10.0; let expected_score = base_score - penalty;
assert_eq!(penalty_challenge.score(&phenotype), expected_score);
}
#[test]
fn test_penalty_adjusted_challenge_accessors() {
let constraint_manager = ConstraintManager::new();
let challenge = PenaltyTestChallenge::default();
let penalty_challenge = PenaltyAdjustedChallenge::new(challenge, constraint_manager, 5.0);
assert_eq!(penalty_challenge.penalty_weight(), 5.0);
assert_eq!(penalty_challenge.constraint_manager().len(), 0);
assert_eq!(penalty_challenge.inner().target, 0);
}
#[test]
fn test_penalty_adjusted_challenge_no_violations() {
let phenotype = PenaltyTestPhenotype {
values: vec![1, 2, 3, 4, 5],
};
let mut constraint_manager = ConstraintManager::new();
constraint_manager.add_constraint(PenaltyUniqueValuesConstraint);
let challenge = PenaltyTestChallenge::default();
let penalty_challenge = PenaltyAdjustedChallenge::new(challenge, constraint_manager, 10.0);
let expected_score = phenotype.values.iter().sum::<usize>() as f64;
assert_eq!(penalty_challenge.score(&phenotype), expected_score);
}
#[derive(Clone, Debug)]
struct PenaltyTestPhenotype {
values: Vec<usize>,
}
impl Phenotype for PenaltyTestPhenotype {
fn crossover(&mut self, other: &Self) {
if !other.values.is_empty() && !self.values.is_empty() {
self.values[0] = other.values[0];
}
}
fn mutate(&mut self, _rng: &mut RandomNumberGenerator) {
if !self.values.is_empty() {
self.values[0] += 1;
}
}
}
#[derive(Debug)]
struct PenaltyUniqueValuesConstraint;
impl Constraint<PenaltyTestPhenotype> for PenaltyUniqueValuesConstraint {
fn check(&self, phenotype: &PenaltyTestPhenotype) -> Vec<ConstraintViolation> {
let mut seen = HashSet::new();
let mut violations = Vec::new();
for (i, &value) in phenotype.values.iter().enumerate() {
if !seen.insert(value) {
violations.push(ConstraintViolation::new(
"UniqueValues",
format!("Duplicate value {} at position {}", value, i),
));
}
}
violations
}
}
#[derive(Debug, Clone, Default)]
struct PenaltyTestChallenge {
target: usize,
}
impl Challenge<PenaltyTestPhenotype> for PenaltyTestChallenge {
fn score(&self, phenotype: &PenaltyTestPhenotype) -> f64 {
phenotype.values.iter().sum::<usize>() as f64
}
}
}