cadical_veripb_tracer/
lib.rs

1//! # VeriPB Proof Tracer For CaDiCaL Through RustSAT
2
3use std::io;
4
5use pigeons::{AbsConstraintId, Conclusion, ConstraintId, OutputGuarantee, Proof};
6use rustsat::{
7    encodings::{cert::CollectClauses as CollectCertClauses, CollectClauses},
8    solvers::SolverResult,
9    types::{Clause, Lit, RsHashSet, Var},
10};
11use rustsat_cadical::{CaDiCaL, CaDiCaLClause, ClauseId, ProofTracerHandle, TraceProof};
12
13#[derive(Debug)]
14pub struct CadicalTracer<ProofW: io::Write> {
15    /// The proof to be passed back and forth between the tracer and the kernel
16    proof: Option<Proof<ProofW>>,
17    /// The constraint ID mapper
18    cmap: ConstraintMapper,
19    /// The [`AbsConstraintId`] of the last found core
20    core_id: Option<AbsConstraintId>,
21    /// The set of weakened clauses
22    weakened_clauses: RsHashSet<ClauseId>,
23    assumptions: Vec<Lit>,
24}
25
26impl<ProofW: io::Write> CadicalTracer<ProofW> {
27    pub fn new(proof: Proof<ProofW>) -> Self {
28        CadicalTracer {
29            proof: Some(proof),
30            cmap: ConstraintMapper::default(),
31            core_id: None,
32            weakened_clauses: RsHashSet::default(),
33            assumptions: vec![],
34        }
35    }
36
37    pub fn proof_mut(&mut self) -> &mut Proof<ProofW> {
38        self.proof.as_mut().expect("expected proof")
39    }
40
41    pub fn take_proof(&mut self) -> Proof<ProofW> {
42        let Some(proof) = self.proof.take() else {
43            panic!("expected proof")
44        };
45        proof
46    }
47
48    pub fn replace_proof(&mut self, proof: Proof<ProofW>) -> Option<Proof<ProofW>> {
49        self.proof.replace(proof)
50    }
51
52    pub fn core_id(&self) -> Option<AbsConstraintId> {
53        self.core_id
54    }
55}
56
57impl<ProofW: io::Write> CadicalTracer<ProofW>
58where
59    ProofW: io::Write,
60{
61    fn add_derived(
62        &mut self,
63        id: ClauseId,
64        redundant: bool,
65        clause: &CaDiCaLClause,
66        antecedents: &[ClauseId],
67    ) -> AbsConstraintId {
68        debug_assert!(!antecedents.is_empty());
69        let proof = self.proof.as_mut().expect("expected proof");
70        let veripb_id = write_derived(
71            proof,
72            clause,
73            antecedents.iter().map(|id| self.cmap.map(*id)),
74        );
75        self.cmap.add_clause_checked(veripb_id, id);
76        #[cfg(feature = "verbose")]
77        proof
78            .equals(clause, Some(pigeons::ConstraintId::last(1)))
79            .expect("failed to write proof");
80        if !redundant {
81            proof
82                .move_ids_to_core([pigeons::ConstraintId::from(veripb_id)])
83                .expect("failed to write proof");
84        }
85        veripb_id
86    }
87}
88
89impl<ProofW: io::Write> TraceProof for CadicalTracer<ProofW>
90where
91    ProofW: io::Write,
92{
93    fn add_original_clause(
94        &mut self,
95        id: ClauseId,
96        _redundant: bool,
97        _clause: &CaDiCaLClause,
98        restored: bool,
99    ) {
100        if restored {
101            return;
102        }
103        debug_assert_eq!(usize::try_from(id.0).unwrap(), self.cmap.map.len());
104        let proof = self.proof.as_mut().expect("expected proof");
105        #[cfg(feature = "verbose")]
106        {
107            proof
108                .equals(_clause, Some(self.cmap.map(id).into()))
109                .expect("failed to write proof");
110        }
111        let veripb_id = self.cmap.map(id);
112        if veripb_id >= proof.first_proof_id() {
113            proof
114                .move_ids_to_core([veripb_id.into()])
115                .expect("failed to write proof");
116        }
117    }
118
119    fn add_derived_clause(
120        &mut self,
121        id: ClauseId,
122        redundant: bool,
123        clause: &CaDiCaLClause,
124        antecedents: &[ClauseId],
125    ) {
126        self.add_derived(id, redundant, clause, antecedents);
127    }
128
129    fn delete_clause(&mut self, id: ClauseId, redundant: bool, _clause: &CaDiCaLClause) {
130        if !redundant {
131            // don't delete clauses that are not redundant
132            // NOTE: in cadicals proof tracer itself, this is `!redundant &&
133            // self.weakened_clauses.contains(&id)`, but that might delete clauses in the proof
134            // that scuttle thinks are still there
135            return;
136        }
137        let id = self.cmap.map(id);
138        let proof = self.proof.as_mut().expect("expected proof");
139        proof
140            .delete_ids::<Var, CaDiCaLClause, _, _>([id.into()], None)
141            .expect("failed to write proof")
142    }
143
144    fn strengthen(&mut self, id: ClauseId) {
145        self.proof
146            .as_mut()
147            .expect("expected proof")
148            .move_ids_to_core([self.cmap.map(id).into()])
149            .expect("failed to write proof");
150    }
151
152    fn weaken_minus(&mut self, id: ClauseId, _clause: &CaDiCaLClause) {
153        self.weakened_clauses.insert(id);
154    }
155
156    fn report_status(&mut self, status: SolverResult, id: ClauseId) {
157        let id = if id.0 > 0 {
158            Some(self.cmap.map(id))
159        } else {
160            None
161        };
162        #[cfg(feature = "verbose")]
163        self.proof
164            .as_mut()
165            .expect("expected proof")
166            .comment(&format_args!(
167                "CaDiCaL: finished solving: {status}, id: {id:?}",
168            ))
169            .expect("failed to write proof");
170        if let Some(id) = id {
171            debug_assert_eq!(status, SolverResult::Unsat);
172            self.proof
173                .as_mut()
174                .expect("expected proof")
175                .update_default_conclusion::<Var>(
176                    OutputGuarantee::None,
177                    &Conclusion::Unsat(Some(ConstraintId::from(id))),
178                );
179            #[cfg(feature = "verbose")]
180            self.proof
181                .as_mut()
182                .expect("expected proof")
183                .equals(&rustsat::clause![], Some(ConstraintId::from(id)))
184                .expect("failed to write proof");
185        }
186    }
187
188    fn solve_query(&mut self) {
189        #[cfg(feature = "verbose")]
190        {
191            use itertools::Itertools;
192            let proof = self.proof.as_mut().expect("expected proof");
193            proof
194                .comment(&"CaDiCaL: start solving with the following assumptions")
195                .expect("failed to write proof");
196            proof
197                .comment(&format_args!(
198                    "[{}]",
199                    self.assumptions
200                        .iter()
201                        .map(|l| pigeons::Axiom::from(*l))
202                        .format(", ")
203                ))
204                .expect("failed to write proof");
205        }
206    }
207
208    fn add_assumption(&mut self, assumption: Lit) {
209        self.assumptions.push(assumption)
210    }
211
212    fn reset_assumptions(&mut self) {
213        self.assumptions.clear()
214    }
215
216    fn add_assumption_clause(
217        &mut self,
218        id: ClauseId,
219        clause: &CaDiCaLClause,
220        antecedents: &[ClauseId],
221    ) {
222        //#[cfg(feature = "verbose")]
223        //{
224        //    let proof = self.proof.as_mut().expect("expected proof");
225        //    proof
226        //        .comment(&"the next constraint is a core found by CaDiCaL")
227        //        .expect("failed to write proof");
228        //}
229        let id = self.add_derived(id, true, clause, antecedents);
230        self.core_id = Some(id);
231    }
232}
233
234fn write_derived<ProofW, I>(
235    proof: &mut Proof<ProofW>,
236    _clause: &CaDiCaLClause,
237    antecedents: I,
238) -> AbsConstraintId
239where
240    ProofW: io::Write,
241    I: IntoIterator<Item = AbsConstraintId, IntoIter: DoubleEndedIterator>,
242{
243    cfg_if::cfg_if! {
244        if #[cfg(feature = "rup-hints")] {
245            use pigeons::ConstraintId;
246            proof.reverse_unit_prop(_clause, antecedents.into_iter().map(ConstraintId::from))
247                .expect("failed to write proof")
248        } else {
249            use pigeons::{OperationLike, OperationSequence};
250            let mut antecedents = antecedents.into_iter().rev();
251            let Some(first) = antecedents.next() else {
252                panic!("need antecedents for derived clause")
253            };
254            let derivation = antecedents.fold(OperationSequence::<rustsat::types::Var>::from(first), |der, id| {
255                (der + id).saturate()
256            });
257            proof.operations(&derivation).expect("failed to write proof")
258        }
259    }
260}
261
262#[derive(Default, Debug)]
263struct ConstraintMapper {
264    map: Vec<AbsConstraintId>,
265}
266
267impl ConstraintMapper {
268    pub fn add_clause(&mut self, id: AbsConstraintId) {
269        self.map.push(id);
270    }
271
272    pub fn add_clause_checked(&mut self, id: AbsConstraintId, clause_id: ClauseId) {
273        self.map.push(id);
274        assert_eq!(u64::try_from(self.map.len()).unwrap(), clause_id.0);
275    }
276
277    pub fn map(&self, id: ClauseId) -> AbsConstraintId {
278        self.map[usize::try_from(id.0 - 1).expect("`ClauseId` does not fit in `usize`")]
279    }
280}
281
282pub struct CadicalCertCollector<'cadical, 'term, 'learn, ProofW: io::Write> {
283    cadical: &'cadical mut CaDiCaL<'term, 'learn>,
284    pt_handle: &'cadical ProofTracerHandle<CadicalTracer<ProofW>>,
285}
286
287impl<'cadical, 'term, 'learn, ProofW: io::Write>
288    CadicalCertCollector<'cadical, 'term, 'learn, ProofW>
289{
290    pub fn new(
291        cadical: &'cadical mut CaDiCaL<'term, 'learn>,
292        pt_handle: &'cadical ProofTracerHandle<CadicalTracer<ProofW>>,
293    ) -> Self {
294        Self { cadical, pt_handle }
295    }
296}
297
298impl<ProofW: io::Write + 'static> CollectCertClauses for CadicalCertCollector<'_, '_, '_, ProofW>
299where
300    ProofW: io::Write,
301{
302    fn extend_cert_clauses<T>(&mut self, cl_iter: T) -> Result<(), rustsat::OutOfMemory>
303    where
304        T: IntoIterator<Item = (Clause, AbsConstraintId)>,
305    {
306        for (cl, id) in cl_iter.into_iter() {
307            self.add_cert_clause(cl, id)?;
308        }
309        Ok(())
310    }
311
312    fn add_cert_clause(
313        &mut self,
314        cl: Clause,
315        id: AbsConstraintId,
316    ) -> Result<(), rustsat::OutOfMemory> {
317        let tracer = self.cadical.proof_tracer_mut(self.pt_handle);
318        tracer.cmap.add_clause(id);
319        self.cadical.add_clause(cl)
320    }
321}
322
323// Passthrough
324impl<ProofW: io::Write> CollectClauses for CadicalCertCollector<'_, '_, '_, ProofW> {
325    fn n_clauses(&self) -> usize {
326        self.cadical.n_clauses()
327    }
328
329    fn extend_clauses<T>(&mut self, cl_iter: T) -> Result<(), rustsat::OutOfMemory>
330    where
331        T: IntoIterator<Item = Clause>,
332    {
333        self.cadical.extend_clauses(cl_iter)
334    }
335
336    fn add_clause(&mut self, cl: Clause) -> Result<(), rustsat::OutOfMemory> {
337        self.cadical.add_clause(cl)
338    }
339}