eyeling 1.12.5 → 1.12.6
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.
- package/HANDBOOK.md +132 -33
- package/examples/bayes-diagnosis.n3 +143 -0
- package/examples/fuse.n3 +2 -0
- package/examples/liar.n3 +2 -0
- package/examples/output/bayes-diagnosis.n3 +21 -0
- package/examples/output/cobalt-kepler-kitchen.n3 +3 -3
- package/examples/output/cranberry-calculus.n3 +3 -3
- package/examples/output/jade-eigen-loom.n3 +8 -8
- package/examples/output/ruby-runge-workshop.n3 +1 -1
- package/examples/output/saffron-slopeworks.n3 +2 -2
- package/examples/output/topaz-markov-mill.n3 +8 -8
- package/examples/output/two-two-four.n3 +7 -7
- package/examples/output/ultramarine-simpson-forge.n3 +6 -6
- package/eyeling-builtins.ttl +17 -17
- package/eyeling.js +100 -14
- package/lib/parser.js +100 -14
- package/package.json +1 -1
- package/test/examples.test.js +38 -16
package/HANDBOOK.md
CHANGED
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
- [Chapter 16 — Extending Eyeling (without breaking it)](#ch16)
|
|
27
27
|
- [Epilogue](#epilogue)
|
|
28
28
|
- [Appendix A — Eyeling user notes](#app-a)
|
|
29
|
-
- [Appendix B —
|
|
29
|
+
- [Appendix B — Notation3: when facts can carry their own logic](#app-b)
|
|
30
30
|
- [Appendix C — N3 beyond Prolog: logic that survives the open web](#app-c)
|
|
31
|
+
- [Appendix D — LLM + Eyeling: A Repeatable Logic Toolchain](#app-d)
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
@@ -113,7 +114,7 @@ If you want to follow the code in the same order Eyeling “thinks”, read:
|
|
|
113
114
|
1. `lib/prelude.js` — the AST (terms, triples, rules), namespaces, prefix handling.
|
|
114
115
|
2. `lib/lexer.js` — N3/Turtle-ish tokenization.
|
|
115
116
|
3. `lib/parser.js` — parsing tokens into triples, formulas, and rules.
|
|
116
|
-
4. `lib/rules.js` — small rule
|
|
117
|
+
4. `lib/rules.js` — small rule helpers (rule-local blank lifting and rule utilities).
|
|
117
118
|
5. `lib/engine.js` — the core inference engine:
|
|
118
119
|
- equality + alpha equivalence for formulas
|
|
119
120
|
- unification + substitutions
|
|
@@ -281,7 +282,7 @@ Internally:
|
|
|
281
282
|
|
|
282
283
|
## Chapter 5 — Rule normalization: “compile-time” semantics (`lib/rules.js`)
|
|
283
284
|
|
|
284
|
-
Before rules hit the engine, Eyeling performs
|
|
285
|
+
Before rules hit the engine, Eyeling performs one lightweight transformation. A second “make it work” trick—deferring built-ins that can’t run yet—happens later inside the goal prover.
|
|
285
286
|
|
|
286
287
|
### 5.1 Lifting blank nodes in rule bodies into variables
|
|
287
288
|
|
|
@@ -303,19 +304,28 @@ This avoids the “existential in the body” trap and matches how most rule aut
|
|
|
303
304
|
|
|
304
305
|
Blanks in the **conclusion** are _not_ lifted — they remain blanks and later become existentials (Chapter 9).
|
|
305
306
|
|
|
306
|
-
### 5.2
|
|
307
|
+
### 5.2 Builtin deferral in forward-rule bodies
|
|
307
308
|
|
|
308
|
-
|
|
309
|
+
In a depth-first proof, the order of goals matters. Many built-ins only become informative once parts of the triple are **already instantiated** (for example comparisons, pattern tests, and other built-ins that don’t normally create bindings).
|
|
309
310
|
|
|
310
|
-
|
|
311
|
-
- `string:matches`, `string:contains`, …
|
|
312
|
-
- `log:notIncludes`, `log:forAllIn`, `log:outputString`, …
|
|
311
|
+
If such a builtin runs while its subject/object still contain variables or blanks, it may return **no solutions** (because it can’t decide yet) or only the **empty delta** (`{}`), even though it would succeed (or fail) once other goals have bound the needed values.
|
|
313
312
|
|
|
314
|
-
Eyeling
|
|
313
|
+
Eyeling supports a runtime deferral mechanism inside `proveGoals(...)`, enabled only when proving the bodies of forward rules.
|
|
315
314
|
|
|
316
|
-
|
|
315
|
+
What happens when `proveGoals(..., { deferBuiltins: true })` sees a builtin goal:
|
|
317
316
|
|
|
318
|
-
|
|
317
|
+
- Eyeling evaluates the builtin once.
|
|
318
|
+
- If the builtin yields **no deltas**, or only **empty deltas** (`[{}]`), and:
|
|
319
|
+
- there are still other goals remaining, and
|
|
320
|
+
- the builtin goal still contains variables/blanks, and
|
|
321
|
+
- the goal list hasn’t already been rotated too many times,
|
|
322
|
+
- then Eyeling **rotates that builtin goal to the end** of the current goal list and continues with the next goal first.
|
|
323
|
+
|
|
324
|
+
A small counter (`deferCount`) caps how many rotations can happen (at most the length of the current goal list), so the prover can’t loop forever by endlessly “trying later”.
|
|
325
|
+
|
|
326
|
+
There is one extra guard for a small whitelist of built-ins that are considered satisfiable even when both subject and object are completely unbound (see `__builtinIsSatisfiableWhenFullyUnbound`). For these, if evaluation yields no deltas and there is nothing left to bind (either it is the last goal, or deferral has already been exhausted), Eyeling treats the builtin as a vacuous success (`[{}]`) so it doesn’t block the proof.
|
|
327
|
+
|
|
328
|
+
This is intentionally enabled for **forward-chaining rule bodies only**. Backward rules keep their normal left-to-right goal order, which can be important for termination on some programs.
|
|
319
329
|
|
|
320
330
|
### 5.3 Materializing anonymous RDF collections into N3 list terms
|
|
321
331
|
|
|
@@ -640,7 +650,7 @@ A rule whose conclusion is `false` is treated as a hard failure. During forward
|
|
|
640
650
|
- Eyeling proves the premise (it only needs one solution)
|
|
641
651
|
- if the premise is provable, it prints a message and exits with status code 2
|
|
642
652
|
|
|
643
|
-
This is Eyeling’s way to express
|
|
653
|
+
This is Eyeling’s way to express hard consistency checks and detect inconsistencies.
|
|
644
654
|
|
|
645
655
|
### 9.5 Rule-producing rules (meta-rules)
|
|
646
656
|
|
|
@@ -776,7 +786,6 @@ That one sentence explains a lot of “why does it behave like _that_?”:
|
|
|
776
786
|
- Builtins are evaluated **during backward proof** (goal solving), just like facts and backward rules.
|
|
777
787
|
- A builtin may produce **zero solutions** (fail), **one solution** (deterministic succeed), or **many solutions** (a generator).
|
|
778
788
|
- Most builtins behave like relations, not like functions: they can sometimes run “backwards” (bind the subject from the object) if the implementation supports it.
|
|
779
|
-
- Some builtins are **pure tests** (constraints): they never introduce new bindings; they only succeed or fail. Eyeling recognizes a subset of these and tends to schedule them _late_ in forward-rule premises so they run after other goals have had a chance to bind variables.
|
|
780
789
|
|
|
781
790
|
### 11.3.0 Reading builtin “signatures” in this handbook
|
|
782
791
|
|
|
@@ -825,13 +834,13 @@ These builtins hash a string and return a lowercase hex digest as a plain string
|
|
|
825
834
|
|
|
826
835
|
Eyeling’s `math:` builtins fall into three broad categories:
|
|
827
836
|
|
|
828
|
-
1. **Comparisons**:
|
|
837
|
+
1. **Comparisons**: test-style predicates (`>`, `<`, `=`, …).
|
|
829
838
|
2. **Arithmetic on numbers**: sums, products, division, rounding, etc.
|
|
830
839
|
3. **Unary analytic functions**: trig/hyperbolic functions and a few helpers.
|
|
831
840
|
|
|
832
841
|
A key design choice: Eyeling parses numeric terms fairly strictly, but comparisons accept a wider “numeric-like” domain including durations and date/time values in some cases.
|
|
833
842
|
|
|
834
|
-
### 11.3.2.1 Numeric comparisons
|
|
843
|
+
### 11.3.2.1 Numeric comparisons
|
|
835
844
|
|
|
836
845
|
These builtins succeed or fail; they do not introduce new bindings.
|
|
837
846
|
|
|
@@ -1158,9 +1167,9 @@ It does not enumerate arbitrary reversals; it’s a deterministic transform once
|
|
|
1158
1167
|
|
|
1159
1168
|
Removes all occurrences of an item from a list.
|
|
1160
1169
|
|
|
1161
|
-
Important
|
|
1170
|
+
Important requirement: the item to remove must be **ground** (fully known) before the builtin will run.
|
|
1162
1171
|
|
|
1163
|
-
#### `list:notMember` (
|
|
1172
|
+
#### `list:notMember` (test)
|
|
1164
1173
|
|
|
1165
1174
|
**Shape:** `(a b c) list:notMember x`
|
|
1166
1175
|
|
|
@@ -1225,7 +1234,7 @@ The `log:` family is where N3 stops being “RDF with rules” and becomes a _me
|
|
|
1225
1234
|
|
|
1226
1235
|
This is simply **term unification**: it succeeds if the two terms can be unified and returns any bindings that result.
|
|
1227
1236
|
|
|
1228
|
-
#### `log:notEqualTo` (
|
|
1237
|
+
#### `log:notEqualTo` (test)
|
|
1229
1238
|
|
|
1230
1239
|
Succeeds iff the terms **cannot** be unified. No new bindings.
|
|
1231
1240
|
|
|
@@ -1362,7 +1371,7 @@ Also supported:
|
|
|
1362
1371
|
|
|
1363
1372
|
- The object may be the literal `true`, meaning the empty formula, which is always included (subject to the priority gating above).
|
|
1364
1373
|
|
|
1365
|
-
#### `log:notIncludes` (
|
|
1374
|
+
#### `log:notIncludes` (test)
|
|
1366
1375
|
|
|
1367
1376
|
Negation-as-failure version: it succeeds iff `log:includes` would yield no solutions (under the same scoping rules).
|
|
1368
1377
|
|
|
@@ -1377,7 +1386,7 @@ Negation-as-failure version: it succeeds iff `log:includes` would yield no solut
|
|
|
1377
1386
|
|
|
1378
1387
|
This is essentially a list-producing “findall”.
|
|
1379
1388
|
|
|
1380
|
-
#### `log:forAllIn` (
|
|
1389
|
+
#### `log:forAllIn` (test)
|
|
1381
1390
|
|
|
1382
1391
|
**Shape:** `( WhereFormula ThenFormula ) log:forAllIn Scope`
|
|
1383
1392
|
|
|
@@ -1455,7 +1464,7 @@ A tiny `sprintf` subset:
|
|
|
1455
1464
|
- Any other specifier (`%d`, `%f`, …) causes the builtin to fail.
|
|
1456
1465
|
- Missing arguments are treated as empty strings.
|
|
1457
1466
|
|
|
1458
|
-
### Containment and prefix/suffix tests
|
|
1467
|
+
### Containment and prefix/suffix tests
|
|
1459
1468
|
|
|
1460
1469
|
- `string:contains`
|
|
1461
1470
|
- `string:containsIgnoringCase`
|
|
@@ -1464,12 +1473,12 @@ A tiny `sprintf` subset:
|
|
|
1464
1473
|
|
|
1465
1474
|
All are pure tests: they succeed or fail.
|
|
1466
1475
|
|
|
1467
|
-
### Case-insensitive equality tests
|
|
1476
|
+
### Case-insensitive equality tests
|
|
1468
1477
|
|
|
1469
1478
|
- `string:equalIgnoringCase`
|
|
1470
1479
|
- `string:notEqualIgnoringCase`
|
|
1471
1480
|
|
|
1472
|
-
### Lexicographic comparisons
|
|
1481
|
+
### Lexicographic comparisons
|
|
1473
1482
|
|
|
1474
1483
|
- `string:greaterThan`
|
|
1475
1484
|
- `string:lessThan`
|
|
@@ -1485,7 +1494,7 @@ Eyeling compiles patterns using JavaScript `RegExp`, with a small compatibility
|
|
|
1485
1494
|
- If the pattern uses Unicode property escapes (like `\p{L}`) or code point escapes (`\u{...}`), Eyeling enables the `/u` flag.
|
|
1486
1495
|
- In Unicode mode, some “identity escapes” that would be SyntaxErrors in JS are sanitized in a conservative way.
|
|
1487
1496
|
|
|
1488
|
-
#### `string:matches` / `string:notMatches` (
|
|
1497
|
+
#### `string:matches` / `string:notMatches` (tests)
|
|
1489
1498
|
|
|
1490
1499
|
**Shape:** `data string:matches pattern`
|
|
1491
1500
|
|
|
@@ -1512,7 +1521,7 @@ From a logic-programming point of view, printing is awkward: if you print _durin
|
|
|
1512
1521
|
The predicate `log:outputString` is the only officially supported “side-effect channel”, and even it is handled in two phases:
|
|
1513
1522
|
|
|
1514
1523
|
1. **During reasoning (declarative phase):**
|
|
1515
|
-
`log:outputString` behaves like a
|
|
1524
|
+
`log:outputString` behaves like a pure test builtin (implemented in `lib/builtins.js`): it succeeds when its arguments are well-formed and sufficiently bound (notably, when the object is a string literal that can be emitted). Importantly, it does _not_ print anything at this time. If a rule derives a triple like:
|
|
1516
1525
|
|
|
1517
1526
|
```n3
|
|
1518
1527
|
:k log:outputString "Hello\n".
|
|
@@ -1926,13 +1935,13 @@ Logic & reasoning background (Wikipedia):
|
|
|
1926
1935
|
|
|
1927
1936
|
<a id="app-b"></a>
|
|
1928
1937
|
|
|
1929
|
-
## Appendix B —
|
|
1938
|
+
## Appendix B — Notation3: when facts can carry their own logic
|
|
1930
1939
|
|
|
1931
|
-
RDF succeeded by making a radical
|
|
1940
|
+
RDF succeeded by making a radical design choice feel natural: reduce meaning to small, uniform statements—triples—that can be published, merged, and queried across boundaries. A triple does not presume a database schema, a programming language, or a particular application. It presumes only that names (IRIs) can be shared, and that graphs can be combined.
|
|
1932
1941
|
|
|
1933
1942
|
That strength also marks RDF’s limit. The moment a graph is expected to _do_ something—normalize values, reconcile vocabularies, derive implied relationships, enforce a policy, compute a small transformation—logic tends to migrate into code. The graph becomes an inert substrate while the decisive semantics hide in scripts, services, ETL pipelines, or bespoke rule engines. What remains portable is the data; what often becomes non-portable is the meaning.
|
|
1934
1943
|
|
|
1935
|
-
|
|
1944
|
+
Notation3 (N3) sits precisely at that seam. It remains a readable way to write RDF, but it also treats _graphs themselves_ as objects that can be described, matched, and related. The N3 Community Group’s specification presents N3 as an assertion and logic language that extends RDF rather than replacing it: [https://w3c.github.io/N3/spec/](https://w3c.github.io/N3/spec/).
|
|
1936
1945
|
|
|
1937
1946
|
The essential move is quotation: writing a graph inside braces as a thing that can be discussed. Once graphs can be quoted, rules become graph-to-graph transformations. The familiar implication form, `{ … } => { … } .`, reads as a piece of prose: whenever the antecedent pattern holds, the consequent pattern follows. Tim Berners-Lee’s design note frames this as a web-friendly logic with variables and nested graphs: [https://www.w3.org/DesignIssues/Notation3.html](https://www.w3.org/DesignIssues/Notation3.html).
|
|
1938
1947
|
|
|
@@ -1946,25 +1955,115 @@ In that context, public conformance suites become more than scoreboards: they ar
|
|
|
1946
1955
|
|
|
1947
1956
|
The comparison with older tools is historically instructive. Cwm (Closed World Machine) was an early, influential RDF data processor and forward-chaining reasoner—part of the lineage that treated RDF (often written in N3) as something executable: [https://www.w3.org/2000/10/swap/doc/cwm](https://www.w3.org/2000/10/swap/doc/cwm).
|
|
1948
1957
|
|
|
1949
|
-
What motivates
|
|
1958
|
+
What motivates Notation3, in the end, is architectural restraint. It refuses to let “logic” become merely a private feature of an application stack. It keeps meaning close to the graph: rules are expressed as graph patterns; results are expressed as triples; computation is pulled in through well-defined built-ins rather than arbitrary code. This produces a style of working where integration and inference are not sidecar scripts, but publishable artifacts—documents that can be inspected, shared, tested, and reused.
|
|
1950
1959
|
|
|
1951
1960
|
In that sense, N3 is less a bid to make the web “smarter” than a bid to make meaning _portable_: not only facts that travel, but also the explicit steps by which facts can be connected, extended, and made actionable—without abandoning the simplicity that made triples travel in the first place.
|
|
1952
1961
|
|
|
1953
|
-
---
|
|
1962
|
+
---
|
|
1963
|
+
|
|
1964
|
+
<a id="app-c"></a>
|
|
1954
1965
|
|
|
1955
1966
|
## Appendix C — N3 beyond Prolog: logic that survives the open web
|
|
1956
1967
|
|
|
1957
1968
|
At first glance, an N3 rule set can feel familiar if you’ve used Prolog: variables, unification, and rules that read like “if this pattern holds, then that pattern follows.” But N3 is not just “logic programming with a different syntax.” It is logic shaped for a different environment: not a single program with a single database, but a world of distributed graphs that can be published, merged, and cited across boundaries.
|
|
1958
1969
|
|
|
1959
|
-
That change of environment forces a change in what “beyond Prolog” even means. It is less about being more powerful in the abstract, and more about being
|
|
1970
|
+
That change of environment forces a change in what “beyond Prolog” even means. It is less about being more powerful in the abstract, and more about being _more portable as meaning_ — logic that stays connected when it moves between documents, vocabularies, and authors.
|
|
1960
1971
|
|
|
1961
1972
|
Several design moves push N3 into that web-native space:
|
|
1962
1973
|
|
|
1963
1974
|
- **Global identity is the default.** Names are IRIs. A rule does not merely compute with local symbols; it operates over identifiers meant to be shared across datasets.
|
|
1964
1975
|
- **Graphs are the unit of exchange.** The input is a graph; the output is a graph. Inference produces new triples rather than hidden internal state, so results can travel the same way the facts do.
|
|
1965
|
-
- **Statements can be treated as data.** Quoted graphs let you talk
|
|
1976
|
+
- **Statements can be treated as data.** Quoted graphs let you talk _about_ assertions: claims, policies, provenance, “this source says …,” “this formula implies …,” and other meta-level structure that is awkward in a plain predicate database.
|
|
1966
1977
|
- **Rules can be publishable artifacts.** Rules can live alongside data as text, be versioned, reviewed, and reused — the “meaning” is not forced back into an external codebase.
|
|
1967
1978
|
- **Web-like computation can be pulled into rule bodies.** Built-ins make room for the small computations that real integration needs (strings, lists, comparisons), and some N3 workflows even treat IRIs as pointers to more knowledge.
|
|
1968
1979
|
|
|
1969
|
-
In that sense, Prolog is a superb engine for proving things
|
|
1980
|
+
In that sense, Prolog is a superb engine for proving things _inside_ a chosen world. N3 is a way to write rules so they keep working _across_ worlds: across documents, across graph boundaries, and across the open-ended growth of linked data. When an engine like Eyeling solves rule bodies with a Prolog-like prover but still saturates forward consequences, it’s exactly this bridge: Prolog-style execution serving a web-scale, graph-first notion of meaning.
|
|
1981
|
+
|
|
1982
|
+
---
|
|
1983
|
+
|
|
1984
|
+
<a id="app-d"></a>
|
|
1985
|
+
|
|
1986
|
+
## Appendix D — LLM + Eyeling: A Repeatable Logic Toolchain
|
|
1987
|
+
|
|
1988
|
+
Eyeling is a deterministic N3 engine: given facts and rules, it derives consequences to a fixpoint using forward rules proved by a backward engine. That makes it a good “meaning boundary” for LLM-assisted workflows: the LLM can draft and refactor N3, but **Eyeling is what decides what follows**.
|
|
1989
|
+
|
|
1990
|
+
A practical pattern is to treat the LLM as a **syntax-and-structure generator** and Eyeling as the **semantic validator**.
|
|
1991
|
+
|
|
1992
|
+
### 1) Constrain the LLM to output compilable N3
|
|
1993
|
+
|
|
1994
|
+
If the LLM is allowed to emit prose or “almost N3”, you’ll spend your time cleaning up. Instead, require:
|
|
1995
|
+
|
|
1996
|
+
- **Only N3** (no explanations in the artifact).
|
|
1997
|
+
- A fixed prefix set (or a required `@base`).
|
|
1998
|
+
- One artifact per file (facts + rules), optionally with a separate test file.
|
|
1999
|
+
- “No invention” rules for IRIs: new symbols must be declared or use a designated namespace.
|
|
2000
|
+
|
|
2001
|
+
This is less about prompt craft and more about creating a stable interface between a text generator and a compiler-like consumer.
|
|
2002
|
+
|
|
2003
|
+
### 2) Use Eyeling as the compile check and the semantic check
|
|
2004
|
+
|
|
2005
|
+
Run Eyeling immediately after generation:
|
|
2006
|
+
|
|
2007
|
+
- **Parse failures** → feed the error back to the LLM and request a corrected N3 file (same vocabulary, minimal diff).
|
|
2008
|
+
- **Runtime failures / fuses** → treat as a spec violation, not “the model being creative”.
|
|
2009
|
+
|
|
2010
|
+
Eyeling explicitly supports **inference fuses**: a forward rule with head `false` is a hard failure. This is extremely useful as a guardrail when you want “never allow X” constraints to stop the run.
|
|
2011
|
+
|
|
2012
|
+
Example fuse:
|
|
2013
|
+
|
|
2014
|
+
```n3
|
|
2015
|
+
@prefix : <http://example/> .
|
|
2016
|
+
|
|
2017
|
+
{ ?u :role :Admin.
|
|
2018
|
+
?u :disabled true.
|
|
2019
|
+
} => false.
|
|
2020
|
+
```
|
|
2021
|
+
|
|
2022
|
+
If you don’t want “stop the world”, derive a `:Violation` fact instead, and keep going.
|
|
2023
|
+
|
|
2024
|
+
### 3) Make the workflow test-driven (golden closures)
|
|
2025
|
+
|
|
2026
|
+
The most robust way to keep LLM-generated logic plausible is to make it live under tests:
|
|
2027
|
+
|
|
2028
|
+
- Keep tiny **fixtures** (facts) alongside the rules.
|
|
2029
|
+
- Run Eyeling to produce the **derived closure** (Eyeling can emit only newly derived forward facts, and can optionally include compact proof comments).
|
|
2030
|
+
- Compare against an expected output (“golden file”) in CI.
|
|
2031
|
+
|
|
2032
|
+
This turns rule edits into a normal change-management loop: diffs are explicit, reviewable, and reproducible.
|
|
2033
|
+
|
|
2034
|
+
### 4) Use proofs/traces as the input to the LLM, not the other way around
|
|
2035
|
+
|
|
2036
|
+
If you want a natural-language explanation, don’t ask the model to “explain the rules from memory”. Instead:
|
|
2037
|
+
|
|
2038
|
+
1. Run Eyeling with proof/trace enabled (Eyeling has explicit tracing hooks and proof-comment support in its output pipeline).
|
|
2039
|
+
2. Give the LLM the **derived triples + proof comments** and ask it to summarize:
|
|
2040
|
+
- what was derived,
|
|
2041
|
+
- which rule(s) fired,
|
|
2042
|
+
- which premises mattered.
|
|
2043
|
+
|
|
2044
|
+
This keeps explanations anchored to what Eyeling actually derived.
|
|
2045
|
+
|
|
2046
|
+
### 5) The refinement loop: edits are N3 diffs, not “better prompting”
|
|
2047
|
+
|
|
2048
|
+
When output looks wrong, the fix should be a change in the artifact:
|
|
2049
|
+
|
|
2050
|
+
- tighten a premise,
|
|
2051
|
+
- split one rule into two,
|
|
2052
|
+
- add an exception rule,
|
|
2053
|
+
- introduce a new predicate to separate concepts,
|
|
2054
|
+
- add a fuse or a `:Violation` derivation,
|
|
2055
|
+
- add a test case that locks in the intended behavior.
|
|
2056
|
+
|
|
2057
|
+
Then regenerate/rewrite **only the N3**, rerun Eyeling, and review the diff.
|
|
2058
|
+
|
|
2059
|
+
### A prompt shape that tends to behave well
|
|
2060
|
+
|
|
2061
|
+
A simple structure that keeps the LLM honest:
|
|
2062
|
+
|
|
2063
|
+
- “Output **only** N3.”
|
|
2064
|
+
- “Use exactly these prefixes.”
|
|
2065
|
+
- “Do not introduce new IRIs outside `<base>#*`.”
|
|
2066
|
+
- “Include at least N minimal tests as facts in a separate block/file.”
|
|
2067
|
+
- “If something is unknown, emit a placeholder fact (`:needsFact`) rather than guessing.”
|
|
1970
2068
|
|
|
2069
|
+
The point isn’t that the LLM is “right”; it’s that **Eyeling makes the result checkable**, and the artifact becomes a maintainable program rather than a one-off generation.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# =================================================================================
|
|
2
|
+
# Bayes Diagnosis
|
|
3
|
+
#
|
|
4
|
+
# This N3 program encodes a small Bayesian diagnostic model and computes posteriors
|
|
5
|
+
# using Eyeling’s deterministic inference + builtins (lists + arithmetic).
|
|
6
|
+
#
|
|
7
|
+
# NOTE: Probability values here are illustrative only and not medical advice.
|
|
8
|
+
# =================================================================================
|
|
9
|
+
|
|
10
|
+
@prefix : <https://example.org/diag#> .
|
|
11
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
12
|
+
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
13
|
+
|
|
14
|
+
# -------------------------------------------
|
|
15
|
+
# 1) MODEL (a simple BN: Disease -> Symptoms)
|
|
16
|
+
# -------------------------------------------
|
|
17
|
+
|
|
18
|
+
:COVID19 a :Disease; :prior 0.05 .
|
|
19
|
+
:Influenza a :Disease; :prior 0.03 .
|
|
20
|
+
:AllergicRhinitis a :Disease; :prior 0.10 .
|
|
21
|
+
:BacterialPneumonia a :Disease; :prior 0.01 .
|
|
22
|
+
|
|
23
|
+
# Conditional probabilities P(symptom | disease)
|
|
24
|
+
# Stored as: :Disease :pGiven [ :symptom :S ; :p 0.xx ] .
|
|
25
|
+
|
|
26
|
+
:COVID19 :pGiven [ :symptom :Fever; :p 0.70 ] .
|
|
27
|
+
:COVID19 :pGiven [ :symptom :DryCough; :p 0.65 ] .
|
|
28
|
+
:COVID19 :pGiven [ :symptom :LossOfSmell; :p 0.40 ] .
|
|
29
|
+
:COVID19 :pGiven [ :symptom :Sneezing; :p 0.15 ] .
|
|
30
|
+
:COVID19 :pGiven [ :symptom :ShortBreath; :p 0.20 ] .
|
|
31
|
+
|
|
32
|
+
:Influenza :pGiven [ :symptom :Fever; :p 0.80 ] .
|
|
33
|
+
:Influenza :pGiven [ :symptom :DryCough; :p 0.50 ] .
|
|
34
|
+
:Influenza :pGiven [ :symptom :LossOfSmell; :p 0.05 ] .
|
|
35
|
+
:Influenza :pGiven [ :symptom :Sneezing; :p 0.20 ] .
|
|
36
|
+
:Influenza :pGiven [ :symptom :ShortBreath; :p 0.10 ] .
|
|
37
|
+
|
|
38
|
+
:AllergicRhinitis :pGiven [ :symptom :Fever; :p 0.05 ] .
|
|
39
|
+
:AllergicRhinitis :pGiven [ :symptom :DryCough; :p 0.15 ] .
|
|
40
|
+
:AllergicRhinitis :pGiven [ :symptom :LossOfSmell; :p 0.10 ] .
|
|
41
|
+
:AllergicRhinitis :pGiven [ :symptom :Sneezing; :p 0.80 ] .
|
|
42
|
+
:AllergicRhinitis :pGiven [ :symptom :ShortBreath; :p 0.05 ] .
|
|
43
|
+
|
|
44
|
+
:BacterialPneumonia :pGiven [ :symptom :Fever; :p 0.70 ] .
|
|
45
|
+
:BacterialPneumonia :pGiven [ :symptom :DryCough; :p 0.60 ] .
|
|
46
|
+
:BacterialPneumonia :pGiven [ :symptom :LossOfSmell; :p 0.02 ] .
|
|
47
|
+
:BacterialPneumonia :pGiven [ :symptom :Sneezing; :p 0.05 ] .
|
|
48
|
+
:BacterialPneumonia :pGiven [ :symptom :ShortBreath; :p 0.60 ] .
|
|
49
|
+
|
|
50
|
+
# ----------------------------------------------
|
|
51
|
+
# 2) CASE (symptom evidence: present true/false)
|
|
52
|
+
# ----------------------------------------------
|
|
53
|
+
|
|
54
|
+
:Case a :PatientCase;
|
|
55
|
+
:diseases ( :COVID19 :Influenza :AllergicRhinitis :BacterialPneumonia );
|
|
56
|
+
:evidence (
|
|
57
|
+
[ :symptom :Fever; :present true ]
|
|
58
|
+
[ :symptom :DryCough; :present true ]
|
|
59
|
+
[ :symptom :LossOfSmell; :present true ]
|
|
60
|
+
[ :symptom :Sneezing; :present false ]
|
|
61
|
+
[ :symptom :ShortBreath; :present true ]
|
|
62
|
+
).
|
|
63
|
+
|
|
64
|
+
# -------------------------------------------------------------
|
|
65
|
+
# 3) GUARDS (inference fuses) — fail hard if probs out of [0,1]
|
|
66
|
+
# -------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
{ ?d :prior ?p. ?p math:lessThan 0. } => false.
|
|
69
|
+
{ ?d :prior ?p. ?p math:greaterThan 1. } => false.
|
|
70
|
+
|
|
71
|
+
{ ?d :pGiven [ :p ?p ]. ?p math:lessThan 0. } => false.
|
|
72
|
+
{ ?d :pGiven [ :p ?p ]. ?p math:greaterThan 1. } => false.
|
|
73
|
+
|
|
74
|
+
# ----------
|
|
75
|
+
# 4) HELPERS
|
|
76
|
+
# ----------
|
|
77
|
+
|
|
78
|
+
# pairList(d, (x1 x2 ...)) -> ((d x1) (d x2) ...)
|
|
79
|
+
{ ( ?d () ) :pairList () } <= true.
|
|
80
|
+
|
|
81
|
+
{ ( ?d ?xs ) :pairList ?pairs } <= {
|
|
82
|
+
?xs list:firstRest ( ?x ?rest ).
|
|
83
|
+
( ?d ?rest ) :pairList ?tailPairs.
|
|
84
|
+
?pairs list:firstRest ( ( ?d ?x ) ?tailPairs ).
|
|
85
|
+
}.
|
|
86
|
+
|
|
87
|
+
# factor for one evidence item:
|
|
88
|
+
# if present -> P(symptom | disease)
|
|
89
|
+
# if absent -> 1 - P(symptom | disease)
|
|
90
|
+
{ ( ?d ?ev ) :factor ?p } <= {
|
|
91
|
+
?ev :symptom ?s.
|
|
92
|
+
?ev :present true.
|
|
93
|
+
?d :pGiven [ :symptom ?s; :p ?p ].
|
|
94
|
+
}.
|
|
95
|
+
|
|
96
|
+
{ ( ?d ?ev ) :factor ?q } <= {
|
|
97
|
+
?ev :symptom ?s.
|
|
98
|
+
?ev :present false.
|
|
99
|
+
?d :pGiven [ :symptom ?s; :p ?p ].
|
|
100
|
+
( 1 ?p ) math:difference ?q.
|
|
101
|
+
}.
|
|
102
|
+
|
|
103
|
+
# unnormalized score(d) = prior(d) * Π_e factor(d,e)
|
|
104
|
+
{ ?d :scoreFor ?score } <= {
|
|
105
|
+
?d :prior ?prior.
|
|
106
|
+
:Case :evidence ?evs.
|
|
107
|
+
( ?d ?evs ) :pairList ?pairs.
|
|
108
|
+
( ?pairs :factor ) list:map ?factors.
|
|
109
|
+
?factors math:product ?likelihood.
|
|
110
|
+
( ?prior ?likelihood ) math:product ?score.
|
|
111
|
+
}.
|
|
112
|
+
|
|
113
|
+
# --------------------------------------------------------
|
|
114
|
+
# 5) POSTERIOR: normalize across diseases in the case list
|
|
115
|
+
# --------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
# Compute the score list and total once.
|
|
118
|
+
{
|
|
119
|
+
:Case :diseases ?ds.
|
|
120
|
+
( ?ds :scoreFor ) list:map ?scores.
|
|
121
|
+
?scores math:sum ?total.
|
|
122
|
+
} => {
|
|
123
|
+
:Case :scores ?scores;
|
|
124
|
+
:evidenceTotal ?total.
|
|
125
|
+
}.
|
|
126
|
+
|
|
127
|
+
# Emit one result node per disease: posterior = score / total
|
|
128
|
+
{
|
|
129
|
+
:Case :diseases ?ds.
|
|
130
|
+
:Case :scores ?scores.
|
|
131
|
+
:Case :evidenceTotal ?total.
|
|
132
|
+
|
|
133
|
+
?ds list:iterate ( ?i ?d ).
|
|
134
|
+
( ?scores ?i ) list:memberAt ?score.
|
|
135
|
+
( ?score ?total ) math:quotient ?post.
|
|
136
|
+
} => {
|
|
137
|
+
:Case :result [
|
|
138
|
+
:disease ?d;
|
|
139
|
+
:unnormalized ?score;
|
|
140
|
+
:posterior ?post
|
|
141
|
+
].
|
|
142
|
+
}.
|
|
143
|
+
|
package/examples/fuse.n3
CHANGED
package/examples/liar.n3
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@prefix : <https://example.org/diag#> .
|
|
2
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
3
|
+
|
|
4
|
+
:Case :scores ("0.0015470000000000002"^^xsd:decimal "0.000048000000000000015"^^xsd:decimal "7.499999999999999e-7"^^xsd:decimal "0.000047879999999999996"^^xsd:decimal) .
|
|
5
|
+
:Case :evidenceTotal "0.0016436300000000003"^^xsd:decimal .
|
|
6
|
+
:Case :result _:sk_0 .
|
|
7
|
+
_:sk_0 :disease :COVID19 .
|
|
8
|
+
_:sk_0 :unnormalized "0.0015470000000000002"^^xsd:decimal .
|
|
9
|
+
_:sk_0 :posterior "0.9412093962753174"^^xsd:decimal .
|
|
10
|
+
:Case :result _:sk_1 .
|
|
11
|
+
_:sk_1 :disease :Influenza .
|
|
12
|
+
_:sk_1 :unnormalized "0.000048000000000000015"^^xsd:decimal .
|
|
13
|
+
_:sk_1 :posterior "0.029203652890249024"^^xsd:decimal .
|
|
14
|
+
:Case :result _:sk_2 .
|
|
15
|
+
_:sk_2 :disease :AllergicRhinitis .
|
|
16
|
+
_:sk_2 :unnormalized "7.499999999999999e-7"^^xsd:decimal .
|
|
17
|
+
_:sk_2 :posterior "0.00045630707641014084"^^xsd:decimal .
|
|
18
|
+
:Case :result _:sk_3 .
|
|
19
|
+
_:sk_3 :disease :BacterialPneumonia .
|
|
20
|
+
_:sk_3 :unnormalized "0.000047879999999999996"^^xsd:decimal .
|
|
21
|
+
_:sk_3 :posterior "0.029130643758023392"^^xsd:decimal .
|
|
@@ -6,37 +6,37 @@ _:b1 :apoapsis "7006.999999999999"^^xsd:decimal .
|
|
|
6
6
|
_:b1 :period "5828.516637686015"^^xsd:decimal .
|
|
7
7
|
_:b1 :specificEnergy "-28.471460128571426"^^xsd:decimal .
|
|
8
8
|
_:b1 :angularMomentum "52822.34661955968"^^xsd:decimal .
|
|
9
|
+
_:b1 :stateAtM _:sk_0 .
|
|
9
10
|
_:sk_0 :M 1.0 .
|
|
10
11
|
_:sk_0 :E "1.0008419256335213"^^xsd:decimal .
|
|
11
12
|
_:sk_0 :nu "-2.2873045281884816"^^xsd:decimal .
|
|
12
13
|
_:sk_0 :r "6996.222844390736"^^xsd:decimal .
|
|
13
14
|
_:sk_0 :x "-4594.807855276618"^^xsd:decimal .
|
|
14
15
|
_:sk_0 :y "-5275.87669126782"^^xsd:decimal .
|
|
15
|
-
_:b1 :stateAtM _:sk_0 .
|
|
16
16
|
_:b2 :periapsis "26294.4"^^xsd:decimal .
|
|
17
17
|
_:b2 :apoapsis "26825.6"^^xsd:decimal .
|
|
18
18
|
_:b2 :period "43077.75744086394"^^xsd:decimal .
|
|
19
19
|
_:b2 :specificEnergy "-7.503773377259035"^^xsd:decimal .
|
|
20
20
|
_:b2 :angularMomentum "102887.16660222781"^^xsd:decimal .
|
|
21
|
+
_:b2 :stateAtM _:sk_1 .
|
|
21
22
|
_:sk_1 :M 0.3 .
|
|
22
23
|
_:sk_1 :E "0.3029834341902831"^^xsd:decimal .
|
|
23
24
|
_:sk_1 :nu "0.6416326023742479"^^xsd:decimal .
|
|
24
25
|
_:sk_1 :r "26306.497927630408"^^xsd:decimal .
|
|
25
26
|
_:sk_1 :x "21074.653903376588"^^xsd:decimal .
|
|
26
27
|
_:sk_1 :y "15744.54813798468"^^xsd:decimal .
|
|
27
|
-
_:b2 :stateAtM _:sk_1 .
|
|
28
28
|
_:b3 :periapsis "42159.7836"^^xsd:decimal .
|
|
29
29
|
_:b3 :apoapsis "42168.2164"^^xsd:decimal .
|
|
30
30
|
_:b3 :period "86163.57055057827"^^xsd:decimal .
|
|
31
31
|
_:b3 :specificEnergy "-4.7267863793757705"^^xsd:decimal .
|
|
32
32
|
_:b3 :angularMomentum "129640.22855575853"^^xsd:decimal .
|
|
33
|
+
_:b3 :stateAtM _:sk_2 .
|
|
33
34
|
_:sk_2 :M 2.0 .
|
|
34
35
|
_:sk_2 :E "2.00009092595867"^^xsd:decimal .
|
|
35
36
|
_:sk_2 :nu "2.0000000098093915"^^xsd:decimal .
|
|
36
37
|
_:sk_2 :r "42165.754990121"^^xsd:decimal .
|
|
37
38
|
_:sk_2 :x "-17547.1459258647"^^xsd:decimal .
|
|
38
39
|
_:sk_2 :y "38341.21234055235"^^xsd:decimal .
|
|
39
|
-
_:b3 :stateAtM _:sk_2 .
|
|
40
40
|
:Transfer1 :transferSemiMajorAxis "24582"^^xsd:decimal .
|
|
41
41
|
:Transfer1 :v1 "7.546053290107541"^^xsd:decimal .
|
|
42
42
|
:Transfer1 :v2 "3.074666284127684"^^xsd:decimal .
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
3
3
|
|
|
4
4
|
:VecA :dotWithVecB "12"^^xsd:decimal .
|
|
5
|
+
:VecA :angleToVecB _:sk_0 .
|
|
5
6
|
_:sk_0 :radians "0.9272952180016123"^^xsd:decimal .
|
|
6
7
|
_:sk_0 :degrees "53.13010235415598"^^xsd:decimal .
|
|
7
|
-
:VecA :angleToVecB _:sk_0 .
|
|
8
8
|
:Shot1 :vx "21.213203435596427"^^xsd:decimal .
|
|
9
9
|
:Shot1 :vy "21.213203435596423"^^xsd:decimal .
|
|
10
10
|
:Shot1 :timeOfFlight "4.324812117348913"^^xsd:decimal .
|
|
11
11
|
:Shot1 :range "91.74311926605502"^^xsd:decimal .
|
|
12
12
|
:Shot1 :maxHeight "22.935779816513755"^^xsd:decimal .
|
|
13
|
+
:Shot1 :positionAtSample _:sk_1 .
|
|
13
14
|
_:sk_1 :t 2.5 .
|
|
14
15
|
_:sk_1 :x "53.033008588991066"^^xsd:decimal .
|
|
15
16
|
_:sk_1 :y "22.37675858899106"^^xsd:decimal .
|
|
16
|
-
:Shot1 :positionAtSample _:sk_1 .
|
|
17
17
|
:DataSet1 :mean "2.642857142857143"^^xsd:decimal .
|
|
18
18
|
:DataSet1 :variance "9.083163265306123"^^xsd:decimal .
|
|
19
19
|
:DataSet1 :stddev "3.0138286721886036"^^xsd:decimal .
|
|
20
|
+
:DataSet1 :outlier _:sk_2 .
|
|
20
21
|
_:sk_2 :value 10.0 .
|
|
21
22
|
_:sk_2 :zScore "2.44112843076783"^^xsd:decimal .
|
|
22
|
-
:DataSet1 :outlier _:sk_2 .
|
|
@@ -16,45 +16,45 @@
|
|
|
16
16
|
:PCA1 :_b "-10.193877551020407"^^xsd:decimal .
|
|
17
17
|
:PCA1 :thetaRad "-0.5758598575998168"^^xsd:decimal .
|
|
18
18
|
:PCA1 :thetaDeg "-32.99433943147409"^^xsd:decimal .
|
|
19
|
+
:PCA1 :score _:sk_0 .
|
|
19
20
|
_:sk_0 :point _:b1 .
|
|
20
21
|
_:sk_0 :u "-2.226965310551135"^^xsd:decimal .
|
|
21
22
|
_:sk_0 :v "-5.227147834641157"^^xsd:decimal .
|
|
22
23
|
_:sk_0 :md2 "1.813452314350555"^^xsd:decimal .
|
|
23
|
-
:PCA1 :score _:
|
|
24
|
+
:PCA1 :score _:sk_1 .
|
|
24
25
|
_:sk_1 :point _:b2 .
|
|
25
26
|
_:sk_1 :u "-1.9327971142113742"^^xsd:decimal .
|
|
26
27
|
_:sk_1 :v "-3.843867287441055"^^xsd:decimal .
|
|
27
28
|
_:sk_1 :md2 "1.007995131390371"^^xsd:decimal .
|
|
28
|
-
:PCA1 :score _:
|
|
29
|
+
:PCA1 :score _:sk_2 .
|
|
29
30
|
_:sk_2 :point _:b3 .
|
|
30
31
|
_:sk_2 :u "-1.7475401529576473"^^xsd:decimal .
|
|
31
32
|
_:sk_2 :v "-2.292841865886967"^^xsd:decimal .
|
|
32
33
|
_:sk_2 :md2 "0.4034041082672275"^^xsd:decimal .
|
|
33
|
-
:PCA1 :score _:
|
|
34
|
+
:PCA1 :score _:sk_3 .
|
|
34
35
|
_:sk_3 :point _:b4 .
|
|
35
36
|
_:sk_3 :u "-1.9434725145050398"^^xsd:decimal .
|
|
36
37
|
_:sk_3 :v "-0.15470938409392798"^^xsd:decimal .
|
|
37
38
|
_:sk_3 :md2 "0.09948798150629935"^^xsd:decimal .
|
|
38
|
-
:PCA1 :score _:
|
|
39
|
+
:PCA1 :score _:sk_4 .
|
|
39
40
|
_:sk_4 :point _:b5 .
|
|
40
41
|
_:sk_4 :u "-2.6295054339395856"^^xsd:decimal .
|
|
41
42
|
_:sk_4 :v "2.73827503229205"^^xsd:decimal .
|
|
42
43
|
_:sk_4 :md2 "0.6417615664115196"^^xsd:decimal .
|
|
43
|
-
:PCA1 :score _:
|
|
44
|
+
:PCA1 :score _:sk_5 .
|
|
44
45
|
_:sk_5 :point _:b6 .
|
|
45
46
|
_:sk_5 :u "-4.5680175568635235"^^xsd:decimal .
|
|
46
47
|
_:sk_5 :v "7.56032550374887"^^xsd:decimal .
|
|
47
48
|
_:sk_5 :md2 "4.065911517796796"^^xsd:decimal .
|
|
48
|
-
:PCA1 :score _:
|
|
49
|
+
:PCA1 :score _:sk_6 .
|
|
49
50
|
_:sk_6 :point _:b7 .
|
|
50
51
|
_:sk_6 :u "15.048298083028305"^^xsd:decimal .
|
|
51
52
|
_:sk_6 :v "1.219965836022185"^^xsd:decimal .
|
|
52
53
|
_:sk_6 :md2 "5.967987380277233"^^xsd:decimal .
|
|
53
|
-
:PCA1 :score _:sk_6 .
|
|
54
54
|
:PCA1 :sigma1 "6.207810973771468"^^xsd:decimal .
|
|
55
55
|
:PCA1 :sigma2 "4.0271300936862655"^^xsd:decimal .
|
|
56
|
+
:PCA1 :ellipse95 _:sk_7 .
|
|
56
57
|
_:sk_7 :k "2.4476519360399265"^^xsd:decimal .
|
|
57
58
|
_:sk_7 :a "15.194560548521636"^^xsd:decimal .
|
|
58
59
|
_:sk_7 :b "9.857012770495839"^^xsd:decimal .
|
|
59
60
|
_:sk_7 :area "470.52568540824956"^^xsd:decimal .
|
|
60
|
-
:PCA1 :ellipse95 _:sk_7 .
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
:Interp1 :derivativeAtX0 "2.3999999999999853"^^xsd:decimal .
|
|
10
10
|
:Interp1 :yAtBracketA "-1"^^xsd:decimal .
|
|
11
11
|
:Interp1 :yAtBracketB "1"^^xsd:decimal .
|
|
12
|
+
:Interp1 :rootBracket _:sk_0 .
|
|
12
13
|
_:sk_0 :a 0.0 .
|
|
13
14
|
_:sk_0 :b 1.0 .
|
|
14
15
|
_:sk_0 :ya "-1"^^xsd:decimal .
|
|
15
16
|
_:sk_0 :yb "1"^^xsd:decimal .
|
|
16
|
-
:Interp1 :rootBracket _:sk_0 .
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
:Reg1 :rSquared "0.7193278283366806"^^xsd:decimal .
|
|
14
14
|
:Reg1 :sse "32.33904761904761"^^xsd:decimal .
|
|
15
15
|
:Reg1 :rmse "2.010567321026817"^^xsd:decimal .
|
|
16
|
+
:Reg1 :highResidual _:sk_0 .
|
|
16
17
|
_:sk_0 :point _:b8 .
|
|
17
18
|
_:sk_0 :x 8.0 .
|
|
18
19
|
_:sk_0 :y 15.0 .
|
|
19
20
|
_:sk_0 :yhat "10.666666666666666"^^xsd:decimal .
|
|
20
21
|
_:sk_0 :residual "4.333333333333334"^^xsd:decimal .
|
|
21
|
-
:Reg1 :
|
|
22
|
+
:Reg1 :prediction _:sk_1 .
|
|
22
23
|
_:sk_1 :x 8.5 .
|
|
23
24
|
_:sk_1 :y "11.369047619047619"^^xsd:decimal .
|
|
24
|
-
:Reg1 :prediction _:sk_1 .
|