1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::error::IrError;
12
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15pub struct DomainInfo {
16 pub name: String,
18 pub size: Option<usize>,
20 pub domain_type: DomainType,
22 pub constraints: Vec<String>,
24 pub metadata: HashMap<String, String>,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub enum DomainType {
31 Finite,
33 Integer,
35 Real,
37 Complex,
39 Boolean,
41 Custom(String),
43}
44
45impl DomainInfo {
46 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 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 pub fn integer(name: impl Into<String>) -> Self {
70 Self::new(name, DomainType::Integer)
71 }
72
73 pub fn real(name: impl Into<String>) -> Self {
75 Self::new(name, DomainType::Real)
76 }
77
78 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 pub fn with_constraint(mut self, constraint: impl Into<String>) -> Self {
91 self.constraints.push(constraint.into());
92 self
93 }
94
95 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 pub fn with_size(mut self, size: usize) -> Self {
103 self.size = Some(size);
104 self
105 }
106
107 pub fn is_compatible_with(&self, other: &DomainInfo) -> bool {
114 if self.domain_type != other.domain_type {
116 return false;
117 }
118
119 if let (Some(size1), Some(size2)) = (self.size, other.size) {
121 if size1 != size2 {
122 return false;
123 }
124 }
125
126 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 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 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 (DomainType::Boolean, DomainType::Integer | DomainType::Real) => true,
161
162 (DomainType::Integer, DomainType::Real) => true,
164
165 (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 (a, b) if a == b => self.is_compatible_with(target),
176
177 _ => false,
178 }
179 }
180}
181
182#[derive(Clone, Debug, Default, Serialize, Deserialize)]
184pub struct DomainRegistry {
185 domains: HashMap<String, DomainInfo>,
186}
187
188impl DomainRegistry {
189 pub fn new() -> Self {
191 Self {
192 domains: HashMap::new(),
193 }
194 }
195
196 pub fn with_builtins() -> Self {
198 let mut registry = Self::new();
199
200 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 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 pub fn register_or_replace(&mut self, domain: DomainInfo) {
232 self.domains.insert(domain.name.clone(), domain);
233 }
234
235 pub fn get(&self, name: &str) -> Option<&DomainInfo> {
237 self.domains.get(name)
238 }
239
240 pub fn contains(&self, name: &str) -> bool {
242 self.domains.contains_key(name)
243 }
244
245 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 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 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 pub fn domain_names(&self) -> Vec<String> {
268 self.domains.keys().cloned().collect()
269 }
270
271 pub fn len(&self) -> usize {
273 self.domains.len()
274 }
275
276 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}