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