kele 0.0.1a1__tar.gz → 0.0.1a2__tar.gz

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 (100) hide show
  1. {kele-0.0.1a1 → kele-0.0.1a2}/.pre-commit-config.yaml +9 -0
  2. {kele-0.0.1a1 → kele-0.0.1a2}/.ruff.toml +2 -5
  3. {kele-0.0.1a1 → kele-0.0.1a2}/CONTRIBUTING.md +3 -2
  4. {kele-0.0.1a1 → kele-0.0.1a2}/PKG-INFO +13 -3
  5. {kele-0.0.1a1 → kele-0.0.1a2}/README.md +10 -1
  6. {kele-0.0.1a1 → kele-0.0.1a2}/README.zh.md +10 -1
  7. {kele-0.0.1a1 → kele-0.0.1a2}/kele/__init__.py +8 -6
  8. kele-0.0.1a2/kele/_utils.py +23 -0
  9. kele-0.0.1a2/kele/_version.py +1 -0
  10. {kele-0.0.1a1 → kele-0.0.1a2}/kele/config.py +52 -14
  11. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/__init__.py +9 -4
  12. kele-0.0.1a2/kele/control/builtin_hooks.py +119 -0
  13. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/callback.py +14 -4
  14. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/__init__.py +7 -1
  15. kele-0.0.1a2/kele/control/grounding_selector/_rule_strategies/README.md +37 -0
  16. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/_rule_strategies/__init__.py +2 -2
  17. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/_rule_strategies/_sequential_strategy.py +5 -3
  18. kele-0.0.1a2/kele/control/grounding_selector/_rule_strategies/strategy_protocol.py +27 -0
  19. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/_selector_utils.py +2 -10
  20. kele-0.0.1a2/kele/control/grounding_selector/_term_strategies/README.md +34 -0
  21. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/_term_strategies/__init__.py +2 -2
  22. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/_term_strategies/_exhausted_strategy.py +5 -3
  23. kele-0.0.1a2/kele/control/grounding_selector/_term_strategies/strategy_protocol.py +26 -0
  24. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/rule_selector.py +14 -5
  25. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/grounding_selector/term_selector.py +16 -6
  26. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/infer_path.py +9 -8
  27. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/metrics.py +7 -8
  28. kele-0.0.1a2/kele/control/registry.py +112 -0
  29. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/status.py +190 -49
  30. {kele-0.0.1a1 → kele-0.0.1a2}/kele/egg_equiv.pyi +3 -3
  31. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/_equiv_elem.py +2 -1
  32. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/_utils.py +4 -2
  33. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/equivalence.py +8 -7
  34. {kele-0.0.1a1 → kele-0.0.1a2}/kele/executer/executing.py +40 -24
  35. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/__init__.py +2 -7
  36. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/__init__.py +2 -2
  37. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/__init__.py +4 -4
  38. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_assertion.py +27 -14
  39. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_conn.py +4 -2
  40. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_op.py +6 -3
  41. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_root.py +4 -4
  42. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_rule.py +5 -5
  43. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_term.py +11 -10
  44. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_tftable.py +1 -0
  45. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_tupletable.py +62 -58
  46. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/grounded_class.py +60 -17
  47. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/grounded_ds_utils.py +3 -4
  48. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/rule_check.py +6 -5
  49. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounding.py +38 -36
  50. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/builtin_base/builtin_facts.py +3 -1
  51. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/builtin_base/builtin_operators.py +1 -1
  52. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/builtin_base/builtin_rules.py +1 -0
  53. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/fact_base.py +37 -16
  54. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/ontology_base.py +1 -1
  55. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/rule_base.py +53 -32
  56. {kele-0.0.1a1 → kele-0.0.1a2}/kele/main.py +55 -37
  57. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/__init__.py +12 -12
  58. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/_cnf_converter.py +2 -2
  59. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/_sat_solver.py +3 -3
  60. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/base_classes.py +92 -20
  61. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/dnf_converter.py +10 -5
  62. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/external.py +2 -1
  63. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/sub_concept.py +5 -4
  64. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/syntacticsugar.py +3 -4
  65. {kele-0.0.1a1 → kele-0.0.1a2}/mypy.ini +1 -1
  66. {kele-0.0.1a1 → kele-0.0.1a2}/pyproject.toml +23 -3
  67. {kele-0.0.1a1 → kele-0.0.1a2}/uv.lock +108 -1
  68. kele-0.0.1a1/kele/_version.py +0 -1
  69. kele-0.0.1a1/kele/control/grounding_selector/_rule_strategies/README.md +0 -13
  70. kele-0.0.1a1/kele/control/grounding_selector/_rule_strategies/strategy_protocol.py +0 -51
  71. kele-0.0.1a1/kele/control/grounding_selector/_term_strategies/strategy_protocol.py +0 -50
  72. {kele-0.0.1a1 → kele-0.0.1a2}/.gitattributes +0 -0
  73. {kele-0.0.1a1 → kele-0.0.1a2}/.github/dependabot.yml +0 -0
  74. {kele-0.0.1a1 → kele-0.0.1a2}/.github/workflows/license_check.yml +0 -0
  75. {kele-0.0.1a1 → kele-0.0.1a2}/.github/workflows/lint.yml +0 -0
  76. {kele-0.0.1a1 → kele-0.0.1a2}/.github/workflows/release.yml +0 -0
  77. {kele-0.0.1a1 → kele-0.0.1a2}/.github/workflows/test.yml +0 -0
  78. {kele-0.0.1a1 → kele-0.0.1a2}/.gitignore +0 -0
  79. {kele-0.0.1a1 → kele-0.0.1a2}/Cargo.lock +0 -0
  80. {kele-0.0.1a1 → kele-0.0.1a2}/Cargo.toml +0 -0
  81. {kele-0.0.1a1 → kele-0.0.1a2}/LICENSE +0 -0
  82. {kele-0.0.1a1 → kele-0.0.1a2}/_scripts/ci/examples_static_comment.py +0 -0
  83. {kele-0.0.1a1 → kele-0.0.1a2}/_scripts/ci/run_examples_static_ci.sh +0 -0
  84. {kele-0.0.1a1 → kele-0.0.1a2}/hatch_build.py +0 -0
  85. {kele-0.0.1a1 → kele-0.0.1a2}/kele/control/README_metrics.md +0 -0
  86. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/README.md +0 -0
  87. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/__init__.py +0 -0
  88. {kele-0.0.1a1 → kele-0.0.1a2}/kele/equality/_egg_equiv/src/lib.rs +0 -0
  89. {kele-0.0.1a1 → kele-0.0.1a2}/kele/executer/__init__.py +0 -0
  90. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/README.md +0 -0
  91. {kele-0.0.1a1 → kele-0.0.1a2}/kele/grounder/grounded_rule_ds/_nodes/_typing_polars.py +1 -1
  92. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/README.md +0 -0
  93. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/__init__.py +1 -1
  94. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/builtin_base/__init__.py +0 -0
  95. {kele-0.0.1a1 → kele-0.0.1a2}/kele/knowledge_bases/builtin_base/builtin_concepts.py +0 -0
  96. {kele-0.0.1a1 → kele-0.0.1a2}/kele/py.typed +0 -0
  97. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/CONCEPT_README.md +0 -0
  98. {kele-0.0.1a1 → kele-0.0.1a2}/kele/syntax/connectives.py +0 -0
  99. {kele-0.0.1a1 → kele-0.0.1a2}/licensecheck.json +0 -0
  100. {kele-0.0.1a1 → kele-0.0.1a2}/pytest.ini +0 -0
