Skip to main content

csp_solver/constraint/
soft.rs

1//! Generic closure-based soft constraint.
2
3use crate::domain::Domain;
4
5use super::lambda::CheckerFn;
6use super::traits::{Constraint, SoftConstraint, VarId};
7
8/// A soft constraint backed by a closure, analogous to `LambdaConstraint`.
9///
10/// When the checker returns `false`, the constraint is "violated" and its
11/// `penalty` is added to the objective during optimization search.
12pub struct SoftLambdaConstraint<D: Domain> {
13    pub(crate) scope: Vec<VarId>,
14    pub(crate) checker: CheckerFn<D>,
15    pub(crate) penalty: f64,
16    label: String,
17}
18
19impl<D: Domain> std::fmt::Debug for SoftLambdaConstraint<D> {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        write!(
22            f,
23            "SoftLambdaConstraint({}, {:?}, penalty={})",
24            self.label, self.scope, self.penalty
25        )
26    }
27}
28
29impl<D: Domain> SoftLambdaConstraint<D> {
30    pub fn new(
31        scope: Vec<VarId>,
32        checker: impl Fn(&[Option<D::Value>]) -> bool + 'static,
33        penalty: f64,
34        label: impl Into<String>,
35    ) -> Self {
36        Self {
37            scope,
38            checker: Box::new(checker),
39            penalty,
40            label: label.into(),
41        }
42    }
43}
44
45impl<D: Domain> Constraint<D> for SoftLambdaConstraint<D> {
46    fn scope(&self) -> &[VarId] {
47        &self.scope
48    }
49
50    /// Soft constraints always "pass" — they never prune domains.
51    /// Their violation is tracked as cost, not as infeasibility.
52    fn check(&self, _assignment: &[Option<D::Value>]) -> bool {
53        true
54    }
55}
56
57impl<D: Domain> SoftConstraint<D> for SoftLambdaConstraint<D> {
58    fn penalty(&self) -> f64 {
59        self.penalty
60    }
61}
62
63impl<D: Domain> SoftLambdaConstraint<D> {
64    /// Check whether the underlying predicate is satisfied.
65    /// This is separate from `Constraint::check` (which always returns true
66    /// so that soft constraints don't prune) — use this to compute penalty costs.
67    pub fn is_satisfied(&self, assignment: &[Option<D::Value>]) -> bool {
68        (self.checker)(assignment)
69    }
70}