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 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 — Notation 3: when facts can carry their own logic](#app-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 “compiler passes” (blank lifting, constraint delaying).
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 two lightweight transformations.
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 Delaying constraints
307
+ ### 5.2 Builtin deferral in forward-rule bodies
307
308
 
308
- Some built-ins don’t generate bindings; they only test conditions:
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
- - `math:greaterThan`, `math:lessThan`, `math:equalTo`,
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 treats these as “constraints” and moves them to the _end_ of a forward rule premise. This is a Prolog-style heuristic:
313
+ Eyeling supports a runtime deferral mechanism inside `proveGoals(...)`, enabled only when proving the bodies of forward rules.
315
314
 
316
- > Bind variables first; only then run pure checks.
315
+ What happens when `proveGoals(..., { deferBuiltins: true })` sees a builtin goal:
317
316
 
318
- It’s not logically necessary, but it improves the chance that constraints run with variables already grounded, reducing wasted search.
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 constraints and detect inconsistencies.
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**: constraint-style predicates (`>`, `<`, `=`, …).
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 (constraints)
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 constraint: the item to remove must be **ground** (fully known) before the builtin will run.
1170
+ Important requirement: the item to remove must be **ground** (fully known) before the builtin will run.
1162
1171
 
1163
- #### `list:notMember` (constraint)
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` (constraint)
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` (constraint)
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` (constraint)
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 (constraints)
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 (constraints)
1476
+ ### Case-insensitive equality tests
1468
1477
 
1469
1478
  - `string:equalIgnoringCase`
1470
1479
  - `string:notEqualIgnoringCase`
1471
1480
 
1472
- ### Lexicographic comparisons (constraints)
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` (constraints)
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 constraint-style 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:
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 — Notation 3: when facts can carry their own logic
1938
+ ## Appendix B — Notation3: when facts can carry their own logic
1930
1939
 
1931
- RDF succeeded by making a radical constraint 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.
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
- Notation 3 (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/).
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 Notation 3, 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.
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 *more portable as meaning* — logic that stays connected when it moves between documents, vocabularies, and authors.
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 *about* assertions: claims, policies, provenance, “this source says …,” “this formula implies …,” and other meta-level structure that is awkward in a plain predicate database.
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 *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.
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
@@ -2,6 +2,8 @@
2
2
  # Inference fuse
3
3
  # ==============
4
4
 
5
+ # expect-exit: 2
6
+
5
7
  @prefix : <https://eyereasoner.github.io/ns#>.
6
8
 
7
9
  :stone :color :black.
package/examples/liar.n3 CHANGED
@@ -3,6 +3,8 @@
3
3
  # Example from Patrick Hochstenbach
4
4
  # =================================
5
5
 
6
+ # expect-exit: 2
7
+
6
8
  @prefix : <https://eyereasoner.github.io/ns#>.
7
9
  @prefix log: <http://www.w3.org/2000/10/swap/log#>.
8
10
 
@@ -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 _:sk_0 .
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 _:sk_1 .
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 _:sk_2 .
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 _:sk_3 .
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 _:sk_4 .
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 _:sk_5 .
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 :highResidual _:sk_0 .
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 .