1mod dimacs;
8mod finalizer;
9mod inference_code;
10mod proof_atomics;
11
12use std::fs::File;
13use std::io::Write;
14use std::path::Path;
15
16use dimacs::DimacsProof;
17use drcp_format::Deduction;
18use drcp_format::Inference;
19use drcp_format::writer::ProofWriter;
20pub(crate) use finalizer::*;
21pub use inference_code::*;
22use proof_atomics::ProofAtomics;
23
24#[cfg(doc)]
25use crate::Solver;
26use crate::containers::HashMap;
27use crate::containers::KeyGenerator;
28use crate::engine::Assignments;
29use crate::engine::variable_names::VariableNames;
30use crate::predicates::Predicate;
31use crate::variables::Literal;
32
33#[derive(Debug, Default)]
40pub struct ProofLog {
41 internal_proof: Option<ProofImpl>,
42}
43
44impl ProofLog {
45 pub fn cp(file_path: &Path, log_hints: bool) -> std::io::Result<ProofLog> {
47 let file = File::create(file_path)?;
48
49 let sink = if file_path.extension().is_some_and(|ext| ext == ".gz") {
50 Sink::GzippedFile(flate2::write::GzEncoder::new(
51 file,
52 flate2::Compression::fast(),
53 ))
54 } else {
55 Sink::File(file)
56 };
57
58 let writer = ProofWriter::new(sink);
59
60 Ok(ProofLog {
61 internal_proof: Some(ProofImpl::CpProof {
62 writer,
63 propagation_order_hint: if log_hints { Some(vec![]) } else { None },
64 logged_domain_inferences: HashMap::default(),
65 proof_atomics: ProofAtomics::default(),
66 }),
67 })
68 }
69
70 pub fn dimacs(file_path: &Path) -> std::io::Result<ProofLog> {
72 let file = File::create(file_path)?;
73 Ok(ProofLog {
74 internal_proof: Some(ProofImpl::DimacsProof(DimacsProof::new(file))),
75 })
76 }
77
78 pub(crate) fn log_inference(
80 &mut self,
81 constraint_tags: &mut KeyGenerator<ConstraintTag>,
82 inference_code: InferenceCode,
83 premises: impl IntoIterator<Item = Predicate>,
84 propagated: Option<Predicate>,
85 variable_names: &VariableNames,
86 assignments: &Assignments,
87 ) -> std::io::Result<ConstraintTag> {
88 let inference_tag = constraint_tags.next_key();
89
90 let Some(ProofImpl::CpProof {
91 writer,
92 propagation_order_hint: Some(propagation_sequence),
93 proof_atomics,
94 ..
95 }) = self.internal_proof.as_mut()
96 else {
97 return Ok(inference_tag);
98 };
99
100 let inference = Inference {
101 constraint_id: inference_tag.into(),
102 premises: premises
103 .into_iter()
104 .filter(|&predicate| !is_likely_a_constant(predicate, variable_names, assignments))
105 .map(|premise| proof_atomics.map_predicate_to_proof_atomic(premise, variable_names))
106 .collect(),
107 consequent: propagated.map(|predicate| {
108 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names)
109 }),
110 generated_by: Some(inference_code.tag().into()),
111 label: Some(inference_code.label()),
112 };
113
114 writer.log_inference(inference)?;
115
116 propagation_sequence.push(Some(inference_tag));
117
118 Ok(inference_tag)
119 }
120
121 pub(crate) fn log_domain_inference(
123 &mut self,
124 predicate: Predicate,
125 variable_names: &VariableNames,
126 constraint_tags: &mut KeyGenerator<ConstraintTag>,
127 assignments: &Assignments,
128 ) -> std::io::Result<Option<ConstraintTag>> {
129 assert!(assignments.is_initial_bound(predicate));
130
131 if is_likely_a_constant(predicate, variable_names, assignments) {
132 return Ok(None);
136 }
137
138 let inference_tag = constraint_tags.next_key();
139
140 let Some(ProofImpl::CpProof {
141 writer,
142 propagation_order_hint: Some(propagation_sequence),
143 logged_domain_inferences,
144 proof_atomics,
145 ..
146 }) = self.internal_proof.as_mut()
147 else {
148 return Ok(Some(inference_tag));
149 };
150
151 if let Some(hint_idx) = logged_domain_inferences.get(&predicate).copied() {
152 let tag = propagation_sequence[hint_idx]
153 .take()
154 .expect("the logged_domain_inferences always points to some index");
155 propagation_sequence.push(Some(tag));
156
157 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
158
159 return Ok(Some(tag));
160 }
161
162 let inference = Inference {
163 constraint_id: inference_tag.into(),
164 premises: vec![],
165 consequent: Some(
166 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names),
167 ),
168 generated_by: None,
169 label: Some("initial_domain"),
170 };
171
172 writer.log_inference(inference)?;
173
174 propagation_sequence.push(Some(inference_tag));
175
176 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
177
178 Ok(Some(inference_tag))
179 }
180
181 pub(crate) fn log_deduction(
186 &mut self,
187 premises: impl IntoIterator<Item = Predicate>,
188 variable_names: &VariableNames,
189 constraint_tags: &mut KeyGenerator<ConstraintTag>,
190 assignments: &Assignments,
191 ) -> std::io::Result<ConstraintTag> {
192 let constraint_tag = constraint_tags.next_key();
193
194 match &mut self.internal_proof {
195 Some(ProofImpl::CpProof {
196 writer,
197 propagation_order_hint,
198 proof_atomics,
199 logged_domain_inferences,
200 ..
201 }) => {
202 logged_domain_inferences.clear();
204
205 let deduction = Deduction {
206 constraint_id: constraint_tag.into(),
207 premises: premises
208 .into_iter()
209 .filter(|&predicate| {
210 !is_likely_a_constant(predicate, variable_names, assignments)
211 })
212 .map(|premise| {
213 proof_atomics.map_predicate_to_proof_atomic(premise, variable_names)
214 })
215 .collect(),
216 sequence: propagation_order_hint
217 .as_ref()
218 .iter()
219 .flat_map(|vec| vec.iter().rev().copied())
220 .flatten()
221 .map(|tag| tag.into())
222 .collect(),
223 };
224
225 writer.log_deduction(deduction)?;
226
227 if let Some(hints) = propagation_order_hint.as_mut() {
229 hints.clear();
230 }
231
232 Ok(constraint_tag)
233 }
234
235 Some(ProofImpl::DimacsProof(writer)) => {
236 let clause = premises.into_iter().map(|predicate| !predicate);
237 writer.learned_clause(clause, variable_names)?;
238 Ok(constraint_tag)
239 }
240
241 None => Ok(constraint_tag),
242 }
243 }
244
245 pub(crate) fn unsat(self, variable_names: &VariableNames) -> std::io::Result<()> {
246 match self.internal_proof {
247 Some(ProofImpl::CpProof { mut writer, .. }) => {
248 writer.log_conclusion::<&str>(drcp_format::Conclusion::Unsat)
249 }
250 Some(ProofImpl::DimacsProof(mut writer)) => writer
251 .learned_clause(std::iter::empty(), variable_names)
252 .map(|_| ()),
253 None => Ok(()),
254 }
255 }
256
257 pub(crate) fn optimal(
258 self,
259 objective_bound: Predicate,
260 variable_names: &VariableNames,
261 ) -> std::io::Result<()> {
262 match self.internal_proof {
263 Some(ProofImpl::CpProof {
264 mut writer,
265 mut proof_atomics,
266 ..
267 }) => {
268 let atomic =
269 proof_atomics.map_predicate_to_proof_atomic(objective_bound, variable_names);
270
271 writer.log_conclusion::<&str>(drcp_format::Conclusion::DualBound(atomic))
272 }
273
274 Some(ProofImpl::DimacsProof(_)) => {
275 panic!("Cannot conclude optimality in DIMACS proof")
276 }
277
278 None => Ok(()),
279 }
280 }
281
282 pub fn is_logging_inferences(&self) -> bool {
283 matches!(
284 self.internal_proof,
285 Some(ProofImpl::CpProof {
286 propagation_order_hint: Some(_),
287 ..
288 })
289 )
290 }
291
292 pub(crate) fn reify_predicate(&mut self, literal: Literal, predicate: Predicate) {
293 let Some(ProofImpl::CpProof {
294 ref mut proof_atomics,
295 ..
296 }) = self.internal_proof
297 else {
298 return;
299 };
300
301 proof_atomics.reify_predicate(literal, predicate);
302 }
303
304 pub(crate) fn is_logging_proof(&self) -> bool {
305 self.internal_proof.is_some()
306 }
307}
308
309fn is_likely_a_constant(
311 predicate: Predicate,
312 variable_names: &VariableNames,
313 assignments: &Assignments,
314) -> bool {
315 let domain = predicate.get_domain();
316
317 let is_fixed =
318 assignments.get_initial_lower_bound(domain) == assignments.get_initial_upper_bound(domain);
319
320 let is_unnamed = variable_names.get_int_name(domain).is_none();
321
322 is_fixed && is_unnamed
323}
324
325#[derive(Debug)]
330enum Sink {
331 File(File),
332 GzippedFile(flate2::write::GzEncoder<File>),
333}
334
335impl Write for Sink {
336 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
337 match self {
338 Sink::File(file) => file.write(buf),
339 Sink::GzippedFile(gz_encoder) => gz_encoder.write(buf),
340 }
341 }
342
343 fn flush(&mut self) -> std::io::Result<()> {
344 match self {
345 Sink::File(file) => file.flush(),
346 Sink::GzippedFile(gz_encoder) => gz_encoder.flush(),
347 }
348 }
349}
350
351#[derive(Debug)]
352#[allow(
353 clippy::large_enum_variant,
354 reason = "there will only ever be one per solver"
355)]
356#[allow(
357 variant_size_differences,
358 reason = "there will only ever be one per solver"
359)]
360enum ProofImpl {
361 CpProof {
362 writer: ProofWriter<Sink, i32>,
363 propagation_order_hint: Option<Vec<Option<ConstraintTag>>>,
371 proof_atomics: ProofAtomics,
372 logged_domain_inferences: HashMap<Predicate, usize>,
375 },
376 DimacsProof(DimacsProof<File>),
377}