@@ -24,3 +24,12 @@ repos:
24
24
  hooks:
25
25
  - id: mypy
26
26
  args: ["."]
27
+
28
+ - repo: https://github.com/pycqa/flake8
29
+ rev: 7.0.0
30
+ hooks:
31
+ - id: flake8
32
+ args: ["kele"]
33
+ additional_dependencies:
34
+ - Flake8-pyproject==1.2.4
35
+ - pydoclint[flake8]==0.8.3
@@ -9,9 +9,7 @@ lint.select = [
9
9
  "C4", # flake8-comprehensions
10
10
  "C90", # mccabe
11
11
  "COM", # flake8-commas
12
- "DOC202", "DOC402", "DOC403", "DOC501", "DOC502", "D101", "D102", "D103", "D104", "D419", # pydoclint
13
- # TODO: 这里有个问题的,501用于提醒有raise命令无注释。我需要这个检查,但我不需要必须按docstring的格式解决这一问题,毕竟仓库用的是
14
- # reStructuredText格式。但我没有找到可用的reStructuredText linter,所以暂时不行。ruff的#9003好像也不太一样
12
+ "D101", "D102", "D103", "D104", "D419",
15
13
  "DTZ", # flake8-datetimez
16
14
  "E", # pycodestyle
17
15
  "ERA", # eradicate
@@ -60,10 +58,9 @@ lint.ignore = [
60
58
  "COM812", # Do not force trailing commas
61
59
  "Q000", # Using single quotes instead of double quotes
62
60
  "TRY003", # Allow exception message with any length
63
- "PTH123", # Allow builtin-open
64
61
  ]
65
62
 
66
- exclude = ['docs/', '.git']
63
+ exclude = ['docs/', '.git', '.venv', '__pycache__', 'target']
67
64
 
68
65
  [lint.per-file-ignores]
69
66
  # 对 tests 文件夹中的所有 Python 文件忽略规则
@@ -14,8 +14,8 @@ If you have any questions, please use the GitHub discussions.
14
14
  The code structure of KELE is following the standard Python package structure.
15
15
  We organize the package code into the folder named `KELE`, the tests into the folder named `tests`,
16
16
  and the documents into the folder named `docs`.
17
- The file `pyproject.toml` is used to define the package metadata.
18
- There are also some other files such as `.ruff.toml`, `mypy.ini`, `.pre-commit-config.yaml` used to format and lint the code.
17
+ The file `pyproject.toml` is used to define the package metadata and lint configuration.
18
+ There are also some other files such as `.ruff.toml`, `mypy.ini`, and `.pre-commit-config.yaml` used to format and lint the code.
19
19
 
20
20
  ## How to get involved
21
21
 
@@ -24,6 +24,7 @@ We use the git flow mode to merge the pull requests.
24
24
  Please provide the essential information with proper formatting in the git commit message and the pull request description.
25
25
 
26
26
  Please make sure that your code is properly formatted, linted and typed when you submit a pull request.
27
+ We lint with `ruff`, and we supplement its DOC-series docstring checks with `pydoclint` (via `flake8`) to support reST-style docstrings. Static typing is checked with `mypy`.
27
28
  The comments in the code are expected to be enough for other developers to understand your code.
28
29
  Please add docstrings to the code in reStructuredText style.
29
30
  If necessary, please update documentations and add tests for your changes.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kele
3
- Version: 0.0.1a1
4
- Summary: 推理引擎
3
+ Version: 0.0.1a2
4
+ Summary: Knowledge Equations based Logic Engine, a forward chaining inference engine with Assertional Logic
5
5
  Author: Bingqian Li, BoYang Zhang, Weiming Hong, Hao Zhang, Yi Zhou
6
6
  Maintainer-email: Bingqian Li <libq2022@alumni.shanghaitech.edu.cn>, Yi Zhou <yi_zhou@ustc.edu.cn>
7
7
  License: BSD-3-Clause
@@ -17,6 +17,7 @@ Requires-Dist: polars>=1.32
17
17
  Requires-Dist: prometheus-client>=0.22
18
18
  Requires-Dist: psutil>=7.0
19
19
  Requires-Dist: pydantic<3.0,>=2.0.0
20
+ Requires-Dist: python-baseconv<2.0,>=1.0
20
21
  Requires-Dist: python-sat<2.0,>=1.7.dev0
21
22
  Requires-Dist: pyvis<0.4,>=0.3
22
23
  Requires-Dist: pyyaml<7.0,>=6.0
@@ -51,6 +52,7 @@ It supports **term-level facts**, **nested terms**, **equivalence axioms**, and
51
52
  - **Equivalence axioms**: convenient equivalence expressions with internal maintenance
52
53
  - **Nested compound terms**: operators can nest to build complex structures
53
54
  - **Implement functions for operators**: implement functions for operators (e.g., arithmetic, equation solving)
55
+ - **Built-in hook enabler**: enable built-in hooks for inspection/debugging via `BuiltinHookEnabler`
54
56
 
55
57
  > Implement functions for operators ≈ Prolog meta-predicates / ASP HEX external predicates (not identical semantics, similar usage).
56
58
 
@@ -150,13 +152,21 @@ print(engine.infer_query(query))
150
152
 
151
153
  * **Tutorial**: https://msg-bq.github.io/
152
154
 
155
+ ### 🧩 Custom registries
156
+
157
+ KELE exposes a top-level `register` hub so you can customize internal strategies (such as term/rule selectors) to fit domain-specific needs.
158
+
159
+ * Built-in tasks: `register.rule_selector` and `register.term_selector`.
160
+ * Each task registry supports `register(name)` as a decorator plus `get(name)` helpers.
161
+ * Additional registries may be introduced in the future; today only rule/term selectors are supported.
162
+
153
163
  ### 🗺️ Roadmap
154
164
 
155
165
  WIP
156
166
 
157
167
  ### 🤝 Contributing
158
168
 
159
- Issues/PRs welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md), and consider enabling `ruff` and `mypy`.
169
+ Issues/PRs welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md), and consider enabling `ruff`, `mypy`, and `pydoclint` (via `flake8`). Ruff's `DOC` rules are powered by `pydoclint`.
160
170
 
