eyekind 0.5.0__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.
- eyekind-0.5.0/LICENSE.md +9 -0
- eyekind-0.5.0/PKG-INFO +438 -0
- eyekind-0.5.0/README.md +412 -0
- eyekind-0.5.0/eyekind/__init__.py +42 -0
- eyekind-0.5.0/eyekind/__main__.py +3 -0
- eyekind-0.5.0/eyekind/arith.py +118 -0
- eyekind-0.5.0/eyekind/builtins.py +193 -0
- eyekind-0.5.0/eyekind/cli.py +164 -0
- eyekind-0.5.0/eyekind/common.py +275 -0
- eyekind-0.5.0/eyekind/dsl.py +628 -0
- eyekind-0.5.0/eyekind/engine.py +842 -0
- eyekind-0.5.0/eyekind/functions.py +37 -0
- eyekind-0.5.0/eyekind/program.py +285 -0
- eyekind-0.5.0/eyekind/proof.py +639 -0
- eyekind-0.5.0/eyekind/reader.py +428 -0
- eyekind-0.5.0/eyekind/terms.py +614 -0
- eyekind-0.5.0/eyekind/writer.py +242 -0
- eyekind-0.5.0/eyekind.egg-info/PKG-INFO +438 -0
- eyekind-0.5.0/eyekind.egg-info/SOURCES.txt +33 -0
- eyekind-0.5.0/eyekind.egg-info/dependency_links.txt +1 -0
- eyekind-0.5.0/eyekind.egg-info/entry_points.txt +2 -0
- eyekind-0.5.0/eyekind.egg-info/top_level.txt +1 -0
- eyekind-0.5.0/pyproject.toml +29 -0
- eyekind-0.5.0/setup.cfg +4 -0
- eyekind-0.5.0/tests/test_arithmetic.py +83 -0
- eyekind-0.5.0/tests/test_check_report.py +103 -0
- eyekind-0.5.0/tests/test_cli.py +116 -0
- eyekind-0.5.0/tests/test_conformance.py +41 -0
- eyekind-0.5.0/tests/test_core.py +344 -0
- eyekind-0.5.0/tests/test_docs.py +67 -0
- eyekind-0.5.0/tests/test_dsl.py +140 -0
- eyekind-0.5.0/tests/test_examples.py +88 -0
- eyekind-0.5.0/tests/test_proof.py +162 -0
- eyekind-0.5.0/tests/test_syntax.py +190 -0
- eyekind-0.5.0/tests/test_terms.py +82 -0
eyekind-0.5.0/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
### Copyright 2021-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
eyekind-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eyekind
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: A dependency-free Python rule language with forward and backward reasoning and checkable proofs.
|
|
5
|
+
Author: Jos De Roo
|
|
6
|
+
License: # MIT License
|
|
7
|
+
|
|
8
|
+
### Copyright 2021-2026 Jos De Roo, KNoWS office of IDLab, Ghent University - imec
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
15
|
+
|
|
16
|
+
Project-URL: Homepage, https://github.com/eyereasoner/eyekind
|
|
17
|
+
Project-URL: Documentation, https://eyereasoner.github.io/eyekind/
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE.md
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# eyekind
|
|
28
|
+
|
|
29
|
+
[](https://pypi.org/project/eyekind/)
|
|
30
|
+
[](https://doi.org/10.5281/zenodo.23191223)
|
|
31
|
+
|
|
32
|
+
<img src="https://josd.github.io/images/eye.png" alt="EYE" width="100">
|
|
33
|
+
|
|
34
|
+
*eyekind — reasoning you can see.*
|
|
35
|
+
|
|
36
|
+
A standalone, dependency-free **Python rule language** with forward and backward
|
|
37
|
+
reasoning and checkable proofs.
|
|
38
|
+
|
|
39
|
+
**[Playground](https://eyereasoner.github.io/eyekind/playground/)** ·
|
|
40
|
+
**[Examples](https://eyereasoner.github.io/eyekind/examples/)** ·
|
|
41
|
+
**[Example decks](https://eyereasoner.github.io/eyekind/examples/deck/)** ·
|
|
42
|
+
**[Specification](https://eyereasoner.github.io/eyekind/SPEC)** ·
|
|
43
|
+
**[Conformance suite](conformance/)** ([run it in your browser](https://eyereasoner.github.io/eyekind/playground/conformance)) ·
|
|
44
|
+
**[PyPI](https://pypi.org/project/eyekind/)**
|
|
45
|
+
|
|
46
|
+
Most software computes an answer and the reasoning evaporates. eyekind makes a
|
|
47
|
+
different bargain: **an answer can arrive together with the reasoning that
|
|
48
|
+
supports it, in a form another program can check.** You write facts and rules;
|
|
49
|
+
eyekind draws conclusions, and every conclusion can come with a proof that a
|
|
50
|
+
separate checker verifies against your program.
|
|
51
|
+
|
|
52
|
+
- [The idea in a few lines](#the-idea-in-a-few-lines)
|
|
53
|
+
- [Run it](#run-it)
|
|
54
|
+
- [Writing programs](#writing-programs)
|
|
55
|
+
- [Proofs, and what checking one means](#proofs-and-what-checking-one-means)
|
|
56
|
+
- [Checking the translation](#checking-the-translation)
|
|
57
|
+
- [From Python](#from-python)
|
|
58
|
+
- [The examples](#the-examples)
|
|
59
|
+
- [What is deliberately absent](#what-is-deliberately-absent)
|
|
60
|
+
- [The name](#the-name)
|
|
61
|
+
|
|
62
|
+
## The idea in a few lines
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from eyekind import *
|
|
66
|
+
|
|
67
|
+
fact(human('socrates'))
|
|
68
|
+
implies(human(X), mortal(X))
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A fact and a rule. eyekind concludes `mortal('socrates')`. Ask for the reasoning
|
|
72
|
+
and you get a document that names the fact it used, the rule it applied, and
|
|
73
|
+
the substitution that connects them:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
mortal('socrates')
|
|
77
|
+
|
|
78
|
+
clause(1, fact(human('socrates')))
|
|
79
|
+
clause(2, implies(human(X), mortal(X)))
|
|
80
|
+
|
|
81
|
+
step(mortal('socrates'), clause(2), {'X': 'socrates'}, [human('socrates')])
|
|
82
|
+
step(human('socrates'), clause(1), {}, [])
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That proof is itself Python, one expression per line, and a checker that never
|
|
86
|
+
consults the reasoner re-establishes every step against the program. Save the
|
|
87
|
+
two lines above, with their import, as `socrates.py` and try it:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
python -m eyekind --proof socrates.py | python -m eyekind --check-proof - socrates.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The checker prints a report ending in `verdict('checked')`. The certificate for
|
|
94
|
+
a ten-thousand-step derivation works exactly the same way.
|
|
95
|
+
|
|
96
|
+
What a checked derivation adds is a place for disagreement to land. When a
|
|
97
|
+
conclusion is wrong, there are only three possibilities, and a proof tells you
|
|
98
|
+
which: the inference was invalid, the rule did not say what you meant, or the
|
|
99
|
+
input fact was wrong. A checked proof settles the first, so you can spend your
|
|
100
|
+
attention on the other two.
|
|
101
|
+
|
|
102
|
+
## Run it
|
|
103
|
+
|
|
104
|
+
Python 3.9 or newer. No dependencies, no build step. From the root of a
|
|
105
|
+
checkout:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
python -m eyekind examples/socrates.py
|
|
109
|
+
python -m eyekind --proof examples/socrates.py
|
|
110
|
+
python -m eyekind --proof examples/socrates.py | python -m eyekind --check-proof - examples/socrates.py
|
|
111
|
+
./test
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`./test` runs every test, one numbered line each: the unit tests, the
|
|
115
|
+
examples (each must reproduce its saved output, proof and report), and the
|
|
116
|
+
conformance suite, in process and through the command line. `./test examples`
|
|
117
|
+
runs one section, and `./test -k skolem` only the tests that mention skolem.
|
|
118
|
+
|
|
119
|
+
To use eyekind from anywhere, install it; an editable install keeps using the
|
|
120
|
+
checkout, so your edits take effect at once:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
pip install -e .
|
|
124
|
+
eyekind --proof examples/socrates.py
|
|
125
|
+
python examples/socrates.py --proof
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Once eyekind is installed, a program is also a script: `python program.py` runs
|
|
129
|
+
it with the same options as `eyekind program.py`. Run `eyekind --help` for the full
|
|
130
|
+
command line, which [SPEC §14](SPEC.md#14-command-line) describes.
|
|
131
|
+
|
|
132
|
+
Or in the browser: the [playground](https://eyereasoner.github.io/eyekind/playground/)
|
|
133
|
+
edits, runs and checks any example, with eyekind running in the page through
|
|
134
|
+
Pyodide. To run it from a checkout, serve it (`python -m http.server`) and open
|
|
135
|
+
`/playground/`.
|
|
136
|
+
|
|
137
|
+
## Writing programs
|
|
138
|
+
|
|
139
|
+
A program is a Python module that starts with `from eyekind import *` and states
|
|
140
|
+
clauses with these calls:
|
|
141
|
+
|
|
142
|
+
| Call | Meaning |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| `fact(*Heads)` | Each `Head` holds; one fact per argument. |
|
|
145
|
+
| `implies(Premise, Conclusion)` | A forward rule: whenever `Premise` holds, conclude `Conclusion`, until nothing new follows. |
|
|
146
|
+
| `implied_by(Conclusion, Premise)` | A backward rule: `Conclusion` holds when `Premise` does, decided when a goal asks for it. |
|
|
147
|
+
| `query(*Body)` | Publish every instance of `Body` that holds. |
|
|
148
|
+
| `contradiction(*Body)` | Stop with exit code 65 when `Body` holds: an integrity constraint. |
|
|
149
|
+
| `facts_from(path)`, `facts_from(text=...)` | State every expression of a document as a fact, without running it. |
|
|
150
|
+
|
|
151
|
+
A premise joins its goals with `&`, and so can a forward rule's conclusion: `implies(p(X) & q(X), r(X) & s(X))`. A conclusion
|
|
152
|
+
is never `true` or `false`: `query(...)` and `contradiction(...)` say those,
|
|
153
|
+
and proofs show them that way.
|
|
154
|
+
|
|
155
|
+
**Data that is not a program.** Everything eyekind writes, conclusions, proofs
|
|
156
|
+
and check reports, is a *document*: plain expressions, one per line, with no
|
|
157
|
+
`from eyekind import *` and no `fact(...)` around them, so it cannot be imported.
|
|
158
|
+
`facts_from` reads such a document as data and states each expression as a
|
|
159
|
+
fact. It never executes the text, so it is also the safe way to take in data
|
|
160
|
+
you do not trust; a data file written as a program would run as Python code.
|
|
161
|
+
It is what lets one run reason over another, as
|
|
162
|
+
[audited-grants](examples/audited-grants.py) reasons over its own check report:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
facts_from('data.py') # a file of expressions such as score('ann', 3)
|
|
166
|
+
facts_from(text=check_report(result.proof_report)) # or text, such as another run's report
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The two kinds of rule compose. A forward rule's body may call backward
|
|
170
|
+
definitions, and a backward goal may use facts that forward reasoning
|
|
171
|
+
established, so you let knowledge accumulate and then ask a focused question:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from eyekind import *
|
|
175
|
+
|
|
176
|
+
fact(parent('alice', 'bob'))
|
|
177
|
+
fact(parent('bob', 'carol'))
|
|
178
|
+
|
|
179
|
+
implies(parent(X, Y), ancestor(X, Y))
|
|
180
|
+
implies(ancestor(X, Y) & parent(Y, Z), ancestor(X, Z))
|
|
181
|
+
|
|
182
|
+
implied_by(related(X, Y), ancestor(X, Y))
|
|
183
|
+
implied_by(related(X, Y), ancestor(Y, X))
|
|
184
|
+
|
|
185
|
+
query(related('alice', 'carol'))
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
With nothing asked, a run prints the facts it derived; `query(...)` asks the
|
|
189
|
+
program's own questions, and `--goal "Goal"` asks from outside instead.
|
|
190
|
+
|
|
191
|
+
**Nothing to declare.** A name the program uses without defining it is a
|
|
192
|
+
variable when it starts with a capital or an underscore, like `X` or `_Rest`,
|
|
193
|
+
and a predicate otherwise, like `parent`. Python's builtins keep their meaning
|
|
194
|
+
in ordinary Python code, so a program can compute with `int`, `getattr` or
|
|
195
|
+
`sorted`; inside a statement such as `fact(type('socrates', 'human'))` a
|
|
196
|
+
builtin name is a predicate, except for the few a program computes with there:
|
|
197
|
+
`print`, `range`, `len`, `list`, `dict`, `set`, `tuple`, `str`, `enumerate`,
|
|
198
|
+
`zip`, `sorted`, `reversed`, `isinstance`, `open`, `repr`, `chr`, `ord`,
|
|
199
|
+
`iter`, `any`, `all`, `map` and `filter`, and builtins beginning with an
|
|
200
|
+
uppercase letter. A program that wants one of those as
|
|
201
|
+
a predicate says so, `range = preds('range')`. A call of
|
|
202
|
+
an undeclared predicate on its own, such as a misspelled `fcat(p(1))`, states
|
|
203
|
+
nothing, so eyekind stops with an error instead of ignoring it.
|
|
204
|
+
|
|
205
|
+
**Terms are Python values.** An atom is a string, `'socrates'`; a number is an
|
|
206
|
+
`int` of any size or a finite `float`; a list is a list, and `[H, *T]` has head `H`
|
|
207
|
+
and tail `T`; a compound term is a call, `parent('alice', 'bob')`; and `_` is a
|
|
208
|
+
fresh variable at every occurrence. Goals join with `&` (and), `|` (or) and `~`
|
|
209
|
+
(not), alongside `call`, `once` and `findall`. Python reads `&` and `|` before
|
|
210
|
+
comparisons, so write `(X > 1) & (Y < 2)`; a chained comparison such as
|
|
211
|
+
`X > 1 & Y < 2` stops the program with an explanation instead of meaning
|
|
212
|
+
something else.
|
|
213
|
+
|
|
214
|
+
**Numbers are Python's.** Integers are exact and unbounded, so the Fibonacci
|
|
215
|
+
example computes F(10000), a 2,090-digit integer, exactly. An expression means
|
|
216
|
+
what it means in Python: `4 / 2` is `2.0`, `//` and `%` are floor division and
|
|
217
|
+
modulo, `round` rounds half to even. `1` and `1.0` do not unify, yet compare
|
|
218
|
+
exactly. Infinity and NaN are rejected, including overflowing float literals
|
|
219
|
+
such as `1e999` in programs, goals and documents. A result that is not a
|
|
220
|
+
finite real number stops the run rather than becoming a value.
|
|
221
|
+
|
|
222
|
+
The primitives (`unify`, `is_`, `eq`, comparisons, type tests, `functor`,
|
|
223
|
+
`univ`, `atom_concat` and the rest) are listed in [SPEC §5](SPEC.md#5-goals-controls-and-primitives),
|
|
224
|
+
and the arithmetic functions in [SPEC §6](SPEC.md#6-arithmetic). Everything
|
|
225
|
+
else, such as membership, mapping or graph traversal, is written as ordinary
|
|
226
|
+
clauses.
|
|
227
|
+
|
|
228
|
+
**Domains get representations, not syntax.** eyekind has no built-in notion of
|
|
229
|
+
RDF or of anything else: IRIs, typed literals, triples and quoted graphs are
|
|
230
|
+
just terms, such as `literal(V, lang(L))` or `triple(S, P, O)`. A forward head
|
|
231
|
+
may contain variables its body never binds, and then says that something
|
|
232
|
+
exists. Each such unknown becomes a Skolem function of the activation:
|
|
233
|
+
`implies(person(X), has_parent(X, P))` concludes
|
|
234
|
+
`has_parent('ann', skolem(2, 'P', ['ann']))`, the `P` of rule 2 for Ann. The
|
|
235
|
+
same activation always gives the same term, in any round and any run, which
|
|
236
|
+
keeps conclusions deduplicated, and a different one a different term, so two
|
|
237
|
+
unknowns never pass for one.
|
|
238
|
+
|
|
239
|
+
## Proofs, and what checking one means
|
|
240
|
+
|
|
241
|
+
A proof holds claims, the source clauses it displays, and one inference record
|
|
242
|
+
per step. The checker establishes seven conditions:
|
|
243
|
+
|
|
244
|
+
| | Condition | What it establishes |
|
|
245
|
+
| --- | --- | --- |
|
|
246
|
+
| **C1** | resolution | Every step is an instance of a clause of the program, with conclusion and premises agreeing under one substitution |
|
|
247
|
+
| **C2** | well-foundedness | The derivation has no cycles |
|
|
248
|
+
| **C3** | justification | Every step's justification is known, well-formed and unique |
|
|
249
|
+
| **C4** | coverage | Every claim and every premise is accounted for |
|
|
250
|
+
| **C5** | re-decision | Built-in calculations are recomputed rather than trusted |
|
|
251
|
+
| **C6** | boundary consistency | No trusted absence or collection is contradicted by the evidence at hand |
|
|
252
|
+
| **C7** | relevance | Every claim answers the question asked, and every step serves a claim |
|
|
253
|
+
|
|
254
|
+
Two properties make this worth more than a log. **The checker follows the
|
|
255
|
+
certificate:** it never calls the reasoner to fill a gap, so a missing step
|
|
256
|
+
fails the check rather than being quietly re-derived. **The source is the
|
|
257
|
+
authority:** the clauses a proof displays cannot override the program, so you
|
|
258
|
+
cannot smuggle a rule into a proof, nor pad it with steps no claim needs.
|
|
259
|
+
|
|
260
|
+
The report is Python data too, so one program's evidence is material another
|
|
261
|
+
program can reason over, with `facts_from('report.py')`:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
condition('C1', 'resolution', 'ok', 2)
|
|
265
|
+
condition('C2', 'well_founded', 'ok', 2)
|
|
266
|
+
condition('C3', 'justification', 'ok', 2)
|
|
267
|
+
condition('C4', 'coverage', 'ok', 2)
|
|
268
|
+
condition('C5', 're_decision', 'ok', 0)
|
|
269
|
+
condition('C6', 'boundary_consistency', 'ok', 0)
|
|
270
|
+
condition('C7', 'relevance', 'ok', 3)
|
|
271
|
+
steps(2)
|
|
272
|
+
verified(2)
|
|
273
|
+
recomputed(0)
|
|
274
|
+
composed(0)
|
|
275
|
+
trusted(0)
|
|
276
|
+
claims(1)
|
|
277
|
+
verdict('checked')
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Failures appear as `failed(N)` outcomes with `failure(Condition, Conclusion,
|
|
281
|
+
Detail)` facts, an invalid proof gives `verdict(failed(N))` and exit code 2,
|
|
282
|
+
and `--json` gives the same report as JSON. Proofs are parsed as data and never
|
|
283
|
+
executed. Simple lines use a direct reader; other syntax uses Python's `ast`
|
|
284
|
+
module, with the same accepted terms, so checking a proof from someone else
|
|
285
|
+
runs none of their code. Every proof eyekind generates is checked before it is
|
|
286
|
+
returned.
|
|
287
|
+
|
|
288
|
+
### Why negation and aggregation are safe
|
|
289
|
+
|
|
290
|
+
A negation (`~G`) concludes something from what is *not* there, and a
|
|
291
|
+
collection (`findall(T, G, L)`) from *everything* that is there. Both are only
|
|
292
|
+
right once nothing more can be derived: a fact added later would make an
|
|
293
|
+
earlier absence false, or a collected list incomplete. eyekind guards them in
|
|
294
|
+
four ways.
|
|
295
|
+
|
|
296
|
+
**Ask only once the answer is final.** When it loads a program, eyekind works
|
|
297
|
+
out which clauses each body goal depends on. A dependency is *closed* when the
|
|
298
|
+
goal sits inside `~` or `findall`. Every clause gets a rank, at least that of
|
|
299
|
+
everything it depends on, plus one across a closed dependency, and forward
|
|
300
|
+
rules run rank by rank, each to its fixpoint. By the time a rule asks
|
|
301
|
+
`~already_received(P)` or collects `findall(X, p(X), L)`, every rule that
|
|
302
|
+
could still produce such a fact has finished. A program with no such ranking
|
|
303
|
+
is rejected with "unstratified negation or collection dependency":
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
implies(~q, p)
|
|
307
|
+
implies(p, q) # p needs q to be absent, yet p yields q: rejected
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Positive cycles, such as transitive closure, are fine. Ranks compare whole
|
|
311
|
+
terms rather than predicate names, so `t(X, 'type', Y)` and
|
|
312
|
+
`t(X, 'status', Y)` can sit at different ranks, which matters when
|
|
313
|
+
everything is a triple. A goal that is a variable, reachable from a forward
|
|
314
|
+
rule, is rejected, because nobody can tell what it depends on.
|
|
315
|
+
|
|
316
|
+
**Negate only what is bound.** The variables of `~G` must be bound when it
|
|
317
|
+
is solved. With a variable, as in `~p(X)`, the question would quietly become
|
|
318
|
+
"is there no `X` at all?", so eyekind stops with an error instead. Bind the
|
|
319
|
+
variables first: `applicant(P), income(P, I), I < 2500,
|
|
320
|
+
~already_received(P)`. When "no value at all" is what you mean, say so with
|
|
321
|
+
`_`: `~q(X, _)` holds when `q(X, Y)` has no solution for any `Y`.
|
|
322
|
+
|
|
323
|
+
**Say what was taken on trust.** A search that found nothing cannot be proved
|
|
324
|
+
the way a derivation can. So a proof records each negation as an `'absent'`
|
|
325
|
+
step and each collection as a `'collected'` step, the report lists them as
|
|
326
|
+
`obligation('absent', 'theory_scoped', Goal)`, and the verdict is
|
|
327
|
+
`checked_with_obligations` rather than `checked`. *Theory-scoped* says what
|
|
328
|
+
the claim is: absent according to this program, a closed-world assumption
|
|
329
|
+
made visible. `--strict-proof` rejects any proof that leans on one.
|
|
330
|
+
|
|
331
|
+
**Refute them when the evidence allows.** The checker cannot prove an
|
|
332
|
+
obligation, but it can catch one out (C6). An absence fails when a fact of
|
|
333
|
+
the program, a step of the proof or a recomputed primitive is a solution
|
|
334
|
+
after all, and a collection fails when such a solution is missing from its
|
|
335
|
+
list. A valid proof with obligations is valid *conditional on* them, and the
|
|
336
|
+
report says which they are.
|
|
337
|
+
|
|
338
|
+
## Checking the translation
|
|
339
|
+
|
|
340
|
+
A checked proof shows that the conclusions follow from the program. It cannot
|
|
341
|
+
show that the program says what the law, the policy or the textbook says; the
|
|
342
|
+
person who knows the source has to judge that. What eyekind can do is point at the
|
|
343
|
+
parts of a program that make no difference to its conclusions, because those
|
|
344
|
+
are where a translation is most likely to be decorative, incomplete or untested:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
from eyekind import *
|
|
348
|
+
|
|
349
|
+
fact(p('a'))
|
|
350
|
+
fact(q('b'))
|
|
351
|
+
implied_by(s(X), q(X))
|
|
352
|
+
fact(z('c'))
|
|
353
|
+
implies(p('a') & ~s('a'), 'ok')
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
$ eyekind --unused program.py
|
|
358
|
+
unused(line(4), fact(q('b')))
|
|
359
|
+
unused(line(5), implied_by(s(X), q(X)))
|
|
360
|
+
unused(line(6), fact(z('c')))
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
A clause is listed when no conclusion's proof uses it. A negation or a
|
|
364
|
+
collection may consult a clause without its proof recording that search, as
|
|
365
|
+
`~s('a')` consults `q('b')` and the rule for `s`; for each such clause eyekind
|
|
366
|
+
leaves it out, runs the program again, and lists it only when the conclusions
|
|
367
|
+
stay the same. The example programs `research-portal.py` and
|
|
368
|
+
`package-holiday.py` list no clauses.
|
|
369
|
+
|
|
370
|
+
## From Python
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from eyekind import check_proof, load_text, run
|
|
374
|
+
|
|
375
|
+
socrates = load_text("""
|
|
376
|
+
from eyekind import *
|
|
377
|
+
fact(human('socrates'))
|
|
378
|
+
implies(human(X), mortal(X))
|
|
379
|
+
""")
|
|
380
|
+
result = run(socrates, goal='mortal(X)', proof=True)
|
|
381
|
+
print(result.answers) # ["mortal('socrates')"]
|
|
382
|
+
print(result.bindings) # [{'X': "'socrates'"}]
|
|
383
|
+
print(check_proof(socrates, result.proof)['valid']) # True
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`load('program.py', ...)` loads program files and `load_text(source)` source
|
|
387
|
+
text. `run()` returns a `Result` with `answers`, `bindings`, `inferred`,
|
|
388
|
+
`stdout`, `proof`, `proof_report`, `stats` and `halt_code`; options are `goal`,
|
|
389
|
+
`goals`, `proof`, `max_depth` (1000000), `max_iterations` (1000 per stratum)
|
|
390
|
+
and `max_inferences` (1000000), and exceeding a bound raises `EyekindError` rather
|
|
391
|
+
than return a partial result. `check_report(report)` formats a report and
|
|
392
|
+
`read_term(text)` reads a term back. A goal given as text reads a bare name as
|
|
393
|
+
a variable, so a goal with no arguments is written `p()` or `'p'`.
|
|
394
|
+
|
|
395
|
+
## The examples
|
|
396
|
+
|
|
397
|
+
The [example collection](https://eyereasoner.github.io/eyekind/examples/) is 63
|
|
398
|
+
complete programs, each with its conclusions, proof and C1-C7 report saved
|
|
399
|
+
beside it (`examples/output/`, `examples/proof/`, `examples/check/`), and a
|
|
400
|
+
[card deck](https://eyereasoner.github.io/eyekind/examples/deck/) that explains it
|
|
401
|
+
for a wide audience.
|
|
402
|
+
|
|
403
|
+
These are meant to be edited: change a fact, run it, look at what changed.
|
|
404
|
+
Because the artifacts are saved, you can see exactly what your change did:
|
|
405
|
+
|
|
406
|
+
```sh
|
|
407
|
+
./test # every test, one numbered line each
|
|
408
|
+
python tools/update_examples.py # regenerate artifacts after an intended change
|
|
409
|
+
python conformance/run.py # the conformance suite of SPEC.md
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## What is deliberately absent
|
|
413
|
+
|
|
414
|
+
Cut, conditional commitment, mutable databases, attributed variables,
|
|
415
|
+
constraint libraries, tabling, and filesystem or network built-ins. Several of
|
|
416
|
+
these omissions are load-bearing: without cut and without a mutable database, a
|
|
417
|
+
derivation is a function of the program and its input, which is what makes a
|
|
418
|
+
certificate meaningful. A program is free to use Python to *compute* its
|
|
419
|
+
clauses (read a CSV, loop over a range), but once stated a clause is a term,
|
|
420
|
+
and reasoning over it calls no Python code of yours. Undefined predicates fail,
|
|
421
|
+
under the closed-world convention. How the engine itself works is described in
|
|
422
|
+
[SPEC Appendix C](SPEC.md#appendix-c-implementation-notes).
|
|
423
|
+
|
|
424
|
+
Begin with one question and a few facts. Give the relationships names, write
|
|
425
|
+
the rules you already believe, and let the program show you what they imply.
|
|
426
|
+
The first *unexpected* answer is often worth the most, because it points at a
|
|
427
|
+
specific rule or assumption to revisit.
|
|
428
|
+
|
|
429
|
+
## The name
|
|
430
|
+
|
|
431
|
+
**eyekind** joins a *p*, from the Prolog its reasoning comes from, to *eye*, after
|
|
432
|
+
the [EYE](https://github.com/eyereasoner/eye) family of reasoners it belongs
|
|
433
|
+
to. And said aloud it sounds like *py*: in eyekind everything, from the rules and
|
|
434
|
+
the data to the proofs and the check reports, is done in Python.
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
[MIT](LICENSE.md)
|