kele 0.0.1a1__cp313-cp313-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. kele/__init__.py +38 -0
  2. kele/_version.py +1 -0
  3. kele/config.py +243 -0
  4. kele/control/README_metrics.md +102 -0
  5. kele/control/__init__.py +20 -0
  6. kele/control/callback.py +255 -0
  7. kele/control/grounding_selector/__init__.py +5 -0
  8. kele/control/grounding_selector/_rule_strategies/README.md +13 -0
  9. kele/control/grounding_selector/_rule_strategies/__init__.py +24 -0
  10. kele/control/grounding_selector/_rule_strategies/_sequential_strategy.py +42 -0
  11. kele/control/grounding_selector/_rule_strategies/strategy_protocol.py +51 -0
  12. kele/control/grounding_selector/_selector_utils.py +123 -0
  13. kele/control/grounding_selector/_term_strategies/__init__.py +24 -0
  14. kele/control/grounding_selector/_term_strategies/_exhausted_strategy.py +34 -0
  15. kele/control/grounding_selector/_term_strategies/strategy_protocol.py +50 -0
  16. kele/control/grounding_selector/rule_selector.py +98 -0
  17. kele/control/grounding_selector/term_selector.py +89 -0
  18. kele/control/infer_path.py +306 -0
  19. kele/control/metrics.py +357 -0
  20. kele/control/status.py +286 -0
  21. kele/egg_equiv.pyd +0 -0
  22. kele/egg_equiv.pyi +11 -0
  23. kele/equality/README.md +8 -0
  24. kele/equality/__init__.py +4 -0
  25. kele/equality/_egg_equiv/src/lib.rs +267 -0
  26. kele/equality/_equiv_elem.py +67 -0
  27. kele/equality/_utils.py +36 -0
  28. kele/equality/equivalence.py +141 -0
  29. kele/executer/__init__.py +4 -0
  30. kele/executer/executing.py +139 -0
  31. kele/grounder/README.md +83 -0
  32. kele/grounder/__init__.py +17 -0
  33. kele/grounder/grounded_rule_ds/__init__.py +6 -0
  34. kele/grounder/grounded_rule_ds/_nodes/__init__.py +24 -0
  35. kele/grounder/grounded_rule_ds/_nodes/_assertion.py +353 -0
  36. kele/grounder/grounded_rule_ds/_nodes/_conn.py +116 -0
  37. kele/grounder/grounded_rule_ds/_nodes/_op.py +57 -0
  38. kele/grounder/grounded_rule_ds/_nodes/_root.py +71 -0
  39. kele/grounder/grounded_rule_ds/_nodes/_rule.py +119 -0
  40. kele/grounder/grounded_rule_ds/_nodes/_term.py +390 -0
  41. kele/grounder/grounded_rule_ds/_nodes/_tftable.py +15 -0
  42. kele/grounder/grounded_rule_ds/_nodes/_tupletable.py +444 -0
  43. kele/grounder/grounded_rule_ds/_nodes/_typing_polars.py +26 -0
  44. kele/grounder/grounded_rule_ds/grounded_class.py +461 -0
  45. kele/grounder/grounded_rule_ds/grounded_ds_utils.py +91 -0
  46. kele/grounder/grounded_rule_ds/rule_check.py +373 -0
  47. kele/grounder/grounding.py +118 -0
  48. kele/knowledge_bases/README.md +112 -0
  49. kele/knowledge_bases/__init__.py +6 -0
  50. kele/knowledge_bases/builtin_base/__init__.py +1 -0
  51. kele/knowledge_bases/builtin_base/builtin_concepts.py +13 -0
  52. kele/knowledge_bases/builtin_base/builtin_facts.py +43 -0
  53. kele/knowledge_bases/builtin_base/builtin_operators.py +105 -0
  54. kele/knowledge_bases/builtin_base/builtin_rules.py +14 -0
  55. kele/knowledge_bases/fact_base.py +158 -0
  56. kele/knowledge_bases/ontology_base.py +67 -0
  57. kele/knowledge_bases/rule_base.py +194 -0
  58. kele/main.py +464 -0
  59. kele/py.typed +0 -0
  60. kele/syntax/CONCEPT_README.md +117 -0
  61. kele/syntax/__init__.py +40 -0
  62. kele/syntax/_cnf_converter.py +161 -0
  63. kele/syntax/_sat_solver.py +116 -0
  64. kele/syntax/base_classes.py +1482 -0
  65. kele/syntax/connectives.py +20 -0
  66. kele/syntax/dnf_converter.py +145 -0
  67. kele/syntax/external.py +17 -0
  68. kele/syntax/sub_concept.py +87 -0
  69. kele/syntax/syntacticsugar.py +201 -0
  70. kele-0.0.1a1.dist-info/METADATA +166 -0
  71. kele-0.0.1a1.dist-info/RECORD +74 -0
  72. kele-0.0.1a1.dist-info/WHEEL +4 -0
  73. kele-0.0.1a1.dist-info/licenses/LICENSE +28 -0
  74. kele-0.0.1a1.dist-info/licenses/licensecheck.json +20 -0
