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 #[cfg(feature = "gzipped-proofs")]
51 let writer = {
52 let encoder = flate2::write::GzEncoder::new(file, flate2::Compression::fast());
53 ProofWriter::new(encoder)
54 };
55
56 #[cfg(not(feature = "gzipped-proofs"))]
57 let writer = ProofWriter::new(file);
58
59 Ok(ProofLog {
60 internal_proof: Some(ProofImpl::CpProof {
61 writer,
62 propagation_order_hint: if log_hints { Some(vec![]) } else { None },
63 logged_domain_inferences: HashMap::default(),
64 inference_codes: KeyedVec::default(),
65 proof_atomics: ProofAtomics::default(),
66 constraint_tags: KeyGenerator::default(),
67 }),
68 })
69 }
70
71 pub fn dimacs(file_path: &Path) -> std::io::Result<ProofLog> {
73 let file = File::create(file_path)?;
74 Ok(ProofLog {
75 internal_proof: Some(ProofImpl::DimacsProof(DimacsProof::new(file))),
76 })
77 }
78
79 pub(crate) fn log_inference(
81 &mut self,
82 inference_code: InferenceCode,
83 premises: impl IntoIterator<Item = Predicate>,
84 propagated: Option<Predicate>,
85 variable_names: &VariableNames,
86 ) -> std::io::Result<ConstraintTag> {
87 let Some(ProofImpl::CpProof {
88 writer,
89 propagation_order_hint: Some(propagation_sequence),
90 inference_codes,
91 constraint_tags,
92 proof_atomics,
93 ..
94 }) = self.internal_proof.as_mut()
95 else {
96 return Ok(ConstraintTag::create_from_index(0));
97 };
98
99 let (tag, label) = inference_codes[inference_code].clone();
100
101 let inference_tag = constraint_tags.next_key();
102
103 let inference = Inference {
104 constraint_id: inference_tag.into(),
105 premises: premises
106 .into_iter()
107 .map(|premise| proof_atomics.map_predicate_to_proof_atomic(premise, variable_names))
108 .collect(),
109 consequent: propagated.map(|predicate| {
110 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names)
111 }),
112 generated_by: Some(tag.into()),
113 label: Some(label),
114 };
115
116 writer.log_inference(inference)?;
117
118 propagation_sequence.push(Some(inference_tag));
119
120 Ok(inference_tag)
121 }
122
123 pub(crate) fn log_domain_inference(
125 &mut self,
126 predicate: Predicate,
127 variable_names: &VariableNames,
128 ) -> std::io::Result<ConstraintTag> {
129 let Some(ProofImpl::CpProof {
130 writer,
131 propagation_order_hint: Some(propagation_sequence),
132 logged_domain_inferences,
133 constraint_tags,
134 proof_atomics,
135 ..
136 }) = self.internal_proof.as_mut()
137 else {
138 return Ok(ConstraintTag::create_from_index(0));
139 };
140
141 if let Some(hint_idx) = logged_domain_inferences.get(&predicate).copied() {
142 let tag = propagation_sequence[hint_idx]
143 .take()
144 .expect("the logged_domain_inferences always points to some index");
145 propagation_sequence.push(Some(tag));
146
147 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
148
149 return Ok(tag);
150 }
151
152 let inference_tag = constraint_tags.next_key();
153
154 let inference = Inference {
155 constraint_id: inference_tag.into(),
156 premises: vec![],
157 consequent: Some(
158 proof_atomics.map_predicate_to_proof_atomic(predicate, variable_names),
159 ),
160 generated_by: None,
161 label: Some("initial_domain"),
162 };
163
164 writer.log_inference(inference)?;
165
166 propagation_sequence.push(Some(inference_tag));
167
168 let _ = logged_domain_inferences.insert(predicate, propagation_sequence.len() - 1);
169
170 Ok(inference_tag)
171 }
172
173 pub(crate) fn log_deduction(
178 &mut self,
179 premises: impl IntoIterator<Item = Predicate>,
180 variable_names: &VariableNames,
181 ) -> std::io::Result<ConstraintTag> {
182 match &mut self.internal_proof {
183 Some(ProofImpl::CpProof {
184 writer,
185 propagation_order_hint,
186 constraint_tags,
187 proof_atomics,
188 logged_domain_inferences,
189 ..
190 }) => {
191 logged_domain_inferences.clear();
193
194 let constraint_tag = constraint_tags.next_key();
195
196 let deduction = Deduction {
197 constraint_id: constraint_tag.into(),
198 premises: premises
199 .into_iter()
200 .map(|premise| {
201 proof_atomics.map_predicate_to_proof_atomic(premise, variable_names)
202 })
203 .collect(),
204 sequence: propagation_order_hint
205 .as_ref()
206 .iter()
207 .flat_map(|vec| vec.iter().rev().copied())
208 .flatten()
209 .map(|tag| tag.into())
210 .collect(),
211 };
212
213 writer.log_deduction(deduction)?;
214
215 if let Some(hints) = propagation_order_hint.as_mut() {
217 hints.clear();
218 }
219
220 Ok(constraint_tag)
221 }
222
223 Some(ProofImpl::DimacsProof(writer)) => {
224 let clause = premises.into_iter().map(|predicate| !predicate);
225 writer.learned_clause(clause, variable_names)?;
226 Ok(ConstraintTag::create_from_index(0))
227 }
228
229 None => Ok(ConstraintTag::create_from_index(0)),
230 }
231 }
232
233 pub(crate) fn unsat(self, variable_names: &VariableNames) -> std::io::Result<()> {
234 match self.internal_proof {
235 Some(ProofImpl::CpProof { mut writer, .. }) => {
236 writer.log_conclusion::<&str>(drcp_format::Conclusion::Unsat)
237 }
238 Some(ProofImpl::DimacsProof(mut writer)) => writer
239 .learned_clause(std::iter::empty(), variable_names)
240 .map(|_| ()),
241 None => Ok(()),
242 }
243 }
244
245 pub(crate) fn optimal(
246 self,
247 objective_bound: Predicate,
248 variable_names: &VariableNames,
249 ) -> std::io::Result<()> {
250 match self.internal_proof {
251 Some(ProofImpl::CpProof {
252 mut writer,
253 mut proof_atomics,
254 ..
255 }) => {
256 let atomic =
257 proof_atomics.map_predicate_to_proof_atomic(objective_bound, variable_names);
258
259 writer.log_conclusion::<&str>(drcp_format::Conclusion::DualBound(atomic))
260 }
261
262 Some(ProofImpl::DimacsProof(_)) => {
263 panic!("Cannot conclude optimality in DIMACS proof")
264 }
265
266 None => Ok(()),
267 }
268 }
269
270 pub(crate) fn is_logging_inferences(&self) -> bool {
271 matches!(
272 self.internal_proof,
273 Some(ProofImpl::CpProof {
274 propagation_order_hint: Some(_),
275 ..
276 })
277 )
278 }
279
280 pub(crate) fn reify_predicate(&mut self, literal: Literal, predicate: Predicate) {
281 let Some(ProofImpl::CpProof {
282 ref mut proof_atomics,
283 ..
284 }) = self.internal_proof
285 else {
286 return;
287 };
288
289 proof_atomics.reify_predicate(literal, predicate);
290 }
291
292 pub(crate) fn create_inference_code(
295 &mut self,
296 constraint_tag: ConstraintTag,
297 inference_label: impl InferenceLabel,
298 ) -> InferenceCode {
299 match &mut self.internal_proof {
300 Some(ProofImpl::CpProof {
301 inference_codes, ..
302 }) => inference_codes.push((constraint_tag, inference_label.to_str())),
303
304 _ => InferenceCode::create_from_index(0),
306 }
307 }
308
309 pub(crate) fn new_constraint_tag(&mut self) -> ConstraintTag {
311 match self.internal_proof {
312 Some(ProofImpl::CpProof {
313 ref mut constraint_tags,
314 ..
315 }) => constraint_tags.next_key(),
316 _ => ConstraintTag::create_from_index(0),
317 }
318 }
319}
320
321#[derive(Debug)]
322#[allow(
323 clippy::large_enum_variant,
324 reason = "there will only every be one per solver"
325)]
326enum ProofImpl {
327 CpProof {
328 #[cfg(feature = "gzipped-proofs")]
329 writer: ProofWriter<flate2::write::GzEncoder<File>, i32>,
330 #[cfg(not(feature = "gzipped-proofs"))]
331 writer: ProofWriter<File, i32>,
332 inference_codes: KeyedVec<InferenceCode, (ConstraintTag, Arc<str>)>,
333 constraint_tags: KeyGenerator<ConstraintTag>,
335 propagation_order_hint: Option<Vec<Option<ConstraintTag>>>,
343 proof_atomics: ProofAtomics,
344 logged_domain_inferences: HashMap<Predicate, usize>,
347 },
348 DimacsProof(DimacsProof<File>),
349}