161
171
  If you have any questions about using the engine—including usage, syntax/semantics, or theoretical foundations—please open an issue or contact us.
162
172
 
@@ -25,6 +25,7 @@ It supports **term-level facts**, **nested terms**, **equivalence axioms**, and
25
25
  - **Equivalence axioms**: convenient equivalence expressions with internal maintenance
26
26
  - **Nested compound terms**: operators can nest to build complex structures
27
27
  - **Implement functions for operators**: implement functions for operators (e.g., arithmetic, equation solving)
28
+ - **Built-in hook enabler**: enable built-in hooks for inspection/debugging via `BuiltinHookEnabler`
28
29
 
29
30
  > Implement functions for operators ≈ Prolog meta-predicates / ASP HEX external predicates (not identical semantics, similar usage).
30
31
 
@@ -124,13 +125,21 @@ print(engine.infer_query(query))
124
125
 
125
126
  * **Tutorial**: https://msg-bq.github.io/
126
127
 
128
+ ### 🧩 Custom registries
129
+
130
+ KELE exposes a top-level `register` hub so you can customize internal strategies (such as term/rule selectors) to fit domain-specific needs.
131
+
132
+ * Built-in tasks: `register.rule_selector` and `register.term_selector`.
133
+ * Each task registry supports `register(name)` as a decorator plus `get(name)` helpers.
134
+ * Additional registries may be introduced in the future; today only rule/term selectors are supported.
135
+
127
136
  ### 🗺️ Roadmap
