1#![allow(dead_code, missing_docs)] use rustc_hash::{FxHashMap, FxHashSet};
12use std::collections::BTreeMap;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ModelValue {
17 pub term: TermId,
19 pub value: Value,
21 pub theory: Theory,
23 pub is_witness: bool,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum Value {
30 Bool(bool),
32 Int(i64),
34 Rat(i64, u64),
36 BitVec(u64, usize),
38 Array(Box<ArrayValue>),
40 UFApp(String, Vec<Value>),
42 Constructor(String, Vec<Value>),
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct ArrayValue {
49 pub default: Value,
51 pub mappings: BTreeMap<Value, Value>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum Theory {
58 Core,
59 Arithmetic,
60 BitVector,
61 Array,
62 Datatype,
63 String,
64 Uninterpreted,
65}
66
67pub type TermId = usize;
69
70#[derive(Debug, Clone, Default)]
72pub struct ModelBuilderStats {
73 pub models_built: u64,
74 pub witnesses_generated: u64,
75 pub consistency_checks: u64,
76 pub minimization_steps: u64,
77 pub theory_calls: u64,
78}
79
80#[derive(Debug, Clone)]
82pub struct ModelBuilderConfig {
83 pub enable_minimization: bool,
85 pub generate_witnesses: bool,
87 pub check_consistency: bool,
89 pub max_minimization_iters: usize,
91}
92
93impl Default for ModelBuilderConfig {
94 fn default() -> Self {
95 Self {
96 enable_minimization: true,
97 generate_witnesses: true,
98 check_consistency: true,
99 max_minimization_iters: 10,
100 }
101 }
102}
103
104pub struct AdvancedModelBuilder {
106 assignments: FxHashMap<TermId, ModelValue>,
108 equiv_classes: FxHashMap<TermId, TermId>,
110 witness_obligations: FxHashSet<TermId>,
112 theory_models: FxHashMap<Theory, TheoryModel>,
114 config: ModelBuilderConfig,
116 stats: ModelBuilderStats,
118}
119
120#[derive(Debug, Clone)]
122struct TheoryModel {
123 assignments: FxHashMap<TermId, Value>,
125 constraints: Vec<TermId>,
127}
128
129impl AdvancedModelBuilder {
130 pub fn new(config: ModelBuilderConfig) -> Self {
132 Self {
133 assignments: FxHashMap::default(),
134 equiv_classes: FxHashMap::default(),
135 witness_obligations: FxHashSet::default(),
136 theory_models: FxHashMap::default(),
137 config,
138 stats: ModelBuilderStats::default(),
139 }
140 }
141
142 pub fn build_model(
144 &mut self,
145 boolean_assignments: &FxHashMap<TermId, bool>,
146 equiv_classes: &FxHashMap<TermId, TermId>,
147 ) -> Result<Model, String> {
148 self.stats.models_built += 1;
149 self.equiv_classes = equiv_classes.clone();
150
151 self.build_theory_models(boolean_assignments)?;
153
154 if self.config.generate_witnesses {
156 self.generate_witnesses()?;
157 }
158
159 if self.config.check_consistency {
161 self.check_cross_theory_consistency()?;
162 }
163
164 if self.config.enable_minimization {
166 self.minimize_model()?;
167 }
168
169 let model = Model {
171 assignments: self.assignments.clone(),
172 equiv_classes: self.equiv_classes.clone(),
173 };
174
175 Ok(model)
176 }
177
178 fn build_theory_models(
180 &mut self,
181 boolean_assignments: &FxHashMap<TermId, bool>,
182 ) -> Result<(), String> {
183 self.build_arithmetic_model(boolean_assignments)?;
185
186 self.build_bitvector_model(boolean_assignments)?;
188
189 self.build_array_model(boolean_assignments)?;
191
192 self.build_datatype_model(boolean_assignments)?;
194
195 self.build_uf_model(boolean_assignments)?;
197
198 Ok(())
199 }
200
201 fn build_arithmetic_model(
203 &mut self,
204 boolean_assignments: &FxHashMap<TermId, bool>,
205 ) -> Result<(), String> {
206 self.stats.theory_calls += 1;
207
208 let mut bounds: FxHashMap<TermId, (Option<i64>, Option<i64>)> = FxHashMap::default();
210
211 for (&term, &value) in boolean_assignments {
212 if value {
213 self.update_arithmetic_bounds(term, &mut bounds)?;
215 }
216 }
217
218 for (term, (lower, upper)) in bounds {
220 let value = match (lower, upper) {
221 (Some(l), Some(u)) if l <= u => {
222 Value::Int((l + u) / 2)
224 }
225 (Some(l), None) => Value::Int(l),
226 (None, Some(u)) => Value::Int(u),
227 (None, None) => Value::Int(0),
228 _ => return Err("Inconsistent arithmetic bounds".to_string()),
229 };
230
231 self.assignments.insert(
232 term,
233 ModelValue {
234 term,
235 value,
236 theory: Theory::Arithmetic,
237 is_witness: false,
238 },
239 );
240 }
241
242 Ok(())
243 }
244
245 fn update_arithmetic_bounds(
247 &self,
248 _term: TermId,
249 bounds: &mut FxHashMap<TermId, (Option<i64>, Option<i64>)>,
250 ) -> Result<(), String> {
251 let var = 0; let bound_pair = bounds.entry(var).or_insert((None, None));
255
256 if bound_pair.0.is_none_or(|v| v < 0) {
258 bound_pair.0 = Some(0);
259 }
260
261 Ok(())
262 }
263
264 fn build_bitvector_model(
266 &mut self,
267 _boolean_assignments: &FxHashMap<TermId, bool>,
268 ) -> Result<(), String> {
269 self.stats.theory_calls += 1;
270
271 Ok(())
275 }
276
277 fn build_array_model(
279 &mut self,
280 _boolean_assignments: &FxHashMap<TermId, bool>,
281 ) -> Result<(), String> {
282 self.stats.theory_calls += 1;
283
284 Ok(())
288 }
289
290 fn build_datatype_model(
292 &mut self,
293 _boolean_assignments: &FxHashMap<TermId, bool>,
294 ) -> Result<(), String> {
295 self.stats.theory_calls += 1;
296
297 Ok(())
300 }
301
302 fn build_uf_model(
304 &mut self,
305 _boolean_assignments: &FxHashMap<TermId, bool>,
306 ) -> Result<(), String> {
307 self.stats.theory_calls += 1;
308
309 Ok(())
313 }
314
315 fn generate_witnesses(&mut self) -> Result<(), String> {
317 let obligations: Vec<_> = self.witness_obligations.iter().copied().collect();
318
319 for term in obligations {
320 self.stats.witnesses_generated += 1;
321
322 let theory = self.determine_theory(term);
324 let witness_value = self.generate_theory_witness(term, theory)?;
325
326 self.assignments.insert(
327 term,
328 ModelValue {
329 term,
330 value: witness_value,
331 theory,
332 is_witness: true,
333 },
334 );
335 }
336
337 Ok(())
338 }
339
340 fn determine_theory(&self, _term: TermId) -> Theory {
342 Theory::Core
344 }
345
346 fn generate_theory_witness(&self, _term: TermId, theory: Theory) -> Result<Value, String> {
348 match theory {
349 Theory::Arithmetic => Ok(Value::Int(0)),
350 Theory::BitVector => Ok(Value::BitVec(0, 32)),
351 Theory::Core => Ok(Value::Bool(false)),
352 _ => Ok(Value::Int(0)),
353 }
354 }
355
356 fn check_cross_theory_consistency(&mut self) -> Result<(), String> {
358 self.stats.consistency_checks += 1;
359
360 let mut shared_terms: FxHashMap<TermId, Vec<Theory>> = FxHashMap::default();
362
363 for assignment in self.assignments.values() {
364 shared_terms
365 .entry(assignment.term)
366 .or_default()
367 .push(assignment.theory);
368 }
369
370 for (term, theories) in shared_terms {
371 if theories.len() > 1 {
372 let values: Vec<_> = theories
374 .iter()
375 .filter_map(|&theory| {
376 self.theory_models
377 .get(&theory)
378 .and_then(|m| m.assignments.get(&term))
379 })
380 .collect();
381
382 if values.windows(2).any(|w| w[0] != w[1]) {
383 return Err(format!("Cross-theory inconsistency for term {}", term));
384 }
385 }
386 }
387
388 Ok(())
389 }
390
391 fn minimize_model(&mut self) -> Result<(), String> {
393 let mut iteration = 0;
394
395 while iteration < self.config.max_minimization_iters {
396 self.stats.minimization_steps += 1;
397 iteration += 1;
398
399 let initial_size = self.assignments.len();
400
401 let terms: Vec<_> = self.assignments.keys().copied().collect();
403
404 for term in terms {
405 if !self.is_assignment_necessary(term)? {
407 self.assignments.remove(&term);
408 }
409 }
410
411 if self.assignments.len() == initial_size {
413 break;
414 }
415 }
416
417 Ok(())
418 }
419
420 fn is_assignment_necessary(&self, _term: TermId) -> Result<bool, String> {
422 Ok(true)
425 }
426
427 pub fn add_witness_obligation(&mut self, term: TermId) {
429 self.witness_obligations.insert(term);
430 }
431
432 pub fn stats(&self) -> &ModelBuilderStats {
434 &self.stats
435 }
436
437 pub fn reset(&mut self) {
439 self.assignments.clear();
440 self.equiv_classes.clear();
441 self.witness_obligations.clear();
442 self.theory_models.clear();
443 }
444}
445
446#[derive(Debug, Clone)]
448pub struct Model {
449 pub assignments: FxHashMap<TermId, ModelValue>,
451 pub equiv_classes: FxHashMap<TermId, TermId>,
453}
454
455impl Model {
456 pub fn eval(&self, term: TermId) -> Option<&Value> {
458 if let Some(assignment) = self.assignments.get(&term) {
460 return Some(&assignment.value);
461 }
462
463 if let Some(&repr) = self.equiv_classes.get(&term)
465 && let Some(assignment) = self.assignments.get(&repr)
466 {
467 return Some(&assignment.value);
468 }
469
470 None
471 }
472
473 pub fn is_true(&self, term: TermId) -> bool {
475 matches!(self.eval(term), Some(Value::Bool(true)))
476 }
477
478 pub fn theory_assignments(&self, theory: Theory) -> Vec<&ModelValue> {
480 self.assignments
481 .values()
482 .filter(|v| v.theory == theory)
483 .collect()
484 }
485
486 pub fn witnesses(&self) -> Vec<&ModelValue> {
488 self.assignments.values().filter(|v| v.is_witness).collect()
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn test_model_builder_creation() {
498 let config = ModelBuilderConfig::default();
499 let builder = AdvancedModelBuilder::new(config);
500 assert_eq!(builder.stats.models_built, 0);
501 }
502
503 #[test]
504 fn test_arithmetic_model_simple() {
505 let config = ModelBuilderConfig::default();
506 let mut builder = AdvancedModelBuilder::new(config);
507
508 let assignments = FxHashMap::default();
509 let equiv_classes = FxHashMap::default();
510
511 let result = builder.build_model(&assignments, &equiv_classes);
512 assert!(result.is_ok());
513 assert_eq!(builder.stats.models_built, 1);
514 }
515
516 #[test]
517 fn test_witness_generation() {
518 let config = ModelBuilderConfig {
519 generate_witnesses: true,
520 ..Default::default()
521 };
522 let mut builder = AdvancedModelBuilder::new(config);
523
524 builder.add_witness_obligation(42);
525 assert!(builder.witness_obligations.contains(&42));
526
527 let result = builder.generate_witnesses();
528 assert!(result.is_ok());
529 assert_eq!(builder.stats.witnesses_generated, 1);
530 }
531
532 #[test]
533 fn test_model_evaluation() {
534 let mut assignments = FxHashMap::default();
535 assignments.insert(
536 1,
537 ModelValue {
538 term: 1,
539 value: Value::Int(42),
540 theory: Theory::Arithmetic,
541 is_witness: false,
542 },
543 );
544
545 let model = Model {
546 assignments,
547 equiv_classes: FxHashMap::default(),
548 };
549
550 assert_eq!(model.eval(1), Some(&Value::Int(42)));
551 assert_eq!(model.eval(2), None);
552 }
553
554 #[test]
555 fn test_model_with_equivalence() {
556 let mut assignments = FxHashMap::default();
557 assignments.insert(
558 1,
559 ModelValue {
560 term: 1,
561 value: Value::Int(42),
562 theory: Theory::Arithmetic,
563 is_witness: false,
564 },
565 );
566
567 let mut equiv_classes = FxHashMap::default();
568 equiv_classes.insert(2, 1); let model = Model {
571 assignments,
572 equiv_classes,
573 };
574
575 assert_eq!(model.eval(2), Some(&Value::Int(42)));
576 }
577
578 #[test]
579 fn test_theory_assignments_filter() {
580 let mut assignments = FxHashMap::default();
581 assignments.insert(
582 1,
583 ModelValue {
584 term: 1,
585 value: Value::Int(42),
586 theory: Theory::Arithmetic,
587 is_witness: false,
588 },
589 );
590 assignments.insert(
591 2,
592 ModelValue {
593 term: 2,
594 value: Value::BitVec(0xff, 8),
595 theory: Theory::BitVector,
596 is_witness: false,
597 },
598 );
599
600 let model = Model {
601 assignments,
602 equiv_classes: FxHashMap::default(),
603 };
604
605 let arith_assignments = model.theory_assignments(Theory::Arithmetic);
606 assert_eq!(arith_assignments.len(), 1);
607 assert_eq!(arith_assignments[0].term, 1);
608 }
609
610 #[test]
611 fn test_witness_filter() {
612 let mut assignments = FxHashMap::default();
613 assignments.insert(
614 1,
615 ModelValue {
616 term: 1,
617 value: Value::Int(0),
618 theory: Theory::Arithmetic,
619 is_witness: true,
620 },
621 );
622 assignments.insert(
623 2,
624 ModelValue {
625 term: 2,
626 value: Value::Int(5),
627 theory: Theory::Arithmetic,
628 is_witness: false,
629 },
630 );
631
632 let model = Model {
633 assignments,
634 equiv_classes: FxHashMap::default(),
635 };
636
637 let witnesses = model.witnesses();
638 assert_eq!(witnesses.len(), 1);
639 assert_eq!(witnesses[0].term, 1);
640 }
641
642 #[test]
643 fn test_consistency_check() {
644 let config = ModelBuilderConfig {
645 check_consistency: true,
646 ..Default::default()
647 };
648 let mut builder = AdvancedModelBuilder::new(config);
649
650 let result = builder.check_cross_theory_consistency();
651 assert!(result.is_ok());
652 assert_eq!(builder.stats.consistency_checks, 1);
653 }
654
655 #[test]
656 fn test_minimization() {
657 let config = ModelBuilderConfig {
658 enable_minimization: true,
659 max_minimization_iters: 5,
660 ..Default::default()
661 };
662 let mut builder = AdvancedModelBuilder::new(config);
663
664 builder.assignments.insert(
666 1,
667 ModelValue {
668 term: 1,
669 value: Value::Int(42),
670 theory: Theory::Arithmetic,
671 is_witness: false,
672 },
673 );
674
675 let result = builder.minimize_model();
676 assert!(result.is_ok());
677 assert!(builder.stats.minimization_steps > 0);
678 }
679
680 #[test]
681 fn test_reset() {
682 let mut builder = AdvancedModelBuilder::new(ModelBuilderConfig::default());
683
684 builder.assignments.insert(
685 1,
686 ModelValue {
687 term: 1,
688 value: Value::Int(42),
689 theory: Theory::Arithmetic,
690 is_witness: false,
691 },
692 );
693 builder.add_witness_obligation(2);
694
695 builder.reset();
696 assert!(builder.assignments.is_empty());
697 assert!(builder.witness_obligations.is_empty());
698 }
699}