rustsat_cadical/prooftracer.rs
1//! # CaDiCaL Proof Tracing Functionality
2
3use std::ffi::{c_int, c_void};
4
5use rustsat::{
6 solvers::SolverResult,
7 types::{Assignment, Clause, Lit},
8 utils::from_raw_parts_maybe_null,
9};
10
11use crate::ffi;
12
13/// The ID of a clause internal to CaDiCaL
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15#[repr(transparent)]
16pub struct ClauseId(pub i64);
17
18/// A conclusion for an incremental proof
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum Conclusion {
21 /// The solver found a conflict of the input clauses
22 Conflict,
23 /// The solver found the input clauses to be unsatisfiable with the assumptions
24 Assumptions,
25 /// The solver found the input clauses to be unsatisfiable with the temporary constraint
26 Constraint,
27}
28
29/// Trait that must be implement for a type that can be used to trace a proof generated by CaDiCaL
30///
31/// This is the equivalent to the proof tracer Cpp-API of CaDiCaL
32///
33/// Rather than passing [`Clause`] to the tracer, we use [`CaDiCaLClause`], which
34/// lazily converts IPASIR literals to [`Lit`] to not perform any work if the clause is not actually
35/// used
36#[allow(unused_variables)]
37pub trait TraceProof {
38 /// Notify the tracer that a original clause has been added.
39 ///
40 /// Includes ID and whether the clause is redundant or irredundant
41 fn add_original_clause(
42 &mut self,
43 id: ClauseId,
44 redundant: bool,
45 clause: &CaDiCaLClause,
46 restored: bool,
47 ) {
48 }
49
50 /// Notify the observer that a new clause has been derived.
51 ///
52 /// Includes ID and whether the clause is redundant or irredundant
53 /// If antecedents are derived they will be included here.
54 fn add_derived_clause(
55 &mut self,
56 id: ClauseId,
57 redundant: bool,
58 clause: &CaDiCaLClause,
59 antecedents: &[ClauseId],
60 ) {
61 }
62
63 /// Notify the observer that a clause is deleted.
64 ///
65 /// Includes ID and redundant/irredundant
66 fn delete_clause(&mut self, id: ClauseId, redundant: bool, clause: &CaDiCaLClause) {}
67
68 /// Notify the observer to remember that the clause might be restored later
69 fn weaken_minus(&mut self, id: ClauseId, clause: &CaDiCaLClause) {}
70
71 /// Notify the observer that a clause is strengthened
72 fn strengthen(&mut self, id: ClauseId) {}
73
74 /// Notify the observer that the solve call ends with status [`SolverResult`]
75 /// If the status is UNSAT and an empty clause has been derived, the second
76 /// argument will contain its id.
77 /// Note that the empty clause is already added through [`TraceProof::add_derived_clause`]
78 /// and finalized with [`TraceProof::finalize_clause`]
79 fn report_status(&mut self, status: SolverResult, id: ClauseId) {}
80
81 /// Notify the observer that a clause is finalized.
82 fn finalize_clause(&mut self, id: ClauseId, clause: &CaDiCaLClause) {}
83
84 /// Notify the observer that the proof begins with a set of reserved ids
85 /// for original clauses. Given ID is the first derived clause ID.
86 fn begin_proof(&mut self, id: ClauseId) {}
87
88 /// Notify the observer that an assumption has been added
89 fn solve_query(&mut self) {}
90
91 /// Notify the observer that an assumption has been added
92 fn add_assumption(&mut self, assumption: Lit) {}
93
94 /// Notify the observer that a constraint has been added
95 // Arguments: Data, length, constraint_clause
96 fn add_constraint(&mut self, constraint: &CaDiCaLClause) {}
97
98 /// Notify the observer that assumptions and constraints are reset
99 fn reset_assumptions(&mut self) {}
100
101 /// Notify the observer that this clause could be derived, which
102 /// is the negation of a core of failing assumptions/constraints.
103 /// If antecedents are derived they will be included here.
104 fn add_assumption_clause(
105 &mut self,
106 id: ClauseId,
107 clause: &CaDiCaLClause,
108 antecedents: &[ClauseId],
109 ) {
110 }
111
112 /// Notify the observer that conclude unsat was requested.
113 /// will give either the id of the empty clause, the id of a failing
114 /// assumption clause or the ids of the failing constrain clauses
115 fn conclude_unsat(&mut self, conclusion: Conclusion, failing: &[ClauseId]) {}
116
117 /// Notify the observer that conclude sat was requested.
118 /// will give the complete model as a vector.
119 fn conclude_sat(&mut self, solution: &CaDiCaLAssignment) {}
120
121 /// Notify the observer that conclude unknown was requested.
122 /// will give the current trail as a vector.
123 fn conclude_unknown(&mut self, solution: &CaDiCaLAssignment) {}
124
125 /// Notify the observer that two literals are equivalent
126 ///
127 /// You receive literals, not variables. You can also get notified
128 /// multiple times. You can also get notified of BVA variables, aka
129 /// variables you did not declare.
130 fn notify_equivalence(&mut self, first: Lit, second: Lit) {}
131}
132
133/// A handle to an attached proof tracer in order to be able to detach it again
134///
135/// This is intentionally not [`Clone`] or [`Copy`] so that it cannot be used after the tracer has
136/// been disconnected from the solver
137#[derive(Debug)]
138pub struct ProofTracerHandle<PT> {
139 c_class: *mut ffi::CCaDiCaLTracer,
140 tracer: *mut PT,
141 trait_ptr: *mut *mut dyn TraceProof,
142}
143
144impl<PT> Drop for ProofTracerHandle<PT> {
145 fn drop(&mut self) {
146 let trait_ptr = unsafe { Box::from_raw(self.trait_ptr) };
147 drop(trait_ptr);
148 let tracer = unsafe { Box::from_raw(self.tracer) };
149 drop(tracer);
150 }
151}
152
153/// Error stating that a provided proof tracer was not connected to the solver
154#[derive(Clone, Copy, Debug, thiserror::Error)]
155#[error("the provided proof tracer handle is not connected to the solver")]
156pub struct ProofTracerNotConnected;
157
158impl super::CaDiCaL<'_, '_> {
159 /// Connects a proof tracer to the solver
160 ///
161 /// **Note**: in order to not leak memory, dropping the [`ProofTracerHandle`] will drop the
162 /// proof tracer. Ensure therefore that the handle is not dropped before the solver is not used
163 /// anymore, or call [`Self::disconnect_proof_tracer`], if you do not need the proof tracer
164 /// anymore.
165 pub fn connect_proof_tracer<PT>(
166 &mut self,
167 tracer: PT,
168 antecedents: bool,
169 ) -> ProofTracerHandle<PT>
170 where
171 PT: TraceProof + 'static,
172 {
173 let tracer = Box::new(tracer);
174 let tracer = Box::into_raw(tracer);
175 let trait_ptr = Box::new(tracer as *mut dyn TraceProof);
176 let trait_ptr = Box::into_raw(trait_ptr);
177 let ptr = unsafe {
178 ffi::ccadical_connect_proof_tracer(
179 self.handle,
180 trait_ptr.cast::<c_void>(),
181 ffi::prooftracer::DISPATCH_CALLBACKS,
182 antecedents,
183 )
184 };
185 ProofTracerHandle {
186 c_class: ptr,
187 tracer,
188 trait_ptr,
189 }
190 }
191
192 /// Disconnects a proof tracer from the solver
193 ///
194 /// # Errors
195 ///
196 /// If the handle is not connected to the given solver, returns [`ProofTracerNotConnected`]
197 // We intentionally pass the handle by value here so that it cannot be used afterwards, since
198 // it is not Clone of Copy
199 #[allow(clippy::needless_pass_by_value)]
200 pub fn disconnect_proof_tracer<PT>(
201 &mut self,
202 handle: ProofTracerHandle<PT>,
203 ) -> Result<PT, ProofTracerNotConnected>
204 where
205 PT: TraceProof + 'static,
206 {
207 if !unsafe { ffi::ccadical_disconnect_proof_tracer(self.handle, handle.c_class) } {
208 return Err(ProofTracerNotConnected);
209 }
210 let trait_ptr = unsafe { Box::from_raw(handle.trait_ptr) };
211 drop(trait_ptr);
212 let tracer = unsafe { Box::from_raw(handle.tracer) };
213 // avoid dropping tracer in drop implementation
214 let _ = std::mem::ManuallyDrop::new(handle);
215 Ok(*tracer)
216 }
217
218 /// Gets a mutable reference to a connected proof tracer
219 // We are intentionally taking self, since the solver "owns" the tracer, even though the
220 // compiler doesn't know this
221 #[allow(clippy::unused_self)]
222 // The handle can only have originated from connect_proof_tracer, the pointer can therefore
223 // never be null
224 #[allow(clippy::missing_panics_doc)]
225 pub fn proof_tracer_mut<PT>(&mut self, handle: &ProofTracerHandle<PT>) -> &mut PT
226 where
227 PT: TraceProof + 'static,
228 {
229 unsafe { handle.tracer.as_mut() }.expect("unexpected null ptr")
230 }
231}
232
233/// A clause passed from CaDiCaL
234///
235/// This performs lazy conversion to [`Lit`] in order to avoid unnecessary memory allocations and
236/// work
237#[derive(Debug)]
238pub struct CaDiCaLClause<'slf> {
239 lits: &'slf [c_int],
240}
241
242impl CaDiCaLClause<'_> {
243 pub(crate) fn new(cl_len: usize, cl_data: *const c_int) -> Self {
244 Self {
245 lits: unsafe { from_raw_parts_maybe_null(cl_data, cl_len) },
246 }
247 }
248
249 /// Gets an iterator over the literals in the clause
250 ///
251 /// # Panics
252 ///
253 /// If CaDiCaL passes an invalid IPASIR literal
254 pub fn iter(&self) -> impl Iterator<Item = Lit> + '_ {
255 self.lits
256 .iter()
257 .map(|&lit| Lit::from_ipasir(lit).expect("got invalid literal from CaDiCaL"))
258 }
259
260 /// Gets the length of the clause
261 #[inline]
262 #[must_use]
263 pub fn len(&self) -> usize {
264 self.lits.len()
265 }
266
267 /// Checks if the clause is empty
268 #[inline]
269 #[must_use]
270 pub fn is_empty(&self) -> bool {
271 self.lits.is_empty()
272 }
273
274 /// Converts the clause into an owned [`Clause`]
275 ///
276 /// This is costly because of IPASIR to [`Lit`] conversion and memory allocation
277 #[must_use]
278 pub fn to_owned(&self) -> Clause {
279 self.iter().collect()
280 }
281}
282
283#[cfg(feature = "pigeons")]
284impl pigeons::ConstraintLike<rustsat::types::Var> for CaDiCaLClause<'_> {
285 fn rhs(&self) -> isize {
286 1
287 }
288
289 fn sum_iter(&self) -> impl Iterator<Item = (isize, pigeons::Axiom<rustsat::types::Var>)> {
290 self.iter().map(|l| (1, pigeons::Axiom::from(l)))
291 }
292}
293
294/// An assignment passed from CaDiCaL
295///
296/// This performs lazy conversion to [`Lit`] in order to avoid unnecessary memory allocations and
297/// work
298#[derive(Debug)]
299pub struct CaDiCaLAssignment<'slf> {
300 lits: &'slf [c_int],
301}
302
303impl CaDiCaLAssignment<'_> {
304 pub(crate) fn new(assign_len: usize, assign_data: *const c_int) -> Self {
305 Self {
306 lits: unsafe { from_raw_parts_maybe_null(assign_data, assign_len) },
307 }
308 }
309
310 /// Gets an iterator over the literals in the assignment
311 ///
312 /// # Panics
313 ///
314 /// If CaDiCaL passes an invalid IPASIR literal
315 pub fn iter(&self) -> impl Iterator<Item = Lit> + '_ {
316 self.lits
317 .iter()
318 .map(|&lit| Lit::from_ipasir(lit).expect("got invalid literal from CaDiCaL"))
319 }
320
321 /// Gets the length of the clause
322 #[inline]
323 #[must_use]
324 pub fn len(&self) -> usize {
325 self.lits.len()
326 }
327
328 /// Checks if the clause is empty
329 #[inline]
330 #[must_use]
331 pub fn is_empty(&self) -> bool {
332 self.lits.is_empty()
333 }
334
335 /// Converts the clause into an owned [`rustsat::types::Assignment`]
336 ///
337 /// This is costly because of IPASIR to [`Lit`] conversion and memory allocation
338 #[must_use]
339 pub fn to_owned(&self) -> Assignment {
340 self.iter().collect()
341 }
342}
343
344#[cfg(test)]
345mod test {
346 use crate::CaDiCaL;
347
348 struct Tracer;
349
350 impl super::TraceProof for Tracer {}
351
352 #[test]
353 fn connect_tracer() {
354 let mut slv = CaDiCaL::default();
355 let handle = slv.connect_proof_tracer(Tracer, false);
356 let _ = slv.disconnect_proof_tracer(handle).unwrap();
357 }
358}