128
137
 
129
138
  WIP
130
139
 
131
140
  ### 🤝 Contributing
132
141
 
133
- Issues/PRs welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md), and consider enabling `ruff` and `mypy`.
142
+ Issues/PRs welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md), and consider enabling `ruff`, `mypy`, and `pydoclint` (via `flake8`). Ruff's `DOC` rules are powered by `pydoclint`.
134
143
 
135
144
  If you have any questions about using the engine—including usage, syntax/semantics, or theoretical foundations—please open an issue or contact us.
136
145
 
@@ -25,6 +25,7 @@ KELE 是基于[断言逻辑](https://link.springer.com/chapter/10.1007/978-3-319
25
25
  - **等词公理**:便捷表达等价关系,引擎内部自行维护
26
26
  - **可嵌套复合项**:允许嵌套项,算子可互相嵌套构成更复杂的复合结构
27
27
  - **算子的外部实现**:支持使用函数对算子进行自定义“实现”,如加法、解方程等
28
+ - **内置 hook 启用器**:通过 `BuiltinHookEnabler` 启用内置 hooks 用于检查/调试
28
29
 
29
30
  > 外部实现 ≈ Prolog 元谓词 / ASP 中 HEX external predicate(语义不完全相同,但使用体验相近)。
30
31
 
@@ -118,13 +119,21 @@ print(engine.infer_query(query))
118
119
 
119
120
  * **使用教程**:https://msg-bq.github.io/
120
121
 
122
+ ### 🧩 自定义注册
123
+
124
+ KELE 暴露了顶层 `register` 入口,用于自定义内部策略(如 term 与 rule 的选择策略),以适应领域的细节需求。
125
+
126
+ * 内置任务:`register.rule_selector` 与 `register.term_selector`。
127
+ * 每个任务注册表支持 `register(name)` 装饰器与 `available()` 查看已注册策略。
128
+ * 目前仅支持规则/项选择器,后续可能会增加更多注册点。
129
+
121
130
  ### 🗺️ Roadmap
122
131
 
123
132
  WIP
124
133
 
125
134
  ### 🤝 参与贡献
126
135
 
127
- 欢迎 Issue/PR!请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md),遵循相关规范;建议启用 `ruff`、`mypy`。
136
+ 欢迎 Issue/PR!请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md),遵循相关规范;建议启用 `ruff`、`mypy`,以及 `pydoclint`(通过 `flake8` 运行)。`ruff` 的 `DOC` 系列规则由 `pydoclint` 支持。
128
137
 
129
138
  如果对引擎的使用有问题,但不限于使用、语法语义、理论基础等任何方面的问题,都欢迎提 issue 或与我们联系。
130
139
 
@@ -1,15 +1,16 @@
1
1
  """支持断言逻辑的推理引擎"""
2
- from kele.main import EngineRunResult, InferenceEngine, QueryStructure
3
2
  from kele.config import (
4
3
  Config,
5
- RunControlConfig,
6
- InferenceStrategyConfig,
7
- GrounderConfig,
8
4
  ExecutorConfig,
9
- PathConfig,
5
+ GrounderConfig,
6
+ InferenceStrategyConfig,
10
7
  KBConfig,
8
+ PathConfig,
9
+ RunControlConfig,
11
10
  )
12
- from kele.syntax.base_classes import Constant, Concept, Operator, Variable, CompoundTerm, Assertion, Formula, Rule
11
+ from kele.control import register
12
+ from kele.main import EngineRunResult, InferenceEngine, QueryStructure
13
+ from kele.syntax.base_classes import Assertion, CompoundTerm, Concept, Constant, Formula, Operator, Rule, Variable
13
14
 
14
15
  try:
15
16
  from ._version import version as __version__
