Skip to main content

oxiz_solver/model/
completion.rs

1//! Model Completion.
2//!
3//! Fills in values for unassigned variables to produce a complete model
4//! that satisfies all constraints.
5//!
6//! ## Strategies
7//!
8//! - **Default values**: Assign default values based on sort
9//! - **Witness completion**: Use witness terms from theory solving
10//! - **Optimal completion**: Minimize model size or maximize interpretability
11//!
12//! ## References
13//!
14//! - Z3's `model/model_evaluator.cpp`
15
16use super::builder::{Model, Value, VarId};
17use rustc_hash::FxHashMap;
18
19/// Strategy for completing missing values.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum CompletionStrategy {
22    /// Use default values (0, false, empty).
23    Default,
24    /// Use witness terms from solving.
25    Witness,
26    /// Minimize model complexity.
27    Minimal,
28}
29
30/// Configuration for model completion.
31#[derive(Debug, Clone)]
32pub struct CompletionConfig {
33    /// Completion strategy.
34    pub strategy: CompletionStrategy,
35    /// Default integer value.
36    pub default_int: i64,
37    /// Default boolean value.
38    pub default_bool: bool,
39}
40
41impl Default for CompletionConfig {
42    fn default() -> Self {
43        Self {
44            strategy: CompletionStrategy::Default,
45            default_int: 0,
46            default_bool: false,
47        }
48    }
49}
50
51/// Statistics for model completion.
52#[derive(Debug, Clone, Default)]
53pub struct CompletionStats {
54    /// Number of variables completed.
55    pub vars_completed: u64,
56    /// Number of defaults used.
57    pub defaults_used: u64,
58    /// Number of witnesses used.
59    pub witnesses_used: u64,
60}
61
62/// Model completion engine.
63#[derive(Debug)]
64pub struct ModelCompleter {
65    /// Configuration.
66    config: CompletionConfig,
67    /// Witness values (from theory solving).
68    witnesses: FxHashMap<VarId, Value>,
69    /// Statistics.
70    stats: CompletionStats,
71}
72
73impl ModelCompleter {
74    /// Create a new model completer.
75    pub fn new(config: CompletionConfig) -> Self {
76        Self {
77            config,
78            witnesses: FxHashMap::default(),
79            stats: CompletionStats::default(),
80        }
81    }
82
83    /// Create with default configuration.
84    pub fn default_config() -> Self {
85        Self::new(CompletionConfig::default())
86    }
87
88    /// Register a witness value for a variable.
89    pub fn add_witness(&mut self, var: VarId, value: Value) {
90        self.witnesses.insert(var, value);
91    }
92
93    /// Complete a partial model by filling in missing values.
94    ///
95    /// Returns the completed model.
96    pub fn complete(&mut self, partial_model: &Model) -> Model {
97        let mut completed = partial_model.clone();
98
99        // Get all variables that need completion
100        let missing_vars = self.find_missing_vars(&completed);
101
102        for var in missing_vars {
103            let value = self.complete_variable(var);
104            completed.assign_theory(var, value);
105            self.stats.vars_completed += 1;
106        }
107
108        completed
109    }
110
111    /// Find variables that are not assigned in the model.
112    fn find_missing_vars(&self, model: &Model) -> Vec<VarId> {
113        // In a real implementation, this would query all declared variables
114        // and find which ones are not in the model
115        let mut missing = Vec::new();
116
117        // Check witnesses for unassigned variables
118        for &var in self.witnesses.keys() {
119            if model.get_theory(var).is_none() {
120                missing.push(var);
121            }
122        }
123
124        missing
125    }
126
127    /// Complete a single variable.
128    fn complete_variable(&mut self, var: VarId) -> Value {
129        match self.config.strategy {
130            CompletionStrategy::Witness => {
131                if let Some(witness) = self.witnesses.get(&var) {
132                    self.stats.witnesses_used += 1;
133                    witness.clone()
134                } else {
135                    self.stats.defaults_used += 1;
136                    self.default_value()
137                }
138            }
139            CompletionStrategy::Default => {
140                self.stats.defaults_used += 1;
141                self.default_value()
142            }
143            CompletionStrategy::Minimal => {
144                // Try witness first, then minimal value
145                if let Some(witness) = self.witnesses.get(&var) {
146                    self.stats.witnesses_used += 1;
147                    witness.clone()
148                } else {
149                    self.stats.defaults_used += 1;
150                    self.minimal_value()
151                }
152            }
153        }
154    }
155
156    /// Get default value based on configuration.
157    fn default_value(&self) -> Value {
158        Value::Int(self.config.default_int)
159    }
160
161    /// Get minimal value (smallest representable).
162    fn minimal_value(&self) -> Value {
163        Value::Int(0)
164    }
165
166    /// Get statistics.
167    pub fn stats(&self) -> &CompletionStats {
168        &self.stats
169    }
170
171    /// Reset completer state.
172    pub fn reset(&mut self) {
173        self.witnesses.clear();
174        self.stats = CompletionStats::default();
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_completer_creation() {
184        let completer = ModelCompleter::default_config();
185        assert_eq!(completer.stats().vars_completed, 0);
186    }
187
188    #[test]
189    fn test_add_witness() {
190        let mut completer = ModelCompleter::default_config();
191        completer.add_witness(0, Value::Int(42));
192
193        assert!(completer.witnesses.contains_key(&0));
194    }
195
196    #[test]
197    fn test_complete_with_witnesses() {
198        let config = CompletionConfig {
199            strategy: CompletionStrategy::Witness,
200            ..Default::default()
201        };
202        let mut completer = ModelCompleter::new(config);
203        completer.add_witness(0, Value::Int(10));
204        completer.add_witness(1, Value::Bool(true));
205
206        let partial = Model::new();
207        let _completed = completer.complete(&partial);
208
209        // Check witnesses were used
210        assert!(completer.stats().witnesses_used > 0);
211    }
212
213    #[test]
214    fn test_default_completion() {
215        let config = CompletionConfig {
216            strategy: CompletionStrategy::Default,
217            default_int: 100,
218            ..Default::default()
219        };
220
221        let completer = ModelCompleter::new(config);
222        let default_val = completer.default_value();
223
224        assert_eq!(default_val, Value::Int(100));
225    }
226}