1#![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#[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>;
186type OptTermCallbackStore<'a> = Option<Box<TermCallbackPtr<'a>>>;
188type OptLearnCallbackStore<'a> = Option<Box<LearnCallbackPtr<'a>>>;
190
191pub 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 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 self.stats.n_clauses += 1;
291 self.update_avg_clause_len(clause);
292 self.state = InternalSolverState::Input;
293 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 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 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 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 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 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 #[must_use]
436 pub fn get_active(&self) -> i64 {
437 unsafe { ffi::ccadical_active(self.handle) }
438 }
439
440 #[must_use]
442 pub fn get_irredundant(&self) -> i64 {
443 unsafe { ffi::ccadical_irredundant(self.handle) }
444 }
445
446 #[must_use]
448 pub fn get_redundant(&self) -> i64 {
449 unsafe { ffi::ccadical_redundant(self.handle) }
450 }
451
452 #[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 pub fn print_stats(&self) {
465 unsafe { ffi::ccadical_print_statistics(self.handle) }
466 }
467
468 pub fn simplify(&mut self, rounds: u32) -> anyhow::Result<SolverResult> {
486 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 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 pub fn simplify_assumps(
534 &mut self,
535 assumps: &[Lit],
536 rounds: u32,
537 ) -> anyhow::Result<SolverResult> {
538 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 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 #[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 #[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 #[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 #[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 #[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 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 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 -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 self.stats.n_clauses += 1;
790 self.update_avg_clause_len(clause);
791 self.state = InternalSolverState::Input;
792 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 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 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 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#[derive(Debug)]
958pub struct Interrupter {
959 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 fn phase_lit(&mut self, lit: Lit) -> anyhow::Result<()> {
975 unsafe { ffi::ccadical_phase(self.handle, lit.to_ipasir()) };
976 Ok(())
977 }
978
979 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1219pub enum Config {
1220 Default,
1222 Plain,
1224 Sat,
1226 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#[derive(Debug, Clone, Copy)]
1261pub enum Limit {
1262 Terminate(c_int),
1264 Conflicts(c_int),
1266 Decisions(c_int),
1268 Preprocessing(c_int),
1270 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1306pub enum ProofFormat {
1307 Drat {
1309 binary: bool,
1311 },
1312 #[cfg(cadical_version = "v1.7.0")]
1313 Lrat {
1315 binary: bool,
1317 },
1318 #[cfg(cadical_version = "v1.9.0")]
1319 Frat {
1321 binary: bool,
1323 drat: bool,
1325 },
1326 #[cfg(cadical_version = "v1.9.0")]
1327 VeriPB {
1329 checked_deletion: bool,
1331 drat: bool,
1333 },
1334 #[cfg(cadical_version = "v2.0.0")]
1335 Idrup {
1337 binary: bool,
1339 },
1340 #[cfg(cadical_version = "v2.0.0")]
1341 Lidrup {
1343 binary: bool,
1345 },
1346}
1347
1348impl Default for ProofFormat {
1349 fn default() -> Self {
1350 ProofFormat::Drat { binary: true }
1351 }
1352}
1353
1354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1356#[cfg(cadical_version = "v2.2.0")]
1357#[non_exhaustive]
1358pub enum Statistic {
1359 Conflicts,
1361 Decisions,
1363 Ticks,
1365 Propagations,
1367 Clauses,
1369 Redundant,
1371 Irredundant,
1373 Fixed,
1375 Eliminated,
1377 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}