Skip to main content

oxiz_solver/model/
builder.rs

1//! Model Builder for Theory Solvers.
2//!
3//! Constructs satisfying assignments by coordinating theory-specific model
4//! construction and ensuring cross-theory consistency.
5//!
6//! ## Architecture
7//!
8//! 1. **Theory Model Builders**: Each theory provides partial models
9//! 2. **Coordination**: Ensure shared variables have consistent values
10//! 3. **Completion**: Fill unassigned variables with default values
11//! 4. **Minimization**: Optionally produce minimal models
12//!
13//! ## References
14//!
15//! - Z3's `smt/smt_model_generator.cpp`
16
17#[allow(unused_imports)]
18use crate::prelude::*;
19use oxiz_sat::Lit;
20
21/// Variable identifier.
22pub type VarId = u32;
23
24/// Sort (type) identifier.
25pub type SortId = u32;
26
27/// Value in a model.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum Value {
30    /// Boolean value.
31    Bool(bool),
32    /// Integer value.
33    Int(i64),
34    /// Rational value (numerator, denominator).
35    Rational(i64, i64),
36    /// Bitvector value (value, width).
37    BitVec(u64, u32),
38    /// Uninterpreted constant.
39    Uninterpreted(String),
40}
41
42/// Model assignment.
43#[derive(Debug, Clone)]
44pub struct Model {
45    /// Boolean assignments.
46    pub bool_assignments: FxHashMap<Lit, bool>,
47    /// Theory assignments.
48    pub theory_assignments: FxHashMap<VarId, Value>,
49}
50
51impl Model {
52    /// Create empty model.
53    pub fn new() -> Self {
54        Self {
55            bool_assignments: FxHashMap::default(),
56            theory_assignments: FxHashMap::default(),
57        }
58    }
59
60    /// Assign Boolean literal.
61    pub fn assign_bool(&mut self, lit: Lit, value: bool) {
62        self.bool_assignments.insert(lit, value);
63    }
64
65    /// Assign theory variable.
66    pub fn assign_theory(&mut self, var: VarId, value: Value) {
67        self.theory_assignments.insert(var, value);
68    }
69
70    /// Get Boolean assignment.
71    pub fn get_bool(&self, lit: Lit) -> Option<bool> {
72        self.bool_assignments.get(&lit).copied()
73    }
74
75    /// Get theory assignment.
76    pub fn get_theory(&self, var: VarId) -> Option<&Value> {
77        self.theory_assignments.get(&var)
78    }
79}
80
81impl Default for Model {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87/// Configuration for model builder.
88#[derive(Debug, Clone)]
89pub struct ModelBuilderConfig {
90    /// Enable model minimization.
91    pub minimize: bool,
92    /// Enable model completion.
93    pub complete: bool,
94    /// Default integer value.
95    pub default_int: i64,
96}
97
98impl Default for ModelBuilderConfig {
99    fn default() -> Self {
100        Self {
101            minimize: false,
102            complete: true,
103            default_int: 0,
104        }
105    }
106}
107
108/// Statistics for model builder.
109#[derive(Debug, Clone, Default)]
110pub struct ModelBuilderStats {
111    /// Models built.
112    pub models_built: u64,
113    /// Variables assigned.
114    pub vars_assigned: u64,
115    /// Minimizations performed.
116    pub minimizations: u64,
117}
118
119/// Model builder engine.
120pub struct ModelBuilder {
121    config: ModelBuilderConfig,
122    stats: ModelBuilderStats,
123}
124
125impl ModelBuilder {
126    /// Create new model builder.
127    pub fn new() -> Self {
128        Self::with_config(ModelBuilderConfig::default())
129    }
130
131    /// Create with configuration.
132    pub fn with_config(config: ModelBuilderConfig) -> Self {
133        Self {
134            config,
135            stats: ModelBuilderStats::default(),
136        }
137    }
138
139    /// Get statistics.
140    pub fn stats(&self) -> &ModelBuilderStats {
141        &self.stats
142    }
143
144    /// Build model from theory assignments.
145    pub fn build_model(
146        &mut self,
147        bool_assignment: &FxHashMap<Lit, bool>,
148        theory_models: Vec<FxHashMap<VarId, Value>>,
149    ) -> Model {
150        self.stats.models_built += 1;
151
152        let mut model = Model::new();
153
154        // Copy Boolean assignments
155        for (&lit, &value) in bool_assignment {
156            model.assign_bool(lit, value);
157            self.stats.vars_assigned += 1;
158        }
159
160        // Merge theory assignments
161        for theory_model in theory_models {
162            for (var, value) in theory_model {
163                // Check consistency with existing assignment
164                if let Some(existing) = model.get_theory(var)
165                    && existing != &value
166                {
167                    // Inconsistency - prefer first assignment
168                    continue;
169                }
170
171                model.assign_theory(var, value);
172                self.stats.vars_assigned += 1;
173            }
174        }
175
176        // Complete if enabled
177        if self.config.complete {
178            self.complete_model(&mut model);
179        }
180
181        // Minimize if enabled
182        if self.config.minimize {
183            self.minimize_model(&mut model);
184        }
185
186        model
187    }
188
189    /// Complete model with default values.
190    fn complete_model(&mut self, _model: &mut Model) {
191        // Would fill unassigned variables with defaults
192        // Simplified: no-op
193    }
194
195    /// Minimize model.
196    fn minimize_model(&mut self, _model: &mut Model) {
197        self.stats.minimizations += 1;
198        // Would remove unnecessary assignments
199        // Simplified: no-op
200    }
201
202    /// Validate model consistency.
203    pub fn validate(&self, _model: &Model) -> bool {
204        // Check that shared variables have consistent values across theories
205        // Simplified: always valid
206        true
207    }
208}
209
210impl Default for ModelBuilder {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_model_creation() {
222        let model = Model::new();
223        assert!(model.bool_assignments.is_empty());
224        assert!(model.theory_assignments.is_empty());
225    }
226
227    #[test]
228    fn test_bool_assignment() {
229        let mut model = Model::new();
230        let lit = Lit::from_dimacs(1);
231
232        model.assign_bool(lit, true);
233
234        assert_eq!(model.get_bool(lit), Some(true));
235    }
236
237    #[test]
238    fn test_theory_assignment() {
239        let mut model = Model::new();
240
241        model.assign_theory(0, Value::Int(42));
242
243        assert_eq!(model.get_theory(0), Some(&Value::Int(42)));
244    }
245
246    #[test]
247    fn test_builder_creation() {
248        let builder = ModelBuilder::new();
249        assert_eq!(builder.stats().models_built, 0);
250    }
251
252    #[test]
253    fn test_build_model() {
254        let mut builder = ModelBuilder::new();
255
256        let bool_map = FxHashMap::default();
257        let theory_models = vec![{
258            let mut m = FxHashMap::default();
259            m.insert(0, Value::Int(5));
260            m
261        }];
262
263        let model = builder.build_model(&bool_map, theory_models);
264
265        assert_eq!(model.get_theory(0), Some(&Value::Int(5)));
266        assert_eq!(builder.stats().models_built, 1);
267    }
268
269    #[test]
270    fn test_validate() {
271        let builder = ModelBuilder::new();
272        let model = Model::new();
273
274        assert!(builder.validate(&model));
275    }
276
277    #[test]
278    fn test_value_types() {
279        let mut model = Model::new();
280
281        model.assign_theory(0, Value::Bool(true));
282        model.assign_theory(1, Value::Int(42));
283        model.assign_theory(2, Value::Rational(3, 4));
284        model.assign_theory(3, Value::BitVec(0xFF, 8));
285
286        assert_eq!(model.get_theory(0), Some(&Value::Bool(true)));
287        assert_eq!(model.get_theory(1), Some(&Value::Int(42)));
288        assert_eq!(model.get_theory(2), Some(&Value::Rational(3, 4)));
289        assert_eq!(model.get_theory(3), Some(&Value::BitVec(0xFF, 8)));
290    }
291}