eyeling 1.15.2 → 1.15.4

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
@@ -29,6 +29,7 @@
29
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
31
  - [Appendix D — LLM + Eyeling: A Repeatable Logic Toolchain](#app-d)
32
+ - [Appendix E — How Eyeling reaches 100% on `notation3tests`](#app-e)
32
33
 
33
34
  ---
34
35
 
@@ -560,6 +561,47 @@ Implementation notes:
560
561
  - Keys are _structural_. Atoms use stable IDs; lists use element keys; variables use their identity (so two different variables are **not** conflated). This keeps the cycle check conservative and avoids accidental pruning.
561
562
  - This is not full tabling: it does not memoize answers, it only guards against immediate cycles (the common “A depends on A” loops).
562
563
 
564
+ ### 8.4.1 Minimal completed-goal tabling
565
+
566
+ Eyeling has a **very small, deliberately conservative answer table** for backward goals.
567
+
568
+ What is cached:
569
+
570
+ - only **completed** answer sets
571
+ - keyed by the **fully substituted goal list**
572
+ - only when the proof is entered from a “top-level” call shape (no active per-branch `visited` context)
573
+ - only when the engine is not in a result-limiting mode such as `maxResults`
574
+
575
+ What is **not** cached:
576
+
577
+ - pending / in-progress goals
578
+ - recursive dependency states
579
+ - partial answer streams
580
+ - branch-local states inside an active recursive proof
581
+
582
+ This matters because exposing **pending** answers without dependency propagation would change the meaning of recursive programs. Eyeling therefore caches only results that are already complete and replays them only when the surrounding proof context is equivalent.
583
+
584
+ The cache is invalidated whenever any of the following changes:
585
+
586
+ - the number of known facts
587
+ - the number of backward rules
588
+ - the scoped-closure level
589
+ - whether a frozen scoped snapshot is active
590
+
591
+ So this is **not SLG tabling** and not a general recursion engine. It is best understood as a reuse optimization for repeated backward proofs in a stable proof environment.
592
+
593
+ Typical win cases:
594
+
595
+ - many repeated `log:query` directives with the **same premise**
596
+ - repeated forward-rule body proofs that ask the same completed backward question
597
+ - “query-like” workloads where the expensive part is a repeated backward proof and the fact store does not change between calls
598
+
599
+ Typical non-win cases:
600
+
601
+ - first-time proofs
602
+ - recursive subgoals whose value depends on future answers
603
+ - workloads where the fact set changes between almost every call
604
+
563
605
  ### 8.5 Backward rules: indexed by head predicate
564
606
 
565
607
  Backward rules are indexed in `backRules.__byHeadPred`. When proving a goal with IRI predicate `p`, Eyeling retrieves:
@@ -705,7 +747,7 @@ Forward chaining runs inside an _outer loop_ that alternates:
705
747
 
706
748
  This produces deterministic behavior for scoped operations: they observe a stable snapshot, not a moving target.
707
749
 
708
- **Implementation note (performance):** the two-phase scheme is only needed when the program actually uses scoped built-ins. If no rule contains `log:collectAllIn`, `log:forAllIn`, `log:includes`, or `log:notIncludes`, Eyeling now **skips Phase B entirely** and runs only a single saturation. This avoids re-running the forward fixpoint and can prevent a “query-like” forward rule (one whose body contains an expensive backward proof search) from being executed twice.
750
+ **Implementation note (performance):** the two-phase scheme is only needed when the program actually uses scoped built-ins. If no rule contains `log:collectAllIn`, `log:forAllIn`, `log:includes`, or `log:notIncludes`, Eyeling **skips Phase B entirely** and runs only a single saturation. This avoids re-running the forward fixpoint and can prevent a “query-like” forward rule (one whose body contains an expensive backward proof search) from being executed twice.
709
751
 
710
752
  **Implementation note (performance):** in Phase A there is no snapshot, so scoped built-ins (and priority-gated scoped queries) are guaranteed to “delay” by failing.
711
753
  Instead of proving the entire forward-rule body only to fail at the end, Eyeling precomputes whether a forward rule depends on scoped built-ins and skips it until a snapshot exists and the requested closure level is reached. This can avoid very expensive proof searches in programs that combine recursion with `log:*In` built-ins.
@@ -1378,6 +1420,8 @@ Each enumerated rule is standardized apart (fresh variable names) before unifica
1378
1420
 
1379
1421
  This is “forward-rule-like” in spirit (premise ⇒ conclusion), but the instantiated conclusion triples are **not added back into the fact store**; they are just what Eyeling prints.
1380
1422
 
1423
+ **Implementation note (performance):** repeated top-level `log:query` directives with the **same premise formula** are a good fit for Eyeling’s minimal completed-goal tabling (§8.4.1). The first query still performs the full backward proof; later identical premises can reuse the completed answer set as long as the saturated closure and scoped-query context are unchanged.
1424
+
1381
1425
  **Important details:**
1382
1426
 
1383
1427
  - Only **top-level** `{...} log:query {...}.` directives are recognized. Inside quoted formulas (or inside rule bodies/heads) it is just an ordinary triple.
@@ -2160,3 +2204,347 @@ A simple structure that keeps the LLM honest:
2160
2204
  - “If something is unknown, emit a placeholder fact (`:needsFact`) rather than guessing.”
2161
2205
 
2162
2206
  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.
2207
+
2208
+ ---
2209
+
2210
+ <a id="app-e"></a>
2211
+
2212
+ ## Appendix E — How Eyeling reaches 100% on `notation3tests`
2213
+
2214
+ ### E.1 The goal
2215
+
2216
+ Eyeling does not treat [notation3tests](https://codeberg.org/phochste/notation3tests/) as a side check.
2217
+
2218
+ It treats the suite as an **external semantic contract**.
2219
+
2220
+ That means:
2221
+
2222
+ - the target is public
2223
+ - the target is reproducible
2224
+ - the target is outside the local codebase
2225
+ - success means interoperability, not self-consistency
2226
+
2227
+ ---
2228
+
2229
+ ### E.2 The test loop
2230
+
2231
+ The workflow is simple and strict:
2232
+
2233
+ - clone the external [notation3tests](https://codeberg.org/phochste/notation3tests/) suite
2234
+ - package the current Eyeling tree
2235
+ - install that package into the suite
2236
+ - run the suite’s Eyeling target
2237
+ - fix semantics, not cosmetics
2238
+
2239
+ This keeps the suite honest and keeps Eyeling honest.
2240
+
2241
+ ---
2242
+
2243
+ ### E.3 The prompt packet
2244
+
2245
+ A typical conformance-fix prompt is not open-ended.
2246
+
2247
+ It usually includes a small, repeatable packet:
2248
+
2249
+ - the Eyeling source as an attached zip `https://github.com/eyereasoner/eyeling/archive/refs/heads/main.zip`
2250
+ - pointers to the failing tests
2251
+ - the exact failing output, or the exact command needed to reproduce it
2252
+ - a pointer to the N3 spec `https://w3c.github.io/N3/spec/`
2253
+ - a pointer to the builtin definitions `https://w3c.github.io/N3/spec/builtins.html`
2254
+ - a direct request to fix the issue in the engine
2255
+ - a direct request to update `HANDBOOK.md`
2256
+
2257
+ The request is usually phrased in a narrow way:
2258
+
2259
+ - fix this specific failing conformance case
2260
+ - preserve existing passing behavior
2261
+ - make the smallest coherent patch
2262
+ - add or update a regression test if needed
2263
+ - update the handbook so the semantic rule is documented, not just implemented
2264
+ - do not stop at making the test green; align the implementation with the spec and explain the semantic reason in `HANDBOOK.md`
2265
+
2266
+ The model is not asked to “improve the reasoner” in general.
2267
+
2268
+ It is asked to repair one semantic gap against: the code, the failing test, the spec, and the handbook.
2269
+
2270
+ ---
2271
+
2272
+ ### E.4 The core idea
2273
+
2274
+ Eyeling reaches 100% by making the engine match the semantics that the suite exercises.
2275
+
2276
+ That means getting these right:
2277
+
2278
+ - N3 syntax
2279
+ - rule forms
2280
+ - quoted formulas
2281
+ - variable and blank-node behavior
2282
+ - builtin relations
2283
+ - closure and duplicate control
2284
+
2285
+ The result is not “test gaming.”
2286
+
2287
+ The result is semantic alignment.
2288
+
2289
+ ---
2290
+
2291
+ ### E.5 One rule core, many surfaces
2292
+
2293
+ The suite uses different surface forms for the same logical ideas.
2294
+
2295
+ Eyeling accepts and normalizes them into one internal rule model:
2296
+
2297
+ - `{ P } => { C } .`
2298
+ - `{ H } <= { B } .`
2299
+ - top-level `log:implies`
2300
+ - top-level `log:impliedBy`
2301
+
2302
+ That matters because conformance depends on recognizing equivalence across syntax, not just parsing one preferred style.
2303
+
2304
+ ---
2305
+
2306
+ ### E.6 Normalize first, reason second
2307
+
2308
+ A large share of conformance work happens **before** execution.
2309
+
2310
+ Eyeling normalizes the tricky parts early:
2311
+
2312
+ - body blanks become variables
2313
+ - head blanks stay existential
2314
+ - RDF collection encodings become list terms
2315
+ - rule syntax variants become one rule representation
2316
+
2317
+ This removes ambiguity before the engine starts proving anything.
2318
+
2319
+ ---
2320
+
2321
+ ### E.7 Body blanks vs. head blanks
2322
+
2323
+ This is one of the decisive details.
2324
+
2325
+ In Eyeling:
2326
+
2327
+ - blanks in rule bodies act like placeholders
2328
+ - blanks in rule heads act like fresh existentials
2329
+
2330
+ That split is essential.
2331
+
2332
+ Without it:
2333
+
2334
+ - rule matching goes wrong
2335
+ - proofs become unstable
2336
+ - existential output becomes noisy
2337
+ - conformance drops
2338
+
2339
+ ---
2340
+
2341
+ ### E.8 Builtins must behave like relations
2342
+
2343
+ Eyeling does not treat builtins as one-way helper functions.
2344
+
2345
+ It treats them as **relations inside proof search**.
2346
+
2347
+ That means a builtin can:
2348
+
2349
+ - succeed
2350
+ - fail
2351
+ - bind variables
2352
+ - stay satisfiable without yet binding anything
2353
+
2354
+ This is critical for the suite, because many builtin cases are really tests of search behavior, not just value computation.
2355
+
2356
+ ---
2357
+
2358
+ ### E.9 Delay builtins when needed
2359
+
2360
+ Some builtins only become useful after neighboring goals bind enough variables.
2361
+
2362
+ Eyeling handles that by deferring non-informative builtins inside conjunctions.
2363
+
2364
+ So instead of failing too early, the engine:
2365
+
2366
+ - rotates the builtin later
2367
+ - keeps proving the remaining goals
2368
+ - retries once more information exists
2369
+
2370
+ This preserves logical behavior while staying operationally efficient.
2371
+
2372
+ ---
2373
+
2374
+ ### E.10 Formulas are first-class terms
2375
+
2376
+ Quoted formulas are not treated as strings.
2377
+
2378
+ They are treated as structured logical objects.
2379
+
2380
+ That gives Eyeling the machinery it needs for:
2381
+
2382
+ - formula matching
2383
+ - nested reasoning
2384
+ - `log:includes`
2385
+ - `log:conclusion`
2386
+ - formula comparison by alpha-equivalence
2387
+
2388
+ This is a major reason the higher-level N3 tests pass cleanly.
2389
+
2390
+ ---
2391
+
2392
+ ### E.11 Alpha-equivalence matters
2393
+
2394
+ Two formulas that differ only in internal names must still count as the same formula when their structure matches.
2395
+
2396
+ Eyeling therefore compares formulas by structure, not by accidental naming.
2397
+
2398
+ That removes a common source of false mismatches in:
2399
+
2400
+ - quoted formulas
2401
+ - nested graphs
2402
+ - rule introspection
2403
+ - scoped reasoning
2404
+
2405
+ ---
2406
+
2407
+ ### E.12 Lists must have one meaning
2408
+
2409
+ The suite exercises list behavior in more than one spelling.
2410
+
2411
+ Eyeling unifies them:
2412
+
2413
+ - concrete N3 lists
2414
+ - RDF `first/rest` collection encodings
2415
+
2416
+ By materializing anonymous RDF collections into list terms, Eyeling gives both forms one semantic path through the engine.
2417
+
2418
+ That keeps list reasoning consistent across the whole suite.
2419
+
2420
+ ---
2421
+
2422
+ ### E.13 Existentials must be stable
2423
+
2424
+ A rule head with blanks must not generate endless fresh variants of the same logical result.
2425
+
2426
+ Eyeling stabilizes this by skolemizing head blanks per firing instance.
2427
+
2428
+ So one logical firing yields:
2429
+
2430
+ - one stable witness
2431
+ - one stable derived shape
2432
+ - one meaningful duplicate check
2433
+
2434
+ This is what lets closure reach a real fixpoint.
2435
+
2436
+ ---
2437
+
2438
+ ### E.14 Duplicate suppression is semantic, not cosmetic
2439
+
2440
+ The engine does not merely try to avoid repeated printing.
2441
+
2442
+ It tries to avoid repeated derivation of the same fact.
2443
+
2444
+ That requires:
2445
+
2446
+ - stable term ids
2447
+ - indexed fact storage
2448
+ - reliable duplicate keys
2449
+ - stable existential handling
2450
+
2451
+ Without that, a reasoner can look busy forever and still fail conformance.
2452
+
2453
+ ---
2454
+
2455
+ ### E.15 Closure must really close
2456
+
2457
+ Full conformance depends on real saturation behavior.
2458
+
2459
+ Eyeling therefore treats closure as:
2460
+
2461
+ - repeated rule firing
2462
+ - repeated proof over indexed facts
2463
+ - duplicate-aware insertion
2464
+ - termination at fixpoint
2465
+
2466
+ This is what turns the engine from a parser plus demos into a conformance-grade reasoner.
2467
+
2468
+ ---
2469
+
2470
+ ### E.16 Performance choices support correctness
2471
+
2472
+ Several implementation choices are operational, but they directly protect conformance:
2473
+
2474
+ - predicate-based indexing
2475
+ - subject/object refinement
2476
+ - smallest-bucket candidate selection
2477
+ - fast duplicate keys
2478
+ - skipping already-known ground heads
2479
+
2480
+ These choices reduce accidental nontermination and prevent operational noise from becoming semantic failure.
2481
+
2482
+ ---
2483
+
2484
+ ### E.17 The suite stays external
2485
+
2486
+ This is a key discipline.
2487
+
2488
+ Eyeling does not define success by a private in-repo imitation of [notation3tests](https://codeberg.org/phochste/notation3tests/).
2489
+
2490
+ It runs against the external suite.
2491
+
2492
+ That means:
2493
+
2494
+ - the compliance test suite is shared
2495
+ - the contract is public
2496
+ - the result is independently meaningful
2497
+
2498
+ A green run says something real.
2499
+
2500
+ ---
2501
+
2502
+ ### E.18 Every failure becomes an invariant
2503
+
2504
+ Eyeling reaches 100% because failures are not patched superficially.
2505
+
2506
+ Each failure is turned into an engine rule.
2507
+
2508
+ Examples:
2509
+
2510
+ - parser failure → broader syntax support
2511
+ - list failure → one unified list model
2512
+ - formula failure → alpha-equivalence discipline
2513
+ - builtin failure → relational evaluation
2514
+ - closure failure → stable existential handling
2515
+
2516
+ That is how the suite shapes the engine.
2517
+
2518
+ ---
2519
+
2520
+ ### E.19 Why 100% happens
2521
+
2522
+ Eyeling gets to 100% because all the key layers line up:
2523
+
2524
+ - the parser accepts the full rule surface
2525
+ - normalization removes semantic ambiguity
2526
+ - formulas are real terms
2527
+ - builtins participate in proof search
2528
+ - existential output is stable
2529
+ - closure reaches a true fixpoint
2530
+ - the public suite remains the judge
2531
+
2532
+ Once those pieces are in place, 100% is the visible result of a coherent design.
2533
+
2534
+ ---
2535
+
2536
+ ### E.20 Final takeaway
2537
+
2538
+ Eyeling reaches full [notation3tests](https://codeberg.org/phochste/notation3tests/) conformance by making “pass the suite” and “implement N3 correctly enough to interoperate” the same task.
2539
+
2540
+ That is the method:
2541
+
2542
+ - external suite
2543
+ - one semantic core
2544
+ - early normalization
2545
+ - relational builtins
2546
+ - formula-aware reasoning
2547
+ - stable existential output
2548
+ - duplicate-safe fixpoint closure
2549
+
2550
+ That is why the result is 100%.
@@ -0,0 +1,193 @@
1
+ # ====================================================================================
2
+ # Floating-point-first EMA tracking envelope
3
+ #
4
+ # Why this example exists:
5
+ # It is another floating-point-first certificate in the same family as the servo,
6
+ # RC-discharge, and thermal-cooling examples, but now phrased as an exponential
7
+ # moving average (EMA) tracker.
8
+ #
9
+ # Physical story:
10
+ # Let e(k) be the absolute tracking error of an EMA estimator relative to a constant
11
+ # target. Then
12
+ #
13
+ # e(k+1) = a * e(k)
14
+ #
15
+ # where a is the complement of the EMA gain. In this example we choose
16
+ #
17
+ # a = exp(-Ts/tau) = exp(-1/6)
18
+ #
19
+ # with Ts = 0.5 s and tau = 3 s.
20
+ #
21
+ # Since exp(-1/6) is transcendental, we certify it by a double interval:
22
+ #
23
+ # 8.464817248e-1 <= exp(-1/6) <= 8.464817249e-1
24
+ #
25
+ # and propagate a floating-point envelope for the tracking error.
26
+ #
27
+ # What is certified:
28
+ # * the double interval is nonempty and strictly below 1
29
+ # * the tracking-error envelope shrinks at every sample
30
+ # * from an initial error of 8.0e0, the estimator is guaranteed within 2.5e-1
31
+ # by sample 21
32
+ # * with Ts = 5.0e-1 s, that means guaranteed settling by 1.05e1 s
33
+ # ====================================================================================
34
+
35
+ @prefix : <http://example.org/floating-ema#>.
36
+ @prefix math: <http://www.w3.org/2000/10/swap/math#>.
37
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
38
+
39
+ # ----------
40
+ # Parameters
41
+ # ----------
42
+
43
+ :ema a :SampledEMAEnvelope;
44
+ :samplePeriod 5.0e-1;
45
+ :timeConstant 3.0e0;
46
+ :exactDecaySymbol "exp(-1/6)";
47
+ :decayLower 8.464817248e-1;
48
+ :decayUpper 8.464817249e-1;
49
+ :initialAbsTrackingError 8.0e0;
50
+ :tolerance 2.5e-1;
51
+ :maxStep 24.
52
+
53
+ # ----------------------------------------------------------------
54
+ # The double interval is a finite certificate for a transcendental
55
+ # ----------------------------------------------------------------
56
+
57
+ {
58
+ :ema :decayLower ?lo.
59
+ :ema :decayUpper ?hi.
60
+ ?lo math:lessThan ?hi.
61
+ ?hi math:lessThan 1.0e0.
62
+ ?lo math:greaterThan 0.0e0.
63
+ }
64
+ =>
65
+ {
66
+ :ema :decayCertificate :CertifiedDoubleInterval.
67
+ :ema :contractiveDecay true.
68
+ }.
69
+
70
+ # ----------------
71
+ # Initial envelope
72
+ # ----------------
73
+
74
+ {
75
+ :ema :initialAbsTrackingError ?e0.
76
+ }
77
+ =>
78
+ {
79
+ :ema :trackingErrorEnvelopeAt (0 ?e0 ?e0).
80
+ }.
81
+
82
+ # -------------------------------------
83
+ # Envelope propagation
84
+ # lower(k+1) = decayLower * lower(k)
85
+ # upper(k+1) = decayUpper * upper(k)
86
+ # -------------------------------------
87
+
88
+ {
89
+ :ema :maxStep ?max;
90
+ :decayLower ?aLo;
91
+ :decayUpper ?aHi.
92
+ :ema :trackingErrorEnvelopeAt (?k ?lo ?hi).
93
+ ?k math:lessThan ?max.
94
+ ( ?k 1 ) math:sum ?k1.
95
+ ( ?aLo ?lo ) math:product ?lo1.
96
+ ( ?aHi ?hi ) math:product ?hi1.
97
+ }
98
+ =>
99
+ {
100
+ :ema :trackingErrorEnvelopeAt (?k1 ?lo1 ?hi1).
101
+ }.
102
+
103
+ # -----------------------------
104
+ # The envelope shrinks strictly
105
+ # -----------------------------
106
+
107
+ {
108
+ :ema :trackingErrorEnvelopeAt (?k ?lo ?hi).
109
+ ( ?k 1 ) math:sum ?k1.
110
+ :ema :trackingErrorEnvelopeAt (?k1 ?lo1 ?hi1).
111
+ ?hi1 math:lessThan ?hi.
112
+ }
113
+ =>
114
+ {
115
+ :ema :strictContractionAt ?k.
116
+ }.
117
+
118
+ # --------------------------------
119
+ # Settlement in the tolerance band
120
+ # --------------------------------
121
+
122
+ {
123
+ :ema :trackingErrorEnvelopeAt (?k ?lo ?hi).
124
+ :ema :tolerance ?tol.
125
+ ?hi math:lessThan ?tol.
126
+ }
127
+ =>
128
+ {
129
+ :ema :settledCandidate ?k.
130
+ }.
131
+
132
+ {
133
+ :ema :settledCandidate ?k.
134
+ ?k math:greaterThan 0.
135
+ ( ?k 1 ) math:difference ?km1.
136
+ :ema :trackingErrorEnvelopeAt (?km1 ?prevLo ?prevHi).
137
+ :ema :tolerance ?tol.
138
+ ?prevHi math:notLessThan ?tol.
139
+ }
140
+ =>
141
+ {
142
+ :ema :firstSettledStep ?k.
143
+ }.
144
+
145
+ {
146
+ :ema :firstSettledStep ?k.
147
+ :ema :samplePeriod ?ts.
148
+ ( ?k ?ts ) math:product ?t.
149
+ }
150
+ =>
151
+ {
152
+ :ema :firstSettledTime ?t.
153
+ }.
154
+
155
+ # -----------------------------------------------------------
156
+ # Readable engineering summary for the floating-point witness
157
+ # -----------------------------------------------------------
158
+
159
+ {
160
+ :ema :firstSettledStep ?k;
161
+ :firstSettledTime ?t;
162
+ :tolerance ?tol;
163
+ :exactDecaySymbol ?sym;
164
+ :decayLower ?aLo;
165
+ :decayUpper ?aHi.
166
+ }
167
+ log:query
168
+ {
169
+ :result :summary (
170
+ "exact-decay" ?sym
171
+ "double-lower" ?aLo
172
+ "double-upper" ?aHi
173
+ "tolerance" ?tol
174
+ "first-settled-step" ?k
175
+ "first-settled-time-s" ?t
176
+ ).
177
+ }.
178
+
179
+ {
180
+ :ema :trackingErrorEnvelopeAt (?k ?lo ?hi).
181
+ }
182
+ log:query
183
+ {
184
+ :result :envelope (?k ?lo ?hi).
185
+ }.
186
+
187
+ {
188
+ :ema :strictContractionAt ?k.
189
+ }
190
+ log:query
191
+ {
192
+ :result :contractionAt ?k.
193
+ }.