#[cfg(feature = "z3-solver")]
use z3::{Config, Context, Optimize, SatResult, Solver, ast};
use crate::ir::{BinaryOp, Constraint, ConstraintIR, Expr, VarId};
use crate::surrogate::{SolutionResult, SolutionValue};
use anyhow::{Result, anyhow};
use std::collections::HashMap;
pub struct Z3Backend {
#[cfg(feature = "z3-solver")]
context: Context,
timeout_ms: u32,
}
impl Z3Backend {
pub fn new(timeout_ms: u32) -> Result<Self> {
#[cfg(feature = "z3-solver")]
{
let mut cfg = Config::new();
cfg.set_timeout_msec(timeout_ms as u64);
let context = Context::new(&cfg);
Ok(Self {
context,
timeout_ms,
})
}
#[cfg(not(feature = "z3-solver"))]
{
Err(anyhow!(
"Z3 solver not available. Enable the 'z3-solver' feature to use Z3."
))
}
}
#[cfg(feature = "z3-solver")]
pub fn solve(&self, constraints: &ConstraintIR) -> Result<SolutionResult> {
let solver = Solver::new(&self.context);
let mut var_map: HashMap<VarId, ast::Int> = HashMap::new();
for var_id in constraints.get_variable_ids() {
let var_name = format!("v_{}", var_id.0);
let z3_var = ast::Int::new_const(&self.context, var_name);
var_map.insert(var_id.clone(), z3_var);
}
for constraint in constraints.get_constraints() {
let z3_constraint = self.translate_constraint(constraint, &var_map)?;
solver.assert(&z3_constraint);
}
match solver.check() {
SatResult::Sat => {
let model = solver
.get_model()
.ok_or_else(|| anyhow!("No model available"))?;
let mut assignment = HashMap::new();
for (var_id, z3_var) in &var_map {
if let Some(value) = model.eval(z3_var, true) {
let value_str = value.to_string();
if let Ok(int_val) = value_str.parse::<i64>() {
assignment.insert(var_id.to_string(), SolutionValue::Integer(int_val));
} else if let Ok(float_val) = value_str.parse::<f64>() {
assignment.insert(var_id.to_string(), SolutionValue::Real(float_val));
} else {
assignment.insert(var_id.to_string(), SolutionValue::Integer(0));
}
}
}
Ok(SolutionResult {
satisfiable: true,
assignment,
confidence: 1.0, estimated_time_ms: 0.0, method: "Z3-SMT".to_string(),
winning_strategy: Some("SMT".to_string()),
winning_backend: Some("Z3".to_string()),
winning_branch_id: None,
variable_ranges: None,
})
}
SatResult::Unsat => Err(anyhow!("Constraints are unsatisfiable")),
SatResult::Unknown => Err(anyhow!("Z3 could not determine satisfiability")),
}
}
#[cfg(feature = "z3-solver")]
fn translate_constraint(
&self,
constraint: &Constraint,
var_map: &HashMap<VarId, ast::Int>,
) -> Result<ast::Bool> {
match constraint {
Constraint::LessEqual { lhs, rhs } => {
let lhs_ast = self.translate_expr(lhs, var_map)?;
let rhs_ast = self.translate_expr(rhs, var_map)?;
Ok(lhs_ast.le(&rhs_ast))
}
Constraint::Equal { lhs, rhs } => {
let lhs_ast = self.translate_expr(lhs, var_map)?;
let rhs_ast = self.translate_expr(rhs, var_map)?;
Ok(lhs_ast._eq(&rhs_ast))
}
Constraint::NotEqual { lhs, rhs } => {
let lhs_ast = self.translate_expr(lhs, var_map)?;
let rhs_ast = self.translate_expr(rhs, var_map)?;
Ok(lhs_ast._eq(&rhs_ast).not())
}
}
}
#[cfg(feature = "z3-solver")]
fn translate_expr(&self, expr: &Expr, var_map: &HashMap<VarId, ast::Int>) -> Result<ast::Int> {
match expr {
Expr::Var(var_id) => var_map
.get(var_id)
.cloned()
.ok_or_else(|| anyhow!("Variable {:?} not found in variable map", var_id)),
Expr::Const(value) => {
Ok(ast::Int::from_i64(&self.context, *value as i64))
}
Expr::Binary { op, lhs, rhs } => {
let lhs_ast = self.translate_expr(lhs, var_map)?;
let rhs_ast = self.translate_expr(rhs, var_map)?;
match op {
BinaryOp::Add => Ok(lhs_ast + rhs_ast),
BinaryOp::Sub => Ok(lhs_ast - rhs_ast),
BinaryOp::Mul => Ok(lhs_ast * rhs_ast),
BinaryOp::Div => {
Ok(lhs_ast / rhs_ast)
}
}
}
}
}
#[cfg(not(feature = "z3-solver"))]
pub fn solve(&self, _constraints: &ConstraintIR) -> Result<SolutionResult> {
Err(anyhow!(
"Z3 solver not available. Enable the 'z3-solver' feature to use Z3."
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(feature = "z3-solver")]
fn test_z3_simple_constraint() {
use crate::ir::{Constraint, ConstraintIR, Expr, VarId};
let backend = Z3Backend::new(5000).unwrap();
let x = VarId(0);
let y = VarId(1);
let mut ir = ConstraintIR::new();
ir.add_constraint(Constraint::Equal {
lhs: Expr::Var(x.clone()),
rhs: Expr::Const(3.0),
});
ir.add_constraint(Constraint::Equal {
lhs: Expr::Binary {
op: BinaryOp::Add,
lhs: Box::new(Expr::Var(x.clone())),
rhs: Box::new(Expr::Var(y.clone())),
},
rhs: Expr::Const(10.0),
});
let result = backend.solve(&ir);
assert!(result.is_ok());
let solution = result.unwrap();
assert_eq!(solution.confidence, 1.0);
assert!(solution.assignment.contains_key("v_0"));
assert!(solution.assignment.contains_key("v_1"));
}
}