rate_common/
sick.rs

1//! SICK incorrectness certificates
2
3use crate::{
4    as_error,
5    assignment::{stable_under_unit_propagation, Assignment},
6    clause::{Reason, RedundancyProperty},
7    clausedatabase::ClauseStorage,
8    literal::Literal,
9    memory::{Array, HeapSpace, Vector},
10    parser::{run_parser, HashTable, Parser},
11    puts, requires,
12};
13use serde_derive::{Deserialize, Serialize};
14
15/// A SICK certificate.
16#[derive(Serialize, Deserialize, Debug, Default)]
17pub struct Sick {
18    /// The string identifying the proof format
19    pub proof_format: String,
20    //// The line in the proof that failed
21    pub proof_step: Option<usize>,
22    /// The trail of the formula before any inference checks
23    pub natural_model: Vector<Literal>,
24    /// The list of witnesses (none for RUP, one for each pivot for RAT)
25    pub witness: Option<Vector<Witness>>,
26}
27
28/// The refutation of an inference given a witness
29#[derive(Clone, Serialize, Deserialize, Debug, Default)]
30pub struct Witness {
31    /// The candidate clause that failed to produce a conflict
32    pub failing_clause: Vector<Literal>,
33    /// The trail after the inference check failed
34    pub failing_model: Vector<Literal>,
35    /// If RAT, the pivot literal.
36    pub pivot: Option<Literal>,
37}
38
39// Can't derive HeapSpace for Option<T> yet.
40impl HeapSpace for Sick {
41    fn heap_space(&self) -> usize {
42        self.natural_model.heap_space() + self.witness.as_ref().map_or(0, HeapSpace::heap_space)
43    }
44}
45
46impl HeapSpace for Witness {
47    fn heap_space(&self) -> usize {
48        self.failing_clause.heap_space() + self.failing_model.heap_space()
49    }
50}
51
52/// Check a SICK certificate and prints an appropriate error message.
53/// Returns Ok(()) if accepted.
54pub fn check_incorrectness_certificate(
55    formula_filename: &str,
56    proof_filename: &str,
57    sick: Sick,
58    verbose: bool,
59) -> Result<(), ()> {
60    match check_incorrectness_certificate_aux(formula_filename, proof_filename, sick, verbose) {
61        Ok(()) => Ok(()),
62        Err(string) => {
63            as_error!({
64                puts!("{}\n", &string);
65                puts!("Proof claimed incorrect but validation failed, please report a bug!\n");
66            });
67            Err(())
68        }
69    }
70}
71
72/// Parses both formula and proof and checks the SICK certificate.
73/// Returns an error of what went wrong.
74fn check_incorrectness_certificate_aux(
75    formula_filename: &str,
76    proof_filename: &str,
77    sick: Sick,
78    verbose: bool,
79) -> Result<(), String> {
80    let redundancy_property = match sick.proof_format.as_ref() {
81        "DRAT-arbitrary-pivot" => RedundancyProperty::RAT,
82        "DRAT-pivot-is-first-literal" => RedundancyProperty::RAT,
83        "PR" => RedundancyProperty::PR,
84        format => return Err(format!("Unsupported proof format: {}", format)),
85    };
86    let mut clause_ids = HashTable::new();
87    let mut parser = Parser::default();
88    parser.max_proof_steps = sick.proof_step;
89    parser.verbose = verbose;
90    run_parser(
91        &mut parser,
92        formula_filename,
93        proof_filename,
94        &mut clause_ids,
95    );
96
97    let mut assignment = Assignment::new(parser.maxvar);
98    for &literal in &sick.natural_model {
99        if assignment[-literal] {
100            return Err(format!(
101                "Natural model is inconsistent in variable {}",
102                literal.variable()
103            ));
104        }
105        assignment.push(literal, Reason::assumed());
106    }
107    let proof_step = match sick.proof_step {
108        Some(proof_step) => proof_step,
109        None => {
110            return Ok(());
111        }
112    };
113    if proof_step > parser.proof.len() {
114        return Err(format!(
115            "Specified proof step exceeds proof size: {}",
116            proof_step
117        ));
118    }
119    let lemma = parser.proof[proof_step - 1].clause();
120    requires!(lemma.index < parser.clause_db.number_of_clauses());
121    if parser.clause_db.clause(lemma).is_empty() {
122        return Ok(());
123    }
124    let natural_model_length = assignment.len();
125
126    for &literal in parser.clause_db.clause(lemma) {
127        if !assignment[-literal] {
128            return Err(format!(
129                "Natural model does not falsify lemma literal {}",
130                literal
131            ));
132        }
133    }
134    // Delete the lemma, so it is not considered to be part of the formula.
135    clause_ids.delete_clause(&parser.clause_db, lemma);
136    for &clause in &clause_ids {
137        if clause < lemma
138            && !stable_under_unit_propagation(&assignment, parser.clause_db.clause(clause))
139        {
140            return Err(format!(
141                "Natural model is not the shared UP-model for clause {}",
142                parser.clause_db.clause_to_string(clause)
143            ));
144        }
145    }
146    let witnesses = sick.witness.unwrap_or_else(Vector::new);
147    const PIVOT: &str = "RAT requires to specify a pivot for each witness";
148    if redundancy_property == RedundancyProperty::RAT {
149        let mut specified_pivots: Vec<Literal> = witnesses
150            .iter()
151            .map(|witness| witness.pivot.expect(PIVOT))
152            .collect();
153        let mut expected_pivots: Vec<Literal> = parser
154            .clause_db
155            .clause(lemma)
156            .iter()
157            .filter(|&&l| l != Literal::BOTTOM)
158            .cloned()
159            .collect();
160        if !specified_pivots.is_empty() {
161            match sick.proof_format.as_ref() {
162                "DRAT-arbitrary-pivot" => {
163                    specified_pivots.sort();
164                    expected_pivots.sort();
165                    if specified_pivots != expected_pivots {
166                        return Err(format!("Using proof_format = \"{}\": you need exactly one counterexample for each pivot in the lemma\nlemma: {}\npivots: {}",
167                        sick.proof_format,
168                        expected_pivots.iter().map(|l| format!("{}", l)).collect::<Vec<_>>().join(" "),
169                        specified_pivots.iter().map(|l| format!("{}", l)).collect::<Vec<_>>().join(" "),
170                        ));
171                    }
172                }
173                "DRAT-pivot-is-first-literal" => {
174                    if specified_pivots.len() > 1 {
175                        return Err(format!("Using proof_format = \"{}\", the first literal must be specified as pivot and nothing else",
176                        sick.proof_format));
177                    }
178                }
179                _ => unreachable!(),
180            };
181        }
182    }
183    for witness in witnesses {
184        parser.clause_db.open_clause();
185        for &literal in &witness.failing_clause {
186            parser.clause_db.push_literal(literal, verbose);
187        }
188        parser.clause_db.push_literal(Literal::new(0), verbose);
189        let failing_clause_tmp = parser.clause_db.last_clause();
190        let failing_clause = match clause_ids.find_equal_clause(
191            &parser.clause_db,
192            failing_clause_tmp,
193            /*delete=*/ false,
194        ) {
195            Some(clause) => clause,
196            None => {
197                return Err(format!(
198                    "Failing clause is not present in the formula: {}",
199                    parser.clause_db.clause_to_string(failing_clause_tmp)
200                ))
201            }
202        };
203        parser.clause_db.pop_clause();
204        let lemma_slice = parser.clause_db.clause(lemma);
205        for &literal in &witness.failing_model {
206            if assignment[-literal] {
207                return Err(format!(
208                    "Failing model is inconsistent in variable {}",
209                    literal.variable()
210                ));
211            }
212            assignment.push(literal, Reason::assumed());
213        }
214        let resolvent: Vec<Literal> = match redundancy_property {
215            RedundancyProperty::RAT => {
216                let pivot = witness.pivot.expect(PIVOT);
217                lemma_slice
218                    .iter()
219                    .chain(witness.failing_clause.iter())
220                    .filter(|&&lit| lit.variable() != pivot.variable())
221                    .cloned()
222                    .collect()
223            }
224            RedundancyProperty::PR => {
225                let mut literal_is_in_witness =
226                    Array::new(false, parser.maxvar.array_size_for_literals());
227                for &literal in parser.witness_db.witness(lemma) {
228                    literal_is_in_witness[literal] = true;
229                }
230                if witness
231                    .failing_clause
232                    .iter()
233                    .any(|&lit| literal_is_in_witness[lit])
234                {
235                    return Err(format!(
236                        "Reduct of failing clause {} is satisified under witness {}",
237                        parser.clause_db.clause_to_string(failing_clause),
238                        parser.witness_db.witness_to_string(failing_clause)
239                    ));
240                }
241                lemma_slice
242                    .iter()
243                    .chain(
244                        witness
245                            .failing_clause
246                            .iter()
247                            .filter(|&&lit| !literal_is_in_witness[-lit]),
248                    )
249                    .cloned()
250                    .collect()
251            }
252        };
253        // TODO also for PR
254        if redundancy_property == RedundancyProperty::RAT {
255            for &literal in &resolvent {
256                if !assignment[-literal] {
257                    return Err(format!(
258                        "Failing model does not falsify resolvent literal {}",
259                        literal
260                    ));
261                }
262            }
263        }
264        for &clause in &clause_ids {
265            if clause < lemma
266                && !stable_under_unit_propagation(&assignment, parser.clause_db.clause(clause))
267            {
268                return Err(format!(
269                    "Failing model is not the shared UP-model for clause {}",
270                    parser.clause_db.clause_to_string(clause)
271                ));
272            }
273        }
274        while assignment.len() > natural_model_length {
275            assignment.pop();
276        }
277    }
278    Ok(())
279}