Skip to main content

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}