Skip to main content

csp_solver/constraint/
soft.rs

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