#[allow(unused_imports)]
use crate::prelude::*;
use oxiz_core::TermId;
#[derive(Debug, Clone)]
pub struct Assignment {
pub term: TermId,
pub value: TermId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MinimizationStrategy {
NumericValue,
AssignmentCount,
BooleanTrue,
Custom,
}
#[derive(Debug, Clone)]
pub struct MinimizerConfig {
pub strategy: MinimizationStrategy,
pub minimize_values: bool,
pub remove_unconstrained: bool,
pub max_iterations: usize,
}
impl Default for MinimizerConfig {
fn default() -> Self {
Self {
strategy: MinimizationStrategy::NumericValue,
minimize_values: true,
remove_unconstrained: true,
max_iterations: 100,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MinimizerStats {
pub assignments_removed: u64,
pub values_minimized: u64,
pub iterations: u64,
}
#[derive(Debug)]
pub struct ModelMinimizer {
assignments: Vec<Assignment>,
essential: FxHashSet<TermId>,
config: MinimizerConfig,
stats: MinimizerStats,
}
impl ModelMinimizer {
pub fn new(config: MinimizerConfig) -> Self {
Self {
assignments: Vec::new(),
essential: FxHashSet::default(),
config,
stats: MinimizerStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(MinimizerConfig::default())
}
pub fn add_assignment(&mut self, term: TermId, value: TermId) {
self.assignments.push(Assignment { term, value });
}
pub fn mark_essential(&mut self, term: TermId) {
self.essential.insert(term);
}
pub fn minimize(&mut self) -> Vec<Assignment> {
let mut minimized = self.assignments.clone();
for _ in 0..self.config.max_iterations {
self.stats.iterations += 1;
let mut changed = false;
if self.config.remove_unconstrained && self.try_remove_assignments(&mut minimized) {
changed = true;
}
if self.config.minimize_values && self.try_minimize_values(&mut minimized) {
changed = true;
}
if !changed {
break;
}
}
minimized
}
fn try_remove_assignments(&mut self, assignments: &mut Vec<Assignment>) -> bool {
let mut removed = false;
assignments.retain(|assignment| {
if self.essential.contains(&assignment.term) {
true } else {
removed = true;
self.stats.assignments_removed += 1;
false
}
});
removed
}
fn try_minimize_values(&mut self, _assignments: &mut Vec<Assignment>) -> bool {
self.stats.values_minimized += 1;
false
}
pub fn assignments(&self) -> &[Assignment] {
&self.assignments
}
pub fn stats(&self) -> &MinimizerStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = MinimizerStats::default();
}
}
impl Default for ModelMinimizer {
fn default() -> Self {
Self::default_config()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimizer_creation() {
let minimizer = ModelMinimizer::default_config();
assert_eq!(minimizer.stats().assignments_removed, 0);
}
#[test]
fn test_add_assignment() {
let mut minimizer = ModelMinimizer::default_config();
let term = TermId::new(0);
let value = TermId::new(1);
minimizer.add_assignment(term, value);
assert_eq!(minimizer.assignments().len(), 1);
}
#[test]
fn test_mark_essential() {
let mut minimizer = ModelMinimizer::default_config();
let term = TermId::new(0);
minimizer.mark_essential(term);
assert!(minimizer.essential.contains(&term));
}
#[test]
fn test_minimize_removes_unconstrained() {
let mut minimizer = ModelMinimizer::default_config();
let term1 = TermId::new(0);
let term2 = TermId::new(1);
let value = TermId::new(10);
minimizer.add_assignment(term1, value);
minimizer.add_assignment(term2, value);
minimizer.mark_essential(term1);
let minimized = minimizer.minimize();
assert_eq!(minimized.len(), 1);
assert_eq!(minimized[0].term, term1);
assert_eq!(minimizer.stats().assignments_removed, 1);
}
#[test]
fn test_stats() {
let mut minimizer = ModelMinimizer::default_config();
minimizer.stats.iterations = 5;
assert_eq!(minimizer.stats().iterations, 5);
minimizer.reset_stats();
assert_eq!(minimizer.stats().iterations, 0);
}
}