Skip to main content

rustsat_cadical/
lib.rs

1//! # rustsat-cadical - Interface to the CaDiCaL SAT Solver for RustSAT
2//!
3//! Armin Biere's SAT solver [CaDiCaL](https://github.com/arminbiere/cadical) to be used with the [RustSAT](https://github.com/chrjabs/rustsat) library.
4//!
5//! **Note**: at the moment this crate is known to not work on Windows since CaDiCaL is non-trivial to get to work on Windows.
6//!
7//! ## Features
8//!
9//! - `debug`: if this feature is enabled, the Cpp library will be built with debug and check
10//!   functionality if the Rust project is built in debug mode. API tracing via the
11//!   `CADICAL_API_TRACE` environment variable is also enabled in debug mode.
12//! - `safe`: disable writing through `popen` for more safe usage of the library in applications
13//! - `quiet`: exclude message and profiling code (logging too)
14//! - `logging`: include logging code (but disabled by default)
15//! - `tracing`: always include CaDiCaL API tracing via the `CADICAL_API_TRACE` environment
16//!   variable and the [`CaDiCaL::trace_api_calls`] method
17//!
18//! ## CaDiCaL Versions
19//!
20//! CaDiCaL versions can be selected via cargo crate features.
21//! All CaDiCaL versions from
22//! [Version 1.5.0](https://github.com/arminbiere/cadical/releases/tag/rel-1.5.0)
23//! up to
24//! [Version 2.2.1](https://github.com/arminbiere/cadical/releases/tag/rel-2.2.1)
25//! are available. For the full list of versions and the changelog see
26//! [the CaDiCaL releases](https://github.com/arminbiere/cadical/releases).
27//!
28//! Without any features selected, the newest version will be used.
29//! If conflicting CaDiCaL versions are requested, the newest requested version will be selected.
30//!
31//! If the determined version is _not_ the newest available, and no custom source directory is
32//! specified (see customization below), the CaDiCaL source code is downloaded at compile time,
33//! which requires network access.
34//!
35//! ## Customization
36//!
37//! In order to build a custom version of CaDiCaL, this crate supports two environment variables to
38//! customize the Cpp source code that CaDiCaL is built from.
39//!
40//! - `CADICAL_PATCHES` allows to specify a list of colon-separated paths to patch files that will
41//!   be applied to the CaDiCaL source repository before building it. These patches are applied
42//!   in order of appearance _after_ the patches of this crate have been applied.
43//! - `CADICAL_SRC_DIR` allows for overriding where the Cpp library is built from. By default this
44//!   crate fetches the appropriate code from [the GitHub
45//!   repository](https://github.com/arminbiere/cadical). If this variable is set, the directory specified
46//!   there is used instead. Note that when using this variable, the crate will not apply any
47//!   patches, the user is responsible for applying the appropriate and necessary patches from the
48//!   [`patches/`](https://github.com/chrjabs/rustsat/tree/main/cadical/patches) directory.
49//!
50//! ## Cpp Compiler Features
51//!
52//! By default, the build script of this crate uses the same compiler/platform tests as CaDiCaL's
53//! `configure` script to detect whether certain Cpp compiler features are available.
54//! The following environment variables allow for manually modifying the logic for detecting
55//! compiler features.
56//!
57//! - `CADICAL_RUN_CPP_TESTS`: By default we only consider a compiler/platform feature available if
58//!   the test compiles _and runs_.
59//!   With this environment variable set to `0` or `false` a feature is considered available if the
60//!   test compiles correctly, without trying to execute it.
61//!   This is useful for cross-compilation settings where executing the compiled binary is not
62//!   possible.
63//! - `CADICAL_FLEXIBLE_ARRAY_MEMBERS` enables (`1` or `true`) or disables (`0` or `false`) or
64//!   automatically checks (`auto`) the availability of the flexible array members compiler
65//!   feature.
66//!   Disabling the feature is equivalent to `--no-flexible` in CaDiCaL's `configure` script.
67//! - `CADICAL_CLOSEFROM` marks `closefrom` as available (`1` or `true`) or unavailable (`0` or
68//!   `false`) or automatically checks (`auto`) the availability.
69//!   Disabling the feature is equivalent to `--no-closefrom` in CaDiCaL's `configure` script.
70//! - `CADICAL_UNLOCKED_IO` enables (`1` or `true`) or disables (`0` or `false`) or automatically
71//!   checks (`auto`) the availability of the unlocked IO platform feature.
72//!   Disabling the feature is equivalent to `--no-unlocked` in CaDiCaL's `configure` script.
73//!
74//! By default, the availability of all compiler/platform features is automatically checked.
75//!
76//! ## Minimum Supported Rust Version (MSRV)
77//!
78//! Currently, the MSRV is 1.77.0, the plan is to always support an MSRV that is at least a year
79//! old.
80//!
81//! Bumps in the MSRV will _not_ be considered breaking changes. If you need a specific MSRV, make
82//! sure to pin a precise version of RustSAT.
83//!
84//! Note that the specified minimum-supported Rust version only applies if the _newest_ version of
85//! CaDiCaL is build.
86//! Older versions are pulled down via the [`git2`](https://crates.io/crates/git2) crate, which has
87//! transitive dependencies that have a higher MSRV.
88//!
89//! ## Compiling on Windows / Cross Compilation
90//!
91//! Compiling this crate under Windows can be a bit challenging, but it can be done.
92//! The challenges come from CaDiCaL relying on unix-specific features.
93//! For starters, make sure to use the `x86_64-pc-windows-gnu` target triple.
94//!
95//! Cross compilation for Windows can be achieved with the help of [`cargo
96//! zigbuild`](https://github.com/rust-cross/cargo-zigbuild).
97//! During cross compilation, make sure to set `CADICAL_RUN_CPP_TESTS=0`, since compiler tests
98//! (compiled for Windows) can't be run on the non-Windows host system.
99//! For a working Windows cross compilation setup example, see [this CI
100//! configuration](https://github.com/marceline-cramer/saturn-v/blob/f8bcdc24857bce232981518bb4bec2c8cfcc6acf/.github/workflows/release.yml).
101
102#![cfg_attr(docsrs, feature(doc_cfg))]
103#![warn(clippy::pedantic)]
104#![warn(missing_docs)]
105#![warn(missing_debug_implementations)]
106
107use std::{
108    cmp::Ordering,
109    ffi::{c_int, c_void, CStr, CString, NulError},
110    fmt,
111    path::Path,
112};
113
114use rustsat::types::{Cl, Clause, Lit, TernaryVal, Var};
115use rustsat::{
116    solvers::{
117        ControlSignal, FreezeVar, GetInternalStats, Interrupt, InterruptSolver, Learn,
118        LimitConflicts, LimitDecisions, PhaseLit, Propagate, PropagateResult, Solve,
119        SolveIncremental, SolveStats, SolverResult, SolverState, SolverStats, StateError,
120        Terminate,
121    },
122    utils::Timer,
123};
124use thiserror::Error;
125
126mod ffi;
127
128#[cfg(cadical_version = "v2.0.0")]
129mod prooftracer;
130#[cfg(cadical_version = "v2.0.0")]
131pub use prooftracer::{
132    CaDiCaLAssignment, CaDiCaLClause, ClauseId, Conclusion, ProofTracerHandle,
133    ProofTracerNotConnected, TraceProof,
134};
135
136macro_rules! handle_oom {
137    ($val:expr) => {{
138        let val = $val;
139        if val == $crate::ffi::OUT_OF_MEM {
140            return anyhow::Context::context(
141                Err(rustsat::OutOfMemory::ExternalApi),
142                "cadical out of memory",
143            );
144        }
145        val
146    }};
147    ($val:expr, noanyhow) => {{
148        let val = $val;
149        if val == $crate::ffi::OUT_OF_MEM {
150            return Err(rustsat::OutOfMemory::ExternalApi);
151        }
152        val
153    }};
154}
155
156/// Fatal error returned if the CaDiCaL API returns an invalid value
157#[derive(Error, Clone, Copy, PartialEq, Eq, Debug)]
158#[error("cadical c-api returned an invalid value: {api_call} -> {value}")]
159pub struct InvalidApiReturn {
160    api_call: &'static str,
161    value: c_int,
162}
163
164#[derive(Debug, PartialEq, Eq, Default)]
165enum InternalSolverState {
166    #[default]
167    Configuring,
168    Input,
169    Sat,
170    Unsat(Vec<Lit>),
171}
172
173impl InternalSolverState {
174    fn to_external(&self) -> SolverState {
175        match self {
176            InternalSolverState::Configuring => SolverState::Configuring,
177            InternalSolverState::Input => SolverState::Input,
178            InternalSolverState::Sat => SolverState::Sat,
179            InternalSolverState::Unsat(_) => SolverState::Unsat,
180        }
181    }
182}
183
184type TermCallbackPtr<'a> = Box<dyn FnMut() -> ControlSignal + 'a>;
185type LearnCallbackPtr<'a> = Box<dyn FnMut(Clause) + 'a>;
186/// Double boxing is necessary to get thin pointers for casting
187type OptTermCallbackStore<'a> = Option<Box<TermCallbackPtr<'a>>>;
188/// Double boxing is necessary to get thin pointers for casting
189type OptLearnCallbackStore<'a> = Option<Box<LearnCallbackPtr<'a>>>;
190
191/// The CaDiCaL solver type
192pub struct CaDiCaL<'term, 'learn> {
193    handle: *mut ffi::CCaDiCaL,
194    state: InternalSolverState,
195    terminate_cb: OptTermCallbackStore<'term>,
196    learner_cb: OptLearnCallbackStore<'learn>,
197    stats: SolverStats,
198}
199
200impl fmt::Debug for CaDiCaL<'_, '_> {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        f.debug_struct("CaDiCaL")
203            .field("handle", &self.handle)
204            .field("state", &self.state)
205            .field(
206                "terminate_cb",
207                if self.terminate_cb.is_some() {
208                    &"some callback"
209                } else {
210                    &"no callback"
211                },
212            )
213            .field(
214                "learner_cb",
215                if self.learner_cb.is_some() {
216                    &"some callback"
217                } else {
218                    &"no callback"
219                },
220            )
221            .field("stats", &self.stats)
222            .finish()
223    }
224}
225
226unsafe impl Send for CaDiCaL<'_, '_> {}
227
228impl Default for CaDiCaL<'_, '_> {
229    fn default() -> Self {
230        let handle = unsafe { ffi::ccadical_init() };
231        assert!(
232            !handle.is_null(),
233            "not enough memory to initialize CaDiCaL solver"
234        );
235        let solver = Self {
236            handle,
237            state: InternalSolverState::default(),
238            terminate_cb: None,
239            learner_cb: None,
240            stats: SolverStats::default(),
241        };
242        let quiet = c"quiet";
243        unsafe { ffi::ccadical_set_option_ret(solver.handle, quiet.as_ptr(), 1) };
244        solver
245    }
246}
247
248impl CaDiCaL<'_, '_> {
249    fn get_core_assumps(&self, assumps: &[Lit]) -> Result<Vec<Lit>, InvalidApiReturn> {
250        let mut core = Vec::new();
251        for a in assumps {
252            match unsafe { ffi::ccadical_failed(self.handle, a.to_ipasir()) } {
253                0 => (),
254                1 => core.push(!*a),
255                value => {
256                    return Err(InvalidApiReturn {
257                        api_call: "ccadical_failed",
258                        value,
259                    })
260                }
261            }
262        }
263        Ok(core)
264    }
265
266    #[allow(clippy::cast_precision_loss)]
267    #[inline]
268    fn update_avg_clause_len(&mut self, clause: &Cl) {
269        self.stats.avg_clause_len =
270            (self.stats.avg_clause_len * ((self.stats.n_clauses - 1) as f32) + clause.len() as f32)
271                / self.stats.n_clauses as f32;
272    }
273
274    /// Adds a clause that only exists for the next solver call. Only one such
275    /// clause can exist, a new new clause replaces the old one.
276    ///
277    /// _Note_: If this is used, in addition to [`SolveIncremental::core`],
278    /// [`CaDiCaL::tmp_clause_in_core`] needs to be checked to determine if the
279    /// temporary clause is part of the core.
280    ///
281    /// # Errors
282    ///
283    /// Returns [`rustsat::OutOfMemory`] if CaDiCaL throws `std::bad_alloc`.
284    pub fn add_tmp_clause<C>(&mut self, clause: &C) -> Result<(), rustsat::OutOfMemory>
285    where
286        C: AsRef<Cl> + ?Sized,
287    {
288        let clause = clause.as_ref();
289        // Update wrapper-internal state
290        self.stats.n_clauses += 1;
291        self.update_avg_clause_len(clause);
292        self.state = InternalSolverState::Input;
293        // Call CaDiCaL backend
294        for lit in clause {
295            handle_oom!(
296                unsafe { ffi::ccadical_constrain_mem(self.handle, lit.to_ipasir()) },
297                noanyhow
298            );
299        }
300        handle_oom!(
301            unsafe { ffi::ccadical_constrain_mem(self.handle, 0) },
302            noanyhow
303        );
304        Ok(())
305    }
306
307    /// Checks whether the temporary clause is part of the core if in
308    /// unsatisfiable state. This needs to always be checked in addition to
309    /// [`SolveIncremental::core`] if a [`CaDiCaL::add_tmp_clause`] is used.
310    ///
311    /// # Errors
312    ///
313    /// Returns [`StateError`] if the solver is not in [`SolverState::Unsat`].
314    pub fn tmp_clause_in_core(&mut self) -> anyhow::Result<bool> {
315        match &self.state {
316            InternalSolverState::Unsat(_) => unsafe {
317                Ok(ffi::ccadical_constraint_failed(self.handle) != 0)
318            },
319            state => Err(StateError {
320                required_state: SolverState::Unsat,
321                actual_state: state.to_external(),
322            }
323            .into()),
324        }
325    }
326
327    /// Sets a pre-defined configuration for CaDiCaL's internal options
328    ///
329    /// # Errors
330    ///
331    /// - Returns [`StateError`] if the solver is not in [`SolverState::Configuring`]
332    /// - Returns [`InvalidApiReturn`] if the C-API does not return `true`. This case can be
333    ///   considered a bug in either the CaDiCaL library or this crate.
334    pub fn set_configuration(&mut self, config: Config) -> anyhow::Result<()> {
335        if self.state == InternalSolverState::Configuring {
336            let config_name: &CStr = config.into();
337            let ret = unsafe { ffi::ccadical_configure(self.handle, config_name.as_ptr()) };
338            if ret != 0 {
339                Ok(())
340            } else {
341                Err(InvalidApiReturn {
342                    api_call: "ccadical_configure",
343                    value: 0,
344                }
345                .into())
346            }
347        } else {
348            Err(StateError {
349                required_state: SolverState::Configuring,
350                actual_state: self.state.to_external(),
351            }
352            .into())
353        }
354    }
355
356    /// Sets the value of a CaDiCaL option. For possible options, check CaDiCaL with `cadical --help`.
357    /// Requires state [`SolverState::Configuring`].
358    ///
359    /// # CaDiCaL Documentation
360    ///
361    /// Explicit version of setting an option.  If the option `<name>` exists
362    /// and `<val>` can be parsed then 'true' is returned.  If the option value
363    /// is out of range the actual value is computed as the closest (minimum or
364    /// maximum) value possible, but still `true` is returned.
365    ///
366    /// # Errors
367    ///
368    /// Returns [`InvalidApiReturn`] if the C-API does not return `true`. This is most likely due
369    /// to a wrongly specified `name` or an invalid `value`.
370    pub fn set_option(&mut self, name: &str, value: c_int) -> anyhow::Result<()> {
371        let c_name = CString::new(name)?;
372        if unsafe { ffi::ccadical_set_option_ret(self.handle, c_name.as_ptr(), value) } != 0 {
373            Ok(())
374        } else {
375            Err(InvalidApiReturn {
376                api_call: "ccadical_set_option_ret",
377                value: 0,
378            }
379            .into())
380        }
381    }
382
383    /// Gets the value of a CaDiCaL option. For possible options, check CaDiCaL with `cadical --help`.
384    ///
385    /// # CaDiCaL Documentation
386    ///
387    /// Get the current value of the option `name`.  If `name` is invalid then
388    /// zero is returned.
389    ///
390    /// # Errors
391    ///
392    /// Returns [`InvalidApiReturn`] if the C-API does not return `true`. This is most likely due
393    /// to a wrongly specified `name`.
394    pub fn get_option(&self, name: &str) -> anyhow::Result<c_int> {
395        let c_name = CString::new(name)?;
396        Ok(unsafe { ffi::ccadical_get_option(self.handle, c_name.as_ptr()) })
397    }
398
399    /// Sets an internal limit for CaDiCaL
400    ///
401    /// # CaDiCaL Documentation
402    ///
403    /// Specify search limits, where currently `name` can be "conflicts",
404    /// "decisions", "preprocessing", or "localsearch".  The first two limits
405    /// are unbounded by default.  Thus using a negative limit for conflicts or
406    /// decisions switches back to the default of unlimited search (for that
407    /// particular limit).  The preprocessing limit determines the number of
408    /// preprocessing rounds, which is zero by default.  Similarly, the local
409    /// search limit determines the number of local search rounds (also zero by
410    /// default).  As with `set`, the return value denotes whether the limit
411    /// `name` is valid.  These limits are only valid for the next `solve` or
412    /// `simplify` call and reset to their default after `solve` returns (as
413    /// well as overwritten and reset during calls to `simplify` and
414    /// `lookahead`).  We actually also have an internal "terminate" limit
415    /// which however should only be used for testing and debugging.
416    ///
417    /// # Errors
418    ///
419    /// Returns [`InvalidApiReturn`] if the C-API does not return `true`. This case can be
420    /// considered a bug in either the CaDiCaL library or this crate.
421    pub fn set_limit(&mut self, limit: Limit) -> anyhow::Result<()> {
422        let (name, val) = limit.into();
423        if unsafe { ffi::ccadical_limit_ret(self.handle, name.as_ptr(), val) } != 0 {
424            Ok(())
425        } else {
426            Err(InvalidApiReturn {
427                api_call: "ccadical_limit_ret",
428                value: 0,
429            }
430            .into())
431        }
432    }
433
434    /// Gets the number of active variables
435    #[must_use]
436    pub fn get_active(&self) -> i64 {
437        unsafe { ffi::ccadical_active(self.handle) }
438    }
439
440    /// Gets the number of active irredundant clauses
441    #[must_use]
442    pub fn get_irredundant(&self) -> i64 {
443        unsafe { ffi::ccadical_irredundant(self.handle) }
444    }
445
446    /// Gets the number of active redundant clauses
447    #[must_use]
448    pub fn get_redundant(&self) -> i64 {
449        unsafe { ffi::ccadical_redundant(self.handle) }
450    }
451
452    /// Gets the current literal value at the root level
453    #[must_use]
454    pub fn current_lit_val(&self, lit: Lit) -> TernaryVal {
455        let int_val = unsafe { ffi::ccadical_fixed(self.handle, lit.to_ipasir()) };
456        match int_val.cmp(&0) {
457            Ordering::Greater => TernaryVal::True,
458            Ordering::Less => TernaryVal::False,
459            Ordering::Equal => TernaryVal::DontCare,
460        }
461    }
462
463    /// Prints the solver statistics from the CaDiCaL backend
464    pub fn print_stats(&self) {
465        unsafe { ffi::ccadical_print_statistics(self.handle) }
466    }
467
468    /// Executes the given number of preprocessing rounds
469    ///
470    /// # CaDiCaL Documentation
471    ///
472    /// This function executes the given number of preprocessing rounds. It is
473    /// similar to 'solve' with 'limits ("preprocessing", rounds)' except that
474    /// no CDCL nor local search, nor lucky phases are executed.  The result
475    /// values are also the same: `0=UNKNOWN`, `10=SATISFIABLE`, `20=UNSATISFIABLE`.
476    /// As 'solve' it resets current assumptions and limits before returning.
477    /// The numbers of rounds should not be negative.  If the number of rounds
478    /// is zero only clauses are restored (if necessary) and top level unit
479    /// propagation is performed, which both take some time.
480    ///
481    /// # Errors
482    ///
483    /// Returns [`InvalidApiReturn`] if the C-API returns an unexpected value. This case can be
484    /// considered a bug in the CaDiCaL library or this crate.
485    pub fn simplify(&mut self, rounds: u32) -> anyhow::Result<SolverResult> {
486        // If already solved, return state
487        if let InternalSolverState::Sat = self.state {
488            return Ok(SolverResult::Sat);
489        }
490        if let InternalSolverState::Unsat(_) = self.state {
491            return Ok(SolverResult::Unsat);
492        }
493        let rounds: c_int = rounds.try_into()?;
494        // Simplify with CaDiCaL backend
495        match unsafe { ffi::ccadical_simplify_rounds(self.handle, rounds) } {
496            0 => {
497                self.state = InternalSolverState::Input;
498                Ok(SolverResult::Interrupted)
499            }
500            10 => {
501                self.state = InternalSolverState::Sat;
502                Ok(SolverResult::Sat)
503            }
504            20 => {
505                self.state = InternalSolverState::Unsat(vec![]);
506                Ok(SolverResult::Unsat)
507            }
508            value => Err(InvalidApiReturn {
509                api_call: "ccadical_simplify",
510                value,
511            }
512            .into()),
513        }
514    }
515
516    /// Executes the given number of preprocessing rounds under assumptions
517    ///
518    /// # CaDiCaL Documentation
519    ///
520    /// This function executes the given number of preprocessing rounds. It is
521    /// similar to 'solve' with 'limits ("preprocessing", rounds)' except that
522    /// no CDCL nor local search, nor lucky phases are executed.  The result
523    /// values are also the same: `0=UNKNOWN`, `10=SATISFIABLE`, `20=UNSATISFIABLE`.
524    /// As 'solve' it resets current assumptions and limits before returning.
525    /// The numbers of rounds should not be negative.  If the number of rounds
526    /// is zero only clauses are restored (if necessary) and top level unit
527    /// propagation is performed, which both take some time.
528    ///
529    /// # Errors
530    ///
531    /// Returns [`InvalidApiReturn`] if the C-API returns an unexpected value. This case can be
532    /// considered a bug in the CaDiCaL library or this crate.
533    pub fn simplify_assumps(
534        &mut self,
535        assumps: &[Lit],
536        rounds: u32,
537    ) -> anyhow::Result<SolverResult> {
538        // If already solved, return state
539        if let InternalSolverState::Sat = self.state {
540            return Ok(SolverResult::Sat);
541        }
542        if let InternalSolverState::Unsat(_) = self.state {
543            return Ok(SolverResult::Unsat);
544        }
545        let rounds: c_int = rounds.try_into()?;
546        // Simplify with CaDiCaL backend under assumptions
547        for a in assumps {
548            handle_oom!(unsafe { ffi::ccadical_assume_mem(self.handle, a.to_ipasir()) });
549        }
550        match unsafe { ffi::ccadical_simplify_rounds(self.handle, rounds) } {
551            0 => {
552                self.state = InternalSolverState::Input;
553                Ok(SolverResult::Interrupted)
554            }
555            10 => {
556                self.state = InternalSolverState::Sat;
557                Ok(SolverResult::Sat)
558            }
559            20 => {
560                self.state = InternalSolverState::Unsat(vec![]);
561                Ok(SolverResult::Unsat)
562            }
563            value => Err(InvalidApiReturn {
564                api_call: "ccadical_simplify",
565                value,
566            }
567            .into()),
568        }
569    }
570
571    /// Trace the CaDiCaL API calls to a file at the given path
572    ///
573    /// # Errors
574    ///
575    /// - If opening the file fails
576    /// - [`NulError`] if the provided path contains a nul byte
577    #[cfg(feature = "tracing")]
578    pub fn trace_api_calls<P: AsRef<Path>>(&mut self, path: P) -> anyhow::Result<()> {
579        let path = CString::new(path.as_ref().to_string_lossy().as_bytes())?;
580        if unsafe { ffi::ccadical_trace_api_calls(self.handle, path.as_ptr()) } > 0 {
581            anyhow::bail!("failed to open file path for tracing");
582        }
583        Ok(())
584    }
585
586    /// Attaches a proof tracer of a given format, writing to a specified path
587    ///
588    /// # Errors
589    ///
590    /// If the provided path contains a nul byte
591    // We know that the set options exist and that this should therefore never panic
592    #[allow(clippy::missing_panics_doc)]
593    pub fn trace_proof<P: AsRef<Path>>(
594        &mut self,
595        path: P,
596        format: ProofFormat,
597    ) -> Result<(), NulError> {
598        let path = CString::new(path.as_ref().to_string_lossy().as_bytes())?;
599        let binary = match format {
600            ProofFormat::Drat { binary } => binary,
601            #[cfg(cadical_version = "v1.7.0")]
602            ProofFormat::Lrat { binary } => {
603                self.set_option("lrat", 1)
604                    .expect("we know this option exists");
605                binary
606            }
607            #[cfg(cadical_version = "v1.9.0")]
608            ProofFormat::Frat { binary, drat } => {
609                self.set_option("frat", 1 + c_int::from(drat))
610                    .expect("we know this option exists");
611                binary
612            }
613            #[cfg(cadical_version = "v1.9.0")]
614            ProofFormat::VeriPB {
615                checked_deletion,
616                drat,
617            } => {
618                self.set_option(
619                    "veripb",
620                    1 + c_int::from(!checked_deletion) + 2 * c_int::from(drat),
621                )
622                .expect("we know this option exists");
623                false
624            }
625            #[cfg(cadical_version = "v2.0.0")]
626            ProofFormat::Idrup { binary } => {
627                self.set_option("idrup", 1)
628                    .expect("we know this option exists");
629                binary
630            }
631            #[cfg(cadical_version = "v2.0.0")]
632            ProofFormat::Lidrup { binary } => {
633                self.set_option("lidrup", 1)
634                    .expect("we know this option exists");
635                binary
636            }
637        };
638        self.set_option("binary", c_int::from(binary))
639            .expect("we know this option exists");
640        unsafe {
641            ffi::ccadical_trace_proof_path(self.handle, path.as_ptr());
642        }
643        Ok(())
644    }
645
646    /// Increase the maximum variable index by a number of new variables.
647    /// Initializes `number_of_vars` new variables and protects them from
648    /// being used by the solver as extension variables (BVA).
649    ///
650    /// It returns the new maximum variable which is the highest
651    /// variable name of the consecutive range of newly declared variables.
652    ///
653    /// # Panics
654    ///
655    /// If `num_additional > i32::MAX`
656    #[cfg(cadical_version = "v2.2.0")]
657    pub fn declare_more_variables(&mut self, num_additional: u32) -> Var {
658        Lit::from_ipasir(unsafe {
659            ffi::ccadical_declare_more_variables(
660                self.handle,
661                i32::try_from(num_additional)
662                    .expect("can declare at most `i32::MAX` variables at once"),
663            )
664        })
665        .expect("received invalid variable from CaDiCaL")
666        .var()
667    }
668
669    /// Increase the maximum variable index by one. This is a specialized
670    /// version of [`Self::declare_more_variables`].
671    #[cfg(cadical_version = "v2.2.0")]
672    pub fn declare_one_more_variable(&mut self) -> Var {
673        self.declare_more_variables(1)
674    }
675
676    /// Gets statistic values from the solver
677    #[cfg(cadical_version = "v2.2.0")]
678    #[allow(clippy::missing_panics_doc)]
679    #[must_use]
680    pub fn get_statistic(&self, statistic: Statistic) -> u64 {
681        let statistic: &CStr = statistic.into();
682        let val = unsafe { ffi::ccadical_get_statistic_value(self.handle, statistic.as_ptr()) };
683        assert!(val >= 0, "got invalid statistic value from CaDiCaL");
684        val.unsigned_abs()
685    }
686}
687
688impl Extend<Clause> for CaDiCaL<'_, '_> {
689    fn extend<T: IntoIterator<Item = Clause>>(&mut self, iter: T) {
690        iter.into_iter()
691            .for_each(|cl| self.add_clause(cl).expect("Error adding clause in extend"));
692    }
693}
694
695impl<'a, C> Extend<&'a C> for CaDiCaL<'_, '_>
696where
697    C: AsRef<Cl> + ?Sized,
698{
699    fn extend<T: IntoIterator<Item = &'a C>>(&mut self, iter: T) {
700        iter.into_iter().for_each(|cl| {
701            self.add_clause_ref(cl)
702                .expect("Error adding clause in extend");
703        });
704    }
705}
706
707impl Solve for CaDiCaL<'_, '_> {
708    fn signature(&self) -> &'static str {
709        let c_chars = unsafe { ffi::ccadical_signature() };
710        let c_str = unsafe { CStr::from_ptr(c_chars) };
711        c_str
712            .to_str()
713            .expect("CaDiCaL signature returned invalid UTF-8.")
714    }
715
716    fn reserve(&mut self, max_var: Var) -> anyhow::Result<()> {
717        self.state = InternalSolverState::Input;
718        handle_oom!(unsafe { ffi::ccadical_resize(self.handle, max_var.to_ipasir()) });
719        Ok(())
720    }
721
722    fn solve(&mut self) -> anyhow::Result<SolverResult> {
723        // If already solved, return state
724        if let InternalSolverState::Sat = self.state {
725            return Ok(SolverResult::Sat);
726        }
727        if let InternalSolverState::Unsat(core) = &self.state {
728            if core.is_empty() {
729                return Ok(SolverResult::Unsat);
730            }
731        }
732        let start = Timer::now();
733        // Solve with CaDiCaL backend
734        let res = handle_oom!(unsafe { ffi::ccadical_solve_mem(self.handle) });
735        self.stats.cpu_solve_time += start.elapsed();
736        match res {
737            0 => {
738                self.stats.n_terminated += 1;
739                self.state = InternalSolverState::Input;
740                Ok(SolverResult::Interrupted)
741            }
742            10 => {
743                self.stats.n_sat += 1;
744                self.state = InternalSolverState::Sat;
745                Ok(SolverResult::Sat)
746            }
747            20 => {
748                self.stats.n_unsat += 1;
749                self.state = InternalSolverState::Unsat(vec![]);
750                Ok(SolverResult::Unsat)
751            }
752            value => Err(InvalidApiReturn {
753                api_call: "ccadical_solve",
754                value,
755            }
756            .into()),
757        }
758    }
759
760    fn lit_val(&self, lit: Lit) -> anyhow::Result<TernaryVal> {
761        if self.state != InternalSolverState::Sat {
762            return Err(StateError {
763                required_state: SolverState::Sat,
764                actual_state: self.state.to_external(),
765            }
766            .into());
767        }
768        let lit = lit.to_ipasir();
769        match unsafe { ffi::ccadical_val(self.handle, lit) } {
770            0 => Ok(TernaryVal::DontCare),
771            p if p == lit => Ok(TernaryVal::True),
772            n if n == -lit => Ok(TernaryVal::False),
773            // CaDiCaL returns -1 if variable is higher than max var
774            -1 => Ok(TernaryVal::DontCare),
775            value => Err(InvalidApiReturn {
776                api_call: "ccadical_val",
777                value,
778            }
779            .into()),
780        }
781    }
782
783    fn add_clause_ref<C>(&mut self, clause: &C) -> anyhow::Result<()>
784    where
785        C: AsRef<Cl> + ?Sized,
786    {
787        let clause = clause.as_ref();
788        // Update wrapper-internal state
789        self.stats.n_clauses += 1;
790        self.update_avg_clause_len(clause);
791        self.state = InternalSolverState::Input;
792        // Call CaDiCaL backend
793        for &l in clause {
794            handle_oom!(unsafe { ffi::ccadical_add_mem(self.handle, l.to_ipasir()) });
795        }
796        handle_oom!(unsafe { ffi::ccadical_add_mem(self.handle, 0) });
797        Ok(())
798    }
799}
800
801impl SolveIncremental for CaDiCaL<'_, '_> {
802    fn solve_assumps(&mut self, assumps: &[Lit]) -> anyhow::Result<SolverResult> {
803        let start = Timer::now();
804        // Solve with CaDiCaL backend
805        for a in assumps {
806            handle_oom!(unsafe { ffi::ccadical_assume_mem(self.handle, a.to_ipasir()) });
807        }
808        let res = handle_oom!(unsafe { ffi::ccadical_solve_mem(self.handle) });
809        self.stats.cpu_solve_time += start.elapsed();
810        match res {
811            0 => {
812                self.stats.n_terminated += 1;
813                self.state = InternalSolverState::Input;
814                Ok(SolverResult::Interrupted)
815            }
816            10 => {
817                self.stats.n_sat += 1;
818                self.state = InternalSolverState::Sat;
819                Ok(SolverResult::Sat)
820            }
821            20 => {
822                self.stats.n_unsat += 1;
823                self.state = InternalSolverState::Unsat(self.get_core_assumps(assumps)?);
824                Ok(SolverResult::Unsat)
825            }
826            value => Err(InvalidApiReturn {
827                api_call: "ccadical_solve",
828                value,
829            }
830            .into()),
831        }
832    }
833
834    fn core(&mut self) -> anyhow::Result<Vec<Lit>> {
835        match &self.state {
836            InternalSolverState::Unsat(core) => Ok(core.clone()),
837            other => Err(StateError {
838                required_state: SolverState::Unsat,
839                actual_state: other.to_external(),
840            }
841            .into()),
842        }
843    }
844}
845
846impl<'term> Terminate<'term> for CaDiCaL<'term, '_> {
847    /// Sets a terminator callback that is regularly called during solving.
848    ///
849    /// # Examples
850    ///
851    /// Terminate solver after 10 callback calls.
852    ///
853    /// ```
854    /// use rustsat::solvers::{ControlSignal, Solve, SolverResult, Terminate};
855    /// use rustsat_cadical::CaDiCaL;
856    ///
857    /// let mut solver = CaDiCaL::default();
858    ///
859    /// // Load instance
860    ///
861    /// let mut cnt = 1;
862    /// solver.attach_terminator(move || {
863    ///     if cnt > 10 {
864    ///         ControlSignal::Terminate
865    ///     } else {
866    ///         cnt += 1;
867    ///         ControlSignal::Continue
868    ///     }
869    /// });
870    ///
871    /// let ret = solver.solve().unwrap();
872    ///
873    /// // Assuming an instance is actually loaded and runs long enough
874    /// // assert_eq!(ret, SolverResult::Interrupted);
875    /// ```
876    fn attach_terminator<CB>(&mut self, cb: CB)
877    where
878        CB: FnMut() -> ControlSignal + 'term,
879    {
880        self.terminate_cb = Some(Box::new(Box::new(cb)));
881        let cb_ptr =
882            std::ptr::from_mut(self.terminate_cb.as_mut().unwrap().as_mut()).cast::<c_void>();
883        unsafe {
884            ffi::ccadical_set_terminate(
885                self.handle,
886                cb_ptr,
887                Some(ffi::rustsat_ccadical_terminate_cb),
888            );
889        }
890    }
891
892    fn detach_terminator(&mut self) {
893        self.terminate_cb = None;
894        unsafe { ffi::ccadical_set_terminate(self.handle, std::ptr::null_mut(), None) }
895    }
896}
897
898impl<'learn> Learn<'learn> for CaDiCaL<'_, 'learn> {
899    /// Sets a learner callback that gets passed clauses up to a certain length learned by the solver.
900    ///
901    /// The callback goes out of scope with the solver, afterwards captured variables become accessible.
902    ///
903    /// # Examples
904    ///
905    /// Count number of learned clauses up to length 10.
906    ///
907    /// ```
908    /// use rustsat::solvers::{Solve, SolverResult, Learn};
909    /// use rustsat_cadical::CaDiCaL;
910    ///
911    /// let mut cnt = 0;
912    ///
913    /// {
914    ///     let mut solver = CaDiCaL::default();
915    ///     // Load instance
916    ///
917    ///     solver.attach_learner(|_| cnt += 1, 10);
918    ///
919    ///     solver.solve().unwrap();
920    /// }
921    ///
922    /// // cnt variable can be accessed from here on
923    /// ```
924    fn attach_learner<CB>(&mut self, cb: CB, max_len: usize)
925    where
926        CB: FnMut(Clause) + 'learn,
927    {
928        self.learner_cb = Some(Box::new(Box::new(cb)));
929        let cb_ptr =
930            std::ptr::from_mut(self.learner_cb.as_mut().unwrap().as_mut()).cast::<c_void>();
931        unsafe {
932            ffi::ccadical_set_learn(
933                self.handle,
934                cb_ptr,
935                max_len.try_into().unwrap(),
936                Some(ffi::rustsat_ccadical_learn_cb),
937            );
938        }
939    }
940
941    fn detach_learner(&mut self) {
942        self.terminate_cb = None;
943        unsafe { ffi::ccadical_set_learn(self.handle, std::ptr::null_mut(), 0, None) }
944    }
945}
946
947impl Interrupt for CaDiCaL<'_, '_> {
948    type Interrupter = Interrupter;
949    fn interrupter(&mut self) -> Self::Interrupter {
950        Interrupter {
951            handle: self.handle,
952        }
953    }
954}
955
956/// An Interrupter for the CaDiCaL solver
957#[derive(Debug)]
958pub struct Interrupter {
959    /// The C API handle
960    handle: *mut ffi::CCaDiCaL,
961}
962
963unsafe impl Send for Interrupter {}
964unsafe impl Sync for Interrupter {}
965
966impl InterruptSolver for Interrupter {
967    fn interrupt(&self) {
968        unsafe { ffi::ccadical_terminate(self.handle) }
969    }
970}
971
972impl PhaseLit for CaDiCaL<'_, '_> {
973    /// Forces the default decision phase of a variable to a certain value
974    fn phase_lit(&mut self, lit: Lit) -> anyhow::Result<()> {
975        unsafe { ffi::ccadical_phase(self.handle, lit.to_ipasir()) };
976        Ok(())
977    }
978
979    /// Undoes the effect of a call to [`CaDiCaL::phase_lit`]
980    fn unphase_var(&mut self, var: Var) -> anyhow::Result<()> {
981        unsafe { ffi::ccadical_unphase(self.handle, var.to_ipasir()) };
982        Ok(())
983    }
984}
985
986impl FreezeVar for CaDiCaL<'_, '_> {
987    fn freeze_var(&mut self, var: Var) -> anyhow::Result<()> {
988        unsafe { ffi::ccadical_freeze(self.handle, var.to_ipasir()) };
989        Ok(())
990    }
991
992    fn melt_var(&mut self, var: Var) -> anyhow::Result<()> {
993        unsafe { ffi::ccadical_melt(self.handle, var.to_ipasir()) };
994        Ok(())
995    }
996
997    fn is_frozen(&mut self, var: Var) -> anyhow::Result<bool> {
998        Ok(unsafe { ffi::ccadical_frozen(self.handle, var.to_ipasir()) } != 0)
999    }
1000}
1001
1002#[cfg(cadical_version = "v1.5.4")]
1003impl rustsat::solvers::FlipLit for CaDiCaL<'_, '_> {
1004    fn flip_lit(&mut self, lit: Lit) -> anyhow::Result<bool> {
1005        if self.state != InternalSolverState::Sat {
1006            return Err(StateError {
1007                required_state: SolverState::Sat,
1008                actual_state: self.state.to_external(),
1009            }
1010            .into());
1011        }
1012        Ok(unsafe { ffi::ccadical_flip(self.handle, lit.to_ipasir()) } != 0)
1013    }
1014
1015    fn is_flippable(&mut self, lit: Lit) -> anyhow::Result<bool> {
1016        if self.state != InternalSolverState::Sat {
1017            return Err(StateError {
1018                required_state: SolverState::Sat,
1019                actual_state: self.state.to_external(),
1020            }
1021            .into());
1022        }
1023        Ok(unsafe { ffi::ccadical_flippable(self.handle, lit.to_ipasir()) } != 0)
1024    }
1025}
1026
1027impl LimitConflicts for CaDiCaL<'_, '_> {
1028    fn limit_conflicts(&mut self, limit: Option<u32>) -> anyhow::Result<()> {
1029        self.set_limit(Limit::Conflicts(if let Some(limit) = limit {
1030            limit.try_into()?
1031        } else {
1032            -1
1033        }))
1034    }
1035}
1036
1037impl LimitDecisions for CaDiCaL<'_, '_> {
1038    fn limit_decisions(&mut self, limit: Option<u32>) -> anyhow::Result<()> {
1039        self.set_limit(Limit::Decisions(if let Some(limit) = limit {
1040            limit.try_into()?
1041        } else {
1042            -1
1043        }))
1044    }
1045}
1046
1047impl GetInternalStats for CaDiCaL<'_, '_> {
1048    fn propagations(&self) -> usize {
1049        #[cfg(cadical_version = "v2.2.0")]
1050        let res = usize::try_from(self.get_statistic(Statistic::Propagations))
1051            .expect("more than `usize::MAX` propagations");
1052        #[cfg(not(cadical_version = "v2.2.0"))]
1053        let res = unsafe { ffi::ccadical_propagations(self.handle) }
1054            .try_into()
1055            .unwrap();
1056        res
1057    }
1058
1059    fn decisions(&self) -> usize {
1060        #[cfg(cadical_version = "v2.2.0")]
1061        let res = usize::try_from(self.get_statistic(Statistic::Decisions))
1062            .expect("more than `usize::MAX` decisions");
1063        #[cfg(not(cadical_version = "v2.2.0"))]
1064        let res = unsafe { ffi::ccadical_decisions(self.handle) }
1065            .try_into()
1066            .unwrap();
1067        res
1068    }
1069
1070    fn conflicts(&self) -> usize {
1071        #[cfg(cadical_version = "v2.2.0")]
1072        let res = usize::try_from(self.get_statistic(Statistic::Conflicts))
1073            .expect("more than `usize::MAX` conflicts");
1074        #[cfg(not(cadical_version = "v2.2.0"))]
1075        let res = unsafe { ffi::ccadical_conflicts(self.handle) }
1076            .try_into()
1077            .unwrap();
1078        res
1079    }
1080}
1081
1082#[cfg(cadical_version = "v2.1.3")]
1083impl Propagate for CaDiCaL<'_, '_> {
1084    fn propagate(
1085        &mut self,
1086        assumps: &[Lit],
1087        _phase_saving: bool,
1088    ) -> anyhow::Result<PropagateResult> {
1089        let start = Timer::now();
1090        self.state = InternalSolverState::Input;
1091        // Propagate with cadical backend
1092        for a in assumps {
1093            handle_oom!(unsafe { ffi::ccadical_assume_mem(self.handle, a.to_ipasir()) });
1094        }
1095        let res = handle_oom!(unsafe { ffi::ccadical_propagate(self.handle) });
1096        let mut props = Vec::new();
1097        dbg!(res);
1098        match res {
1099            0 => {
1100                let prop_ptr: *mut Vec<Lit> = &mut props;
1101                unsafe {
1102                    ffi::ccadical_implied(
1103                        self.handle,
1104                        Some(ffi::rustsat_cadical_collect_lits),
1105                        prop_ptr.cast::<std::os::raw::c_void>(),
1106                    );
1107                }
1108            }
1109            10 => {
1110                self.state = InternalSolverState::Sat;
1111                let prop_ptr: *mut Vec<Lit> = &mut props;
1112                unsafe {
1113                    ffi::ccadical_implied(
1114                        self.handle,
1115                        Some(ffi::rustsat_cadical_collect_lits),
1116                        prop_ptr.cast::<std::os::raw::c_void>(),
1117                    );
1118                }
1119            }
1120            20 => {}
1121            value => {
1122                return Err(InvalidApiReturn {
1123                    api_call: "ccadical_propagate",
1124                    value,
1125                }
1126                .into())
1127            }
1128        }
1129        self.stats.cpu_solve_time += start.elapsed();
1130        Ok(PropagateResult {
1131            propagated: props,
1132            conflict: res == 20,
1133        })
1134    }
1135}
1136
1137#[cfg(not(cadical_version = "v2.1.3"))]
1138impl Propagate for CaDiCaL<'_, '_> {
1139    fn propagate(
1140        &mut self,
1141        assumps: &[Lit],
1142        phase_saving: bool,
1143    ) -> anyhow::Result<PropagateResult> {
1144        let start = Timer::now();
1145        self.state = InternalSolverState::Input;
1146        // Propagate with cadical backend
1147        let assumps: Vec<_> = assumps.iter().map(|l| l.to_ipasir()).collect();
1148        let assump_ptr: *const c_int = assumps.as_ptr().cast();
1149        let mut props = Vec::new();
1150        let prop_ptr: *mut Vec<Lit> = &mut props;
1151        let res = handle_oom!(unsafe {
1152            ffi::ccadical_propcheck(
1153                self.handle,
1154                assump_ptr,
1155                assumps.len(),
1156                c_int::from(phase_saving),
1157                Some(ffi::rustsat_cadical_collect_lits),
1158                prop_ptr.cast::<std::os::raw::c_void>(),
1159            )
1160        });
1161        self.stats.cpu_solve_time += start.elapsed();
1162        match res {
1163            10 => Ok(PropagateResult {
1164                propagated: props,
1165                conflict: false,
1166            }),
1167            20 => Ok(PropagateResult {
1168                propagated: props,
1169                conflict: true,
1170            }),
1171            value => Err(InvalidApiReturn {
1172                api_call: "ccadical_propcheck",
1173                value,
1174            }
1175            .into()),
1176        }
1177    }
1178}
1179
1180impl SolveStats for CaDiCaL<'_, '_> {
1181    fn stats(&self) -> SolverStats {
1182        let max_var_idx = unsafe { ffi::ccadical_vars(self.handle) };
1183        let max_var = if max_var_idx > 0 {
1184            Some(Var::new(
1185                (max_var_idx - 1)
1186                    .try_into()
1187                    .expect("got negative number of vars from CaDiCaL"),
1188            ))
1189        } else {
1190            None
1191        };
1192        let mut stats = self.stats.clone();
1193        stats.max_var = max_var;
1194        stats
1195    }
1196
1197    fn max_var(&self) -> Option<Var> {
1198        let max_var_idx = unsafe { ffi::ccadical_vars(self.handle) };
1199        if max_var_idx > 0 {
1200            Some(Var::new(
1201                (max_var_idx - 1)
1202                    .try_into()
1203                    .expect("got negative number of vars from CaDiCaL"),
1204            ))
1205        } else {
1206            None
1207        }
1208    }
1209}
1210
1211impl Drop for CaDiCaL<'_, '_> {
1212    fn drop(&mut self) {
1213        unsafe { ffi::ccadical_release(self.handle) }
1214    }
1215}
1216
1217/// Possible CaDiCaL configurations
1218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1219pub enum Config {
1220    /// Set default advanced internal options
1221    Default,
1222    /// Disable all internal preprocessing options
1223    Plain,
1224    /// Set internal options to target satisfiable instances
1225    Sat,
1226    /// Set internal options to target unsatisfiable instances
1227    Unsat,
1228}
1229
1230impl From<Config> for &'static CStr {
1231    fn from(value: Config) -> Self {
1232        (&value).into()
1233    }
1234}
1235
1236impl From<&Config> for &'static CStr {
1237    fn from(value: &Config) -> Self {
1238        match value {
1239            Config::Default => c"default",
1240            Config::Plain => c"plain",
1241            Config::Sat => c"sat",
1242            Config::Unsat => c"unsat",
1243        }
1244    }
1245}
1246
1247impl fmt::Display for Config {
1248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1249        let str = match self {
1250            Config::Default => "default",
1251            Config::Plain => "plain",
1252            Config::Sat => "sat",
1253            Config::Unsat => "unsat",
1254        };
1255        write!(f, "{str}")
1256    }
1257}
1258
1259/// Possible CaDiCaL limits
1260#[derive(Debug, Clone, Copy)]
1261pub enum Limit {
1262    /// The number of calls to [`InterruptSolver::interrupt()`] before CaDiCaL terminates
1263    Terminate(c_int),
1264    /// A limit on the number of conflicts
1265    Conflicts(c_int),
1266    /// A limit on the number of decisions
1267    Decisions(c_int),
1268    /// A limit on the rounds of internal preprocessing
1269    Preprocessing(c_int),
1270    /// A limit on the internal local search
1271    LocalSearch(c_int),
1272}
1273
1274impl From<&Limit> for (&'static CStr, c_int) {
1275    fn from(val: &Limit) -> Self {
1276        match val {
1277            Limit::Terminate(val) => (c"terminate", *val),
1278            Limit::Conflicts(val) => (c"conflicts", *val),
1279            Limit::Decisions(val) => (c"decisions", *val),
1280            Limit::Preprocessing(val) => (c"preprocessing", *val),
1281            Limit::LocalSearch(val) => (c"localsearch", *val),
1282        }
1283    }
1284}
1285
1286impl From<Limit> for (&'static CStr, c_int) {
1287    fn from(val: Limit) -> Self {
1288        (&val).into()
1289    }
1290}
1291
1292impl fmt::Display for Limit {
1293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1294        match self {
1295            Limit::Terminate(val) => write!(f, "terminate ({val})"),
1296            Limit::Conflicts(val) => write!(f, "conflicts ({val})"),
1297            Limit::Decisions(val) => write!(f, "decisions ({val})"),
1298            Limit::Preprocessing(val) => write!(f, "preprocessing ({val})"),
1299            Limit::LocalSearch(val) => write!(f, "localsearch ({val})"),
1300        }
1301    }
1302}
1303
1304/// The proof formats that CaDiCaL supports
1305#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1306pub enum ProofFormat {
1307    /// The DRAT proof format
1308    Drat {
1309        /// Whether to write a binary or an ASCII proof
1310        binary: bool,
1311    },
1312    #[cfg(cadical_version = "v1.7.0")]
1313    /// The LRAT proof format
1314    Lrat {
1315        /// Whether to write a binary or an ASCII proof
1316        binary: bool,
1317    },
1318    #[cfg(cadical_version = "v1.9.0")]
1319    /// The FRAT proof format
1320    Frat {
1321        /// Whether to write a binary or an ASCII proof
1322        binary: bool,
1323        /// Whether to use DRAT instead of LRAT
1324        drat: bool,
1325    },
1326    #[cfg(cadical_version = "v1.9.0")]
1327    /// The VeriPB proof format
1328    VeriPB {
1329        /// Whether to use checked deletion
1330        checked_deletion: bool,
1331        /// Whether to disable (simulated) RUP with hints
1332        drat: bool,
1333    },
1334    #[cfg(cadical_version = "v2.0.0")]
1335    /// The incremental proof format IDRUP
1336    Idrup {
1337        /// Whether to write a binary or an ASCII proof
1338        binary: bool,
1339    },
1340    #[cfg(cadical_version = "v2.0.0")]
1341    /// The linear incremental proof format LIDRUP
1342    Lidrup {
1343        /// Whether to write a binary or an ASCII proof
1344        binary: bool,
1345    },
1346}
1347
1348impl Default for ProofFormat {
1349    fn default() -> Self {
1350        ProofFormat::Drat { binary: true }
1351    }
1352}
1353
1354/// Possible statistic values that can be read from the solver
1355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1356#[cfg(cadical_version = "v2.2.0")]
1357#[non_exhaustive]
1358pub enum Statistic {
1359    /// The number of conflicts observed so far
1360    Conflicts,
1361    /// The number of decisions made so far
1362    Decisions,
1363    /// The number of "ticks" (pseudo-time) solved
1364    Ticks,
1365    /// The number of propagations made so far
1366    Propagations,
1367    /// The number of clauses in the solver
1368    Clauses,
1369    /// The number of redundant clauses in the solver
1370    Redundant,
1371    /// The number of irredundant clauses in the solver
1372    Irredundant,
1373    /// The number of fixed variables in the solver
1374    Fixed,
1375    /// The number of eliminated variables in the solver
1376    Eliminated,
1377    /// The number of substituted variables in the solver
1378    Substituted,
1379}
1380
1381#[cfg(cadical_version = "v2.2.0")]
1382impl From<Statistic> for &'static CStr {
1383    fn from(value: Statistic) -> Self {
1384        match value {
1385            Statistic::Conflicts => c"conflicts",
1386            Statistic::Decisions => c"decisions",
1387            Statistic::Ticks => c"ticks",
1388            Statistic::Propagations => c"propagations",
1389            Statistic::Clauses => c"clauses",
1390            Statistic::Redundant => c"redundant",
1391            Statistic::Irredundant => c"irredundant",
1392            Statistic::Fixed => c"fixed",
1393            Statistic::Eliminated => c"eliminated",
1394            Statistic::Substituted => c"substituted",
1395        }
1396    }
1397}
1398
1399#[cfg(test)]
1400mod test {
1401    use rustsat::{
1402        lit,
1403        solvers::{Solve, SolverState, StateError},
1404        types::TernaryVal,
1405    };
1406
1407    use super::{CaDiCaL, Config, Limit, ProofFormat};
1408
1409    rustsat_solvertests::basic_unittests!(CaDiCaL, "cadical-[major].[minor].[patch]");
1410    rustsat_solvertests::termination_unittests!(CaDiCaL);
1411    rustsat_solvertests::learner_unittests!(CaDiCaL);
1412    rustsat_solvertests::freezing_unittests!(CaDiCaL);
1413    rustsat_solvertests::propagating_unittests!(CaDiCaL);
1414
1415    #[test]
1416    fn configure() {
1417        let mut solver = CaDiCaL::default();
1418        solver.set_configuration(Config::Default).unwrap();
1419        solver.set_configuration(Config::Plain).unwrap();
1420        solver.set_configuration(Config::Sat).unwrap();
1421        solver.set_configuration(Config::Unsat).unwrap();
1422        solver.add_unit(lit![0]).unwrap();
1423        assert!(solver.set_configuration(Config::Default).is_err_and(|e| e
1424            .downcast::<StateError>()
1425            .unwrap()
1426            == StateError {
1427                required_state: SolverState::Configuring,
1428                actual_state: SolverState::Input
1429            }));
1430    }
1431
1432    #[test]
1433    fn options() {
1434        let mut solver = CaDiCaL::default();
1435        assert_eq!(solver.get_option("arena").unwrap(), 1);
1436        solver.set_option("arena", 0).unwrap();
1437        assert_eq!(solver.get_option("arena").unwrap(), 0);
1438    }
1439
1440    #[test]
1441    fn limit() {
1442        let mut solver = CaDiCaL::default();
1443        solver.set_limit(Limit::Conflicts(100)).unwrap();
1444    }
1445
1446    #[test]
1447    fn backend_stats() {
1448        let mut solver = CaDiCaL::default();
1449        solver.add_binary(lit![0], !lit![1]).unwrap();
1450        solver.add_binary(lit![1], !lit![2]).unwrap();
1451        solver.add_binary(lit![2], !lit![3]).unwrap();
1452        solver.add_binary(lit![3], !lit![4]).unwrap();
1453        solver.add_binary(lit![4], !lit![5]).unwrap();
1454        solver.add_binary(lit![5], !lit![6]).unwrap();
1455        solver.add_binary(lit![6], !lit![7]).unwrap();
1456        solver.add_binary(lit![7], !lit![8]).unwrap();
1457        solver.add_binary(lit![8], !lit![9]).unwrap();
1458
1459        assert_eq!(solver.get_active(), 10);
1460        assert_eq!(solver.get_irredundant(), 9);
1461        assert_eq!(solver.get_redundant(), 0);
1462        assert_eq!(solver.current_lit_val(lit![0]), TernaryVal::DontCare);
1463    }
1464
1465    #[test]
1466    fn proofs() {
1467        let mut formats = vec![];
1468        formats.extend([
1469            ProofFormat::Drat { binary: false },
1470            ProofFormat::Drat { binary: true },
1471        ]);
1472        #[cfg(cadical_version = "v1.7.0")]
1473        formats.extend([
1474            ProofFormat::Lrat { binary: false },
1475            ProofFormat::Lrat { binary: true },
1476        ]);
1477        #[cfg(cadical_version = "v1.9.0")]
1478        formats.extend([
1479            ProofFormat::Frat {
1480                binary: false,
1481                drat: true,
1482            },
1483            ProofFormat::Frat {
1484                binary: true,
1485                drat: true,
1486            },
1487            ProofFormat::VeriPB {
1488                checked_deletion: false,
1489                drat: false,
1490            },
1491            ProofFormat::VeriPB {
1492                checked_deletion: true,
1493                drat: false,
1494            },
1495            ProofFormat::VeriPB {
1496                checked_deletion: false,
1497                drat: true,
1498            },
1499            ProofFormat::VeriPB {
1500                checked_deletion: true,
1501                drat: true,
1502            },
1503        ]);
1504        #[cfg(cadical_version = "v2.0.0")]
1505        formats.extend([
1506            ProofFormat::Idrup { binary: false },
1507            ProofFormat::Idrup { binary: true },
1508            ProofFormat::Lidrup { binary: false },
1509            ProofFormat::Lidrup { binary: true },
1510        ]);
1511        let path = format!("{}/test-proof", std::env::var("OUT_DIR").unwrap());
1512        for format in formats {
1513            let mut slv = CaDiCaL::default();
1514            slv.trace_proof(&path, format).unwrap();
1515        }
1516    }
1517}