Skip to main content

ipfrs_tensorlogic/
ir.rs

1//! TensorLogic IR types
2//!
3//! This module defines the Intermediate Representation types for TensorLogic
4//! that can be stored and retrieved via IPFRS.
5
6use ipfrs_core::Cid;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fmt;
10
11/// A logical term in TensorLogic
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
13pub enum Term {
14    /// Variable (e.g., ?X)
15    Var(String),
16    /// Constant value
17    Const(Constant),
18    /// Function application (e.g., f(X, Y))
19    Fun(String, Vec<Term>),
20    /// Reference to another term via CID
21    Ref(TermRef),
22}
23
24/// Constant value types
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
26pub enum Constant {
27    /// String constant
28    String(String),
29    /// Integer constant
30    Int(i64),
31    /// Boolean constant
32    Bool(bool),
33    /// Floating point constant (stored as string for deterministic hashing)
34    Float(String),
35}
36
37/// Reference to a term via CID
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
39pub struct TermRef {
40    /// CID of the referenced term
41    #[serde(
42        serialize_with = "crate::serialize_cid",
43        deserialize_with = "crate::deserialize_cid"
44    )]
45    pub cid: Cid,
46    /// Optional hint about the term (for optimization)
47    pub hint: Option<String>,
48}
49
50impl TermRef {
51    /// Create a new term reference
52    pub fn new(cid: Cid) -> Self {
53        Self { cid, hint: None }
54    }
55
56    /// Create a term reference with a hint
57    pub fn with_hint(cid: Cid, hint: String) -> Self {
58        Self {
59            cid,
60            hint: Some(hint),
61        }
62    }
63}
64
65impl Term {
66    /// Check if term is a variable
67    #[inline]
68    pub fn is_var(&self) -> bool {
69        matches!(self, Term::Var(_))
70    }
71
72    /// Check if term is a constant
73    #[inline]
74    pub fn is_const(&self) -> bool {
75        matches!(self, Term::Const(_))
76    }
77
78    /// Check if term is ground (contains no variables)
79    #[inline]
80    pub fn is_ground(&self) -> bool {
81        match self {
82            Term::Var(_) => false,
83            Term::Const(_) => true,
84            Term::Fun(_, args) => args.iter().all(|t| t.is_ground()),
85            Term::Ref(_) => true, // References are considered ground
86        }
87    }
88
89    /// Collect all variables in the term
90    pub fn variables(&self) -> Vec<String> {
91        let capacity = self.estimate_var_count();
92        let mut vars = Vec::with_capacity(capacity);
93        self.collect_vars(&mut vars);
94        vars.sort_unstable();
95        vars.dedup();
96        vars
97    }
98
99    /// Estimate the number of unique variables (for capacity hint)
100    #[inline]
101    fn estimate_var_count(&self) -> usize {
102        match self {
103            Term::Var(_) => 1,
104            Term::Const(_) | Term::Ref(_) => 0,
105            Term::Fun(_, args) => args.iter().map(|t| t.estimate_var_count()).sum(),
106        }
107    }
108
109    #[inline]
110    fn collect_vars(&self, vars: &mut Vec<String>) {
111        match self {
112            Term::Var(v) => vars.push(v.clone()),
113            Term::Fun(_, args) => {
114                for arg in args {
115                    arg.collect_vars(vars);
116                }
117            }
118            _ => {}
119        }
120    }
121
122    /// Get the complexity of the term (number of nodes)
123    #[inline]
124    pub fn complexity(&self) -> usize {
125        match self {
126            Term::Var(_) | Term::Const(_) | Term::Ref(_) => 1,
127            Term::Fun(_, args) => 1 + args.iter().map(|t| t.complexity()).sum::<usize>(),
128        }
129    }
130}
131
132impl fmt::Display for Term {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        match self {
135            Term::Var(v) => write!(f, "?{}", v),
136            Term::Const(c) => write!(f, "{}", c),
137            Term::Fun(name, args) => {
138                write!(f, "{}(", name)?;
139                for (i, arg) in args.iter().enumerate() {
140                    if i > 0 {
141                        write!(f, ", ")?;
142                    }
143                    write!(f, "{}", arg)?;
144                }
145                write!(f, ")")
146            }
147            Term::Ref(r) => write!(f, "@{}", r.cid),
148        }
149    }
150}
151
152impl fmt::Display for Constant {
153    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154        match self {
155            Constant::String(s) => write!(f, "\"{}\"", s),
156            Constant::Int(i) => write!(f, "{}", i),
157            Constant::Bool(b) => write!(f, "{}", b),
158            Constant::Float(s) => write!(f, "{}", s),
159        }
160    }
161}
162
163/// A logical predicate
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165pub struct Predicate {
166    /// Predicate name
167    pub name: String,
168    /// Arguments
169    pub args: Vec<Term>,
170}
171
172impl Predicate {
173    /// Create a new predicate
174    pub fn new(name: String, args: Vec<Term>) -> Self {
175        Self { name, args }
176    }
177
178    /// Get the arity (number of arguments)
179    #[inline]
180    pub fn arity(&self) -> usize {
181        self.args.len()
182    }
183
184    /// Check if predicate is ground
185    #[inline]
186    pub fn is_ground(&self) -> bool {
187        self.args.iter().all(|t| t.is_ground())
188    }
189
190    /// Collect all variables
191    pub fn variables(&self) -> Vec<String> {
192        let capacity: usize = self.args.iter().map(|t| t.estimate_var_count()).sum();
193        let mut vars = Vec::with_capacity(capacity);
194        for arg in &self.args {
195            arg.collect_vars(&mut vars);
196        }
197        vars.sort_unstable();
198        vars.dedup();
199        vars
200    }
201}
202
203impl fmt::Display for Predicate {
204    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205        write!(f, "{}(", self.name)?;
206        for (i, arg) in self.args.iter().enumerate() {
207            if i > 0 {
208                write!(f, ", ")?;
209            }
210            write!(f, "{}", arg)?;
211        }
212        write!(f, ")")
213    }
214}
215
216/// A logical rule (Horn clause)
217#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct Rule {
219    /// Head of the rule
220    pub head: Predicate,
221    /// Body of the rule (conjunction)
222    pub body: Vec<Predicate>,
223}
224
225impl Rule {
226    /// Create a new rule
227    pub fn new(head: Predicate, body: Vec<Predicate>) -> Self {
228        Self { head, body }
229    }
230
231    /// Create a fact (rule with empty body)
232    pub fn fact(head: Predicate) -> Self {
233        Self {
234            head,
235            body: Vec::new(),
236        }
237    }
238
239    /// Check if this is a fact
240    #[inline]
241    pub fn is_fact(&self) -> bool {
242        self.body.is_empty()
243    }
244
245    /// Collect all variables in the rule
246    pub fn variables(&self) -> Vec<String> {
247        let mut vars = self.head.variables();
248        for pred in &self.body {
249            for var in pred.variables() {
250                if !vars.contains(&var) {
251                    vars.push(var);
252                }
253            }
254        }
255        vars.sort_unstable();
256        vars
257    }
258}
259
260impl fmt::Display for Rule {
261    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262        write!(f, "{}", self.head)?;
263        if !self.body.is_empty() {
264            write!(f, " :- ")?;
265            for (i, pred) in self.body.iter().enumerate() {
266                if i > 0 {
267                    write!(f, ", ")?;
268                }
269                write!(f, "{}", pred)?;
270            }
271        }
272        write!(f, ".")
273    }
274}
275
276/// A knowledge base containing facts and rules
277#[derive(Debug, Clone, Default, Serialize, Deserialize)]
278pub struct KnowledgeBase {
279    /// Facts (ground predicates)
280    pub facts: Vec<Predicate>,
281    /// Rules
282    pub rules: Vec<Rule>,
283}
284
285impl KnowledgeBase {
286    /// Create a new empty knowledge base
287    pub fn new() -> Self {
288        Self::default()
289    }
290
291    /// Add a fact
292    pub fn add_fact(&mut self, fact: Predicate) {
293        self.facts.push(fact);
294    }
295
296    /// Add a rule
297    pub fn add_rule(&mut self, rule: Rule) {
298        self.rules.push(rule);
299    }
300
301    /// Get all predicates with a given name
302    #[inline]
303    pub fn get_predicates(&self, name: &str) -> Vec<&Predicate> {
304        self.facts.iter().filter(|p| p.name == name).collect()
305    }
306
307    /// Get all rules with a given head predicate name
308    #[inline]
309    pub fn get_rules(&self, name: &str) -> Vec<&Rule> {
310        self.rules.iter().filter(|r| r.head.name == name).collect()
311    }
312
313    /// Get statistics
314    pub fn stats(&self) -> KnowledgeBaseStats {
315        KnowledgeBaseStats {
316            num_facts: self.facts.len(),
317            num_rules: self.rules.len(),
318        }
319    }
320
321    /// Build a predicate-name → CID index from a pre-computed rule-CID map.
322    ///
323    /// `rule_cids` maps each rule's position in `self.rules` to its content-
324    /// addressed [`Cid`].  Rules that have no entry in the map are skipped.
325    ///
326    /// The returned index can be used by distributed reasoners to quickly look
327    /// up which peers might hold rules relevant to a given predicate.
328    ///
329    /// # Example
330    ///
331    /// ```ignore
332    /// let index = kb.index_rules_by_predicate(&cid_map);
333    /// let parent_cids = index.get("parent").cloned().unwrap_or_default();
334    /// ```
335    pub fn index_rules_by_predicate(
336        &self,
337        rule_cids: &HashMap<usize, Cid>,
338    ) -> HashMap<String, Vec<Cid>> {
339        let mut index: HashMap<String, Vec<Cid>> = HashMap::new();
340        for (idx, rule) in self.rules.iter().enumerate() {
341            if let Some(cid) = rule_cids.get(&idx) {
342                index.entry(rule.head.name.clone()).or_default().push(*cid);
343            }
344        }
345        index
346    }
347
348    /// Build a predicate-name → rule-index index without CID information.
349    ///
350    /// Useful for local-only query planning when CIDs have not been computed yet.
351    pub fn index_rules_by_predicate_local(&self) -> HashMap<String, Vec<usize>> {
352        let mut index: HashMap<String, Vec<usize>> = HashMap::new();
353        for (idx, rule) in self.rules.iter().enumerate() {
354            index.entry(rule.head.name.clone()).or_default().push(idx);
355        }
356        index
357    }
358}
359
360/// Knowledge base statistics
361#[derive(Debug, Clone)]
362pub struct KnowledgeBaseStats {
363    /// Number of facts
364    pub num_facts: usize,
365    /// Number of rules
366    pub num_rules: usize,
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn test_term_creation() {
375        let var = Term::Var("X".to_string());
376        assert!(var.is_var());
377        assert!(!var.is_ground());
378
379        let const_term = Term::Const(Constant::String("Alice".to_string()));
380        assert!(const_term.is_const());
381        assert!(const_term.is_ground());
382    }
383
384    #[test]
385    fn test_predicate() {
386        let pred = Predicate::new(
387            "parent".to_string(),
388            vec![
389                Term::Const(Constant::String("Alice".to_string())),
390                Term::Var("X".to_string()),
391            ],
392        );
393
394        assert_eq!(pred.arity(), 2);
395        assert!(!pred.is_ground());
396        assert_eq!(pred.variables(), vec!["X".to_string()]);
397    }
398
399    #[test]
400    fn test_rule() {
401        let head = Predicate::new(
402            "grandparent".to_string(),
403            vec![Term::Var("X".to_string()), Term::Var("Z".to_string())],
404        );
405
406        let body = vec![
407            Predicate::new(
408                "parent".to_string(),
409                vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
410            ),
411            Predicate::new(
412                "parent".to_string(),
413                vec![Term::Var("Y".to_string()), Term::Var("Z".to_string())],
414            ),
415        ];
416
417        let rule = Rule::new(head, body);
418        assert!(!rule.is_fact());
419        assert_eq!(
420            rule.variables(),
421            vec!["X".to_string(), "Y".to_string(), "Z".to_string()]
422        );
423    }
424
425    #[test]
426    fn test_knowledge_base() {
427        let mut kb = KnowledgeBase::new();
428
429        kb.add_fact(Predicate::new(
430            "parent".to_string(),
431            vec![
432                Term::Const(Constant::String("Alice".to_string())),
433                Term::Const(Constant::String("Bob".to_string())),
434            ],
435        ));
436
437        let stats = kb.stats();
438        assert_eq!(stats.num_facts, 1);
439        assert_eq!(stats.num_rules, 0);
440    }
441}