Skip to main content

oxiz_solver/
delayed_combination.rs

1//! Delayed Theory Combination.
2//!
3//! Implements lazy theory combination where theory interaction is delayed
4//! until necessary, reducing overhead for problems with limited sharing.
5//!
6//! ## Architecture
7//!
8//! - **Lazy Equality Sharing**: Only share equalities when conflicts arise
9//! - **On-Demand Interface Reasoning**: Activate theories as needed
10//! - **Conflict-Driven Refinement**: Refine theory interaction based on conflicts
11//!
12//! ## References
13//!
14//! - "Delayed Theory Combination vs. Nelson-Oppen" (Meng & Reynolds, 2015)
15//! - Z3's `smt/theory_combine.cpp`
16
17use oxiz_core::TermId;
18use rustc_hash::FxHashMap;
19
20/// Theory identifier.
21pub type TheoryId = usize;
22
23/// Shared term that appears in multiple theories.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub struct SharedTerm {
26    /// The term.
27    pub term: TermId,
28    /// Theories that use this term.
29    pub theories: u64, // Bitset of theory IDs
30}
31
32/// Equality that needs to be shared.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct DeferredEquality {
35    /// Left-hand side.
36    pub lhs: TermId,
37    /// Right-hand side.
38    pub rhs: TermId,
39    /// Source theory that discovered this equality.
40    pub source_theory: TheoryId,
41}
42
43/// Configuration for delayed combination.
44#[derive(Debug, Clone)]
45pub struct DelayedCombinationConfig {
46    /// Enable lazy equality sharing.
47    pub lazy_sharing: bool,
48    /// Enable conflict-driven refinement.
49    pub conflict_driven: bool,
50    /// Maximum deferred equalities before forcing propagation.
51    pub max_deferred: usize,
52}
53
54impl Default for DelayedCombinationConfig {
55    fn default() -> Self {
56        Self {
57            lazy_sharing: true,
58            conflict_driven: true,
59            max_deferred: 1000,
60        }
61    }
62}
63
64/// Statistics for delayed combination.
65#[derive(Debug, Clone, Default)]
66pub struct DelayedCombinationStats {
67    /// Equalities deferred.
68    pub equalities_deferred: u64,
69    /// Equalities propagated.
70    pub equalities_propagated: u64,
71    /// Forced propagations (max_deferred reached).
72    pub forced_propagations: u64,
73    /// Conflicts detected.
74    pub conflicts_detected: u64,
75}
76
77/// Delayed theory combination engine.
78#[derive(Debug)]
79pub struct DelayedCombination {
80    /// Shared terms indexed by term ID.
81    shared_terms: FxHashMap<TermId, SharedTerm>,
82    /// Deferred equalities.
83    deferred: Vec<DeferredEquality>,
84    /// Active theories (bitset).
85    active_theories: u64,
86    /// Configuration.
87    config: DelayedCombinationConfig,
88    /// Statistics.
89    stats: DelayedCombinationStats,
90}
91
92impl DelayedCombination {
93    /// Create a new delayed combination engine.
94    pub fn new(config: DelayedCombinationConfig) -> Self {
95        Self {
96            shared_terms: FxHashMap::default(),
97            deferred: Vec::new(),
98            active_theories: 0,
99            config,
100            stats: DelayedCombinationStats::default(),
101        }
102    }
103
104    /// Create with default configuration.
105    pub fn default_config() -> Self {
106        Self::new(DelayedCombinationConfig::default())
107    }
108
109    /// Register a shared term.
110    pub fn register_shared_term(&mut self, term: TermId, theory: TheoryId) {
111        let entry = self
112            .shared_terms
113            .entry(term)
114            .or_insert(SharedTerm { term, theories: 0 });
115
116        entry.theories |= 1 << theory;
117    }
118
119    /// Check if a term is shared between multiple theories.
120    pub fn is_shared(&self, term: TermId) -> bool {
121        self.shared_terms
122            .get(&term)
123            .map(|st| st.theories.count_ones() > 1)
124            .unwrap_or(false)
125    }
126
127    /// Defer an equality for later propagation.
128    pub fn defer_equality(&mut self, lhs: TermId, rhs: TermId, source: TheoryId) {
129        if !self.config.lazy_sharing {
130            // Immediate propagation mode
131            self.propagate_equality(lhs, rhs, source);
132            return;
133        }
134
135        self.deferred.push(DeferredEquality {
136            lhs,
137            rhs,
138            source_theory: source,
139        });
140        self.stats.equalities_deferred += 1;
141
142        // Force propagation if too many deferred
143        if self.deferred.len() >= self.config.max_deferred {
144            self.force_propagation();
145        }
146    }
147
148    /// Propagate a single equality immediately.
149    fn propagate_equality(&mut self, _lhs: TermId, _rhs: TermId, _source: TheoryId) {
150        // Simplified: would notify relevant theories
151        self.stats.equalities_propagated += 1;
152    }
153
154    /// Force propagation of all deferred equalities.
155    pub fn force_propagation(&mut self) {
156        if self.deferred.is_empty() {
157            return;
158        }
159
160        self.stats.forced_propagations += 1;
161
162        // Collect deferred equalities to avoid borrow checker issues
163        let equalities: Vec<_> = self.deferred.drain(..).collect();
164        for eq in equalities {
165            self.propagate_equality(eq.lhs, eq.rhs, eq.source_theory);
166        }
167    }
168
169    /// Handle a conflict by activating theory combination.
170    pub fn handle_conflict(&mut self) {
171        if !self.config.conflict_driven {
172            return;
173        }
174
175        self.stats.conflicts_detected += 1;
176
177        // Force propagation of deferred equalities
178        self.force_propagation();
179    }
180
181    /// Activate a theory.
182    pub fn activate_theory(&mut self, theory: TheoryId) {
183        self.active_theories |= 1 << theory;
184    }
185
186    /// Check if a theory is active.
187    pub fn is_theory_active(&self, theory: TheoryId) -> bool {
188        (self.active_theories & (1 << theory)) != 0
189    }
190
191    /// Get theories that share a term.
192    pub fn get_sharing_theories(&self, term: TermId) -> Vec<TheoryId> {
193        if let Some(shared) = self.shared_terms.get(&term) {
194            let mut theories = Vec::new();
195            for i in 0..64 {
196                if (shared.theories & (1 << i)) != 0 {
197                    theories.push(i);
198                }
199            }
200            theories
201        } else {
202            Vec::new()
203        }
204    }
205
206    /// Get statistics.
207    pub fn stats(&self) -> &DelayedCombinationStats {
208        &self.stats
209    }
210
211    /// Reset statistics.
212    pub fn reset_stats(&mut self) {
213        self.stats = DelayedCombinationStats::default();
214    }
215}
216
217impl Default for DelayedCombination {
218    fn default() -> Self {
219        Self::default_config()
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_delayed_combination_creation() {
229        let dc = DelayedCombination::default_config();
230        assert_eq!(dc.stats().equalities_deferred, 0);
231    }
232
233    #[test]
234    fn test_register_shared_term() {
235        let mut dc = DelayedCombination::default_config();
236
237        let term = TermId::new(0);
238        dc.register_shared_term(term, 0);
239        dc.register_shared_term(term, 1);
240
241        assert!(dc.is_shared(term));
242    }
243
244    #[test]
245    fn test_defer_equality() {
246        let mut dc = DelayedCombination::default_config();
247
248        let lhs = TermId::new(0);
249        let rhs = TermId::new(1);
250
251        dc.defer_equality(lhs, rhs, 0);
252
253        assert_eq!(dc.stats().equalities_deferred, 1);
254        assert_eq!(dc.deferred.len(), 1);
255    }
256
257    #[test]
258    fn test_force_propagation() {
259        let mut dc = DelayedCombination::default_config();
260
261        let lhs = TermId::new(0);
262        let rhs = TermId::new(1);
263
264        dc.defer_equality(lhs, rhs, 0);
265        dc.force_propagation();
266
267        assert_eq!(dc.deferred.len(), 0);
268        assert_eq!(dc.stats().equalities_propagated, 1);
269        assert_eq!(dc.stats().forced_propagations, 1);
270    }
271
272    #[test]
273    fn test_activate_theory() {
274        let mut dc = DelayedCombination::default_config();
275
276        dc.activate_theory(2);
277        assert!(dc.is_theory_active(2));
278        assert!(!dc.is_theory_active(3));
279    }
280
281    #[test]
282    fn test_get_sharing_theories() {
283        let mut dc = DelayedCombination::default_config();
284
285        let term = TermId::new(0);
286        dc.register_shared_term(term, 1);
287        dc.register_shared_term(term, 3);
288
289        let theories = dc.get_sharing_theories(term);
290
291        assert_eq!(theories.len(), 2);
292        assert!(theories.contains(&1));
293        assert!(theories.contains(&3));
294    }
295
296    #[test]
297    fn test_handle_conflict() {
298        let mut dc = DelayedCombination::default_config();
299
300        let lhs = TermId::new(0);
301        let rhs = TermId::new(1);
302
303        dc.defer_equality(lhs, rhs, 0);
304        dc.handle_conflict();
305
306        assert_eq!(dc.deferred.len(), 0);
307        assert_eq!(dc.stats().conflicts_detected, 1);
308    }
309}