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