1use std::{
3 io::{BufWriter, Write},
4 mem::replace,
5};
6
7use anyhow::Error;
8
9use varisat_checker::{CheckedProofStep, CheckerData, ProofProcessor};
10use varisat_formula::Lit;
11
12pub struct WriteLrat<'a> {
14 binary: bool,
15 target: BufWriter<Box<dyn Write + 'a>>,
16 delete_open: bool,
17 last_added_id: u64,
18 buffered_deletes: Vec<u64>,
19}
20
21impl<'a> ProofProcessor for WriteLrat<'a> {
22 fn process_step(&mut self, step: &CheckedProofStep, _data: CheckerData) -> Result<(), Error> {
23 match step {
24 CheckedProofStep::AddClause { .. } => (),
25 CheckedProofStep::DuplicatedClause { .. } => (),
26 _ => {
27 if !self.buffered_deletes.is_empty() {
28 let buffered_deletes = replace(&mut self.buffered_deletes, vec![]);
29 self.open_delete()?;
30 self.write_ids(&buffered_deletes)?;
31 }
32 }
33 }
34
35 match step {
36 &CheckedProofStep::AddClause { id, .. } => {
37 self.last_added_id = id;
38 }
39 &CheckedProofStep::DuplicatedClause { id, .. }
40 | &CheckedProofStep::TautologicalClause { id, .. } => {
41 self.last_added_id = id;
42 if self.binary {
43 self.open_delete()?;
44 self.write_ids(&[id])?;
45 } else {
46 self.buffered_deletes.push(id);
49 }
50 }
51 &CheckedProofStep::AtClause {
52 id,
53 clause,
54 propagations,
55 ..
56 } => {
57 self.close_delete()?;
58 self.last_added_id = id;
59 self.write_add_step()?;
60 self.write_ids(&[id])?;
61 self.write_lits(clause)?;
62 self.write_sep()?;
63 self.write_ids(propagations)?;
64 self.write_end()?;
65 }
66 &CheckedProofStep::DeleteAtClause {
67 id,
68 keep_as_redundant,
69 ..
70 }
71 | &CheckedProofStep::DeleteRatClause {
72 id,
73 keep_as_redundant,
74 ..
75 } => {
76 if !keep_as_redundant {
77 self.open_delete()?;
78 self.write_ids(&[id])?;
79 }
80 }
81 &CheckedProofStep::DeleteClause { id, .. } => {
82 self.open_delete()?;
83 self.write_ids(&[id])?;
84 }
85 &CheckedProofStep::UserVar { .. }
86 | &CheckedProofStep::MakeIrredundant { .. }
87 | &CheckedProofStep::Model { .. }
88 | &CheckedProofStep::Assumptions { .. }
89 | &CheckedProofStep::FailedAssumptions { .. } => (),
90 }
91 Ok(())
92 }
93}
94
95impl<'a> WriteLrat<'a> {
96 pub fn new(target: impl Write + 'a, binary: bool) -> WriteLrat<'a> {
103 WriteLrat {
104 binary,
105 target: BufWriter::new(Box::new(target)),
106 delete_open: false,
107 last_added_id: 0,
108 buffered_deletes: vec![],
109 }
110 }
111
112 pub fn flush(&mut self) -> Result<(), Error> {
117 self.close_delete()?;
118 self.target.flush()?;
119 Ok(())
120 }
121
122 fn open_delete(&mut self) -> Result<(), Error> {
124 if !self.delete_open {
125 if !self.binary {
126 self.write_ids(&[self.last_added_id])?;
127 }
128 self.write_delete_step()?;
129 self.delete_open = true;
130 }
131 Ok(())
132 }
133
134 fn close_delete(&mut self) -> Result<(), Error> {
136 if self.delete_open {
137 self.write_end()?;
138 self.delete_open = false;
139 }
140 Ok(())
141 }
142
143 fn write_delete_step(&mut self) -> Result<(), Error> {
145 if self.binary {
146 self.target.write_all(b"d")?;
147 } else {
148 self.target.write_all(b"d ")?;
149 }
150 Ok(())
151 }
152
153 fn write_add_step(&mut self) -> Result<(), Error> {
155 if self.binary {
156 self.target.write_all(b"a")?;
157 }
158 Ok(())
159 }
160
161 fn write_ids(&mut self, ids: &[u64]) -> Result<(), Error> {
163 if self.binary {
164 for &id in ids {
165 leb128::write::unsigned(&mut self.target, (id + 1) * 2)?;
166 }
167 } else {
168 for &id in ids {
169 itoa::write(&mut self.target, id + 1)?;
170 self.target.write_all(b" ")?;
171 }
172 }
173 Ok(())
174 }
175
176 fn write_lits(&mut self, lits: &[Lit]) -> Result<(), Error> {
178 if self.binary {
179 for &lit in lits {
180 leb128::write::unsigned(&mut self.target, lit.code() as u64 + 2)?;
181 }
182 } else {
183 for &lit in lits {
184 itoa::write(&mut self.target, lit.to_dimacs())?;
185 self.target.write_all(b" ")?;
186 }
187 }
188 Ok(())
189 }
190
191 fn write_end(&mut self) -> Result<(), Error> {
193 if self.binary {
194 self.target.write_all(&[0])?
195 } else {
196 self.target.write_all(b"0\n")?
197 }
198 Ok(())
199 }
200
201 fn write_sep(&mut self) -> Result<(), Error> {
203 if self.binary {
204 self.target.write_all(&[0])?
205 } else {
206 self.target.write_all(b"0 ")?
207 }
208 Ok(())
209 }
210}
211
212impl<'a> Drop for WriteLrat<'a> {
213 fn drop(&mut self) {
214 let _ignore_errors = self.close_delete();
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 use proptest::prelude::*;
223
224 use std::{
225 fs::File,
226 path::PathBuf,
227 process::{Command, Stdio},
228 };
229
230 use tempfile::TempDir;
231
232 use varisat::{dimacs::write_dimacs, ProofFormat, Solver};
233 use varisat_checker::Checker;
234 use varisat_formula::{cnf_formula, test::sgen_unsat_formula, CnfFormula};
235
236 fn check_lrat(tool: &str, cnf_file: &PathBuf, proof_file: &PathBuf) -> Result<bool, Error> {
237 let mut child = Command::new(tool)
238 .stdin(Stdio::piped())
239 .stdout(Stdio::piped())
240 .spawn()?;
241
242 let mut stdin = child.stdin.as_mut().unwrap();
243 writeln!(&mut stdin, "(lrat-check {:?} {:?})", cnf_file, proof_file)?;
244
245 let output = child.wait_with_output()?;
246 let stdout = std::str::from_utf8(&output.stdout)?;
247
248 Ok(stdout.contains("s VERIFIED"))
249 }
250
251 fn solve_and_check_lrat(
252 formula: CnfFormula,
253 binary: bool,
254 direct: bool,
255 ) -> Result<bool, Error> {
256 let tmp = TempDir::new()?;
257
258 let lrat_proof = tmp.path().join("proof.lrat");
259 let cnf_file = tmp.path().join("input.cnf");
260
261 let mut dimacs = vec![];
262 let mut proof = vec![];
263
264 let mut write_lrat = WriteLrat::new(File::create(&lrat_proof)?, binary);
265 write_dimacs(&mut File::create(&cnf_file)?, &formula)?;
266
267 let mut solver = Solver::new();
268
269 write_dimacs(&mut dimacs, &formula).unwrap();
270
271 if direct {
272 solver.add_proof_processor(&mut write_lrat);
273 } else {
274 solver.write_proof(&mut proof, ProofFormat::Varisat);
275 }
276
277 solver.add_dimacs_cnf(&mut &dimacs[..]).unwrap();
278
279 assert_eq!(solver.solve().ok(), Some(false));
280
281 solver.close_proof()?;
282
283 drop(solver);
284
285 if !direct {
286 let mut checker = Checker::new();
287 checker.add_processor(&mut write_lrat);
288
289 checker.add_dimacs_cnf(&mut &dimacs[..]).unwrap();
290
291 checker.check_proof(&mut &proof[..]).unwrap();
292 }
293
294 drop(write_lrat);
295
296 check_lrat(
297 if binary { "check-clrat" } else { "check-lrat" },
298 &cnf_file,
299 &lrat_proof,
300 )
301 }
302
303 #[cfg_attr(not(test_check_lrat), ignore)]
304 #[test]
305 fn duplicated_clause_lrat() {
306 for &binary in [false, true].iter() {
307 for &direct in [false, true].iter() {
308 assert!(
309 solve_and_check_lrat(
310 cnf_formula![
311 1, 2;
312 1, 2;
313 -1, -2;
314 3;
315 -3, -1, 2;
316 -4, 1, -2;
317 4;
318 ],
319 binary,
320 direct
321 )
322 .unwrap(),
323 "binary: {:?} direct: {:?}",
324 binary,
325 direct
326 );
327 }
328 }
329 }
330
331 #[cfg_attr(not(test_check_lrat), ignore)]
332 #[test]
333 fn unit_conflict_lrat() {
334 for &binary in [false, true].iter() {
335 for &direct in [false, true].iter() {
336 assert!(
337 solve_and_check_lrat(
338 cnf_formula![
339 1;
340 2, 3;
341 -1;
342 4, 5;
343 ],
344 binary,
345 direct
346 )
347 .unwrap(),
348 "binary: {:?} direct: {:?}",
349 binary,
350 direct
351 );
352 }
353 }
354 }
355
356 proptest! {
357
358 #[cfg_attr(not(test_check_lrat), ignore)]
359 #[test]
360 fn sgen_unsat_lrat(
361 formula in sgen_unsat_formula(1..7usize),
362 binary in proptest::bool::ANY,
363 direct in proptest::bool::ANY,
364 ) {
365 prop_assert!(solve_and_check_lrat(formula, binary, direct).unwrap());
366 }
367 }
368}