Skip to main content

tensorlogic_adapters/
constraint.rs

1//! Predicate constraints and properties.
2
3use serde::{Deserialize, Serialize};
4
5/// Properties that can be associated with predicates
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7pub enum PredicateProperty {
8    /// Predicate is symmetric: P(x,y) ⟺ P(y,x)
9    Symmetric,
10    /// Predicate is transitive: P(x,y) ∧ P(y,z) ⟹ P(x,z)
11    Transitive,
12    /// Predicate is reflexive: ∀x. P(x,x)
13    Reflexive,
14    /// Predicate is irreflexive: ∀x. ¬P(x,x)
15    Irreflexive,
16    /// Predicate is antisymmetric: P(x,y) ∧ P(y,x) ⟹ x = y
17    Antisymmetric,
18    /// Predicate is functional: ∀x,y,z. P(x,y) ∧ P(x,z) ⟹ y = z
19    Functional,
20    /// Predicate is inverse functional: ∀x,y,z. P(y,x) ∧ P(z,x) ⟹ y = z
21    InverseFunctional,
22}
23
24/// Value range constraint for numeric predicates
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26pub struct ValueRange {
27    pub min: Option<f64>,
28    pub max: Option<f64>,
29    pub inclusive_min: bool,
30    pub inclusive_max: bool,
31}
32
33impl ValueRange {
34    pub fn new() -> Self {
35        Self {
36            min: None,
37            max: None,
38            inclusive_min: true,
39            inclusive_max: true,
40        }
41    }
42
43    pub fn with_min(mut self, min: f64, inclusive: bool) -> Self {
44        self.min = Some(min);
45        self.inclusive_min = inclusive;
46        self
47    }
48
49    pub fn with_max(mut self, max: f64, inclusive: bool) -> Self {
50        self.max = Some(max);
51        self.inclusive_max = inclusive;
52        self
53    }
54
55    pub fn contains(&self, value: f64) -> bool {
56        if let Some(min) = self.min {
57            if self.inclusive_min {
58                if value < min {
59                    return false;
60                }
61            } else if value <= min {
62                return false;
63            }
64        }
65
66        if let Some(max) = self.max {
67            if self.inclusive_max {
68                if value > max {
69                    return false;
70                }
71            } else if value >= max {
72                return false;
73            }
74        }
75
76        true
77    }
78}
79
80impl Default for ValueRange {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86/// Functional dependency between predicate arguments
87#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
88pub struct FunctionalDependency {
89    /// Determining arguments (indices)
90    pub determinants: Vec<usize>,
91    /// Dependent arguments (indices)
92    pub dependents: Vec<usize>,
93}
94
95impl FunctionalDependency {
96    pub fn new(determinants: Vec<usize>, dependents: Vec<usize>) -> Self {
97        Self {
98            determinants,
99            dependents,
100        }
101    }
102}
103
104/// Constraints associated with a predicate
105#[derive(Clone, Debug, Default, Serialize, Deserialize)]
106pub struct PredicateConstraints {
107    /// Logical properties of the predicate
108    pub properties: Vec<PredicateProperty>,
109    /// Value ranges for each argument (None if unconstrained)
110    pub value_ranges: Vec<Option<ValueRange>>,
111    /// Functional dependencies between arguments
112    pub functional_dependencies: Vec<FunctionalDependency>,
113}
114
115impl PredicateConstraints {
116    pub fn new() -> Self {
117        Self::default()
118    }
119
120    pub fn with_property(mut self, property: PredicateProperty) -> Self {
121        self.properties.push(property);
122        self
123    }
124
125    pub fn with_value_range(mut self, arg_index: usize, range: ValueRange) -> Self {
126        while self.value_ranges.len() <= arg_index {
127            self.value_ranges.push(None);
128        }
129        self.value_ranges[arg_index] = Some(range);
130        self
131    }
132
133    pub fn with_functional_dependency(mut self, dependency: FunctionalDependency) -> Self {
134        self.functional_dependencies.push(dependency);
135        self
136    }
137
138    pub fn has_property(&self, property: &PredicateProperty) -> bool {
139        self.properties.contains(property)
140    }
141
142    pub fn is_symmetric(&self) -> bool {
143        self.has_property(&PredicateProperty::Symmetric)
144    }
145
146    pub fn is_transitive(&self) -> bool {
147        self.has_property(&PredicateProperty::Transitive)
148    }
149
150    pub fn is_reflexive(&self) -> bool {
151        self.has_property(&PredicateProperty::Reflexive)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_value_range() {
161        let range = ValueRange::new().with_min(0.0, true).with_max(1.0, true);
162
163        assert!(range.contains(0.0));
164        assert!(range.contains(0.5));
165        assert!(range.contains(1.0));
166        assert!(!range.contains(-0.1));
167        assert!(!range.contains(1.1));
168    }
169
170    #[test]
171    fn test_value_range_exclusive() {
172        let range = ValueRange::new().with_min(0.0, false).with_max(1.0, false);
173
174        assert!(!range.contains(0.0));
175        assert!(range.contains(0.5));
176        assert!(!range.contains(1.0));
177    }
178
179    #[test]
180    fn test_predicate_properties() {
181        let constraints = PredicateConstraints::new()
182            .with_property(PredicateProperty::Symmetric)
183            .with_property(PredicateProperty::Transitive);
184
185        assert!(constraints.is_symmetric());
186        assert!(constraints.is_transitive());
187        assert!(!constraints.is_reflexive());
188    }
189
190    #[test]
191    fn test_functional_dependency() {
192        let fd = FunctionalDependency::new(vec![0], vec![1, 2]);
193
194        assert_eq!(fd.determinants, vec![0]);
195        assert_eq!(fd.dependents, vec![1, 2]);
196    }
197}