use crate::chc::ChcSystem;
use crate::pdr::{Spacer, SpacerConfig, SpacerError, SpacerResult};
use oxiz_core::TermManager;
use std::time::{Duration, Instant};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PortfolioError {
#[error("all strategies failed")]
AllFailed,
#[error("timeout after {0:?}")]
Timeout(Duration),
#[error("spacer error: {0}")]
Spacer(#[from] SpacerError),
}
#[derive(Debug, Clone)]
pub struct Strategy {
pub name: String,
pub config: SpacerConfig,
pub time_budget: Option<Duration>,
}
impl Strategy {
pub fn new(name: impl Into<String>, config: SpacerConfig) -> Self {
Self {
name: name.into(),
config,
time_budget: None,
}
}
pub fn with_time_budget(mut self, budget: Duration) -> Self {
self.time_budget = Some(budget);
self
}
pub fn aggressive() -> Self {
Self::new(
"aggressive",
SpacerConfig {
max_level: 50,
max_pobs: 1000,
max_smt_queries: 10000,
use_inductive_gen: false,
use_cegar: false,
verbosity: 0,
},
)
.with_time_budget(Duration::from_secs(5))
}
pub fn conservative() -> Self {
Self::new(
"conservative",
SpacerConfig {
max_level: 1000,
max_pobs: 100000,
max_smt_queries: 10_000_000,
use_inductive_gen: true,
use_cegar: true,
verbosity: 0,
},
)
.with_time_budget(Duration::from_secs(60))
}
pub fn balanced() -> Self {
Self::new(
"balanced",
SpacerConfig {
max_level: 200,
max_pobs: 10000,
max_smt_queries: 100000,
use_inductive_gen: true,
use_cegar: false,
verbosity: 0,
},
)
.with_time_budget(Duration::from_secs(20))
}
pub fn bmc_like() -> Self {
Self::new(
"bmc",
SpacerConfig {
max_level: 100,
max_pobs: 5000,
max_smt_queries: 50000,
use_inductive_gen: false,
use_cegar: false,
verbosity: 0,
},
)
.with_time_budget(Duration::from_secs(10))
}
}
#[derive(Debug, Clone)]
pub struct PortfolioResult {
pub result: SpacerResult,
pub strategy_name: String,
pub time_taken: Duration,
pub strategies_tried: usize,
}
pub struct PortfolioSolver {
strategies: Vec<Strategy>,
global_timeout: Option<Duration>,
}
impl PortfolioSolver {
pub fn new() -> Self {
Self {
strategies: vec![
Strategy::aggressive(),
Strategy::balanced(),
Strategy::conservative(),
],
global_timeout: None,
}
}
pub fn empty() -> Self {
Self {
strategies: Vec::new(),
global_timeout: None,
}
}
pub fn add_strategy(&mut self, strategy: Strategy) {
self.strategies.push(strategy);
}
pub fn with_global_timeout(mut self, timeout: Duration) -> Self {
self.global_timeout = Some(timeout);
self
}
pub fn solve_sequential(
&self,
terms: &mut TermManager,
system: &ChcSystem,
) -> Result<PortfolioResult, PortfolioError> {
let global_start = Instant::now();
let mut strategies_tried = 0;
for strategy in &self.strategies {
if let Some(global_timeout) = self.global_timeout
&& global_start.elapsed() >= global_timeout
{
return Err(PortfolioError::Timeout(global_timeout));
}
strategies_tried += 1;
let strategy_start = Instant::now();
tracing::info!("Trying strategy '{}' ({})", strategy.name, strategies_tried);
let mut spacer = Spacer::with_config(terms, system, strategy.config.clone());
match spacer.solve() {
Ok(SpacerResult::Safe) => {
let time_taken = strategy_start.elapsed();
tracing::info!("Strategy '{}' succeeded in {:?}", strategy.name, time_taken);
return Ok(PortfolioResult {
result: SpacerResult::Safe,
strategy_name: strategy.name.clone(),
time_taken,
strategies_tried,
});
}
Ok(SpacerResult::Unsafe) => {
let time_taken = strategy_start.elapsed();
tracing::info!(
"Strategy '{}' found counterexample in {:?}",
strategy.name,
time_taken
);
return Ok(PortfolioResult {
result: SpacerResult::Unsafe,
strategy_name: strategy.name.clone(),
time_taken,
strategies_tried,
});
}
Ok(SpacerResult::Unknown) => {
tracing::debug!(
"Strategy '{}' returned unknown after {:?}",
strategy.name,
strategy_start.elapsed()
);
}
Err(e) => {
tracing::warn!("Strategy '{}' failed: {}", strategy.name, e);
}
}
if let Some(budget) = strategy.time_budget
&& strategy_start.elapsed() >= budget
{
tracing::debug!(
"Strategy '{}' exceeded time budget {:?}",
strategy.name,
budget
);
}
}
Err(PortfolioError::AllFailed)
}
pub fn strategy_count(&self) -> usize {
self.strategies.len()
}
pub fn strategies(&self) -> &[Strategy] {
&self.strategies
}
}
impl Default for PortfolioSolver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chc::{ChcSystem, PredicateApp};
#[test]
fn test_strategy_creation() {
let aggressive = Strategy::aggressive();
assert_eq!(aggressive.name, "aggressive");
assert!(aggressive.time_budget.is_some());
let conservative = Strategy::conservative();
assert_eq!(conservative.name, "conservative");
assert!(conservative.config.use_inductive_gen);
}
#[test]
fn test_portfolio_creation() {
let portfolio = PortfolioSolver::new();
assert_eq!(portfolio.strategy_count(), 3);
let empty = PortfolioSolver::empty();
assert_eq!(empty.strategy_count(), 0);
}
#[test]
fn test_portfolio_add_strategy() {
let mut portfolio = PortfolioSolver::empty();
portfolio.add_strategy(Strategy::balanced());
assert_eq!(portfolio.strategy_count(), 1);
}
#[test]
#[ignore = "Requires complete arithmetic theory integration"]
fn test_portfolio_simple_safe() {
let mut terms = TermManager::new();
let mut system = ChcSystem::new();
let inv = system.declare_predicate("Inv", [terms.sorts.int_sort]);
let x = terms.mk_var("x", terms.sorts.int_sort);
let zero = terms.mk_int(0);
let init_constraint = terms.mk_eq(x, zero);
system.add_init_rule(
[("x".to_string(), terms.sorts.int_sort)],
init_constraint,
inv,
[x],
);
let neg_constraint = terms.mk_lt(x, zero);
system.add_query(
[("x".to_string(), terms.sorts.int_sort)],
[PredicateApp::new(inv, [x])],
neg_constraint,
);
let portfolio = PortfolioSolver::new();
let result = portfolio.solve_sequential(&mut terms, &system);
assert!(result.is_ok());
let portfolio_result = result.expect("test operation should succeed");
assert_eq!(portfolio_result.result, SpacerResult::Safe);
assert!(portfolio_result.strategies_tried > 0);
}
#[test]
fn test_portfolio_with_timeout() {
let portfolio = PortfolioSolver::new().with_global_timeout(Duration::from_secs(30));
assert!(portfolio.global_timeout.is_some());
assert_eq!(
portfolio.global_timeout.expect("timing operation failed"),
Duration::from_secs(30)
);
}
}