@@ -0,0 +1,161 @@
1
+ """最合理的方案是调用有名的包,不过我没看什么合适的,就先作罢,手搓一个。这个也不是关键瓶颈"""
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING, cast
5
+
6
+ # FIXME: 另外请注意这里只是一个简单的CNF,无法获得最小的CNF(每个clause的元素尽可能少)。而且现在的单测检查规则也不够智能,不是用永真式验证的
7
+ # 而是严格要求一致
8
+
9
+ from .connectives import AND, EQUAL, IMPLIES, NOT, OR
10
+ from .base_classes import Assertion, FACT_TYPE, Formula
11
+
12
+
13
+ def _eliminate_implications(f: FACT_TYPE) -> FACT_TYPE:
14
+ """
15
+ 1. 先递归处理左右子公式
16
+ 2. 如果当前节点是EQUAL,就把 A ↔ B 转换为 (A → B) ∧ (B → A)
17
+ 3. 如果当前节点是 IMPLIES,就把 A → B 转换为 (¬A) ∨ B
18
+ """
19
+ if isinstance(f, Assertion):
20
+ return f
21
+
22
+ # 递归下放
23
+ left = _eliminate_implications(f.formula_left)
24
+ right = None
25
+ if f.formula_right is not None:
26
+ right = _eliminate_implications(f.formula_right)
27
+
28
+ if f.connective == EQUAL:
29
+ if TYPE_CHECKING:
30
+ right = cast('FACT_TYPE', right) # 当connective为EQUAL的时候formula_right一定不为None
31
+
32
+ # 构造两个单向蕴含
33
+ impl1 = Formula(left, IMPLIES, right)
34
+ impl2 = Formula(right, IMPLIES, left)
35
+ # 递归消除里面的蕴含
36
+ return Formula(
37
+ _eliminate_implications(impl1),
38
+ AND,
39
+ _eliminate_implications(impl2)
40
+ )
41
+
42
+ # 处理蕴含
43
+ if f.connective == IMPLIES:
44
+ # A → B ⇒ (¬A) ∨ B
45
+ neg_left = Formula(left, NOT, None)
46
+ return Formula(neg_left, OR, right)
47
+
48
+ return Formula(left, f.connective, right)
49
+
50
+
51
+ def _move_negations_inward(f: FACT_TYPE) -> FACT_TYPE:
52
+ """
53
+ 使用德摩根律,把所有 ¬ 向内推进到原子断言层面。
54
+ """
55
+ if isinstance(f, Assertion):
56
+ return f
57
+
58
+ if f.connective != NOT:
59
+ left = _move_negations_inward(f.formula_left)
60
+ right = None
61
+ if f.formula_right is not None:
62
+ right = _move_negations_inward(f.formula_right)
63
+ return Formula(left, f.connective, right)
64
+
65
+ # 对应f.connective为 'NOT'
66
+ inner = f.formula_left
67
+ # 如果 ¬ 后面又是一个 Formula,就应用德摩根
68
+ if isinstance(inner, Formula):
69
+ if inner.connective == AND:
70
+ # ¬(A ∧ B) => (¬A) ∨ (¬B)
71
+ left = _move_negations_inward(Formula(inner.formula_left, NOT, None))
72
+ right = _move_negations_inward(Formula(inner.formula_right, NOT, None)) if inner.formula_right is not None else None
73
+ return Formula(left, OR, right)
74
+ if inner.connective == OR:
75
+ # ¬(A ∨ B) => (¬A) ∧ (¬B)
76
+ left = _move_negations_inward(Formula(inner.formula_left, NOT, None))
77
+ right = _move_negations_inward(Formula(inner.formula_right, NOT, None)) if inner.formula_right is not None else None
78
+ return Formula(left, AND, right)
79
+ if inner.connective == NOT:
80
+ # ¬¬A => A
81
+ return _move_negations_inward(inner.formula_left)
82
+
83
+ # 如果formula_left是Assertion,不需要处理
84
+ return f
85
+
86
+
87
+ def _distribute_or_over_and(f: FACT_TYPE) -> FACT_TYPE:
88
+ """
89
+ 应用分配律,把 ∨ 分布到 ∧ 上,生成 CNF 形式。
90
+ 只处理当前节点是 OR 的情况,其它先递归。
91
+ """
92
+ if isinstance(f, Assertion):
93
+ return f
94
+
95
+ # 先递归下放
96
+ left = _distribute_or_over_and(f.formula_left)
97
+ right = None
98
+ if f.formula_right is not None:
99
+ right = _distribute_or_over_and(f.formula_right) if isinstance(f.formula_right, FACT_TYPE) else f.formula_right
100
+
101
+ # 只关心 OR 节点
102
+ if f.connective == OR:
103
+ # 如果左子公式是 AND,(A ∧ B) ∨ C => (A ∨ C) ∧ (B ∨ C)
104
+ if isinstance(left, Formula) and left.connective == AND:
105
+ formula_left = left.formula_left
106
+ formula_right = left.formula_right
107
+
108
+ if TYPE_CHECKING: # 当connective为AND的时候formula_right一定不为None
109
+ # HACK: 理想情况下这个通过Formula内部保障,不过这个AND可能还是难以被mypy理解,可能也是工厂模式提供5个子类
110
+ formula_right = cast('FACT_TYPE', formula_right)
111
+
112
+ a = _distribute_or_over_and(Formula(formula_left, OR, right))
113
+ b = _distribute_or_over_and(Formula(formula_right, OR, right))
114
+ return Formula(a, AND, b)
115
+ # 如果右子公式是 AND,A ∨ (B ∧ C) => (A ∨ B) ∧ (A ∨ C)
116
+ if isinstance(right, Formula) and right.connective == AND:
117
+ a = _distribute_or_over_and(Formula(left, OR, right.formula_left))
118
+ b = _distribute_or_over_and(Formula(left, OR, right.formula_right))
119
+ return Formula(a, AND, b)
120
+
121
+ # 其它情况不变
122
+ return Formula(left, f.connective, right)
123
+
124
+
125
+ def to_cnf(formula: FACT_TYPE) -> FACT_TYPE:
126
+ """
127
+ 将任意公式转换为合取范式(CNF)。
128
+ 步骤:
129
+ 1. 消除蕴含
130
+ 2. 消除部分否定
131
+ 3. 分配率替换OR为AND
132
+ """
133
+ step1 = _eliminate_implications(formula)
134
+ step2 = _move_negations_inward(step1)
135
+ return _distribute_or_over_and(step2) # 暂且认为不动点算法是非必要的,因为德摩根律不引入蕴含、分配率也不引入否定和蕴含。
136
+
137
+
138
+ def _split_and(f: FACT_TYPE) -> list[FACT_TYPE]:
139
+ """
140
+ 如果当前公式是 AND 连接符,则递归拆分左右子公式,否则返回当前公式本身。
141
+ """
142
+ # 如果公式是一个原子断言,直接返回
143
+ if isinstance(f, Assertion):
144
+ return [f]
145
+
146
+ # 如果当前公式的连接符是 AND,递归拆分左右子公式
147
+ if f.connective == AND:
148
+ left_facts = _split_and(f.formula_left)
149
+ right_facts = _split_and(f.formula_right) if f.formula_right is not None else []
150
+ return left_facts + right_facts
151
+
152
+ # 如果当前公式的连接符不是 AND,直接返回当前公式
153
+ return [f]
154
+
155
+
156
+ def to_cnf_clauses(formula: FACT_TYPE) -> list[FACT_TYPE]:
157
+ """
158
+ 先将公式转为 CNF,然后分拆为多个子句
159
+ """
160
+ cnf_formula = to_cnf(formula)
161
+ return _split_and(cnf_formula)
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, cast
4
+
5
+ from bidict import bidict
6
+
7
+ from kele.syntax import Assertion, Formula, FACT_TYPE, Rule, Concept, Constant
8
+ from pysat.formula import CNF
9
+ from pysat.solvers import Solver
10
+
11
+ atom_to_var: bidict[Assertion, int] = bidict()
12
+ cnf = CNF()
13
+
14
+
15
+ def _get_var_for_assertion(assertion: Assertion) -> int:
16
+ if assertion not in atom_to_var:
17
+ vid = len(atom_to_var) + 1
18
+ atom_to_var[assertion] = vid
19
+ return vid # hack: 理想情况下应该写atom_to_var[assertion],但此时assertion的hash太慢了
20
+ return atom_to_var[assertion]
21
+
22
+
23
+ def to_pysat_cnf(facts: list[FACT_TYPE]) -> list[list[int]]:
24
+ """
25
+ 输入已经是处理过cnf的了,只需要同等转换
26
+ :raise ValueError: 如果输入的facts不符合CNF格式
27
+ """ # noqa: DOC501
28
+ pysat_program = []
29
+ for clause in facts:
30
+ if isinstance(clause, Assertion):
31
+ pysat_clause = [_get_var_for_assertion(clause)]
32
+ elif clause.connective == 'OR':
33
+ if TYPE_CHECKING:
34
+ clause.formula_right = cast('FACT_TYPE', clause.formula_right)
35
+
36
+ lf = to_pysat_cnf([clause.formula_left])[0]
37
+ rt = to_pysat_cnf([clause.formula_right])[0]
38
+
39
+ pysat_clause = lf + rt
40
+ elif clause.connective == 'NOT':
41
+ if TYPE_CHECKING: # 因为已经转了CNF格式,CNF格式内的NOT只可能出现在Assertion上
42
+ clause.formula_left = cast('Assertion', clause.formula_left)
43
+
44
+ if isinstance(clause.formula_left, Assertion):
45
+ pysat_clause = [-_get_var_for_assertion(clause.formula_left)]
46
+ else: # HACK: 这里确实没必要检查,只是功能初期稳妥一下
47
+ raise TypeError("CNF error.")
48
+ else:
49
+ raise ValueError("CNF conversion error.") # HACK: 这里确实没必要检查,只是功能初期稳妥一下
50
+
51
+ pysat_program.append(pysat_clause)
52
+
53
+ return pysat_program
54
+
55
+
56
+ def rule_to_cnf(rule: Rule) -> list[list[int]]:
57
+ body = to_pysat_cnf(rule.body_units)
58
+ head = to_pysat_cnf(rule.head_units)
59
+ return head + body # 只取head为True的情况
60
+
61
+
62
+ # -----------------
63
+ # 枚举所有模型并统计 Assertion 的可能取值
64
+
65
+ def get_models_for_rule(rule: Rule) -> dict[Assertion, list[bool]]:
66
+ """
67
+ 对于一个Rule,从bool逻辑的角度找到所有可能的model,对应models变量;
68
+ 并具体分析assignment,以判断其中各个assertion是否可能取True或False,对应possible_values。
69
+ HACK: 以后的优化余地是用信息更全面的models控制求解路径,而不是possible_values
70
+ """
71
+ cnf.extend(rule_to_cnf(rule))
72
+
73
+ models = []
74
+ possible_values = {atom: [False, False] for atom in atom_to_var} # (bool, bool)分别表示某assertion是否在某个model中为True或False
75
+ # 若在第一位为True,则表示存在一个model,那个model里此assertion为True;若第二位为True,表明存在一个model,此assertion为False。
76
+
77
+ with Solver(name="glucose4", bootstrap_with=cnf) as solver:
78
+ while solver.solve():
79
+ model = solver.get_model()
80
+ assignment = {}
81
+ for atom, vid in atom_to_var.items():
82
+ val = (vid in model) # PySAT: 正整数 = True, 负整数 = False
83
+ assignment[atom] = val
84
+
85
+ possible_values[atom][0 if val else 1] = True
86
+
87
+ models.append(assignment)
88
+ # 阻断该model,因为SAT求解器会一个个返回。
89
+ solver.add_clause([-lit for lit in model])
90
+
91
+ return possible_values
92
+
93
+
94
+ if __name__ == '__main__':
95
+ Points = Concept("Points") # 点
96
+ A = Constant("A", Points)
97
+ B = Constant("B", Points)
98
+ C = Constant("C", Points)
99
+ D = Constant("D", Points)
100
+ E = Constant("E", Points)
101
+
102
+ a = Assertion(A, B)
103
+ b = Assertion(C, D)
104
+ c = Assertion(E, C)
105
+ d = Assertion(E, D)
106
+
107
+ rule = Rule(head=b, body=[a, Formula(c, 'IMPLIES', d)])
108
+
109
+ cnf = CNF()
110
+ cnf.extend(rule_to_cnf(rule)) # a → b
111
+
112
+ possible_values = get_models_for_rule(rule)
113
+
114
+ print("\n每个 Assertion 的可能取值:") # noqa: T201
115
+ for atom, vals in possible_values.items():
116
+ print(f"{atom}: {vals}") # noqa: T201 # 示例就print了,也不值得进测试