1use crate::ir::{KnowledgeBase, Predicate, Rule};
81use crate::reasoning::{apply_subst_predicate, unify_predicates, Substitution};
82use ipfrs_core::error::Result;
83use std::collections::{HashMap, HashSet};
84
85#[derive(Debug, Clone)]
87struct TableEntry {
88 #[allow(dead_code)]
90 goal: Predicate,
91 solutions: Vec<Substitution>,
93 complete: bool,
95 #[allow(dead_code)]
97 depth: usize,
98}
99
100pub struct TabledInferenceEngine {
102 table: HashMap<String, TableEntry>,
104 max_depth: usize,
106 max_solutions: usize,
108}
109
110impl TabledInferenceEngine {
111 pub fn new() -> Self {
113 Self {
114 table: HashMap::new(),
115 max_depth: 100,
116 max_solutions: 1000,
117 }
118 }
119
120 pub fn with_limits(max_depth: usize, max_solutions: usize) -> Self {
122 Self {
123 table: HashMap::new(),
124 max_depth,
125 max_solutions,
126 }
127 }
128
129 pub fn query(&self, goal: &Predicate, kb: &KnowledgeBase) -> Result<Vec<Substitution>> {
131 let mut engine = Self {
132 table: HashMap::new(),
133 max_depth: self.max_depth,
134 max_solutions: self.max_solutions,
135 };
136
137 engine.solve_tabled(goal, &Substitution::new(), kb, 0)
138 }
139
140 fn solve_tabled(
142 &mut self,
143 goal: &Predicate,
144 subst: &Substitution,
145 kb: &KnowledgeBase,
146 depth: usize,
147 ) -> Result<Vec<Substitution>> {
148 if depth > self.max_depth {
150 return Ok(Vec::new());
151 }
152
153 let goal = apply_subst_predicate(goal, subst);
155
156 let key = self.goal_key(&goal);
158
159 if let Some(entry) = self.table.get(&key) {
161 if entry.complete {
163 return Ok(entry.solutions.clone());
164 }
165 return Ok(Vec::new());
167 }
168
169 let mut entry = TableEntry {
171 goal: goal.clone(),
172 solutions: Vec::new(),
173 complete: false,
174 depth,
175 };
176
177 self.table.insert(key.clone(), entry.clone());
179
180 let mut solutions = Vec::new();
182
183 for fact in kb.get_predicates(&goal.name) {
185 if let Some(new_subst) = unify_predicates(&goal, fact, &Substitution::new()) {
186 solutions.push(new_subst);
187 if solutions.len() >= self.max_solutions {
188 break;
189 }
190 }
191 }
192
193 for rule in kb.get_rules(&goal.name) {
195 if solutions.len() >= self.max_solutions {
196 break;
197 }
198
199 let renamed_rule = self.rename_rule(rule, depth);
201
202 if let Some(new_subst) =
204 unify_predicates(&goal, &renamed_rule.head, &Substitution::new())
205 {
206 let body_solutions =
208 self.solve_conjunction(&renamed_rule.body, &new_subst, kb, depth + 1)?;
209 solutions.extend(body_solutions);
210 }
211 }
212
213 entry.solutions = solutions.clone();
215 entry.complete = true;
216 self.table.insert(key, entry);
217
218 Ok(solutions)
219 }
220
221 fn solve_conjunction(
223 &mut self,
224 goals: &[Predicate],
225 subst: &Substitution,
226 kb: &KnowledgeBase,
227 depth: usize,
228 ) -> Result<Vec<Substitution>> {
229 if goals.is_empty() {
230 return Ok(vec![subst.clone()]);
231 }
232
233 let first = &goals[0];
234 let rest = &goals[1..];
235
236 let first_solutions = self.solve_tabled(first, subst, kb, depth)?;
237
238 let mut all_solutions = Vec::new();
239 for first_subst in first_solutions {
240 let rest_solutions = self.solve_conjunction(rest, &first_subst, kb, depth)?;
241 all_solutions.extend(rest_solutions);
242
243 if all_solutions.len() >= self.max_solutions {
244 break;
245 }
246 }
247
248 Ok(all_solutions)
249 }
250
251 fn goal_key(&self, goal: &Predicate) -> String {
253 format!("{}({})", goal.name, goal.args.len())
254 }
255
256 fn rename_rule(&self, rule: &Rule, suffix: usize) -> Rule {
258 let var_map: HashMap<String, String> = rule
259 .variables()
260 .into_iter()
261 .map(|v| (v.clone(), format!("{}_{}", v, suffix)))
262 .collect();
263
264 let rename_subst: Substitution = var_map
265 .into_iter()
266 .map(|(old, new)| (old, crate::ir::Term::Var(new)))
267 .collect();
268
269 Rule {
270 head: apply_subst_predicate(&rule.head, &rename_subst),
271 body: rule
272 .body
273 .iter()
274 .map(|p| apply_subst_predicate(p, &rename_subst))
275 .collect(),
276 }
277 }
278
279 pub fn table_stats(&self) -> TableStats {
281 TableStats {
282 entries: self.table.len(),
283 complete_entries: self.table.values().filter(|e| e.complete).count(),
284 total_solutions: self.table.values().map(|e| e.solutions.len()).sum(),
285 }
286 }
287
288 pub fn clear_table(&mut self) {
290 self.table.clear();
291 }
292}
293
294impl Default for TabledInferenceEngine {
295 fn default() -> Self {
296 Self::new()
297 }
298}
299
300#[derive(Debug, Clone)]
302pub struct TableStats {
303 pub entries: usize,
305 pub complete_entries: usize,
307 pub total_solutions: usize,
309}
310
311pub struct FixpointEngine {
313 max_iterations: usize,
315}
316
317impl FixpointEngine {
318 pub fn new() -> Self {
320 Self {
321 max_iterations: 100,
322 }
323 }
324
325 pub fn with_max_iterations(max_iterations: usize) -> Self {
327 Self { max_iterations }
328 }
329
330 pub fn compute_fixpoint(&self, kb: &KnowledgeBase) -> Result<KnowledgeBase> {
332 let mut current_kb = kb.clone();
333 let mut iteration = 0;
334
335 loop {
336 iteration += 1;
337 if iteration > self.max_iterations {
338 break;
339 }
340
341 let mut new_facts = Vec::new();
342 let mut changed = false;
343
344 let predicate_names: std::collections::HashSet<String> = current_kb
347 .rules
348 .iter()
349 .map(|r| r.head.name.clone())
350 .collect();
351
352 for predicate_name in predicate_names {
353 for rule in current_kb.get_rules(&predicate_name) {
354 let derived = self.derive_facts_from_rule(rule, ¤t_kb)?;
355 for fact in derived {
356 if !current_kb.facts.contains(&fact) {
358 new_facts.push(fact);
359 changed = true;
360 }
361 }
362 }
363 }
364
365 for fact in new_facts {
367 current_kb.add_fact(fact);
368 }
369
370 if !changed {
372 break;
373 }
374 }
375
376 Ok(current_kb)
377 }
378
379 fn derive_facts_from_rule(&self, rule: &Rule, kb: &KnowledgeBase) -> Result<Vec<Predicate>> {
385 let body_solutions = self.solve_body(&rule.body, &Substitution::new(), kb, 0)?;
387
388 let mut derived = Vec::new();
389 for subst in body_solutions {
390 let grounded_head = apply_subst_predicate(&rule.head, &subst);
391 if !self.has_variables(&grounded_head) {
393 derived.push(grounded_head);
394 }
395 }
396 Ok(derived)
397 }
398
399 fn solve_body(
401 &self,
402 goals: &[Predicate],
403 subst: &Substitution,
404 kb: &KnowledgeBase,
405 depth: usize,
406 ) -> Result<Vec<Substitution>> {
407 if depth > self.max_iterations {
408 return Ok(Vec::new());
409 }
410 if goals.is_empty() {
411 return Ok(vec![subst.clone()]);
412 }
413
414 let current_goal = apply_subst_predicate(&goals[0], subst);
415 let rest = &goals[1..];
416
417 let mut all_solutions: Vec<Substitution> = Vec::new();
418
419 for fact in kb.get_predicates(¤t_goal.name) {
421 if let Some(new_subst) = unify_predicates(¤t_goal, fact, subst) {
422 let tail_solutions = self.solve_body(rest, &new_subst, kb, depth + 1)?;
423 all_solutions.extend(tail_solutions);
424 }
425 }
426
427 for rule in kb.get_rules(¤t_goal.name) {
429 let suffix = depth * 1000 + all_solutions.len();
431 let renamed = self.rename_rule_fixpoint(rule, suffix);
432 if let Some(new_subst) = unify_predicates(¤t_goal, &renamed.head, subst) {
433 let mut combined: Vec<Predicate> = renamed.body.clone();
435 combined.extend_from_slice(rest);
436 let tail_solutions = self.solve_body(&combined, &new_subst, kb, depth + 1)?;
437 all_solutions.extend(tail_solutions);
438 }
439 }
440
441 Ok(all_solutions)
442 }
443
444 fn has_variables(&self, pred: &Predicate) -> bool {
446 pred.args
447 .iter()
448 .any(|t| matches!(t, crate::ir::Term::Var(_)))
449 }
450
451 fn rename_rule_fixpoint(&self, rule: &Rule, suffix: usize) -> Rule {
453 let var_map: HashMap<String, String> = rule
454 .variables()
455 .into_iter()
456 .map(|v| (v.clone(), format!("{}__fp{}", v, suffix)))
457 .collect();
458
459 let rename_subst: Substitution = var_map
460 .into_iter()
461 .map(|(old, new)| (old, crate::ir::Term::Var(new)))
462 .collect();
463
464 Rule {
465 head: apply_subst_predicate(&rule.head, &rename_subst),
466 body: rule
467 .body
468 .iter()
469 .map(|p| apply_subst_predicate(p, &rename_subst))
470 .collect(),
471 }
472 }
473}
474
475impl Default for FixpointEngine {
476 fn default() -> Self {
477 Self::new()
478 }
479}
480
481pub struct StratificationAnalyzer {
483 dependencies: HashMap<String, HashSet<String>>,
485}
486
487impl StratificationAnalyzer {
488 pub fn new() -> Self {
490 Self {
491 dependencies: HashMap::new(),
492 }
493 }
494
495 pub fn analyze(&mut self, kb: &KnowledgeBase) -> StratificationResult {
497 self.build_dependency_graph(kb);
498
499 if self.has_cycles() {
501 StratificationResult::NonStratifiable
502 } else {
503 let strata = self.compute_strata();
505 StratificationResult::Stratifiable(strata)
506 }
507 }
508
509 fn build_dependency_graph(&mut self, kb: &KnowledgeBase) {
511 let predicate_names: HashSet<String> =
513 kb.rules.iter().map(|r| r.head.name.clone()).collect();
514
515 for predicate_name in predicate_names {
516 for rule in kb.get_rules(&predicate_name) {
517 let head = &rule.head.name;
518 let deps: HashSet<String> = rule.body.iter().map(|p| p.name.clone()).collect();
519
520 self.dependencies
521 .entry(head.clone())
522 .or_default()
523 .extend(deps);
524 }
525 }
526 }
527
528 fn has_cycles(&self) -> bool {
530 let mut visited = HashSet::new();
531 let mut rec_stack = HashSet::new();
532
533 for node in self.dependencies.keys() {
534 if self.has_cycle_util(node, &mut visited, &mut rec_stack) {
535 return true;
536 }
537 }
538
539 false
540 }
541
542 fn has_cycle_util(
544 &self,
545 node: &str,
546 visited: &mut HashSet<String>,
547 rec_stack: &mut HashSet<String>,
548 ) -> bool {
549 if rec_stack.contains(node) {
550 return true;
551 }
552
553 if visited.contains(node) {
554 return false;
555 }
556
557 visited.insert(node.to_string());
558 rec_stack.insert(node.to_string());
559
560 if let Some(neighbors) = self.dependencies.get(node) {
561 for neighbor in neighbors {
562 if self.has_cycle_util(neighbor, visited, rec_stack) {
563 return true;
564 }
565 }
566 }
567
568 rec_stack.remove(node);
569 false
570 }
571
572 fn compute_strata(&self) -> Vec<Vec<String>> {
574 let mut strata = Vec::new();
575 let mut remaining: HashSet<String> = self.dependencies.keys().cloned().collect();
576
577 while !remaining.is_empty() {
578 let mut current_stratum = Vec::new();
580
581 for pred in &remaining {
582 let has_remaining_deps = self
583 .dependencies
584 .get(pred)
585 .map(|deps| deps.iter().any(|d| remaining.contains(d)))
586 .unwrap_or(false);
587
588 if !has_remaining_deps {
589 current_stratum.push(pred.clone());
590 }
591 }
592
593 if current_stratum.is_empty() {
594 break;
596 }
597
598 for pred in ¤t_stratum {
599 remaining.remove(pred);
600 }
601
602 strata.push(current_stratum);
603 }
604
605 strata
606 }
607}
608
609impl Default for StratificationAnalyzer {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615#[derive(Debug, Clone)]
617pub enum StratificationResult {
618 Stratifiable(Vec<Vec<String>>),
620 NonStratifiable,
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627 use crate::ir::{Constant, Term};
628
629 #[test]
630 fn test_tabled_inference_basic() {
631 let mut kb = KnowledgeBase::new();
632
633 kb.add_fact(Predicate::new(
635 "parent".to_string(),
636 vec![
637 Term::Const(Constant::String("alice".to_string())),
638 Term::Const(Constant::String("bob".to_string())),
639 ],
640 ));
641 kb.add_fact(Predicate::new(
642 "parent".to_string(),
643 vec![
644 Term::Const(Constant::String("bob".to_string())),
645 Term::Const(Constant::String("charlie".to_string())),
646 ],
647 ));
648
649 kb.add_rule(Rule::new(
651 Predicate::new(
652 "ancestor".to_string(),
653 vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
654 ),
655 vec![Predicate::new(
656 "parent".to_string(),
657 vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
658 )],
659 ));
660
661 kb.add_rule(Rule::new(
663 Predicate::new(
664 "ancestor".to_string(),
665 vec![Term::Var("X".to_string()), Term::Var("Z".to_string())],
666 ),
667 vec![
668 Predicate::new(
669 "parent".to_string(),
670 vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
671 ),
672 Predicate::new(
673 "ancestor".to_string(),
674 vec![Term::Var("Y".to_string()), Term::Var("Z".to_string())],
675 ),
676 ],
677 ));
678
679 let engine = TabledInferenceEngine::new();
680
681 let goal = Predicate::new(
682 "ancestor".to_string(),
683 vec![
684 Term::Const(Constant::String("alice".to_string())),
685 Term::Var("Z".to_string()),
686 ],
687 );
688
689 let solutions = engine.query(&goal, &kb).expect("test: should succeed");
690 assert!(!solutions.is_empty());
691 }
692
693 #[test]
694 fn test_table_stats() {
695 let engine = TabledInferenceEngine::new();
696 let stats = engine.table_stats();
697 assert_eq!(stats.entries, 0);
698 assert_eq!(stats.complete_entries, 0);
699 }
700
701 #[test]
702 fn test_stratification_no_cycles() {
703 let mut kb = KnowledgeBase::new();
704
705 kb.add_rule(Rule::new(
707 Predicate::new(
708 "grandparent".to_string(),
709 vec![Term::Var("X".to_string()), Term::Var("Z".to_string())],
710 ),
711 vec![
712 Predicate::new(
713 "parent".to_string(),
714 vec![Term::Var("X".to_string()), Term::Var("Y".to_string())],
715 ),
716 Predicate::new(
717 "parent".to_string(),
718 vec![Term::Var("Y".to_string()), Term::Var("Z".to_string())],
719 ),
720 ],
721 ));
722
723 let mut analyzer = StratificationAnalyzer::new();
724 let result = analyzer.analyze(&kb);
725
726 match result {
727 StratificationResult::Stratifiable(strata) => {
728 assert!(!strata.is_empty());
729 }
730 StratificationResult::NonStratifiable => {
731 panic!("Expected stratifiable result");
733 }
734 }
735 }
736
737 #[test]
738 fn test_fixpoint_engine() {
739 let engine = FixpointEngine::new();
740 let kb = KnowledgeBase::new();
741
742 let result = engine.compute_fixpoint(&kb).expect("test: should succeed");
744 assert_eq!(result.facts.len(), kb.facts.len());
745 }
746}