Skip to main content

drcp_format/reader/
mod.rs

1//! Proofs can be read using a [`ProofReader`]. It reads the proof line-by-line, and can be thought
2//! of as an Iterator over [`Step`].
3//!
4//! The reader does not do any form of validation of the proof. It is up to the consumer of this
5//! crate to ensure that constraint IDs are valid, that inference labels make sense, etc.
6//!
7//! ```
8//! use std::num::NonZero;
9//! use std::rc::Rc;
10//!
11//! use drcp_format::Conclusion;
12//! use drcp_format::Deduction;
13//! use drcp_format::Inference;
14//! use drcp_format::IntAtomic;
15//! use drcp_format::IntComparison::*;
16//! use drcp_format::Step;
17//! use drcp_format::reader::ProofReader;
18//!
19//! let source = r#"
20//!     a 1 [x1 >= 0]
21//!     a 2 [x2 >= 0]
22//!     i 2 1 0 2 c:1 l:inf_name
23//!     n 3 1 -2 0 2
24//!     c UNSAT
25//! "#;
26//!
27//! let a1 = IntAtomic {
28//!     name: Rc::from("x1".to_owned()),
29//!     comparison: GreaterEqual,
30//!     value: 0,
31//! };
32//!
33//! let a2 = IntAtomic {
34//!     name: Rc::from("x2".to_owned()),
35//!     comparison: GreaterEqual,
36//!     value: 0,
37//! };
38//!
39//! let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
40//!
41//! let inference = reader
42//!     .next_step()
43//!     .expect("no error reading")
44//!     .expect("proof step exists");
45//!
46//! assert_eq!(
47//!     inference,
48//!     Step::Inference(Inference {
49//!         constraint_id: NonZero::new(2).unwrap(),
50//!         premises: vec![a1.clone()],
51//!         consequent: Some(a2.clone()),
52//!         generated_by: Some(NonZero::new(1).unwrap()),
53//!         label: Some("inf_name".into()),
54//!     })
55//! );
56//!
57//! let deduction = reader
58//!     .next_step()
59//!     .expect("no error reading")
60//!     .expect("proof step exists");
61//!
62//! assert_eq!(
63//!     deduction,
64//!     Step::Deduction(Deduction {
65//!         constraint_id: NonZero::new(3).unwrap(),
66//!         premises: vec![a1.clone(), !a2],
67//!         sequence: vec![NonZero::new(2).unwrap()],
68//!     })
69//! );
70//!
71//! let conclusion = reader
72//!     .next_step()
73//!     .expect("no error reading")
74//!     .expect("proof step exists");
75//!
76//! assert_eq!(conclusion, Step::Conclusion(Conclusion::Unsat));
77//!
78//! let eof = reader.next_step().expect("no error reading");
79//! assert_eq!(eof, None);
80//! ```
81
82mod error;
83mod parser;
84
85use std::collections::BTreeMap;
86use std::collections::HashSet;
87use std::io;
88use std::num::NonZero;
89use std::rc::Rc;
90
91pub use error::*;
92
93use crate::IntAtomic;
94use crate::SignedIntValue;
95use crate::Step;
96
97/// A parser of DRCP proofs. See module documentation on [`crate::reader`] for examples on how to
98/// use it.
99#[derive(Debug)]
100pub struct ProofReader<Source, Int> {
101    /// The source of the proof.
102    source: Source,
103
104    /// The defined atomics that are used in the proof.
105    atomics: BTreeMap<NonZero<u32>, IntAtomic<Rc<str>, Int>>,
106
107    /// A set of all the identifiers encountered, used for interning.
108    identifiers: HashSet<Rc<str>>,
109
110    /// The buffer that holds the current line of `source`.
111    line_buffer: Vec<u8>,
112
113    /// The line number currently being processed in the proof.
114    ///
115    /// Used for error reporting.
116    line_nr: usize,
117}
118
119impl<Source, Int> ProofReader<Source, Int> {
120    /// Create a new [`ProofReader`].
121    ///
122    /// Note that [`ProofReader::next_step`] is only implemented for sources that implement
123    /// [`io::BufRead`]. It is up to the user to set up the buffered reader.
124    pub fn new(source: Source) -> Self {
125        ProofReader {
126            source,
127            atomics: BTreeMap::default(),
128            identifiers: HashSet::default(),
129            line_buffer: Vec::new(),
130            line_nr: 0,
131        }
132    }
133}
134
135/// The [`IntAtomic`] type that is produced by the [`ProofReader`].
136pub type ReadAtomic<Int> = IntAtomic<Rc<str>, Int>;
137/// The [`Step`] type that is produced by the [`ProofReader`].
138pub type ReadStep<Int> = Step<Rc<str>, Int, Rc<str>>;
139
140impl<Source, Int> ProofReader<Source, Int>
141where
142    Source: io::BufRead,
143    Int: SignedIntValue,
144{
145    /// Parse the next [`Step`] from the proof file.
146    ///
147    /// The end-of-file is signified by the value `Ok(None)` and is _not_ an error.
148    pub fn next_step(&mut self) -> Result<Option<ReadStep<Int>>, Error> {
149        loop {
150            self.line_buffer.clear();
151            let read_bytes = self.source.read_until(b'\n', &mut self.line_buffer)?;
152
153            // Reading 0 bytes indicates we have reached the end of the file.
154            if read_bytes == 0 {
155                return Ok(None);
156            }
157
158            self.line_nr += 1;
159
160            let line_parser = parser::LineParser::new(
161                &self.line_buffer,
162                self.line_nr,
163                &mut self.identifiers,
164                &mut self.atomics,
165            );
166
167            let Some(proof_line) = line_parser.parse()? else {
168                continue;
169            };
170
171            return Ok(Some(proof_line));
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use crate::Conclusion;
180    use crate::Deduction;
181    use crate::Inference;
182    use crate::IntComparison::*;
183
184    #[test]
185    fn inference_with_consequent() {
186        let source = r#"
187            a 1 [x1 >= 0]
188            a 2 [x2 >= 0]
189            i 2 1 0 2 c:1 l:inf_name
190        "#;
191
192        let a1 = IntAtomic {
193            name: Rc::from("x1".to_owned()),
194            comparison: GreaterEqual,
195            value: 0,
196        };
197
198        let a2 = IntAtomic {
199            name: Rc::from("x2".to_owned()),
200            comparison: GreaterEqual,
201            value: 0,
202        };
203
204        test_single_proof_line(
205            source,
206            Step::Inference(Inference {
207                constraint_id: NonZero::new(2).unwrap(),
208                premises: vec![a1],
209                consequent: Some(a2),
210                generated_by: Some(NonZero::new(1).unwrap()),
211                label: Some("inf_name".into()),
212            }),
213        );
214    }
215
216    #[test]
217    fn inference_with_consequent_hints_reversed() {
218        let source = r#"
219            a 1 [x1 >= 0]
220            a 2 [x2 >= 0]
221            i 2 1 0 2 l:inf_name c:1
222        "#;
223
224        let a1 = IntAtomic {
225            name: Rc::from("x1".to_owned()),
226            comparison: GreaterEqual,
227            value: 0,
228        };
229
230        let a2 = IntAtomic {
231            name: Rc::from("x2".to_owned()),
232            comparison: GreaterEqual,
233            value: 0,
234        };
235
236        test_single_proof_line(
237            source,
238            Step::Inference(Inference {
239                constraint_id: NonZero::new(2).unwrap(),
240                premises: vec![a1],
241                consequent: Some(a2),
242                generated_by: Some(NonZero::new(1).unwrap()),
243                label: Some("inf_name".into()),
244            }),
245        );
246    }
247    #[test]
248    fn inference_with_consequent_and_negative_atomic_values() {
249        let source = r#"
250            a 1 [x1 >= -1]
251            a 2 [x2 >= -10]
252            i 2 1 0 2 c:1 l:inf_name
253        "#;
254
255        let a1 = IntAtomic {
256            name: Rc::from("x1".to_owned()),
257            comparison: GreaterEqual,
258            value: -1,
259        };
260
261        let a2 = IntAtomic {
262            name: Rc::from("x2".to_owned()),
263            comparison: GreaterEqual,
264            value: -10,
265        };
266
267        test_single_proof_line(
268            source,
269            Step::Inference(Inference {
270                constraint_id: NonZero::new(2).unwrap(),
271                premises: vec![a1],
272                consequent: Some(a2),
273                generated_by: Some(NonZero::new(1).unwrap()),
274                label: Some("inf_name".into()),
275            }),
276        );
277    }
278
279    #[test]
280    fn inference_without_consequent() {
281        let source = r#"
282            a 1 [x1 >= 0]
283            a 2 [x2 >= 0]
284            i 2 1 -2 0 c:1 l:inf_name
285        "#;
286
287        let a1 = IntAtomic {
288            name: Rc::from("x1".to_owned()),
289            comparison: GreaterEqual,
290            value: 0,
291        };
292
293        let a2 = IntAtomic {
294            name: Rc::from("x2".to_owned()),
295            comparison: LessEqual,
296            value: -1,
297        };
298
299        test_single_proof_line(
300            source,
301            Step::Inference(Inference {
302                constraint_id: NonZero::new(2).unwrap(),
303                premises: vec![a1, a2],
304                consequent: None,
305                generated_by: Some(NonZero::new(1).unwrap()),
306                label: Some("inf_name".into()),
307            }),
308        );
309    }
310
311    #[test]
312    fn deduction_without_inferences() {
313        let source = r#"
314            a 1 [x1 >= 0]
315            a 2 [x2 >= 0]
316            n 2 1 -2 0
317        "#;
318
319        let a1 = IntAtomic {
320            name: Rc::from("x1".to_owned()),
321            comparison: GreaterEqual,
322            value: 0,
323        };
324
325        let a2 = IntAtomic {
326            name: Rc::from("x2".to_owned()),
327            comparison: LessEqual,
328            value: -1,
329        };
330
331        test_single_proof_line(
332            source,
333            Step::Deduction(Deduction {
334                constraint_id: NonZero::new(2).unwrap(),
335                premises: vec![a1, a2],
336                sequence: vec![],
337            }),
338        );
339    }
340
341    #[test]
342    fn deduction_with_inferences() {
343        let source = r#"
344            a 1 [x1 >= 0]
345            a 2 [x2 >= 0]
346            n 5 1 -2 0 1 3 2 4
347        "#;
348
349        let a1 = IntAtomic {
350            name: Rc::from("x1".to_owned()),
351            comparison: GreaterEqual,
352            value: 0,
353        };
354
355        let a2 = IntAtomic {
356            name: Rc::from("x2".to_owned()),
357            comparison: LessEqual,
358            value: -1,
359        };
360
361        test_single_proof_line(
362            source,
363            Step::Deduction(Deduction {
364                constraint_id: NonZero::new(5).unwrap(),
365                premises: vec![a1, a2],
366                sequence: vec![
367                    NonZero::new(1).unwrap(),
368                    NonZero::new(3).unwrap(),
369                    NonZero::new(2).unwrap(),
370                    NonZero::new(4).unwrap(),
371                ],
372            }),
373        );
374    }
375
376    #[test]
377    fn conclusion_unsat() {
378        let source = r#"
379            c UNSAT
380        "#;
381
382        test_single_proof_line(source, Step::Conclusion(Conclusion::Unsat));
383    }
384
385    #[test]
386    fn conclusion_dual_bound() {
387        let source = r#"
388            a 1 [x1 >= 4]
389            c 1
390        "#;
391
392        let a1 = IntAtomic {
393            name: Rc::from("x1".to_owned()),
394            comparison: GreaterEqual,
395            value: 4,
396        };
397
398        test_single_proof_line(source, Step::Conclusion(Conclusion::DualBound(a1)));
399    }
400
401    fn test_single_proof_line(source: &str, expected_step: ReadStep<i32>) {
402        let mut reader = ProofReader::<_, i32>::new(source.as_bytes());
403
404        let parsed_step = reader
405            .next_step()
406            .expect("no error reading")
407            .expect("there is one proof step");
408
409        assert_eq!(expected_step, parsed_step);
410    }
411}