@@ -35,4 +36,5 @@ __all__ = [
35
36
  'Rule',
36
37
  'RunControlConfig',
37
38
  'Variable',
39
+ 'register',
38
40
  ]
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, TypeVar
4
+
5
+ if TYPE_CHECKING:
6
+ from collections.abc import Callable, Sequence
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ def summarize_items(
12
+ items: Sequence[T],
13
+ *,
14
+ sample_size: int = 5,
15
+ formatter: Callable[[T], str] = str,
16
+ ) -> dict[str, Any]:
17
+ """Summarize a sequence for debug logging."""
18
+ total = len(items)
19
+ sample = [formatter(item) for item in items[:sample_size]]
20
+ return {
21
+ "rows": total,
22
+ "sample": sample,
23
+ }
@@ -0,0 +1 @@
1
+ version = "0.0.1a2"
@@ -1,17 +1,17 @@
1
1
  # ruff: noqa: ERA001 # Commented parameters are either not implemented yet or depend on unfinished upstream/downstream modules.
2
- import warnings
3
- from typing import Any, cast, Literal
4
-
2
+ import json
5
3
  import logging
6
- from dataclasses import dataclass, fields, field
7
- from datetime import datetime, UTC
4
+ import warnings
5
+ from dataclasses import dataclass, field, fields
6
+ from datetime import UTC, datetime
8
7
  from pathlib import Path
9
- import yaml
10
- import json
11
- import tyro
12
- from tyro.conf import OmitArgPrefixes
8
+ from typing import Any, Literal, cast
9
+
13
10
  import dacite
11
+ import tyro
12
+ import yaml
14
13
  from dacite.config import Config as daConfig
14
+ from tyro.conf import OmitArgPrefixes
15
15
 
16
16
  RESULT_LEVEL = 25
17
17
  logging.RESULT = RESULT_LEVEL # type: ignore[attr-defined] # This fails mypy; setattr fails ruff.
@@ -59,12 +59,50 @@ class InferenceStrategyConfig:
59
59
  @dataclass
60
60
  class GrounderConfig:
61
61
  """Grounder-related parameters."""
62
- grounding_rules_num_every_step: int | Literal[-1] = -1
63
- grounding_facts_num_for_each_rule: int | Literal[-1] = -1
62
+ grounding_rules_per_step: int | Literal[-1] = -1
63
+ grounding_facts_per_rule: int | Literal[-1] = -1
64
+ grounding_rules_num_every_step: int | Literal[-1] | None = None
65
+ grounding_facts_num_for_each_rule: int | Literal[-1] | None = None
64
66
  allow_unify_with_nested_term: bool = True # Allow Variables to be replaced by CompoundTerms.
65
67
  conceptual_fuzzy_unification: bool = True # Use strict concept constraints to accelerate inference.
66
68
  # This depends on correct concept subsumption and full constant.belong_concepts settings; beginners should use loose matching.
67
69
 
70
+ def __post_init__(self) -> None:
71
+ if self.grounding_rules_num_every_step is not None:
72
+ warnings.warn(
73
+ "grounding_rules_num_every_step is deprecated; use grounding_rules_per_step instead.",
74
+ DeprecationWarning,
75
+ stacklevel=2,
76
+ )
77
+ if self.grounding_rules_per_step == -1:
78
+ self.grounding_rules_per_step = self.grounding_rules_num_every_step
79
+ elif self.grounding_rules_per_step != self.grounding_rules_num_every_step:
80
+ warnings.warn(
81
+ "Both grounding_rules_per_step and grounding_rules_num_every_step are set; using grounding_rules_per_step.",
82
+ DeprecationWarning,
83
+ stacklevel=2,
84
+ )
85
+
86
+ if self.grounding_facts_num_for_each_rule is not None:
87
+ warnings.warn(
88
+ "grounding_facts_num_for_each_rule is deprecated; use grounding_facts_per_rule instead.",
89
+ DeprecationWarning,
90
+ stacklevel=2,
91
+ )
92
+ if self.grounding_facts_per_rule == -1:
93
+ self.grounding_facts_per_rule = self.grounding_facts_num_for_each_rule
94
+ elif self.grounding_facts_per_rule != self.grounding_facts_num_for_each_rule:
95
+ warnings.warn(
96
+ "Both grounding_facts_per_rule and grounding_facts_num_for_each_rule are set; using grounding_facts_per_rule.",
97
+ DeprecationWarning,
98
+ stacklevel=2,
99
+ )
100
+
101
+ if self.grounding_rules_num_every_step is None:
102
+ self.grounding_rules_num_every_step = self.grounding_rules_per_step
103
+ if self.grounding_facts_num_for_each_rule is None:
104
+ self.grounding_facts_num_for_each_rule = self.grounding_facts_per_rule
105
+
68
106
 
