Skip to main content

qualia_core_db/modalities/
fuzzy_quantifiers.rs

1//! Fuzzy linguistic quantifiers (Ma, Li & Ma ch 5.4; Zadeh) — evaluate statements
2//! like "*most* guardians concur" or "*few* objections" over graded evidence,
3//! keeping governance legible in human terms without collapsing to brittle counts.
4//!
5//! **Scope (a §12-allowed deferral):** this provides the *machinery* — Zadeh's
6//! relative quantifiers as monotone membership curves over the satisfied-proportion,
7//! plus the sigma-count proportion. The **named set** of governance quantifiers and
8//! their exact membership curves ("most", "almost all", and any sensitive ones) are
9//! **Timothy's to coin/ratify** — this module deliberately ships only generic
10//! constructors + the classic illustrative curves, not a governance vocabulary.
11//! Kernel-class `Reduction`.
12
13/// A relative fuzzy quantifier: a monotone non-decreasing map from a proportion in
14/// `[0,1]` to a truth degree in `[0,1]`, represented as a linear ramp `0` below
15/// `low`, `1` above `high`, linear between (`low ≤ high`).
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct RelativeQuantifier {
18    pub low: f64,
19    pub high: f64,
20}
21
22impl RelativeQuantifier {
23    /// "At least a `low`..`high` fraction" ramp — the building block for relative
24    /// quantifiers. `low == high` is a crisp threshold.
25    pub fn ramp(low: f64, high: f64) -> Self {
26        let l = low.clamp(0.0, 1.0);
27        let h = high.clamp(l, 1.0);
28        Self { low: l, high: h }
29    }
30
31    /// Zadeh's classic illustrative "most" (ramp 0.3 → 0.8). Provided as an *example*
32    /// only — governance quantifiers are coined by Timothy, not assumed here.
33    pub fn most_example() -> Self {
34        Self::ramp(0.3, 0.8)
35    }
36
37    /// Truth degree for the given satisfied-proportion.
38    pub fn apply(self, proportion: f64) -> f64 {
39        let p = proportion.clamp(0.0, 1.0);
40        // Check the upper bound first so the degenerate `low == high` (a crisp
41        // threshold) maps `p == high` to full truth rather than 0.
42        if p >= self.high {
43            1.0
44        } else if p <= self.low {
45            0.0
46        } else {
47            (p - self.low) / (self.high - self.low)
48        }
49    }
50}
51
52/// Sigma-count proportion: the fuzzy "fraction satisfied" = `Σ degrees / n`. `None`
53/// for an empty set.
54pub fn fuzzy_proportion(degrees: &[f64]) -> Option<f64> {
55    if degrees.is_empty() {
56        return None;
57    }
58    let s: f64 = degrees.iter().map(|d| d.clamp(0.0, 1.0)).sum();
59    Some(s / degrees.len() as f64)
60}
61
62/// Evaluate "Q elements satisfy P" — apply the quantifier to the sigma-count
63/// proportion of the per-element satisfaction `degrees`. `None` for an empty set.
64pub fn evaluate(degrees: &[f64], quantifier: RelativeQuantifier) -> Option<f64> {
65    Some(quantifier.apply(fuzzy_proportion(degrees)?))
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn ramp_endpoints_and_interpolation() {
74        let q = RelativeQuantifier::ramp(0.3, 0.8);
75        assert_eq!(q.apply(0.2), 0.0); // below low
76        assert_eq!(q.apply(0.9), 1.0); // above high
77        assert!((q.apply(0.55) - 0.5).abs() < 1e-9); // midpoint
78    }
79
80    #[test]
81    fn most_of_a_concurring_group_is_true() {
82        // 5 elements, mostly high satisfaction → "most" is largely true.
83        let degrees = [0.9, 0.8, 0.85, 0.7, 0.95];
84        let v = evaluate(&degrees, RelativeQuantifier::most_example()).unwrap();
85        assert!(v > 0.8, "most should be ~true: {v}");
86        // A divided group → "most" is low.
87        let split = [0.9, 0.1, 0.8, 0.2, 0.1];
88        let v2 = evaluate(&split, RelativeQuantifier::most_example()).unwrap();
89        assert!(v2 < 0.5, "divided group: {v2}");
90    }
91
92    #[test]
93    fn proportion_is_sigma_count() {
94        assert!((fuzzy_proportion(&[1.0, 0.0, 0.5, 0.5]).unwrap() - 0.5).abs() < 1e-9);
95        assert!(fuzzy_proportion(&[]).is_none());
96    }
97
98    #[test]
99    fn crisp_threshold_quantifier() {
100        // "all" ≈ ramp(1,1): only proportion 1.0 yields full truth.
101        let all = RelativeQuantifier::ramp(1.0, 1.0);
102        assert_eq!(all.apply(0.99), 0.0);
103        assert_eq!(all.apply(1.0), 1.0);
104    }
105}