1use std::collections::HashMap;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum RuleArity {
14 Nullary,
16 Unary,
18 Binary,
20 Ternary,
22 NAry(usize),
24}
25
26impl RuleArity {
27 fn rank(self) -> usize {
29 match self {
30 Self::Nullary => 0,
31 Self::Unary => 1,
32 Self::Binary => 2,
33 Self::Ternary => 3,
34 Self::NAry(n) => n,
35 }
36 }
37}
38
39impl PartialOrd for RuleArity {
40 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
41 Some(self.cmp(other))
42 }
43}
44
45impl Ord for RuleArity {
46 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
47 self.rank().cmp(&other.rank())
48 }
49}
50
51#[derive(Clone, Debug)]
57pub struct IndexedRule {
58 pub rule_id: u64,
60 pub head_predicate: String,
62 pub arity: RuleArity,
64 pub confidence: f64,
66 pub body_predicates: Vec<String>,
68 pub depends_on: Vec<u64>,
70 pub active: bool,
72}
73
74#[derive(Clone, Debug, Default)]
80pub struct RuleQuery {
81 pub head_predicate: Option<String>,
83 pub arity: Option<RuleArity>,
85 pub min_confidence: Option<f64>,
87 pub body_contains: Option<String>,
89 pub active_only: bool,
91}
92
93#[derive(Clone, Debug, PartialEq)]
99pub struct RuleIndexStats {
100 pub total_rules: usize,
102 pub active_rules: usize,
104 pub unique_predicates: usize,
106 pub avg_confidence: f64,
108}
109
110#[derive(Debug, Default)]
119pub struct TensorRuleIndex {
120 pub rules: HashMap<u64, IndexedRule>,
122 pub predicate_index: HashMap<String, Vec<u64>>,
124}
125
126impl TensorRuleIndex {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn insert(&mut self, rule: IndexedRule) {
137 if let Some(old) = self.rules.get(&rule.rule_id) {
140 let old_pred = old.head_predicate.clone();
141 if old_pred != rule.head_predicate {
142 if let Some(ids) = self.predicate_index.get_mut(&old_pred) {
143 ids.retain(|&id| id != rule.rule_id);
144 if ids.is_empty() {
145 self.predicate_index.remove(&old_pred);
146 }
147 }
148 }
149 }
150
151 self.predicate_index
153 .entry(rule.head_predicate.clone())
154 .or_default()
155 .push(rule.rule_id);
156
157 self.rules.insert(rule.rule_id, rule);
158 }
159
160 pub fn remove(&mut self, rule_id: u64) -> bool {
164 match self.rules.remove(&rule_id) {
165 None => false,
166 Some(rule) => {
167 if let Some(ids) = self.predicate_index.get_mut(&rule.head_predicate) {
168 ids.retain(|&id| id != rule_id);
169 if ids.is_empty() {
170 self.predicate_index.remove(&rule.head_predicate);
171 }
172 }
173 true
174 }
175 }
176 }
177
178 pub fn deactivate(&mut self, rule_id: u64) -> bool {
182 match self.rules.get_mut(&rule_id) {
183 None => false,
184 Some(rule) => {
185 rule.active = false;
186 true
187 }
188 }
189 }
190
191 pub fn activate(&mut self, rule_id: u64) -> bool {
195 match self.rules.get_mut(&rule_id) {
196 None => false,
197 Some(rule) => {
198 rule.active = true;
199 true
200 }
201 }
202 }
203
204 pub fn query(&self, q: &RuleQuery) -> Vec<&IndexedRule> {
209 let mut results: Vec<&IndexedRule> = self
210 .rules
211 .values()
212 .filter(|r| {
213 if q.active_only && !r.active {
214 return false;
215 }
216 if let Some(ref pred) = q.head_predicate {
217 if &r.head_predicate != pred {
218 return false;
219 }
220 }
221 if let Some(arity) = q.arity {
222 if r.arity != arity {
223 return false;
224 }
225 }
226 if let Some(min_conf) = q.min_confidence {
227 if r.confidence < min_conf {
228 return false;
229 }
230 }
231 if let Some(ref body_pred) = q.body_contains {
232 if !r.body_predicates.iter().any(|bp| bp == body_pred) {
233 return false;
234 }
235 }
236 true
237 })
238 .collect();
239
240 results.sort_by(|a, b| {
241 b.confidence
242 .partial_cmp(&a.confidence)
243 .unwrap_or(std::cmp::Ordering::Equal)
244 .then_with(|| a.rule_id.cmp(&b.rule_id))
245 });
246
247 results
248 }
249
250 pub fn dependents_of(&self, rule_id: u64) -> Vec<&IndexedRule> {
253 let mut results: Vec<&IndexedRule> = self
254 .rules
255 .values()
256 .filter(|r| r.depends_on.contains(&rule_id))
257 .collect();
258 results.sort_by_key(|r| r.rule_id);
259 results
260 }
261
262 pub fn rules_for_predicate(&self, predicate: &str) -> Vec<&IndexedRule> {
267 let ids = match self.predicate_index.get(predicate) {
268 None => return Vec::new(),
269 Some(ids) => ids,
270 };
271
272 let mut results: Vec<&IndexedRule> =
273 ids.iter().filter_map(|id| self.rules.get(id)).collect();
274
275 results.sort_by(|a, b| {
276 b.confidence
277 .partial_cmp(&a.confidence)
278 .unwrap_or(std::cmp::Ordering::Equal)
279 .then_with(|| a.rule_id.cmp(&b.rule_id))
280 });
281
282 results
283 }
284
285 pub fn stats(&self) -> RuleIndexStats {
287 let total_rules = self.rules.len();
288 let active_rules = self.rules.values().filter(|r| r.active).count();
289 let unique_predicates = self.predicate_index.len();
290
291 let avg_confidence = if total_rules == 0 {
292 0.0
293 } else {
294 let sum: f64 = self.rules.values().map(|r| r.confidence).sum();
295 sum / total_rules as f64
296 };
297
298 RuleIndexStats {
299 total_rules,
300 active_rules,
301 unique_predicates,
302 avg_confidence,
303 }
304 }
305}
306
307#[cfg(test)]
312mod tests {
313 use super::*;
314
315 fn make_rule(
318 rule_id: u64,
319 head_predicate: &str,
320 arity: RuleArity,
321 confidence: f64,
322 body_predicates: Vec<&str>,
323 depends_on: Vec<u64>,
324 active: bool,
325 ) -> IndexedRule {
326 IndexedRule {
327 rule_id,
328 head_predicate: head_predicate.to_string(),
329 arity,
330 confidence,
331 body_predicates: body_predicates.into_iter().map(str::to_string).collect(),
332 depends_on,
333 active,
334 }
335 }
336
337 #[test]
340 fn new_starts_empty() {
341 let idx = TensorRuleIndex::new();
342 assert!(idx.rules.is_empty());
343 assert!(idx.predicate_index.is_empty());
344 }
345
346 #[test]
349 fn insert_stores_rule() {
350 let mut idx = TensorRuleIndex::new();
351 let rule = make_rule(1, "parent", RuleArity::Binary, 0.9, vec![], vec![], true);
352 idx.insert(rule);
353 assert_eq!(idx.rules.len(), 1);
354 assert!(idx.rules.contains_key(&1));
355 }
356
357 #[test]
358 fn insert_updates_predicate_index() {
359 let mut idx = TensorRuleIndex::new();
360 let rule = make_rule(1, "parent", RuleArity::Binary, 0.9, vec![], vec![], true);
361 idx.insert(rule);
362 assert!(idx.predicate_index.contains_key("parent"));
363 assert_eq!(idx.predicate_index["parent"], vec![1]);
364 }
365
366 #[test]
367 fn insert_multiple_same_predicate() {
368 let mut idx = TensorRuleIndex::new();
369 idx.insert(make_rule(
370 1,
371 "parent",
372 RuleArity::Binary,
373 0.9,
374 vec![],
375 vec![],
376 true,
377 ));
378 idx.insert(make_rule(
379 2,
380 "parent",
381 RuleArity::Binary,
382 0.7,
383 vec![],
384 vec![],
385 true,
386 ));
387 let ids = &idx.predicate_index["parent"];
388 assert_eq!(ids.len(), 2);
389 assert!(ids.contains(&1));
390 assert!(ids.contains(&2));
391 }
392
393 #[test]
396 fn remove_removes_from_both_stores() {
397 let mut idx = TensorRuleIndex::new();
398 idx.insert(make_rule(
399 1,
400 "parent",
401 RuleArity::Binary,
402 0.9,
403 vec![],
404 vec![],
405 true,
406 ));
407 let removed = idx.remove(1);
408 assert!(removed);
409 assert!(!idx.rules.contains_key(&1));
410 assert!(!idx.predicate_index.contains_key("parent"));
411 }
412
413 #[test]
414 fn remove_returns_false_for_unknown() {
415 let mut idx = TensorRuleIndex::new();
416 assert!(!idx.remove(999));
417 }
418
419 #[test]
420 fn remove_keeps_other_rules_with_same_predicate() {
421 let mut idx = TensorRuleIndex::new();
422 idx.insert(make_rule(
423 1,
424 "parent",
425 RuleArity::Binary,
426 0.9,
427 vec![],
428 vec![],
429 true,
430 ));
431 idx.insert(make_rule(
432 2,
433 "parent",
434 RuleArity::Binary,
435 0.7,
436 vec![],
437 vec![],
438 true,
439 ));
440 idx.remove(1);
441 assert!(idx.predicate_index.contains_key("parent"));
442 assert_eq!(idx.predicate_index["parent"], vec![2]);
443 }
444
445 #[test]
448 fn deactivate_sets_active_false() {
449 let mut idx = TensorRuleIndex::new();
450 idx.insert(make_rule(
451 1,
452 "parent",
453 RuleArity::Binary,
454 0.9,
455 vec![],
456 vec![],
457 true,
458 ));
459 assert!(idx.deactivate(1));
460 assert!(!idx.rules[&1].active);
461 }
462
463 #[test]
464 fn activate_sets_active_true() {
465 let mut idx = TensorRuleIndex::new();
466 idx.insert(make_rule(
467 1,
468 "parent",
469 RuleArity::Binary,
470 0.9,
471 vec![],
472 vec![],
473 false,
474 ));
475 assert!(idx.activate(1));
476 assert!(idx.rules[&1].active);
477 }
478
479 #[test]
480 fn deactivate_returns_false_for_unknown() {
481 let mut idx = TensorRuleIndex::new();
482 assert!(!idx.deactivate(42));
483 }
484
485 #[test]
486 fn activate_returns_false_for_unknown() {
487 let mut idx = TensorRuleIndex::new();
488 assert!(!idx.activate(42));
489 }
490
491 #[test]
494 fn query_head_predicate_filter() {
495 let mut idx = TensorRuleIndex::new();
496 idx.insert(make_rule(
497 1,
498 "parent",
499 RuleArity::Binary,
500 0.9,
501 vec![],
502 vec![],
503 true,
504 ));
505 idx.insert(make_rule(
506 2,
507 "ancestor",
508 RuleArity::Binary,
509 0.8,
510 vec![],
511 vec![],
512 true,
513 ));
514
515 let q = RuleQuery {
516 head_predicate: Some("parent".to_string()),
517 ..Default::default()
518 };
519 let results = idx.query(&q);
520 assert_eq!(results.len(), 1);
521 assert_eq!(results[0].rule_id, 1);
522 }
523
524 #[test]
527 fn query_arity_filter() {
528 let mut idx = TensorRuleIndex::new();
529 idx.insert(make_rule(
530 1,
531 "a",
532 RuleArity::Unary,
533 0.9,
534 vec![],
535 vec![],
536 true,
537 ));
538 idx.insert(make_rule(
539 2,
540 "b",
541 RuleArity::Binary,
542 0.8,
543 vec![],
544 vec![],
545 true,
546 ));
547
548 let q = RuleQuery {
549 arity: Some(RuleArity::Unary),
550 ..Default::default()
551 };
552 let results = idx.query(&q);
553 assert_eq!(results.len(), 1);
554 assert_eq!(results[0].rule_id, 1);
555 }
556
557 #[test]
560 fn query_min_confidence_filter() {
561 let mut idx = TensorRuleIndex::new();
562 idx.insert(make_rule(
563 1,
564 "a",
565 RuleArity::Nullary,
566 0.9,
567 vec![],
568 vec![],
569 true,
570 ));
571 idx.insert(make_rule(
572 2,
573 "b",
574 RuleArity::Nullary,
575 0.3,
576 vec![],
577 vec![],
578 true,
579 ));
580
581 let q = RuleQuery {
582 min_confidence: Some(0.5),
583 ..Default::default()
584 };
585 let results = idx.query(&q);
586 assert_eq!(results.len(), 1);
587 assert_eq!(results[0].rule_id, 1);
588 }
589
590 #[test]
593 fn query_body_contains_filter() {
594 let mut idx = TensorRuleIndex::new();
595 idx.insert(make_rule(
596 1,
597 "a",
598 RuleArity::Unary,
599 0.9,
600 vec!["child", "parent"],
601 vec![],
602 true,
603 ));
604 idx.insert(make_rule(
605 2,
606 "b",
607 RuleArity::Unary,
608 0.8,
609 vec!["sibling"],
610 vec![],
611 true,
612 ));
613
614 let q = RuleQuery {
615 body_contains: Some("parent".to_string()),
616 ..Default::default()
617 };
618 let results = idx.query(&q);
619 assert_eq!(results.len(), 1);
620 assert_eq!(results[0].rule_id, 1);
621 }
622
623 #[test]
626 fn query_active_only_filter() {
627 let mut idx = TensorRuleIndex::new();
628 idx.insert(make_rule(
629 1,
630 "a",
631 RuleArity::Nullary,
632 0.9,
633 vec![],
634 vec![],
635 true,
636 ));
637 idx.insert(make_rule(
638 2,
639 "b",
640 RuleArity::Nullary,
641 0.8,
642 vec![],
643 vec![],
644 false,
645 ));
646
647 let q = RuleQuery {
648 active_only: true,
649 ..Default::default()
650 };
651 let results = idx.query(&q);
652 assert_eq!(results.len(), 1);
653 assert_eq!(results[0].rule_id, 1);
654 }
655
656 #[test]
659 fn query_multiple_filters_anded() {
660 let mut idx = TensorRuleIndex::new();
661 idx.insert(make_rule(
662 1,
663 "parent",
664 RuleArity::Binary,
665 0.9,
666 vec!["child"],
667 vec![],
668 true,
669 ));
670 idx.insert(make_rule(
671 2,
672 "parent",
673 RuleArity::Binary,
674 0.4,
675 vec!["child"],
676 vec![],
677 true,
678 ));
679 idx.insert(make_rule(
680 3,
681 "parent",
682 RuleArity::Unary,
683 0.9,
684 vec!["child"],
685 vec![],
686 true,
687 ));
688
689 let q = RuleQuery {
690 head_predicate: Some("parent".to_string()),
691 arity: Some(RuleArity::Binary),
692 min_confidence: Some(0.8),
693 body_contains: Some("child".to_string()),
694 active_only: true,
695 };
696 let results = idx.query(&q);
697 assert_eq!(results.len(), 1);
698 assert_eq!(results[0].rule_id, 1);
699 }
700
701 #[test]
704 fn query_sorted_by_confidence_desc() {
705 let mut idx = TensorRuleIndex::new();
706 idx.insert(make_rule(
707 1,
708 "a",
709 RuleArity::Nullary,
710 0.3,
711 vec![],
712 vec![],
713 true,
714 ));
715 idx.insert(make_rule(
716 2,
717 "a",
718 RuleArity::Nullary,
719 0.9,
720 vec![],
721 vec![],
722 true,
723 ));
724 idx.insert(make_rule(
725 3,
726 "a",
727 RuleArity::Nullary,
728 0.6,
729 vec![],
730 vec![],
731 true,
732 ));
733
734 let q = RuleQuery::default();
735 let results = idx.query(&q);
736 assert_eq!(results[0].confidence, 0.9);
737 assert_eq!(results[1].confidence, 0.6);
738 assert_eq!(results[2].confidence, 0.3);
739 }
740
741 #[test]
744 fn query_confidence_tiebreak_by_rule_id_asc() {
745 let mut idx = TensorRuleIndex::new();
746 idx.insert(make_rule(
747 3,
748 "a",
749 RuleArity::Nullary,
750 0.7,
751 vec![],
752 vec![],
753 true,
754 ));
755 idx.insert(make_rule(
756 1,
757 "a",
758 RuleArity::Nullary,
759 0.7,
760 vec![],
761 vec![],
762 true,
763 ));
764 idx.insert(make_rule(
765 2,
766 "a",
767 RuleArity::Nullary,
768 0.7,
769 vec![],
770 vec![],
771 true,
772 ));
773
774 let q = RuleQuery::default();
775 let results = idx.query(&q);
776 let ids: Vec<u64> = results.iter().map(|r| r.rule_id).collect();
777 assert_eq!(ids, vec![1, 2, 3]);
778 }
779
780 #[test]
783 fn dependents_of_returns_correct_rules() {
784 let mut idx = TensorRuleIndex::new();
785 idx.insert(make_rule(
786 1,
787 "base",
788 RuleArity::Nullary,
789 0.9,
790 vec![],
791 vec![],
792 true,
793 ));
794 idx.insert(make_rule(
795 2,
796 "derived",
797 RuleArity::Unary,
798 0.8,
799 vec![],
800 vec![1],
801 true,
802 ));
803 idx.insert(make_rule(
804 3,
805 "derived2",
806 RuleArity::Unary,
807 0.7,
808 vec![],
809 vec![1, 2],
810 true,
811 ));
812 idx.insert(make_rule(
813 4,
814 "unrelated",
815 RuleArity::Nullary,
816 0.6,
817 vec![],
818 vec![],
819 true,
820 ));
821
822 let deps = idx.dependents_of(1);
823 let ids: Vec<u64> = deps.iter().map(|r| r.rule_id).collect();
824 assert_eq!(ids, vec![2, 3]);
825 }
826
827 #[test]
828 fn dependents_of_empty_when_none_depend() {
829 let mut idx = TensorRuleIndex::new();
830 idx.insert(make_rule(
831 1,
832 "a",
833 RuleArity::Nullary,
834 0.9,
835 vec![],
836 vec![],
837 true,
838 ));
839 idx.insert(make_rule(
840 2,
841 "b",
842 RuleArity::Nullary,
843 0.8,
844 vec![],
845 vec![],
846 true,
847 ));
848
849 let deps = idx.dependents_of(1);
850 assert!(deps.is_empty());
851 }
852
853 #[test]
856 fn rules_for_predicate_uses_index() {
857 let mut idx = TensorRuleIndex::new();
858 idx.insert(make_rule(
859 1,
860 "parent",
861 RuleArity::Binary,
862 0.9,
863 vec![],
864 vec![],
865 true,
866 ));
867 idx.insert(make_rule(
868 2,
869 "child",
870 RuleArity::Unary,
871 0.8,
872 vec![],
873 vec![],
874 true,
875 ));
876
877 let results = idx.rules_for_predicate("parent");
878 assert_eq!(results.len(), 1);
879 assert_eq!(results[0].rule_id, 1);
880 }
881
882 #[test]
883 fn rules_for_predicate_sorted_by_confidence() {
884 let mut idx = TensorRuleIndex::new();
885 idx.insert(make_rule(
886 1,
887 "parent",
888 RuleArity::Binary,
889 0.4,
890 vec![],
891 vec![],
892 true,
893 ));
894 idx.insert(make_rule(
895 2,
896 "parent",
897 RuleArity::Binary,
898 0.9,
899 vec![],
900 vec![],
901 true,
902 ));
903 idx.insert(make_rule(
904 3,
905 "parent",
906 RuleArity::Binary,
907 0.6,
908 vec![],
909 vec![],
910 true,
911 ));
912
913 let results = idx.rules_for_predicate("parent");
914 assert_eq!(results[0].confidence, 0.9);
915 assert_eq!(results[1].confidence, 0.6);
916 assert_eq!(results[2].confidence, 0.4);
917 }
918
919 #[test]
920 fn rules_for_predicate_returns_empty_for_unknown() {
921 let idx = TensorRuleIndex::new();
922 assert!(idx.rules_for_predicate("nonexistent").is_empty());
923 }
924
925 #[test]
928 fn stats_total_and_active_rules() {
929 let mut idx = TensorRuleIndex::new();
930 idx.insert(make_rule(
931 1,
932 "a",
933 RuleArity::Nullary,
934 0.9,
935 vec![],
936 vec![],
937 true,
938 ));
939 idx.insert(make_rule(
940 2,
941 "b",
942 RuleArity::Nullary,
943 0.8,
944 vec![],
945 vec![],
946 false,
947 ));
948 idx.insert(make_rule(
949 3,
950 "c",
951 RuleArity::Nullary,
952 0.7,
953 vec![],
954 vec![],
955 true,
956 ));
957
958 let s = idx.stats();
959 assert_eq!(s.total_rules, 3);
960 assert_eq!(s.active_rules, 2);
961 }
962
963 #[test]
964 fn stats_unique_predicates() {
965 let mut idx = TensorRuleIndex::new();
966 idx.insert(make_rule(
967 1,
968 "parent",
969 RuleArity::Binary,
970 0.9,
971 vec![],
972 vec![],
973 true,
974 ));
975 idx.insert(make_rule(
976 2,
977 "parent",
978 RuleArity::Binary,
979 0.7,
980 vec![],
981 vec![],
982 true,
983 ));
984 idx.insert(make_rule(
985 3,
986 "child",
987 RuleArity::Unary,
988 0.8,
989 vec![],
990 vec![],
991 true,
992 ));
993
994 let s = idx.stats();
995 assert_eq!(s.unique_predicates, 2);
996 }
997
998 #[test]
999 fn stats_avg_confidence() {
1000 let mut idx = TensorRuleIndex::new();
1001 idx.insert(make_rule(
1002 1,
1003 "a",
1004 RuleArity::Nullary,
1005 0.8,
1006 vec![],
1007 vec![],
1008 true,
1009 ));
1010 idx.insert(make_rule(
1011 2,
1012 "b",
1013 RuleArity::Nullary,
1014 0.6,
1015 vec![],
1016 vec![],
1017 true,
1018 ));
1019
1020 let s = idx.stats();
1021 let expected = (0.8 + 0.6) / 2.0;
1022 assert!((s.avg_confidence - expected).abs() < 1e-10);
1023 }
1024
1025 #[test]
1026 fn stats_avg_confidence_zero_when_empty() {
1027 let idx = TensorRuleIndex::new();
1028 assert_eq!(idx.stats().avg_confidence, 0.0);
1029 }
1030
1031 #[test]
1034 fn rule_arity_ordering() {
1035 assert!(RuleArity::Nullary < RuleArity::Unary);
1036 assert!(RuleArity::Unary < RuleArity::Binary);
1037 assert!(RuleArity::Binary < RuleArity::Ternary);
1038 assert!(RuleArity::Ternary < RuleArity::NAry(4));
1039 assert!(RuleArity::NAry(4) < RuleArity::NAry(10));
1040 assert_eq!(RuleArity::Nullary, RuleArity::Nullary);
1041 }
1042}