#[allow(unused_imports)]
use crate::prelude::*;
use crate::{Clause, Solver, SolverResult};
use core::sync::atomic::{AtomicBool, Ordering};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct PortfolioConfig {
pub num_solvers: usize,
pub timeout: Option<Duration>,
pub enable_sharing: bool,
pub max_shared_length: usize,
pub sharing_frequency: usize,
}
impl Default for PortfolioConfig {
fn default() -> Self {
Self {
num_solvers: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4),
timeout: None,
enable_sharing: true,
max_shared_length: 8,
sharing_frequency: 1000,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolverVariant {
Default,
AggressiveRestart,
ConservativeRestart,
PhaseSaving,
Random,
Luby,
}
impl SolverVariant {
pub fn configure(&self, _solver: &mut Solver) {
}
}
#[derive(Debug, Clone)]
pub struct PortfolioResult {
pub result: SolverResult,
pub solver_id: usize,
pub variant: SolverVariant,
pub elapsed: Duration,
pub total_conflicts: u64,
}
#[derive(Debug, Clone, Default)]
pub struct PortfolioStats {
pub runs: u64,
pub clauses_shared: u64,
pub total_solver_time_ms: u64,
pub wall_clock_time_ms: u64,
}
pub struct PortfolioSolver {
config: PortfolioConfig,
stats: PortfolioStats,
variants: Vec<SolverVariant>,
}
impl PortfolioSolver {
pub fn new(config: PortfolioConfig) -> Self {
let variants = Self::default_variants(config.num_solvers);
Self {
config,
stats: PortfolioStats::default(),
variants,
}
}
pub fn default_config() -> Self {
Self::new(PortfolioConfig::default())
}
fn default_variants(num_solvers: usize) -> Vec<SolverVariant> {
let base_variants = [
SolverVariant::Default,
SolverVariant::AggressiveRestart,
SolverVariant::ConservativeRestart,
SolverVariant::PhaseSaving,
SolverVariant::Random,
SolverVariant::Luby,
];
let mut variants = Vec::with_capacity(num_solvers);
for i in 0..num_solvers {
variants.push(base_variants[i % base_variants.len()]);
}
variants
}
pub fn solve(&mut self, clauses: &[Clause]) -> PortfolioResult {
self.stats.runs += 1;
let start = Instant::now();
let found = Arc::new(AtomicBool::new(false));
let result: Arc<Mutex<Option<PortfolioResult>>> = Arc::new(Mutex::new(None));
let variants = self.variants.clone();
let found_clone = Arc::clone(&found);
let result_clone: Arc<Mutex<Option<PortfolioResult>>> = Arc::clone(&result);
let _max_shared_length = self.config.max_shared_length;
let _solver_results: Vec<_> = (0..self.config.num_solvers)
.into_par_iter()
.map(|solver_id| {
if found_clone.load(Ordering::Relaxed) {
return None;
}
let mut solver = Solver::new();
let variant: SolverVariant = variants[solver_id];
variant.configure(&mut solver);
for clause in clauses {
solver.add_clause(clause.lits.iter().copied());
}
let solve_start = Instant::now();
let sat_result = solver.solve();
let solve_time = solve_start.elapsed();
if !found_clone.swap(true, Ordering::Relaxed) {
let mut result_lock = result_clone.lock().expect("mutex poisoned");
*result_lock = Some(PortfolioResult {
result: sat_result,
solver_id,
variant: variants[solver_id],
elapsed: solve_time,
total_conflicts: solver.stats().conflicts,
});
}
Some(())
})
.collect();
let elapsed = start.elapsed();
self.stats.wall_clock_time_ms += elapsed.as_millis() as u64;
result
.lock()
.expect("mutex poisoned")
.take()
.unwrap_or(PortfolioResult {
result: SolverResult::Unknown,
solver_id: 0,
variant: SolverVariant::Default,
elapsed,
total_conflicts: 0,
})
}
pub fn stats(&self) -> &PortfolioStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = PortfolioStats::default();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_portfolio_config_default() {
let config = PortfolioConfig::default();
assert!(config.num_solvers > 0);
assert!(config.enable_sharing);
}
#[test]
fn test_solver_variants() {
let variants = PortfolioSolver::default_variants(4);
assert_eq!(variants.len(), 4);
assert_eq!(variants[0], SolverVariant::Default);
}
#[test]
fn test_portfolio_solver_creation() {
let solver = PortfolioSolver::default_config();
assert_eq!(solver.stats().runs, 0);
}
}