69
107
  @dataclass
70
108
  class ExecutorConfig:
@@ -103,7 +141,7 @@ class Config:
103
141
 
104
142
 
105
143
  def _load_config_file(path: str) -> dict[str, Any]:
106
- with open(path, encoding="utf-8") as f:
144
+ with Path(path).open(encoding="utf-8") as f:
107
145
  if path.endswith(('.yaml', '.yml')):
108
146
  data = yaml.safe_load(f)
109
147
  elif path.endswith('.json'):
@@ -117,7 +155,7 @@ def _load_config_file(path: str) -> dict[str, Any]:
117
155
 
118
156
 
119
157
  def _save_config(config: dict[str, Any], path: str) -> None:
120
- with open(path, 'w', encoding='utf8') as f:
158
+ with Path(path).open('w', encoding='utf8') as f:
121
159
  yaml.dump(config, f, sort_keys=False)
122
160
 
123
161
 
@@ -197,7 +235,7 @@ def _build_config(user_config: Config | None = None,
197
235
 
198
236
  :raises: ValueError: If `user_config` is used together with `config_file_path`
199
237
  or when `user_config.config` is set.
200
- """ # noqa: DOC501
238
+ """
201
239
  if user_config and (user_config.config or config_file_path):
202
240
  raise ValueError("default config instance and config file cannot be used together")
203
241
 
@@ -1,14 +1,17 @@
1
1
  """用于callbacks和推理路径的记录"""
2
- from .callback import HookMixin, Callback, CallbackManager
2
+ from .callback import Callback, CallbackManager, HookMixin
3
+ from .grounding_selector import GroundingRuleSelector
4
+ from .builtin_hooks import BuiltinHookEnabler, register_assertion_check_hook
5
+ from .infer_path import InferencePath
6
+ from .registry import register
3
7
  from .status import (
4
8
  InferenceStatus,
5
- create_main_loop_manager,
6
9
  create_executor_manager,
10
+ create_main_loop_manager,
7
11
  )
8
- from .grounding_selector import GroundingRuleSelector
9
- from .infer_path import InferencePath
10
12
 
11
13
  __all__ = [
14
+ 'BuiltinHookEnabler',
12
15
  'Callback',
13
16
  'CallbackManager',
14
17
  'GroundingRuleSelector',
@@ -17,4 +20,6 @@ __all__ = [
17
20
  'InferenceStatus',
18
21
  'create_executor_manager',
19
22
  'create_main_loop_manager',
23
+ 'register',
24
+ 'register_assertion_check_hook',
20
25
  ]
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from collections.abc import Callable, Mapping
8
+
9
+ from kele.grounder import GroundedRule
10
+ from kele.grounder.grounded_rule_ds._nodes import _AssertionNode
11
+ from kele.syntax import CompoundTerm, Constant, Rule, Variable
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def register_assertion_check_hook(
17
+ grounded_rule: GroundedRule,
18
+ *,
19
+ rule_name: str | None = None,
20
+ vars_filter: Mapping[str, str] | None = None,
21
+ on_match: Callable[[Rule, _AssertionNode, dict[Variable, Constant | CompoundTerm], bool], None] | None = None,
22
+ break_on_match: bool = False,
23
+ ) -> None:
24
+ """
25
+ Register a built-in hook that observes assertion check results.
26
+
27
+ This is a user-facing inspection hook: it lets you capture whether a specific
28
+ assertion evaluation (for a given variable binding) is True/False, and optionally
29
+ stop on matches for deep debugging.
30
+
31
+ Example:
32
+ def log_match(rule, assertion, combination, result):
33
+ print(rule.name, assertion.content, combination, result)
34
+
35
+ register_assertion_check_hook(
36
+ grounded_rule,
37
+ rule_name="rule_3",
38
+ vars_filter={"p1": "f", "p2": "a", "p13": "b", "p14": "c"},
39
+ on_match=log_match,
40
+ break_on_match=True,
41
+ )
42
+ """
43
+ normalized_vars_filter = dict(vars_filter) if vars_filter is not None else None
44
+
45
+ def _hook(
46
+ *,
47
+ rule: Rule,
48
+ assertion: _AssertionNode,
49
+ combination: dict[Variable, Constant | CompoundTerm],
50
+ result: bool,
51
+ ) -> None:
52
+ if rule_name and rule.name != rule_name:
53
+ return
54
+
55
+ combination_str = {str(k): str(v) for k, v in combination.items()}
56
+ if normalized_vars_filter:
57
+ for key, value in normalized_vars_filter.items():
58
+ if combination_str.get(key) != value:
59
+ return
60
+
61
+ if on_match is None:
62
+ logger.debug(
63
+ "Assertion check hook: rule=%s content=%s combination=%s result=%s",
64
+ rule,
65
+ assertion.content,
66
+ combination_str,
67
+ result,
68
+ )
69
+ else:
70
+ on_match(rule, assertion, combination, result)
71
+
72
+ if break_on_match:
73
+ breakpoint() # noqa: T100
74
+
75
+ grounded_rule.register_hook("assertion_check", _hook)
76
+
77
+
78
+ class BuiltinHookEnabler:
79
+ """
80
+ Enabler for built-in hooks by name.
81
+
82
+ This class does not register hooks by itself. Create an instance and call
83
+ ``enable`` to attach a built-in hook to a grounded rule.
84
+
85
+ Example:
86
+ hooks = BuiltinHookEnabler()
87
+ hooks.enable(
88
+ grounded_rule,
89
+ "assertion_check",
90
+ rule_name="rule_3",
91
+ vars_filter={"p1": "f"},
92
+ )
93
+ """
94
+
95
+ def __init__(self) -> None:
96
+ self._hooks: dict[str, Callable[..., None]] = {
97
+ "assertion_check": register_assertion_check_hook,
98
+ }
99
+
100
+ def available_hooks(self) -> list[str]:
101
+ """
102
+ Return the names of available built-in hooks.
103
+ """
104
+ return sorted(self._hooks)
105
+
106
+ def enable(self, grounded_rule: GroundedRule, name: str, **kwargs: object) -> None:
107
+ """
108
+ Enable a built-in hook by name.
109
+ """
110
+ if name not in self._hooks:
111
+ raise KeyError(f"Unknown built-in hook: {name}")
112
+ self._hooks[name](grounded_rule, **kwargs)
113
+
114
+ def enable_many(self, grounded_rule: GroundedRule, names: list[str], **kwargs: object) -> None:
115
+ """
116
+ Enable multiple built-in hooks by name.
117
+ """
118
+ for hook_name in names:
119
+ self.enable(grounded_rule, hook_name, **kwargs)
@@ -1,15 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections import defaultdict
4
- from typing import Any, TYPE_CHECKING
5
-
4
+ from typing import TYPE_CHECKING, Any
6
5
 
7
6
  if TYPE_CHECKING:
7
+ from collections.abc import Callable
8
+
8
9
  from kele.equality import Equivalence
9
10
  from kele.grounder import GroundedRule
10
11
  from kele.knowledge_bases import FactBase, RuleBase
11
- from kele.syntax import Question, Rule, FACT_TYPE, Variable, Constant, CompoundTerm
12
- from collections.abc import Callable
12
+ from kele.syntax import FACT_TYPE, CompoundTerm, Constant, Question, Rule, Variable
13
13
 
14
14
 
15
15
  class HookMixin:
@@ -41,6 +41,16 @@ class HookMixin:
41
41
  for hook in self._hooks.get(event_name, []):
42
42
  hook(*args, **kwargs)
43
43
 
44
+ def run_hooks(self, event_name: str, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
45
+ """
46
+ 对外公开的钩子触发入口。
47
+
48
+ :param event_name: 事件名称。
49
+ :param args: 传递给钩子的所有位置参数。
50
+ :param kwargs: 传递给钩子的所有关键字参数。
51
+ """
52
+ self._run_hooks(event_name, *args, **kwargs)
53
+
44
54
 
45
55
  class Callback:
46
56
  """回调接口——在推理各阶段采集指标的Hook"""
@@ -1,5 +1,11 @@
1
1
  """grounding相关的选择器"""
2
+ from kele.control.registry import register
3
+
2
4
  from .rule_selector import GroundingRuleSelector
3
5
  from .term_selector import GroundingFlatTermWithWildCardSelector
4
6
 
5
- __all__ = ["GroundingFlatTermWithWildCardSelector", "GroundingRuleSelector"]
7
+ __all__ = [
8
+ "GroundingFlatTermWithWildCardSelector",
9
+ "GroundingRuleSelector",
10
+ "register",
11
+ ]
@@ -0,0 +1,37 @@
1
+ ## 默认strategy
2
+
3
+ ### SequentialCyclic
4
+ 按顺序逐个选择规则,每次选1条。
5
+
6
+ ### SequentialCyclicWithPriority
7
+ 根据规则的priority排序后,按顺序逐个选择规则,每次选1条。
8
+
9
+ ## 创建自己的strategy
10
+ 1. 创建一个py文件,命名要求为`_<name>_strategy.py`;
11
+ 2. 继承RuleSelectionStrategy类,并至少声明此Protocol要求的函数;
12
+ 3. 从`kele.control`导入并使用`@register.rule_selector('<name>')`注册你的策略类,后续即可通过`grounding_rule_strategy`使用策略;
13
+ 4. 注意调整`grounding_rule_strategy`的类型标注(增加Literal的候选值)。
14
+
15
+ 示例:
16
+ ```python
17
+ from kele.control import register
18
+ from kele.control.grounding_selector._rule_strategies.strategy_protocol import RuleSelectionStrategy
19
+
20
+
21
+ @register.rule_selector("MyRuleStrategy")
22
+ class MyRuleStrategy:
23
+ def __init__(self) -> None:
24
+ self._rules = []
25
+
26
+ def set_rules(self, rules):
27
+ self._rules = list(rules)
28
+
29
+ def reset(self) -> None:
30
+ self._rules = []
31
+
32
+ def select_next(self):
33
+ return self._rules[:1]
34
+
35
+ def on_feedback(self, feedback):
36
+ return None
37
+ ```
@@ -3,7 +3,7 @@ import importlib
3
3
  import logging
4
4
  import pathlib
5
5
 
6
- from .strategy_protocol import get_strategy_class
6
+ from kele.control.registry import get_rule_strategy_class
7
7
 
8
8
  current_dir = pathlib.Path(__file__).resolve().parent
9
9
  package_name = __package__ or current_dir.name
@@ -21,4 +21,4 @@ for filename in current_dir.iterdir():
21
21
  logger.exception('Failed to import %s', module_name)
22
22
  continue
23
23
 
24
- __all__ = ["get_strategy_class"]
24
+ __all__ = ["get_rule_strategy_class"]
@@ -1,10 +1,12 @@
1
1
  from collections.abc import Sequence
2
2
 
3
- from .strategy_protocol import Feedback, register_strategy, RuleSelectionStrategy
3
+ from kele.control.registry import register
4
4
  from kele.syntax import Rule
5
5
 
6
+ from .strategy_protocol import Feedback, RuleSelectionStrategy
6
7
 
7
- @register_strategy('SequentialCyclic')
8
+
9
+ @register.rule_selector('SequentialCyclic')
8
10
  class SequentialCyclicStrategy(RuleSelectionStrategy):
9
11
  """
10
12
  按顺序循环遍历策略:
@@ -33,7 +35,7 @@ class SequentialCyclicStrategy(RuleSelectionStrategy):
33
35
  return
34
36
 
35
37
 
36
- @register_strategy("SequentialCyclicWithPriority")
38
+ @register.rule_selector("SequentialCyclicWithPriority")
37
39
  class SequentialCyclicWithPriorityStrategy(SequentialCyclicStrategy):
38
40
  """将规则按优先级排序,优先级高的先取"""
39
41
 
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
5
+
6
+ if TYPE_CHECKING:
7
+ from collections.abc import Sequence
8
+
9
+ from kele.syntax import Rule
10
+
11
+
12
+ @runtime_checkable
13
+ class RuleSelectionStrategy(Protocol):
14
+ """
15
+ 选取策略的统一接口。允许根据需求返回任意规则。
16
+ """
17
+ def __init__(self) -> None: ...
18
+ def set_rules(self, rules: Sequence[Rule]) -> None: ...
19
+ def reset(self) -> None: ...
20
+ def select_next(self) -> Sequence[Rule]: ...
21
+ def on_feedback(self, feedback: Feedback) -> None: ... # 给策略回传一次选择后的反馈
22
+
23
+
24
+ @dataclass
25
+ class Feedback:
26
+ """一次选择后的可选反馈信息;字段都可缺省,策略按需使用。"""
27
+ rule: Rule | None = None # 这次反馈关联到的规则;hack: 后面增加其他的相关信息,可能有facts等等
@@ -1,11 +1,9 @@
1
1
  import itertools
2
- import warnings
3
2
  from collections.abc import Sequence
3
+ from functools import singledispatch
4
4
 
5
5
  from kele.knowledge_bases.builtin_base.builtin_concepts import FREEVARANY_CONCEPT
6
- from kele.syntax import (FACT_TYPE, TERM_TYPE, Assertion, Formula, CompoundTerm,
7
- Constant, Variable, ATOM_TYPE, Rule)
8
- from functools import singledispatch
6
+ from kele.syntax import ATOM_TYPE, FACT_TYPE, TERM_TYPE, Assertion, CompoundTerm, Constant, Formula, Rule
9
7
 
10
8
 
11
9
  @singledispatch
@@ -39,12 +37,6 @@ def _(fact: Constant) -> tuple[CompoundTerm | Constant, ...]:
39
37
  return (fact, )
40
38
 
41
39
 
42
- @_unify_all_terms.register(Variable)
43
- def _(fact: Variable) -> tuple[CompoundTerm | Constant, ...]:
44
- warnings.warn("Variable should not exist in fact", stacklevel=2)
45
- return ()
46
-
47
-
48
40
  class FREEVARANY(Constant):
49
41
  """
50
42
  ANY标签,在free_variables用于占位,暂定为一种特殊的Constant。