Skip to main content

choco_solver/solver/
criterion.rs

1use crate::{CHOCO_BACKEND, CHOCO_LIB, Model, utils::HandleT};
2
3/// Search limits used when finding solutions (e.g., node, fail, restart, backtrack).
4#[derive(Debug, Clone, Default)]
5pub struct Criterion {
6    node_limit: Option<i64>,
7    fail_limit: Option<i64>,
8    restart_limit: Option<i64>,
9    backtrack_limit: Option<i64>,
10}
11
12impl Criterion {
13    /// Creates a new empty Criterion.
14    #[must_use]
15    pub fn new() -> Self {
16        Criterion::default()
17    }
18
19    /// Sets a node limit for the criterion.
20    #[must_use]
21    pub fn with_node_limit(mut self, limit: i64) -> Self {
22        self.node_limit = Some(limit);
23        self
24    }
25
26    /// Sets a fail limit for the criterion.
27    #[must_use]
28    pub fn with_fail_limit(mut self, limit: i64) -> Self {
29        self.fail_limit = Some(limit);
30        self
31    }
32
33    /// Sets a restart limit for the criterion.
34    #[must_use]
35    pub fn with_restart_limit(mut self, limit: i64) -> Self {
36        self.restart_limit = Some(limit);
37        self
38    }
39
40    /// Sets a backtrack limit for the criterion.
41    #[must_use]
42    pub fn with_backtrack_limit(mut self, limit: i64) -> Self {
43        self.backtrack_limit = Some(limit);
44        self
45    }
46}
47
48pub(super) fn make_criterion_var_array(
49    criterions: &Criterion,
50    model: &Model,
51) -> *mut std::os::raw::c_void {
52    CHOCO_BACKEND.with(|backend| {
53        // Safety:
54        // Safe because Criterion instances are created from valid backend handles.
55        unsafe {
56            let Criterion {
57                node_limit,
58                fail_limit,
59                restart_limit,
60                backtrack_limit,
61            } = *criterions;
62
63            let mut raw_criterions = vec![];
64            if let Some(node_limit) = node_limit {
65                assert!(!node_limit.is_negative(), "Node limit must be non-negative");
66
67                raw_criterions.push(
68                    CHOCO_LIB.Java_org_chocosolver_capi_CriterionApi_node_counter(
69                        backend.thread,
70                        model.get_raw_handle(),
71                        node_limit,
72                    ),
73                );
74            };
75            if let Some(fail_limit) = fail_limit {
76                // Safety:
77                // Safe because Criterion instances are created from valid backend handles.
78                assert!(!fail_limit.is_negative(), "Fail limit must be non-negative");
79
80                raw_criterions.push(
81                    CHOCO_LIB.Java_org_chocosolver_capi_CriterionApi_fail_counter(
82                        backend.thread,
83                        model.get_raw_handle(),
84                        fail_limit,
85                    ),
86                );
87            };
88            if let Some(restart_limit) = restart_limit {
89                // Safety:
90                // Safe because Criterion instances are created from valid backend handles.
91                assert!(
92                    !restart_limit.is_negative(),
93                    "Restart limit must be non-negative"
94                );
95
96                raw_criterions.push(
97                    CHOCO_LIB.Java_org_chocosolver_capi_CriterionApi_restart_counter(
98                        backend.thread,
99                        model.get_raw_handle(),
100                        restart_limit,
101                    ),
102                );
103            };
104            if let Some(backtrack_limit) = backtrack_limit {
105                // Safety:
106                // Safe because Criterion instances are created from valid backend handles.
107                assert!(
108                    !backtrack_limit.is_negative(),
109                    "Backtrack limit must be non-negative"
110                );
111
112                raw_criterions.push(
113                    CHOCO_LIB.Java_org_chocosolver_capi_CriterionApi_backtrack_counter(
114                        backend.thread,
115                        model.get_raw_handle(),
116                        backtrack_limit,
117                    ),
118                );
119            };
120            let len_i32: i32 = raw_criterions
121                .len()
122                .try_into()
123                .expect("Criterion array length exceeds i32");
124            let criterion_array = CHOCO_LIB
125                .Java_org_chocosolver_capi_ArrayApi_criterion_create(backend.thread, len_i32);
126            for (i, criterion) in raw_criterions.iter().enumerate() {
127                #[allow(
128                    clippy::cast_possible_truncation,
129                    reason = "Length checked to fit in i32"
130                )]
131                #[allow(clippy::cast_possible_wrap, reason = "Length checked to fit in i32")]
132                CHOCO_LIB.Java_org_chocosolver_capi_ArrayApi_criterion_set(
133                    backend.thread,
134                    criterion_array,
135                    *criterion,
136                    i as i32,
137                );
138            }
139            criterion_array
140        }
141    })
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_criterion_all_methods() {
150        let criterion = Criterion::new()
151            .with_node_limit(1000)
152            .with_fail_limit(500)
153            .with_restart_limit(100)
154            .with_backtrack_limit(200);
155
156        assert_eq!(criterion.node_limit, Some(1000));
157        assert_eq!(criterion.fail_limit, Some(500));
158        assert_eq!(criterion.restart_limit, Some(100));
159        assert_eq!(criterion.backtrack_limit, Some(200));
160    }
161
162    #[test]
163    fn test_criterion_restrictive_limits() {
164        use crate::{Model, constraint::EqualityOperator};
165
166        // Create a model with 3 variables and constraints
167        let model = Model::new(Some("RestrictiveTest"));
168        let x = model.int_var_bounded(0, 10, Some("x"), None);
169        let y = model.int_var_bounded(0, 10, Some("y"), None);
170        let z = model.int_var_bounded(0, 10, Some("z"), None);
171
172        // Post constraints that require search to solve
173        assert!(x.arithm(EqualityOperator::Lt, 5i32).post().is_ok());
174        assert!(y.arithm(EqualityOperator::Gt, 3i32).post().is_ok());
175        assert!(z.arithm(EqualityOperator::Neq, 7i32).post().is_ok());
176
177        // Create a solver with very restrictive criteria
178        let solver = model.solver();
179        let criterion = Criterion::new()
180            .with_node_limit(1)
181            .with_fail_limit(0)
182            .with_restart_limit(0)
183            .with_backtrack_limit(0);
184
185        // Try to solve with restrictive limits
186        let solution = solver.find_solution(&criterion);
187
188        // The solver should not find a solution due to the restrictive limits
189        assert!(solution.is_none());
190    }
191}