use crate::literal::Lit;
#[allow(unused_imports)]
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct Cube {
pub literals: Vec<Lit>,
pub difficulty: f64,
pub depth: usize,
}
impl Cube {
pub fn new(literals: Vec<Lit>) -> Self {
Self {
depth: literals.len(),
literals,
difficulty: 0.0,
}
}
pub fn with_difficulty(literals: Vec<Lit>, difficulty: f64) -> Self {
Self {
depth: literals.len(),
literals,
difficulty,
}
}
pub fn len(&self) -> usize {
self.literals.len()
}
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
pub fn extend(&self, lit: Lit) -> Self {
let mut new_lits = self.literals.clone();
new_lits.push(lit);
Self {
literals: new_lits,
difficulty: 0.0,
depth: self.depth + 1,
}
}
pub fn is_consistent(&self) -> bool {
let mut seen = HashSet::new();
for &lit in &self.literals {
if seen.contains(&lit.negate()) {
return false;
}
seen.insert(lit);
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CubeSplittingStrategy {
Activity,
Lookahead,
MostConstrained,
Balanced,
}
#[derive(Debug, Clone)]
pub struct CubeConfig {
pub max_depth: usize,
pub target_cubes: usize,
pub min_cube_size: usize,
pub max_cube_size: usize,
pub strategy: CubeSplittingStrategy,
pub adaptive_depth: bool,
}
impl Default for CubeConfig {
fn default() -> Self {
Self {
max_depth: 10,
target_cubes: 100,
min_cube_size: 3,
max_cube_size: 15,
strategy: CubeSplittingStrategy::Lookahead,
adaptive_depth: true,
}
}
}
pub struct CubeGenerator {
config: CubeConfig,
cubes: Vec<Cube>,
num_vars: usize,
}
impl CubeGenerator {
pub fn new(num_vars: usize, config: CubeConfig) -> Self {
Self {
config,
cubes: Vec::new(),
num_vars,
}
}
pub fn generate(&mut self, variable_scores: &[f64]) -> Vec<Cube> {
self.cubes.clear();
let initial = Cube::new(Vec::new());
self.split_recursive(initial, variable_scores);
if self.config.adaptive_depth && self.cubes.len() < self.config.target_cubes / 2 {
self.config.max_depth = (self.config.max_depth * 3) / 2;
self.cubes.clear();
let initial = Cube::new(Vec::new());
self.split_recursive(initial, variable_scores);
}
core::mem::take(&mut self.cubes)
}
fn split_recursive(&mut self, cube: Cube, variable_scores: &[f64]) {
if cube.depth >= self.config.max_depth || self.cubes.len() >= self.config.target_cubes {
if cube.len() >= self.config.min_cube_size && cube.is_consistent() {
self.cubes.push(cube);
}
return;
}
if let Some(var) = self.select_splitting_variable(&cube, variable_scores) {
use crate::literal::Var;
let v = Var::new(var as u32);
let pos_cube = cube.extend(Lit::pos(v));
let neg_cube = cube.extend(Lit::neg(v));
self.split_recursive(pos_cube, variable_scores);
self.split_recursive(neg_cube, variable_scores);
} else {
if cube.len() >= self.config.min_cube_size && cube.is_consistent() {
self.cubes.push(cube);
}
}
}
fn select_splitting_variable(&self, cube: &Cube, variable_scores: &[f64]) -> Option<usize> {
let mut assigned = HashSet::new();
for lit in &cube.literals {
assigned.insert(lit.var().index());
}
let mut best_var = None;
let mut best_score = f64::NEG_INFINITY;
for var in 0..self.num_vars {
if assigned.contains(&var) {
continue;
}
let score = if var < variable_scores.len() {
variable_scores[var]
} else {
0.0
};
if score > best_score {
best_score = score;
best_var = Some(var);
}
}
best_var
}
#[allow(dead_code)]
fn estimate_difficulty(&self, cube: &Cube) -> f64 {
let size_factor = 1.0 / (cube.len() as f64 + 1.0);
let depth_factor = cube.depth as f64;
size_factor * depth_factor
}
pub fn config(&self) -> &CubeConfig {
&self.config
}
pub fn num_cubes(&self) -> usize {
self.cubes.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct CubeStats {
pub total_cubes: usize,
pub avg_cube_size: f64,
pub min_cube_size: usize,
pub max_cube_size: usize,
pub max_depth: usize,
pub avg_difficulty: f64,
}
impl CubeStats {
pub fn from_cubes(cubes: &[Cube]) -> Self {
if cubes.is_empty() {
return Self::default();
}
let total = cubes.len();
let sizes: Vec<usize> = cubes.iter().map(|c| c.len()).collect();
let avg_size = sizes.iter().sum::<usize>() as f64 / total as f64;
let min_size = sizes.iter().copied().min().unwrap_or(0);
let max_size = sizes.iter().copied().max().unwrap_or(0);
let max_depth = cubes.iter().map(|c| c.depth).max().unwrap_or(0);
let avg_diff = cubes.iter().map(|c| c.difficulty).sum::<f64>() / total as f64;
Self {
total_cubes: total,
avg_cube_size: avg_size,
min_cube_size: min_size,
max_cube_size: max_size,
max_depth,
avg_difficulty: avg_diff,
}
}
pub fn display(&self) -> String {
format!(
"Cube Generation Statistics:\n\
- Total Cubes: {}\n\
- Avg Size: {:.2}\n\
- Size Range: [{}, {}]\n\
- Max Depth: {}\n\
- Avg Difficulty: {:.4}",
self.total_cubes,
self.avg_cube_size,
self.min_cube_size,
self.max_cube_size,
self.max_depth,
self.avg_difficulty
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CubeResult {
Unsat,
Sat,
Unknown,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::literal::Var;
#[test]
fn test_cube_creation() {
let lit1 = Lit::pos(Var::new(0));
let lit2 = Lit::neg(Var::new(1));
let cube = Cube::new(vec![lit1, lit2]);
assert_eq!(cube.len(), 2);
assert!(!cube.is_empty());
assert_eq!(cube.depth, 2);
}
#[test]
fn test_cube_extend() {
let lit1 = Lit::pos(Var::new(0));
let cube1 = Cube::new(vec![lit1]);
let lit2 = Lit::neg(Var::new(1));
let cube2 = cube1.extend(lit2);
assert_eq!(cube1.len(), 1);
assert_eq!(cube2.len(), 2);
assert_eq!(cube2.depth, 2);
}
#[test]
fn test_cube_consistency() {
let lit1 = Lit::pos(Var::new(0));
let lit2 = Lit::neg(Var::new(1));
let cube = Cube::new(vec![lit1, lit2]);
assert!(cube.is_consistent());
}
#[test]
fn test_cube_inconsistency() {
let lit1 = Lit::pos(Var::new(0));
let lit2 = Lit::neg(Var::new(0));
let cube = Cube::new(vec![lit1, lit2]);
assert!(!cube.is_consistent());
}
#[test]
fn test_empty_cube() {
let cube = Cube::new(Vec::new());
assert_eq!(cube.len(), 0);
assert!(cube.is_empty());
assert!(cube.is_consistent());
}
#[test]
fn test_cube_config_default() {
let config = CubeConfig::default();
assert_eq!(config.max_depth, 10);
assert_eq!(config.target_cubes, 100);
assert!(config.adaptive_depth);
}
#[test]
fn test_cube_generator_creation() {
let config = CubeConfig::default();
let generator = CubeGenerator::new(10, config);
assert_eq!(generator.num_vars, 10);
assert_eq!(generator.num_cubes(), 0);
}
#[test]
fn test_cube_generation() {
let config = CubeConfig {
max_depth: 3,
target_cubes: 10,
min_cube_size: 1,
max_cube_size: 5,
strategy: CubeSplittingStrategy::Activity,
adaptive_depth: false,
};
let mut generator = CubeGenerator::new(5, config);
let scores = vec![1.0, 0.9, 0.8, 0.7, 0.6];
let cubes = generator.generate(&scores);
assert!(!cubes.is_empty());
assert!(cubes.len() <= 10);
for cube in &cubes {
assert!(cube.is_consistent());
}
}
#[test]
fn test_cube_stats() {
let lit1 = Lit::pos(Var::new(0));
let lit2 = Lit::neg(Var::new(1));
let lit3 = Lit::pos(Var::new(2));
let cubes = vec![
Cube::new(vec![lit1]),
Cube::new(vec![lit1, lit2]),
Cube::new(vec![lit1, lit2, lit3]),
];
let stats = CubeStats::from_cubes(&cubes);
assert_eq!(stats.total_cubes, 3);
assert_eq!(stats.min_cube_size, 1);
assert_eq!(stats.max_cube_size, 3);
assert_eq!(stats.avg_cube_size, 2.0);
}
#[test]
fn test_empty_cube_stats() {
let stats = CubeStats::from_cubes(&[]);
assert_eq!(stats.total_cubes, 0);
assert_eq!(stats.avg_cube_size, 0.0);
}
#[test]
fn test_cube_splitting_strategies() {
let strategies = vec![
CubeSplittingStrategy::Activity,
CubeSplittingStrategy::Lookahead,
CubeSplittingStrategy::MostConstrained,
CubeSplittingStrategy::Balanced,
];
for strategy in strategies {
let config = CubeConfig {
strategy,
max_depth: 2,
target_cubes: 4,
min_cube_size: 1,
max_cube_size: 5,
adaptive_depth: false,
};
let mut generator = CubeGenerator::new(3, config);
let scores = vec![1.0, 0.5, 0.2];
let cubes = generator.generate(&scores);
assert!(!cubes.is_empty());
}
}
#[test]
fn test_adaptive_depth() {
let config = CubeConfig {
max_depth: 2,
target_cubes: 100,
min_cube_size: 1,
max_cube_size: 10,
strategy: CubeSplittingStrategy::Activity,
adaptive_depth: true,
};
let mut generator = CubeGenerator::new(3, config);
let scores = vec![1.0, 0.5, 0.2];
let _cubes = generator.generate(&scores);
assert!(generator.config().max_depth >= 2);
}
}