Skip to main content

tensorlogic_ir/
domain.rs

1//! Domain constraints for variables and quantifiers.
2//!
3//! This module provides:
4//! - Domain metadata (size, type, constraints)
5//! - Domain registry for managing variable domains
6//! - Domain validation and compatibility checking
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::error::IrError;
12
13/// Represents a domain constraint for a variable.
14#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15pub struct DomainInfo {
16    /// Domain name (e.g., "Person", "Integer", "Real")
17    pub name: String,
18    /// Domain size (None for infinite domains like Real)
19    pub size: Option<usize>,
20    /// Domain type category
21    pub domain_type: DomainType,
22    /// Additional constraints (e.g., "positive", "bounded")
23    pub constraints: Vec<String>,
24    /// Optional metadata for custom domains
25    pub metadata: HashMap<String, String>,
26}
27
28/// Type category of a domain.
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub enum DomainType {
31    /// Finite discrete domain (e.g., enum, categorical)
32    Finite,
33    /// Integer domain (bounded or unbounded)
34    Integer,
35    /// Real number domain (continuous)
36    Real,
37    /// Complex number domain
38    Complex,
39    /// Boolean domain {true, false}
40    Boolean,
41    /// Custom domain type
42    Custom(String),
43}
44
45impl DomainInfo {
46    /// Create a new domain with given name and type.
47    pub fn new(name: impl Into<String>, domain_type: DomainType) -> Self {
48        Self {
49            name: name.into(),
50            size: None,
51            domain_type,
52            constraints: Vec::new(),
53            metadata: HashMap::new(),
54        }
55    }
56
57    /// Create a finite domain with a specific size.
58    pub fn finite(name: impl Into<String>, size: usize) -> Self {
59        Self {
60            name: name.into(),
61            size: Some(size),
62            domain_type: DomainType::Finite,
63            constraints: Vec::new(),
64            metadata: HashMap::new(),
65        }
66    }
67
68    /// Create an integer domain.
69    pub fn integer(name: impl Into<String>) -> Self {
70        Self::new(name, DomainType::Integer)
71    }
72
73    /// Create a real number domain.
74    pub fn real(name: impl Into<String>) -> Self {
75        Self::new(name, DomainType::Real)
76    }
77
78    /// Create a boolean domain.
79    pub fn boolean(name: impl Into<String>) -> Self {
80        Self {
81            name: name.into(),
82            size: Some(2),
83            domain_type: DomainType::Boolean,
84            constraints: Vec::new(),
85            metadata: HashMap::new(),
86        }
87    }
88
89    /// Add a constraint to this domain.
90    pub fn with_constraint(mut self, constraint: impl Into<String>) -> Self {
91        self.constraints.push(constraint.into());
92        self
93    }
94
95    /// Add metadata to this domain.
96    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
97        self.metadata.insert(key.into(), value.into());
98        self
99    }
100
101    /// Set the size for this domain.
102    pub fn with_size(mut self, size: usize) -> Self {
103        self.size = Some(size);
104        self
105    }
106
107    /// Check if this domain is compatible with another.
108    ///
109    /// Two domains are compatible if:
110    /// - They have the same type category
111    /// - Finite domains have compatible sizes
112    /// - Constraints don't conflict
113    pub fn is_compatible_with(&self, other: &DomainInfo) -> bool {
114        // Type must match
115        if self.domain_type != other.domain_type {
116            return false;
117        }
118
119        // For finite domains, sizes must be compatible
120        if let (Some(size1), Some(size2)) = (self.size, other.size) {
121            if size1 != size2 {
122                return false;
123            }
124        }
125
126        // Check for constraint conflicts (simple check - can be extended)
127        for constraint in &self.constraints {
128            if let Some(negated) = constraint.strip_prefix("not_") {
129                if other.constraints.contains(&negated.to_string()) {
130                    return false;
131                }
132            }
133        }
134
135        // Check the reverse direction
136        for constraint in &other.constraints {
137            if let Some(negated) = constraint.strip_prefix("not_") {
138                if self.constraints.contains(&negated.to_string()) {
139                    return false;
140                }
141            }
142        }
143
144        true
145    }
146
147    /// Check if this domain can be cast to another domain.
148    ///
149    /// Casting rules:
150    /// - Boolean -> Integer -> Real
151    /// - Finite -> Integer (if size fits)
152    /// - Same type is always compatible
153    pub fn can_cast_to(&self, target: &DomainInfo) -> bool {
154        if self == target {
155            return true;
156        }
157
158        match (&self.domain_type, &target.domain_type) {
159            // Boolean can cast to anything numeric
160            (DomainType::Boolean, DomainType::Integer | DomainType::Real) => true,
161
162            // Integer can cast to Real
163            (DomainType::Integer, DomainType::Real) => true,
164
165            // Finite can cast to Integer if small enough
166            (DomainType::Finite, DomainType::Integer) => {
167                if let Some(size) = self.size {
168                    size <= i32::MAX as usize
169                } else {
170                    false
171                }
172            }
173
174            // Same type is compatible
175            (a, b) if a == b => self.is_compatible_with(target),
176
177            _ => false,
178        }
179    }
180}
181
182/// Registry for managing domain information.
183#[derive(Clone, Debug, Default, Serialize, Deserialize)]
184pub struct DomainRegistry {
185    domains: HashMap<String, DomainInfo>,
186}
187
188impl DomainRegistry {
189    /// Create a new empty domain registry.
190    pub fn new() -> Self {
191        Self {
192            domains: HashMap::new(),
193        }
194    }
195
196    /// Create a registry with standard built-in domains.
197    pub fn with_builtins() -> Self {
198        let mut registry = Self::new();
199
200        // Add standard domains
201        let _ = registry.register(DomainInfo::boolean("Bool"));
202        let _ = registry.register(DomainInfo::integer("Int"));
203        let _ = registry.register(DomainInfo::real("Real"));
204        let _ = registry.register(
205            DomainInfo::integer("Nat")
206                .with_constraint("non_negative")
207                .with_metadata("min", "0"),
208        );
209        let _ = registry.register(
210            DomainInfo::real("Probability")
211                .with_constraint("bounded")
212                .with_metadata("min", "0.0")
213                .with_metadata("max", "1.0"),
214        );
215
216        registry
217    }
218
219    /// Register a new domain.
220    pub fn register(&mut self, domain: DomainInfo) -> Result<(), IrError> {
221        if self.domains.contains_key(&domain.name) {
222            return Err(IrError::DomainAlreadyExists {
223                name: domain.name.clone(),
224            });
225        }
226        self.domains.insert(domain.name.clone(), domain);
227        Ok(())
228    }
229
230    /// Register a domain, overwriting if it exists.
231    pub fn register_or_replace(&mut self, domain: DomainInfo) {
232        self.domains.insert(domain.name.clone(), domain);
233    }
234
235    /// Get domain information by name.
236    pub fn get(&self, name: &str) -> Option<&DomainInfo> {
237        self.domains.get(name)
238    }
239
240    /// Check if a domain exists.
241    pub fn contains(&self, name: &str) -> bool {
242        self.domains.contains_key(name)
243    }
244
245    /// Validate that a domain exists.
246    pub fn validate_domain(&self, name: &str) -> Result<&DomainInfo, IrError> {
247        self.get(name).ok_or_else(|| IrError::DomainNotFound {
248            name: name.to_string(),
249        })
250    }
251
252    /// Check if two domains are compatible.
253    pub fn are_compatible(&self, domain1: &str, domain2: &str) -> Result<bool, IrError> {
254        let d1 = self.validate_domain(domain1)?;
255        let d2 = self.validate_domain(domain2)?;
256        Ok(d1.is_compatible_with(d2))
257    }
258
259    /// Check if domain1 can be cast to domain2.
260    pub fn can_cast(&self, from: &str, to: &str) -> Result<bool, IrError> {
261        let d1 = self.validate_domain(from)?;
262        let d2 = self.validate_domain(to)?;
263        Ok(d1.can_cast_to(d2))
264    }
265
266    /// Get all registered domain names.
267    pub fn domain_names(&self) -> Vec<String> {
268        self.domains.keys().cloned().collect()
269    }
270
271    /// Get number of registered domains.
272    pub fn len(&self) -> usize {
273        self.domains.len()
274    }
275
276    /// Check if registry is empty.
277    pub fn is_empty(&self) -> bool {
278        self.domains.is_empty()
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_domain_info_creation() {
288        let domain = DomainInfo::finite("Color", 3);
289        assert_eq!(domain.name, "Color");
290        assert_eq!(domain.size, Some(3));
291        assert_eq!(domain.domain_type, DomainType::Finite);
292    }
293
294    #[test]
295    fn test_domain_compatibility() {
296        let int1 = DomainInfo::integer("Int1");
297        let int2 = DomainInfo::integer("Int2");
298        assert!(int1.is_compatible_with(&int2));
299
300        let int = DomainInfo::integer("Int");
301        let real = DomainInfo::real("Real");
302        assert!(!int.is_compatible_with(&real));
303    }
304
305    #[test]
306    fn test_domain_casting() {
307        let bool_dom = DomainInfo::boolean("Bool");
308        let int_dom = DomainInfo::integer("Int");
309        let real_dom = DomainInfo::real("Real");
310
311        assert!(bool_dom.can_cast_to(&int_dom));
312        assert!(bool_dom.can_cast_to(&real_dom));
313        assert!(int_dom.can_cast_to(&real_dom));
314        assert!(!real_dom.can_cast_to(&int_dom));
315    }
316
317    #[test]
318    fn test_finite_domain_size_compatibility() {
319        let d1 = DomainInfo::finite("D1", 5);
320        let d2 = DomainInfo::finite("D2", 5);
321        let d3 = DomainInfo::finite("D3", 10);
322
323        assert!(d1.is_compatible_with(&d2));
324        assert!(!d1.is_compatible_with(&d3));
325    }
326
327    #[test]
328    fn test_domain_constraints() {
329        let positive = DomainInfo::integer("Positive").with_constraint("positive");
330        let negative = DomainInfo::integer("Negative").with_constraint("not_positive");
331
332        assert!(!positive.is_compatible_with(&negative));
333    }
334
335    #[test]
336    fn test_domain_registry() {
337        let mut registry = DomainRegistry::new();
338        let domain = DomainInfo::finite("Color", 3);
339
340        registry.register(domain.clone()).unwrap();
341        assert!(registry.contains("Color"));
342        assert_eq!(registry.get("Color"), Some(&domain));
343    }
344
345    #[test]
346    fn test_builtin_domains() {
347        let registry = DomainRegistry::with_builtins();
348
349        assert!(registry.contains("Bool"));
350        assert!(registry.contains("Int"));
351        assert!(registry.contains("Real"));
352        assert!(registry.contains("Nat"));
353        assert!(registry.contains("Probability"));
354
355        let prob = registry.get("Probability").unwrap();
356        assert_eq!(prob.metadata.get("min"), Some(&"0.0".to_string()));
357        assert_eq!(prob.metadata.get("max"), Some(&"1.0".to_string()));
358    }
359
360    #[test]
361    fn test_registry_compatibility_check() {
362        let mut registry = DomainRegistry::new();
363        registry.register(DomainInfo::integer("Int1")).unwrap();
364        registry.register(DomainInfo::integer("Int2")).unwrap();
365        registry.register(DomainInfo::real("Real")).unwrap();
366
367        assert!(registry.are_compatible("Int1", "Int2").unwrap());
368        assert!(!registry.are_compatible("Int1", "Real").unwrap());
369    }
370
371    #[test]
372    fn test_registry_casting() {
373        let registry = DomainRegistry::with_builtins();
374
375        assert!(registry.can_cast("Bool", "Int").unwrap());
376        assert!(registry.can_cast("Bool", "Real").unwrap());
377        assert!(registry.can_cast("Int", "Real").unwrap());
378        assert!(!registry.can_cast("Real", "Int").unwrap());
379    }
380
381    #[test]
382    fn test_domain_metadata() {
383        let domain = DomainInfo::real("Temperature")
384            .with_metadata("unit", "celsius")
385            .with_metadata("min", "-273.15");
386
387        assert_eq!(domain.metadata.get("unit"), Some(&"celsius".to_string()));
388        assert_eq!(domain.metadata.get("min"), Some(&"-273.15".to_string()));
389    }
390}