Skip to main content

choco_solver/variables/intvar/
boolvar.rs

1use std::borrow::Borrow;
2use std::ops::BitAnd;
3use std::ops::BitOr;
4use std::ops::Not;
5
6use super::{Handle, HandleT};
7use crate::CHOCO_LIB;
8use crate::Sealed;
9use crate::SolverError;
10
11use crate::CHOCO_BACKEND;
12use crate::constraint::Constraint;
13use crate::model::Model;
14use crate::utils;
15use crate::utils::ModelObject;
16use crate::variables::IntVar;
17use crate::variables::Variable;
18
19/// A boolean decision variable (domain 0/1) belonging to a [`Model`].
20///
21/// `BoolVar` is a thin wrapper over an underlying [`IntVar`] whose domain
22/// is restricted to `[0, 1]` and supports boolean and logical operations.
23pub struct BoolVar<'model> {
24    int_var: IntVar<'model>,
25}
26
27impl HandleT for BoolVar<'_> {
28    fn get_raw_handle(&self) -> *mut std::os::raw::c_void {
29        self.int_var.get_raw_handle()
30    }
31}
32
33impl<'model> ModelObject<'model> for BoolVar<'model> {
34    fn get_model(&self) -> &'model Model {
35        self.int_var.get_model()
36    }
37}
38
39impl<'model> From<BoolVar<'model>> for IntVar<'model> {
40    fn from(bool_var: BoolVar<'model>) -> Self {
41        bool_var.int_var
42    }
43}
44
45impl<'model> TryFrom<IntVar<'model>> for BoolVar<'model> {
46    type Error = SolverError;
47
48    fn try_from(int_var: IntVar<'model>) -> Result<Self, Self::Error> {
49        let lb = int_var.lb();
50        let ub = int_var.ub();
51        if lb == 0 && ub == 1 {
52            Ok(BoolVar { int_var })
53        } else {
54            Err(SolverError::BoolVarConversionError)
55        }
56    }
57}
58
59impl<'model> Borrow<IntVar<'model>> for BoolVar<'model> {
60    fn borrow(&self) -> &IntVar<'model> {
61        &self.int_var
62    }
63}
64
65/// Safety:
66/// - Safe because BoolVar is created from Model and therefore the backend is initialized
67///   and model is initialized.
68unsafe impl<'model> Variable<'model> for BoolVar<'model> {}
69
70impl<'model> BoolVar<'model> {
71    /// Creates a new boolean variable.
72    ///
73    /// # Panics
74    ///
75    /// Panics if the name contains null bytes or if the backend returns a null handle.
76    #[must_use]
77    pub(crate) fn new(model: &'model Model, value: Option<bool>, name: Option<&str>) -> Self {
78        // Safety:
79        // Safe because Model instances are created from valid backend handles.
80        let raw_handle = CHOCO_BACKEND.with(|backend| unsafe {
81            match name {
82                Some(name_str) => {
83                    let c_name = std::ffi::CString::new(name_str)
84                        .expect("Failed to convert name to CString");
85                    match value {
86                        Some(x) => CHOCO_LIB.Java_org_chocosolver_capi_BoolVarApi_boolVar_sb(
87                            backend.thread,
88                            model.get_raw_handle(),
89                            c_name.as_ptr().cast_mut(),
90                            i32::from(x),
91                        ),
92                        None => CHOCO_LIB.Java_org_chocosolver_capi_BoolVarApi_boolVar_s(
93                            backend.thread,
94                            model.get_raw_handle(),
95                            c_name.as_ptr().cast_mut(),
96                        ),
97                    }
98                }
99                None => match value {
100                    Some(x) => CHOCO_LIB.Java_org_chocosolver_capi_BoolVarApi_boolVar_b(
101                        backend.thread,
102                        model.get_raw_handle(),
103                        i32::from(x),
104                    ),
105                    None => CHOCO_LIB.Java_org_chocosolver_capi_BoolVarApi_boolVar(
106                        backend.thread,
107                        model.get_raw_handle(),
108                    ),
109                },
110            }
111        });
112        assert!(
113            !raw_handle.is_null(),
114            "Failed to create BoolVar: received null handle"
115        );
116
117        BoolVar {
118            int_var: IntVar {
119                handle: Handle::new(raw_handle),
120                model,
121            },
122        }
123    }
124
125    #[must_use]
126    pub(crate) fn not_view(bool_var: &BoolVar<'model>) -> BoolVar<'model> {
127        // Safety:
128        // Safe because view is created from BoolVar handle and therefore the backend is initialized.
129        let view_handle = CHOCO_BACKEND.with(|backend| unsafe {
130            CHOCO_LIB.Java_org_chocosolver_capi_ViewApi_bool_not_view(
131                backend.thread,
132                bool_var.get_raw_handle(),
133            )
134        });
135        BoolVar {
136            int_var: IntVar {
137                handle: Handle::new(view_handle),
138                model: bool_var.int_var.model,
139            },
140        }
141    }
142
143    ///
144    /// # Safety
145    ///
146    /// The caller must ensure that:
147    /// - The handle is a valid pointer to a backend boolean variable
148    /// - The backend is initialized
149    /// - The model is valid
150    ///
151    /// # Panics
152    ///
153    /// Panics if the handle is null.
154    pub(crate) unsafe fn from_raw_handle(
155        handle: *mut std::os::raw::c_void,
156        model: &'model Model,
157    ) -> BoolVar<'model> {
158        BoolVar {
159            int_var: IntVar {
160                handle: Handle::new(handle),
161                model,
162            },
163        }
164    }
165
166    /// Posts a constraint ensuring that if self BoolVar is true, then `then_constraint`
167    /// must be satisfied as well. Otherwise, `else_constraint` must be satisfied.
168    pub fn if_then_else(
169        &self,
170        then_constraint: &Constraint<'model>,
171        else_constraint: &Constraint<'model>,
172    ) {
173        // Safety:
174        // Safe because BoolVar is created from Model and therefore the backend is initialized and model handle is valid.
175        CHOCO_BACKEND.with(|backend| unsafe {
176            CHOCO_LIB.Java_org_chocosolver_capi_ReificationApi_if_then_else_bool(
177                backend.thread,
178                self.get_model().get_raw_handle(),
179                self.get_raw_handle(),
180                then_constraint.get_raw_handle(),
181                else_constraint.get_raw_handle(),
182            )
183        });
184    }
185
186    /// Creates an if-then constraint: self BoolVar -> then_constraint.
187    pub fn if_then(&self, then_constraint: &Constraint<'model>) {
188        // Safety:
189        // Safe because BoolVar is created from Model and therefore the backend is initialized and model handle is valid.
190        CHOCO_BACKEND.with(|backend| unsafe {
191            CHOCO_LIB.Java_org_chocosolver_capi_ReificationApi_if_then_bool(
192                backend.thread,
193                self.get_model().get_raw_handle(),
194                self.get_raw_handle(),
195                then_constraint.get_raw_handle(),
196            )
197        });
198    }
199
200    /// Posts an equivalence constraint stating that:
201    /// `self` BoolVar is true <=> `constraint` is satisfied.
202    pub fn if_only_if(&self, constraint: &Constraint<'model>) {
203        // Safety:
204        // Safe because BoolVar is created from Model and therefore the backend is initialized and model handle is valid.
205        CHOCO_BACKEND.with(|backend| unsafe {
206            CHOCO_LIB.Java_org_chocosolver_capi_ReificationApi_reification(
207                backend.thread,
208                self.get_model().get_raw_handle(),
209                self.get_raw_handle(),
210                constraint.get_raw_handle(),
211            )
212        });
213    }
214}
215
216impl<'model> BitAnd for &BoolVar<'model> {
217    type Output = BoolVar<'model>;
218
219    fn bitand(self, rhs: Self) -> Self::Output {
220        [self, rhs].and().reify()
221    }
222}
223
224impl<'model> BitAnd<bool> for &BoolVar<'model> {
225    type Output = BoolVar<'model>;
226
227    fn bitand(self, rhs: bool) -> Self::Output {
228        let rhs_bool_var = BoolVar::new(self.get_model(), Some(rhs), None);
229        [self, &rhs_bool_var].and().reify()
230    }
231}
232
233impl<'model> BitAnd<&BoolVar<'model>> for bool {
234    type Output = BoolVar<'model>;
235
236    fn bitand(self, rhs: &BoolVar<'model>) -> Self::Output {
237        let self_bool_var = BoolVar::new(rhs.get_model(), Some(self), None);
238        [&self_bool_var, rhs].and().reify()
239    }
240}
241
242impl<'model> BitOr for &BoolVar<'model> {
243    type Output = BoolVar<'model>;
244
245    fn bitor(self, rhs: Self) -> Self::Output {
246        [self, rhs].or().reify()
247    }
248}
249
250impl<'model> BitOr<bool> for &BoolVar<'model> {
251    type Output = BoolVar<'model>;
252
253    fn bitor(self, rhs: bool) -> Self::Output {
254        let rhs_bool_var = BoolVar::new(self.get_model(), Some(rhs), None);
255        [self, &rhs_bool_var].or().reify()
256    }
257}
258
259impl<'model> BitOr<&BoolVar<'model>> for bool {
260    type Output = BoolVar<'model>;
261
262    fn bitor(self, rhs: &BoolVar<'model>) -> Self::Output {
263        let self_bool_var = BoolVar::new(rhs.get_model(), Some(self), None);
264        [&self_bool_var, rhs].or().reify()
265    }
266}
267
268impl<'model> Not for &BoolVar<'model> {
269    type Output = BoolVar<'model>;
270
271    fn not(self) -> Self::Output {
272        BoolVar::not_view(self)
273    }
274}
275
276#[allow(private_bounds)]
277/// Logical operations on slices of Constraints/BoolVars
278pub trait BoolVarArrayLogicOps<'model>: Sealed {
279    /// AND of Constraints
280    /// # Arguments
281    /// * `constraints` - slice of Constraints/BoolVars
282    /// # Returns
283    /// Constraint representing the AND of the Constraints/BoolVars
284    /// # Panic:
285    /// if slice is empty
286    fn and(self) -> Constraint<'model>;
287    /// OR of Constraints
288    /// # Arguments
289    /// * `constraints` - slice of Constraints/BoolVars
290    /// # Returns
291    /// Constraint representing the OR of the Constraints/BoolVars
292    /// # Panic:
293    /// if slice is empty
294    fn or(self) -> Constraint<'model>;
295}
296
297impl<'model, Q: Borrow<BoolVar<'model>> + Sealed> BoolVarArrayLogicOps<'model> for &[Q] {
298    fn and(self) -> Constraint<'model> {
299        let array_handle = utils::make_boolvar_array(self);
300        let model = self
301            .first()
302            .expect("Slice shall contains at least one element")
303            .borrow()
304            .get_model();
305        CHOCO_BACKEND.with(|backend|
306            // Safety:
307            // Safe because Constraint is created from Model and therefore the backend is initialized and model handle is valid.
308            unsafe {
309            let constraint_handle = CHOCO_LIB
310                .Java_org_chocosolver_capi_ConstraintApi_and_bv_bv(
311                    backend.thread,
312                    model.get_raw_handle(),
313                    array_handle,
314                );
315         assert!(
316            !constraint_handle.is_null(),
317            "Failed to create AND constraint"
318        );
319        Constraint::new(constraint_handle, model)})
320    }
321    fn or(self) -> Constraint<'model> {
322        let array_handle = utils::make_boolvar_array(self);
323        let model = self
324            .first()
325            .expect("Slice shall contains at least one element")
326            .borrow()
327            .get_model();
328        CHOCO_BACKEND.with(|backend|
329            // Safety:
330            // Safe because Constraint is created from Model and therefore the backend is initialized and model handle is valid.
331            unsafe {
332            let constraint_handle = CHOCO_LIB
333                .Java_org_chocosolver_capi_ConstraintApi_or_bv_bv(
334                    backend.thread,
335                    model.get_raw_handle(),
336                    array_handle,
337                );
338                    assert!(
339            !constraint_handle.is_null(),
340            "Failed to create OR constraint"
341        );
342        Constraint::new(constraint_handle, model)
343            })
344    }
345}
346
347#[cfg(test)]
348mod tests;