use crate::ir::{ConstraintIR, Expr, VarId};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ConstraintContext {
pub assignments: HashMap<VarId, ConstraintValue>,
pub tolerance: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ConstraintValue {
Bool(bool),
Integer(i64),
Real(f64),
String(String),
BitVector { value: u64, width: u32 },
Array(Vec<ConstraintValue>),
}
#[derive(Debug, Clone)]
pub struct ConstraintCheckResult {
pub satisfied: bool,
pub confidence: f32,
pub info: Option<String>,
}
pub trait ConstraintChecker: Send + Sync {
fn check_constraints(
&self,
constraints: &ConstraintIR,
context: &ConstraintContext,
) -> Result<ConstraintCheckResult>;
fn evaluate_expression(
&self,
expr: &Expr,
context: &ConstraintContext,
) -> Result<ConstraintValue>;
fn find_satisfying_assignment(
&self,
constraints: &ConstraintIR,
) -> Result<Option<HashMap<VarId, ConstraintValue>>>;
}
pub struct BasicConstraintChecker {
tolerance: f64,
}
impl BasicConstraintChecker {
pub fn new(tolerance: f64) -> Self {
Self { tolerance }
}
}
impl ConstraintChecker for BasicConstraintChecker {
fn check_constraints(
&self,
constraints: &ConstraintIR,
context: &ConstraintContext,
) -> Result<ConstraintCheckResult> {
let mut all_satisfied = true;
let mut confidence_sum = 0.0;
let mut constraint_count = 0;
for constraint in &constraints.constraints {
let satisfied = self.check_single_constraint(constraint, context)?;
if !satisfied {
all_satisfied = false;
}
confidence_sum += if satisfied { 1.0 } else { 0.0 };
constraint_count += 1;
}
let confidence = if constraint_count > 0 {
confidence_sum / constraint_count as f32
} else {
1.0
};
Ok(ConstraintCheckResult {
satisfied: all_satisfied,
confidence,
info: None,
})
}
fn evaluate_expression(
&self,
expr: &Expr,
context: &ConstraintContext,
) -> Result<ConstraintValue> {
match expr {
Expr::Var(var_id) => context
.assignments
.get(var_id)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Variable {:?} not found in context", var_id)),
Expr::Const(const_val) => Ok(match const_val {
crate::ir::ConstValue::Bool(b) => ConstraintValue::Bool(*b),
crate::ir::ConstValue::Integer(i) => ConstraintValue::Integer(*i),
crate::ir::ConstValue::Real(r) => ConstraintValue::Real(*r),
crate::ir::ConstValue::String(s) => ConstraintValue::String(s.clone()),
crate::ir::ConstValue::BitVector { value, width } => ConstraintValue::BitVector {
value: *value,
width: *width,
},
}),
Expr::Binary { op, lhs, rhs } => {
let lhs_val = self.evaluate_expression(lhs, context)?;
let rhs_val = self.evaluate_expression(rhs, context)?;
self.evaluate_binary_op(op, &lhs_val, &rhs_val)
}
Expr::Unary { op, operand } => {
let operand_val = self.evaluate_expression(operand, context)?;
self.evaluate_unary_op(op, &operand_val)
}
Expr::Call {
function: _,
args: _,
} => {
Err(anyhow::anyhow!("Function calls not yet implemented"))
}
}
}
fn find_satisfying_assignment(
&self,
_constraints: &ConstraintIR,
) -> Result<Option<HashMap<VarId, ConstraintValue>>> {
Ok(None)
}
}
impl BasicConstraintChecker {
fn check_single_constraint(
&self,
constraint: &crate::ir::Constraint,
context: &ConstraintContext,
) -> Result<bool> {
match constraint {
crate::ir::Constraint::Equal { lhs, rhs } => {
let lhs_val = self.evaluate_expression(lhs, context)?;
let rhs_val = self.evaluate_expression(rhs, context)?;
Ok(self.values_equal(&lhs_val, &rhs_val))
}
crate::ir::Constraint::Less { lhs, rhs } => {
let lhs_val = self.evaluate_expression(lhs, context)?;
let rhs_val = self.evaluate_expression(rhs, context)?;
Ok(self.value_less_than(&lhs_val, &rhs_val)?)
}
crate::ir::Constraint::LessEqual { lhs, rhs } => {
let lhs_val = self.evaluate_expression(lhs, context)?;
let rhs_val = self.evaluate_expression(rhs, context)?;
Ok(self.value_less_than(&lhs_val, &rhs_val)?
|| self.values_equal(&lhs_val, &rhs_val))
}
crate::ir::Constraint::And { constraints } => {
for sub_constraint in constraints {
if !self.check_single_constraint(sub_constraint, context)? {
return Ok(false);
}
}
Ok(true)
}
crate::ir::Constraint::Or { constraints } => {
for sub_constraint in constraints {
if self.check_single_constraint(sub_constraint, context)? {
return Ok(true);
}
}
Ok(false)
}
crate::ir::Constraint::Not { constraint } => {
Ok(!self.check_single_constraint(constraint, context)?)
}
crate::ir::Constraint::Implies {
antecedent,
consequent,
} => {
let antecedent_satisfied = self.check_single_constraint(antecedent, context)?;
if !antecedent_satisfied {
Ok(true) } else {
self.check_single_constraint(consequent, context)
}
}
crate::ir::Constraint::Custom { name: _, params: _ } => {
Ok(true) }
}
}
fn evaluate_binary_op(
&self,
op: &crate::ir::BinaryOp,
lhs: &ConstraintValue,
rhs: &ConstraintValue,
) -> Result<ConstraintValue> {
use crate::ir::BinaryOp;
match (lhs, rhs) {
(ConstraintValue::Integer(a), ConstraintValue::Integer(b)) => Ok(match op {
BinaryOp::Add => ConstraintValue::Integer(a + b),
BinaryOp::Sub => ConstraintValue::Integer(a - b),
BinaryOp::Mul => ConstraintValue::Integer(a * b),
BinaryOp::Div => ConstraintValue::Integer(a / b),
BinaryOp::Mod => ConstraintValue::Integer(a % b),
BinaryOp::Equal => ConstraintValue::Bool(a == b),
BinaryOp::NotEqual => ConstraintValue::Bool(a != b),
BinaryOp::Less => ConstraintValue::Bool(a < b),
BinaryOp::LessEqual => ConstraintValue::Bool(a <= b),
BinaryOp::Greater => ConstraintValue::Bool(a > b),
BinaryOp::GreaterEqual => ConstraintValue::Bool(a >= b),
_ => {
return Err(anyhow::anyhow!(
"Unsupported operation {:?} for integers",
op
));
}
}),
(ConstraintValue::Real(a), ConstraintValue::Real(b)) => Ok(match op {
BinaryOp::Add => ConstraintValue::Real(a + b),
BinaryOp::Sub => ConstraintValue::Real(a - b),
BinaryOp::Mul => ConstraintValue::Real(a * b),
BinaryOp::Div => ConstraintValue::Real(a / b),
BinaryOp::Equal => ConstraintValue::Bool((a - b).abs() < self.tolerance),
BinaryOp::NotEqual => ConstraintValue::Bool((a - b).abs() >= self.tolerance),
BinaryOp::Less => ConstraintValue::Bool(a < b),
BinaryOp::LessEqual => ConstraintValue::Bool(a <= b),
BinaryOp::Greater => ConstraintValue::Bool(a > b),
BinaryOp::GreaterEqual => ConstraintValue::Bool(a >= b),
_ => return Err(anyhow::anyhow!("Unsupported operation {:?} for reals", op)),
}),
_ => Err(anyhow::anyhow!("Type mismatch in binary operation")),
}
}
fn evaluate_unary_op(
&self,
op: &crate::ir::UnaryOp,
operand: &ConstraintValue,
) -> Result<ConstraintValue> {
use crate::ir::UnaryOp;
match operand {
ConstraintValue::Bool(b) => Ok(match op {
UnaryOp::Not => ConstraintValue::Bool(!b),
_ => {
return Err(anyhow::anyhow!(
"Unsupported unary operation {:?} for bool",
op
));
}
}),
ConstraintValue::Integer(i) => Ok(match op {
UnaryOp::Negate => ConstraintValue::Integer(-i),
UnaryOp::Abs => ConstraintValue::Integer(i.abs()),
_ => {
return Err(anyhow::anyhow!(
"Unsupported unary operation {:?} for integer",
op
));
}
}),
ConstraintValue::Real(r) => Ok(match op {
UnaryOp::Negate => ConstraintValue::Real(-r),
UnaryOp::Abs => ConstraintValue::Real(r.abs()),
_ => {
return Err(anyhow::anyhow!(
"Unsupported unary operation {:?} for real",
op
));
}
}),
_ => Err(anyhow::anyhow!(
"Unsupported operand type for unary operation"
)),
}
}
fn values_equal(&self, lhs: &ConstraintValue, rhs: &ConstraintValue) -> bool {
match (lhs, rhs) {
(ConstraintValue::Bool(a), ConstraintValue::Bool(b)) => a == b,
(ConstraintValue::Integer(a), ConstraintValue::Integer(b)) => a == b,
(ConstraintValue::Real(a), ConstraintValue::Real(b)) => (a - b).abs() < self.tolerance,
(ConstraintValue::String(a), ConstraintValue::String(b)) => a == b,
_ => false,
}
}
fn value_less_than(&self, lhs: &ConstraintValue, rhs: &ConstraintValue) -> Result<bool> {
match (lhs, rhs) {
(ConstraintValue::Integer(a), ConstraintValue::Integer(b)) => Ok(a < b),
(ConstraintValue::Real(a), ConstraintValue::Real(b)) => Ok(a < b),
_ => Err(anyhow::anyhow!("Cannot compare these value types")),
}
}
}
impl Default for BasicConstraintChecker {
fn default() -> Self {
Self::new(1e-9)
}
}