1#[allow(unused_imports)]
28use crate::prelude::*;
29use oxiz_sat::Lit;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum ExplanationType {
34 Equality,
36 Bounds,
38 Disequality,
40 Arithmetic,
42 Array,
44 BitVector,
46}
47
48#[derive(Debug, Clone)]
50pub struct TheoryExplanation {
51 pub explanation_type: ExplanationType,
53 pub literals: Vec<Lit>,
55 pub description: String,
57 pub proof_trace: Option<Vec<ProofStep>>,
59}
60
61#[derive(Debug, Clone)]
63pub struct ProofStep {
64 pub description: String,
66 pub premises: Vec<Lit>,
68 pub conclusion: String,
70}
71
72#[derive(Debug, Clone)]
74pub struct ExplainerConfig {
75 pub generate_proofs: bool,
77 pub minimize: bool,
79 pub include_descriptions: bool,
81 pub max_size: usize,
83}
84
85impl Default for ExplainerConfig {
86 fn default() -> Self {
87 Self {
88 generate_proofs: false,
89 minimize: true,
90 include_descriptions: false,
91 max_size: 100,
92 }
93 }
94}
95
96#[derive(Debug, Clone, Default)]
98pub struct ExplainerStats {
99 pub explanations_generated: u64,
101 pub total_literals: u64,
103 pub avg_size: f64,
105 pub minimized: u64,
107 pub time_us: u64,
109}
110
111pub struct TheoryExplainer {
113 config: ExplainerConfig,
114 stats: ExplainerStats,
115}
116
117impl TheoryExplainer {
118 pub fn new() -> Self {
120 Self::with_config(ExplainerConfig::default())
121 }
122
123 pub fn with_config(config: ExplainerConfig) -> Self {
125 Self {
126 config,
127 stats: ExplainerStats::default(),
128 }
129 }
130
131 pub fn stats(&self) -> &ExplainerStats {
133 &self.stats
134 }
135
136 pub fn reset_stats(&mut self) {
138 self.stats = ExplainerStats::default();
139 }
140
141 pub fn explain_equality_conflict(
146 &mut self,
147 lits: Vec<Lit>,
148 description: Option<String>,
149 ) -> TheoryExplanation {
150 #[cfg(feature = "std")]
151 let start = std::time::Instant::now();
152
153 let minimized_lits = if self.config.minimize {
154 self.minimize_explanation(&lits)
155 } else {
156 lits.clone()
157 };
158
159 let proof_trace = if self.config.generate_proofs {
160 Some(self.generate_proof_trace(&minimized_lits, ExplanationType::Equality))
161 } else {
162 None
163 };
164
165 let desc = if self.config.include_descriptions {
166 description.unwrap_or_else(|| {
167 format!("Equality conflict with {} literals", minimized_lits.len())
168 })
169 } else {
170 String::new()
171 };
172
173 self.stats.explanations_generated += 1;
174 self.stats.total_literals += minimized_lits.len() as u64;
175 self.update_avg_size();
176 #[cfg(feature = "std")]
177 {
178 self.stats.time_us += start.elapsed().as_micros() as u64;
179 }
180
181 TheoryExplanation {
182 explanation_type: ExplanationType::Equality,
183 literals: minimized_lits,
184 description: desc,
185 proof_trace,
186 }
187 }
188
189 pub fn explain_bounds_conflict(
193 &mut self,
194 lits: Vec<Lit>,
195 description: Option<String>,
196 ) -> TheoryExplanation {
197 #[cfg(feature = "std")]
198 let start = std::time::Instant::now();
199
200 let minimized_lits = if self.config.minimize {
201 self.minimize_explanation(&lits)
202 } else {
203 lits.clone()
204 };
205
206 let proof_trace = if self.config.generate_proofs {
207 Some(self.generate_proof_trace(&minimized_lits, ExplanationType::Bounds))
208 } else {
209 None
210 };
211
212 let desc = if self.config.include_descriptions {
213 description.unwrap_or_else(|| {
214 format!("Bounds conflict with {} constraints", minimized_lits.len())
215 })
216 } else {
217 String::new()
218 };
219
220 self.stats.explanations_generated += 1;
221 self.stats.total_literals += minimized_lits.len() as u64;
222 self.update_avg_size();
223 #[cfg(feature = "std")]
224 {
225 self.stats.time_us += start.elapsed().as_micros() as u64;
226 }
227
228 TheoryExplanation {
229 explanation_type: ExplanationType::Bounds,
230 literals: minimized_lits,
231 description: desc,
232 proof_trace,
233 }
234 }
235
236 pub fn explain_arithmetic_conflict(
241 &mut self,
242 lits: Vec<Lit>,
243 _farkas_coefficients: Option<Vec<i64>>,
244 description: Option<String>,
245 ) -> TheoryExplanation {
246 #[cfg(feature = "std")]
247 let start = std::time::Instant::now();
248
249 let minimized_lits = if self.config.minimize {
253 self.minimize_explanation(&lits)
254 } else {
255 lits.clone()
256 };
257
258 let proof_trace = if self.config.generate_proofs {
259 Some(self.generate_proof_trace(&minimized_lits, ExplanationType::Arithmetic))
260 } else {
261 None
262 };
263
264 let desc = if self.config.include_descriptions {
265 description.unwrap_or_else(|| "Arithmetic conflict via linear combination".to_string())
266 } else {
267 String::new()
268 };
269
270 self.stats.explanations_generated += 1;
271 self.stats.total_literals += minimized_lits.len() as u64;
272 self.update_avg_size();
273 #[cfg(feature = "std")]
274 {
275 self.stats.time_us += start.elapsed().as_micros() as u64;
276 }
277
278 TheoryExplanation {
279 explanation_type: ExplanationType::Arithmetic,
280 literals: minimized_lits,
281 description: desc,
282 proof_trace,
283 }
284 }
285
286 fn minimize_explanation(&mut self, lits: &[Lit]) -> Vec<Lit> {
288 let mut minimized = Vec::new();
294 let _lit_set: FxHashSet<Lit> = lits.iter().copied().collect();
295
296 for &lit in lits {
297 minimized.push(lit);
300 }
301
302 if minimized.len() < lits.len() {
303 self.stats.minimized += 1;
304 }
305
306 if minimized.len() > self.config.max_size {
308 minimized.truncate(self.config.max_size);
309 }
310
311 minimized
312 }
313
314 fn generate_proof_trace(&self, lits: &[Lit], exp_type: ExplanationType) -> Vec<ProofStep> {
316 let mut trace = Vec::new();
317
318 trace.push(ProofStep {
320 description: format!("Theory conflict detected: {:?}", exp_type),
321 premises: lits.to_vec(),
322 conclusion: "Contradiction".to_string(),
323 });
324
325 trace
328 }
329
330 fn update_avg_size(&mut self) {
332 if self.stats.explanations_generated > 0 {
333 self.stats.avg_size =
334 self.stats.total_literals as f64 / self.stats.explanations_generated as f64;
335 }
336 }
337}
338
339impl Default for TheoryExplainer {
340 fn default() -> Self {
341 Self::new()
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use oxiz_sat::Var;
349
350 fn lit(var: u32, positive: bool) -> Lit {
351 let v = Var::new(var);
352 if positive { Lit::pos(v) } else { Lit::neg(v) }
353 }
354
355 #[test]
356 fn test_explainer_creation() {
357 let explainer = TheoryExplainer::new();
358 assert_eq!(explainer.stats().explanations_generated, 0);
359 }
360
361 #[test]
362 fn test_equality_explanation() {
363 let mut explainer = TheoryExplainer::new();
364 let lits = vec![lit(0, true), lit(1, false), lit(2, true)];
365
366 let explanation = explainer.explain_equality_conflict(lits.clone(), None);
367
368 assert_eq!(explanation.explanation_type, ExplanationType::Equality);
369 assert!(!explanation.literals.is_empty());
370 assert_eq!(explainer.stats().explanations_generated, 1);
371 }
372
373 #[test]
374 fn test_bounds_explanation() {
375 let mut explainer = TheoryExplainer::new();
376 let lits = vec![lit(0, true), lit(1, true)];
377
378 let explanation =
379 explainer.explain_bounds_conflict(lits, Some("x > 10 and x < 5".to_string()));
380
381 assert_eq!(explanation.explanation_type, ExplanationType::Bounds);
382 assert_eq!(explainer.stats().explanations_generated, 1);
383 }
384
385 #[test]
386 fn test_arithmetic_explanation() {
387 let mut explainer = TheoryExplainer::new();
388 let lits = vec![lit(0, false), lit(1, false), lit(2, false)];
389
390 let explanation = explainer.explain_arithmetic_conflict(lits, None, None);
391
392 assert_eq!(explanation.explanation_type, ExplanationType::Arithmetic);
393 assert_eq!(explainer.stats().explanations_generated, 1);
394 }
395
396 #[test]
397 fn test_minimization() {
398 let config = ExplainerConfig {
399 minimize: true,
400 ..Default::default()
401 };
402 let mut explainer = TheoryExplainer::with_config(config);
403
404 let lits = vec![lit(0, true), lit(1, false), lit(2, true), lit(3, false)];
405
406 let explanation = explainer.explain_equality_conflict(lits, None);
407
408 assert!(explanation.literals.len() <= 4);
410 }
411
412 #[test]
413 fn test_proof_generation() {
414 let config = ExplainerConfig {
415 generate_proofs: true,
416 ..Default::default()
417 };
418 let mut explainer = TheoryExplainer::with_config(config);
419
420 let lits = vec![lit(0, true), lit(1, false)];
421
422 let explanation = explainer.explain_equality_conflict(lits, None);
423
424 assert!(explanation.proof_trace.is_some());
425 let trace = explanation
426 .proof_trace
427 .expect("test operation should succeed");
428 assert!(!trace.is_empty());
429 }
430
431 #[test]
432 fn test_max_size_enforcement() {
433 let config = ExplainerConfig {
434 max_size: 2,
435 ..Default::default()
436 };
437 let mut explainer = TheoryExplainer::with_config(config);
438
439 let lits = vec![lit(0, true), lit(1, false), lit(2, true), lit(3, false)];
440
441 let explanation = explainer.explain_bounds_conflict(lits, None);
442
443 assert!(explanation.literals.len() <= 2);
444 }
445}