1mod dimacs;
8mod finalizer;
9mod inference_code;
10mod proof_atomics;
11
12use std::fs::File;
13use std::path::Path;
14use std::sync::Arc;
15
16use dimacs::DimacsProof;
17use drcp_format::writer::ProofWriter;
18use drcp_format::Deduction;
19use drcp_format::Inference;
20pub(crate) use finalizer::*;
21pub use inference_code::*;
22use proof_atomics::ProofAtomics;
23
24use crate::containers::HashMap;
25use crate::containers::KeyGenerator;
26use crate::containers::KeyedVec;
27use crate::containers::StorageKey;
28use crate::engine::variable_names::VariableNames;
29use crate::predicates::Predicate;
30use crate::variables::Literal;
31#[cfg(doc)]
32use crate::Solver;
33
34#[derive(Debug, Default)]
41pub struct ProofLog {
42 internal_proof: Option<ProofImpl>,
43}
44
45impl ProofLog {
46 pub fn cp(file_path: &Path, log_hints: bool) -> std::io::Result<ProofLog> {
48 let file = File::create(file_path)?;
49
50 let writer = ProofWriter::new(file);
51
52 Ok(ProofLog {
53 internal_proof: Some(ProofImpl::CpProof {
54 writer,
55 propagation_order_hint: if log_hints { Some(vec![]) } else { None },
56 logged_domain_inferences: HashMap::default(),
57 inference_codes: KeyedVec::default(),
58 proof_atomics: ProofAtomics::default(),
59 constraint_tags: KeyGenerator::default(),
60 }),
61 })
62 }
63
64 pub fn dimacs(file_path: &Path) -> std::io::Result<ProofLog> {
66 let file = File::create(file_path)?;
67 Ok(ProofLog {
68 internal_proof: Some(ProofImpl::DimacsProof(DimacsProof::new(file))),
69 })
70 }
71
72 pub(crate) fn log_inference(
74 &mut self,
75 inference_code: InferenceCode,
76 premises: impl IntoIterator<Item = Predicate>,
77 propagated: Option<Predicate>,
78 variable_names: &VariableNames,
79 ) -> std::io::Result<ConstraintTag> {
80 let Some(ProofImpl::CpProof {
81 writer,
82 propagation_order_hint: Some(propagation_sequence),
83 inference_codes,
84 constraint_tags,
85 proof_atomics,
86 ..
87 }) = self.internal_proof.as_mut()
88 else {
89 return Ok(ConstraintTag::create_from_index(0));
90 };
91
92 let (tag, label) = inference_codes[inference_code].clone();
93
94 let inference_tag = constraint_tags.next_key();
95
96 let inference = Inference {
97 constraint_id: inference_tag.into(),
98 premises: premises
99 .into_iter()
100 .map(|premise| proof_atomics.map_predicate_to_proof_atomic(premise, variable_names))
101 .collect(),
102 consequent: propagated.map(|predicate| {
103 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names)
104 }),
105 generated_by: Some(tag.into()),
106 label: Some(label),
107 };
108
109 writer.log_inference(inference)?;
110
111 propagation_sequence.push(Some(inference_tag));
112
113 Ok(inference_tag)
114 }
115
116 pub(crate) fn log_domain_inference(
118 &mut self,
119 predicate: Predicate,
120 variable_names: &VariableNames,
121 ) -> std::io::Result<ConstraintTag> {
122 let Some(ProofImpl::CpProof {
123 writer,
124 propagation_order_hint: Some(propagation_sequence),
125 logged_domain_inferences,
126 constraint_tags,
127 proof_atomics,
128 ..
129 }) = self.internal_proof.as_mut()
130 else {
131 return Ok(ConstraintTag::create_from_index(0));
132 };
133
134 if let Some(hint_idx) = logged_domain_inferences.get(&predicate).copied() {
135 let tag = propagation_sequence[hint_idx]
136 .take()
137 .expect("the logged_domain_inferences always points to some index");
138 propagation_sequence.push(Some(tag));
139
140 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
141
142 return Ok(tag);
143 }
144
145 let inference_tag = constraint_tags.next_key();
146
147 let inference = Inference {
148 constraint_id: inference_tag.into(),
149 premises: vec![],
150 consequent: Some(
151 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names),
152 ),
153 generated_by: None,
154 label: Some("initial_domain"),
155 };
156
157 writer.log_inference(inference)?;
158
159 propagation_sequence.push(Some(inference_tag));
160
161 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
162
163 Ok(inference_tag)
164 }
165
166 pub(crate) fn log_deduction(
171 &mut self,
172 premises: impl IntoIterator<Item = Predicate>,
173 variable_names: &VariableNames,
174 ) -> std::io::Result<ConstraintTag> {
175 match &mut self.internal_proof {
176 Some(ProofImpl::CpProof {
177 writer,
178 propagation_order_hint,
179 constraint_tags,
180 proof_atomics,
181 logged_domain_inferences,
182 ..
183 }) => {
184 logged_domain_inferences.clear();
186
187 let constraint_tag = constraint_tags.next_key();
188
189 let deduction = Deduction {
190 constraint_id: constraint_tag.into(),
191 premises: premises
192 .into_iter()
193 .map(|premise| {
194 proof_atomics.map_predicate_to_proof_atomic(premise, variable_names)
195 })
196 .collect(),
197 sequence: propagation_order_hint
198 .as_ref()
199 .iter()
200 .flat_map(|vec| vec.iter().rev().copied())
201 .flatten()
202 .map(|tag| tag.into())
203 .collect(),
204 };
205
206 writer.log_deduction(deduction)?;
207
208 if let Some(hints) = propagation_order_hint.as_mut() {
210 hints.clear();
211 }
212
213 Ok(constraint_tag)
214 }
215
216 Some(ProofImpl::DimacsProof(writer)) => {
217 let clause = premises.into_iter().map(|predicate| !predicate);
218 writer.learned_clause(clause, variable_names)?;
219 Ok(ConstraintTag::create_from_index(0))
220 }
221
222 None => Ok(ConstraintTag::create_from_index(0)),
223 }
224 }
225
226 pub(crate) fn unsat(self, variable_names: &VariableNames) -> std::io::Result<()> {
227 match self.internal_proof {
228 Some(ProofImpl::CpProof { mut writer, .. }) => {
229 writer.log_conclusion::<&str>(drcp_format::Conclusion::Unsat)
230 }
231 Some(ProofImpl::DimacsProof(mut writer)) => writer
232 .learned_clause(std::iter::empty(), variable_names)
233 .map(|_| ()),
234 None => Ok(()),
235 }
236 }
237
238 pub(crate) fn optimal(
239 self,
240 objective_bound: Predicate,
241 variable_names: &VariableNames,
242 ) -> std::io::Result<()> {
243 match self.internal_proof {
244 Some(ProofImpl::CpProof {
245 mut writer,
246 mut proof_atomics,
247 ..
248 }) => {
249 let atomic =
250 proof_atomics.map_predicate_to_proof_atomic(objective_bound, variable_names);
251
252 writer.log_conclusion::<&str>(drcp_format::Conclusion::DualBound(atomic))
253 }
254
255 Some(ProofImpl::DimacsProof(_)) => {
256 panic!("Cannot conclude optimality in DIMACS proof")
257 }
258
259 None => Ok(()),
260 }
261 }
262
263 pub(crate) fn is_logging_inferences(&self) -> bool {
264 matches!(
265 self.internal_proof,
266 Some(ProofImpl::CpProof {
267 propagation_order_hint: Some(_),
268 ..
269 })
270 )
271 }
272
273 pub(crate) fn reify_predicate(&mut self, literal: Literal, predicate: Predicate) {
274 let Some(ProofImpl::CpProof {
275 ref mut proof_atomics,
276 ..
277 }) = self.internal_proof
278 else {
279 return;
280 };
281
282 proof_atomics.reify_predicate(literal, predicate);
283 }
284
285 pub(crate) fn create_inference_code(
288 &mut self,
289 constraint_tag: ConstraintTag,
290 inference_label: impl InferenceLabel,
291 ) -> InferenceCode {
292 match &mut self.internal_proof {
293 Some(ProofImpl::CpProof {
294 inference_codes, ..
295 }) => inference_codes.push((constraint_tag, inference_label.to_str())),
296
297 _ => InferenceCode::create_from_index(0),
299 }
300 }
301
302 pub(crate) fn new_constraint_tag(&mut self) -> ConstraintTag {
304 match self.internal_proof {
305 Some(ProofImpl::CpProof {
306 ref mut constraint_tags,
307 ..
308 }) => constraint_tags.next_key(),
309 _ => ConstraintTag::create_from_index(0),
310 }
311 }
312}
313
314#[derive(Debug)]
315enum ProofImpl {
316 CpProof {
317 writer: ProofWriter<File, i32>,
318 inference_codes: KeyedVec<InferenceCode, (ConstraintTag, Arc<str>)>,
319 constraint_tags: KeyGenerator<ConstraintTag>,
321 propagation_order_hint: Option<Vec<Option<ConstraintTag>>>,
329 proof_atomics: ProofAtomics,
330 logged_domain_inferences: HashMap<Predicate, usize>,
333 },
334 DimacsProof(DimacsProof<File>),
335}