eyeling 1.12.6 → 1.12.8

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
@@ -893,6 +893,12 @@ These are “function-like” relations where the subject is usually a list and
893
893
  - Computes the numeric sum.
894
894
  - Chooses an output datatype based on the “widest” numeric datatype seen among inputs and (optionally) the object position; integers stay integers unless the result is non-integer.
895
895
 
896
+ Eyeling also supports a small, EYE-style convenience for timestamp arithmetic:
897
+
898
+ - **DateTime plus duration/seconds**: `(dateTime durationOrSeconds) math:sum dateTime`
899
+ - `xsd:duration` is interpreted as seconds (same model as `math:difference`).
900
+ - Output is a normalized `xsd:dateTime` in UTC lexical form (`...Z`).
901
+
896
902
  #### `math:product`
897
903
 
898
904
  **Shape:** `( $x1 $x2 ... ) math:product $total`
@@ -911,9 +917,10 @@ Eyeling supports:
911
917
 
912
918
  1. **Numeric subtraction**: `c = a - b`.
913
919
  2. **DateTime difference**: `(dateTime1 dateTime2) math:difference duration`
914
- - Produces an `xsd:duration` in whole days (internally computed via seconds then formatted).
920
+ - Produces an **`xsd:duration`** in a seconds-only lexical form such as `"PT900S"^^xsd:duration`.
921
+ - This avoids ambiguity around month/year day-length and still plays well with `math:lessThan`, `math:greaterThan`, etc. because Eyeling's numeric comparison builtins treat `xsd:duration` as seconds.
915
922
 
916
- 3. **DateTime minus duration**: `(dateTime duration) math:difference dateTime`
923
+ 3. **DateTime minus duration**: `(dateTime durationOrSeconds) math:difference dateTime`
917
924
  - Subtracts a duration from a dateTime and yields a new dateTime.
918
925
 
919
926
  If the types don’t fit any supported case, the builtin fails.
@@ -1963,21 +1970,19 @@ In that sense, N3 is less a bid to make the web “smarter” than a bid to make
1963
1970
 
1964
1971
  <a id="app-c"></a>
1965
1972
 
1966
- ## Appendix C — N3 beyond Prolog: logic that survives the open web
1967
-
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.
1973
+ ## Appendix C — N3 beyond Prolog: logic for RDF-style graphs
1969
1974
 
1970
- That change of environment forces a change in whatbeyond 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.
1975
+ Notation3 (N3) rule sets often look similar to Prolog at the surface: they use variables, unification, and implication-style rules (if these patterns match, then these patterns follow”). N3 is typically used in a different setting: instead of a single program operating over a single local database, N3 rules and data are commonly written as documents that can be published, shared, merged, and referenced across systems.
1971
1976
 
1972
- Several design moves push N3 into that web-native space:
1977
+ In practice, that setting is reflected in several common features of N3-style rule writing:
1973
1978
 
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.
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.
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.
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.
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.
1979
+ - **Identifiers are IRIs.** Terms are usually global identifiers rather than local symbols, which supports linking across datasets.
1980
+ - **Input and output are graphs.** Rules consume graph patterns and produce additional triples, so the result of inference can be represented in the same form as the input data.
1981
+ - **Quoted graphs allow statements-as-data.** N3 can treat a graph (a set of triples) as a term, which makes it possible to represent and reason about assertions (e.g., “this source says …” or “this formula implies …”) as data.
1982
+ - **Rules can be distributed as text artifacts.** Rules can live alongside data, be versioned, and be reused without requiring an external host language to “carry” the meaning.
1983
+ - **Built-ins cover common computations.** Many N3 workflows rely on built-ins for operations such as string handling, list processing, comparisons, and related utilities; some workflows also use IRIs as pointers to retrievable content.
1979
1984
 
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.
1985
+ Engines can combine execution styles in different ways. One common pattern is to use a Prolog-like backward-chaining prover to satisfy rule bodies, while still using forward chaining to add the instantiated conclusions to the fact set until no new facts are produced.
1981
1986
 
1982
1987
  ---
1983
1988
 
@@ -0,0 +1,264 @@
1
+ # ============================================================================
2
+ # Bayes Therapy Decision Support
3
+ #
4
+ # This example extends a tiny Bayesian diagnostic model (Disease -> Symptoms)
5
+ # with a decision layer that scores therapies by expected utility.
6
+ #
7
+ # NOTE: All probabilities and weights are illustrative 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: Disease -> Symptoms (Naive Bayes)
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
+ :COVID19 :pGiven [ :symptom :Fever; :p 0.70 ] .
25
+ :COVID19 :pGiven [ :symptom :DryCough; :p 0.65 ] .
26
+ :COVID19 :pGiven [ :symptom :LossOfSmell; :p 0.40 ] .
27
+ :COVID19 :pGiven [ :symptom :Sneezing; :p 0.15 ] .
28
+ :COVID19 :pGiven [ :symptom :ShortBreath; :p 0.20 ] .
29
+
30
+ :Influenza :pGiven [ :symptom :Fever; :p 0.80 ] .
31
+ :Influenza :pGiven [ :symptom :DryCough; :p 0.50 ] .
32
+ :Influenza :pGiven [ :symptom :LossOfSmell; :p 0.05 ] .
33
+ :Influenza :pGiven [ :symptom :Sneezing; :p 0.20 ] .
34
+ :Influenza :pGiven [ :symptom :ShortBreath; :p 0.10 ] .
35
+
36
+ :AllergicRhinitis :pGiven [ :symptom :Fever; :p 0.05 ] .
37
+ :AllergicRhinitis :pGiven [ :symptom :DryCough; :p 0.15 ] .
38
+ :AllergicRhinitis :pGiven [ :symptom :LossOfSmell; :p 0.10 ] .
39
+ :AllergicRhinitis :pGiven [ :symptom :Sneezing; :p 0.80 ] .
40
+ :AllergicRhinitis :pGiven [ :symptom :ShortBreath; :p 0.05 ] .
41
+
42
+ :BacterialPneumonia :pGiven [ :symptom :Fever; :p 0.70 ] .
43
+ :BacterialPneumonia :pGiven [ :symptom :DryCough; :p 0.60 ] .
44
+ :BacterialPneumonia :pGiven [ :symptom :LossOfSmell; :p 0.02 ] .
45
+ :BacterialPneumonia :pGiven [ :symptom :Sneezing; :p 0.05 ] .
46
+ :BacterialPneumonia :pGiven [ :symptom :ShortBreath; :p 0.60 ] .
47
+
48
+ # ----------------------------------------------------------------------
49
+ # 2) THERAPY MODEL: P(improve | disease, therapy) + P(adverse | therapy)
50
+ # ----------------------------------------------------------------------
51
+
52
+ :Paxlovid a :Therapy;
53
+ # aligned with :Case :diseases order
54
+ :successByDisease ( 0.75 0.05 0.02 0.05 );
55
+ :adverse 0.10 .
56
+
57
+ :Oseltamivir a :Therapy;
58
+ :successByDisease ( 0.05 0.60 0.02 0.05 );
59
+ :adverse 0.08 .
60
+
61
+ :Antihistamine a :Therapy;
62
+ :successByDisease ( 0.10 0.10 0.75 0.05 );
63
+ :adverse 0.03 .
64
+
65
+ :Antibiotic a :Therapy;
66
+ :successByDisease ( 0.05 0.05 0.02 0.80 );
67
+ :adverse 0.07 .
68
+
69
+ :SupportiveCare a :Therapy;
70
+ :successByDisease ( 0.30 0.30 0.25 0.20 );
71
+ :adverse 0.01 .
72
+
73
+ :Model :benefitWeight 10; :harmWeight 3 .
74
+
75
+ # -------------------------------------------
76
+ # 3) CASE: evidence (symptoms present/absent)
77
+ # -------------------------------------------
78
+
79
+ :Case a :PatientCase;
80
+ :diseases ( :COVID19 :Influenza :AllergicRhinitis :BacterialPneumonia );
81
+ :therapies ( :Paxlovid :Oseltamivir :SupportiveCare :Antibiotic :Antihistamine );
82
+ :evidence (
83
+ [ :symptom :Fever; :present true ]
84
+ [ :symptom :DryCough; :present true ]
85
+ [ :symptom :LossOfSmell; :present false ]
86
+ [ :symptom :Sneezing; :present false ]
87
+ [ :symptom :ShortBreath; :present false ]
88
+ ).
89
+
90
+ # -----------------------------------------------------------
91
+ # 4) GUARDS (inference fuses): probabilities must be in [0,1]
92
+ # -----------------------------------------------------------
93
+
94
+ { ?d :prior ?p. ?p math:lessThan 0. } => false.
95
+ { ?d :prior ?p. ?p math:greaterThan 1. } => false.
96
+
97
+ { ?d :pGiven [ :p ?p ]. ?p math:lessThan 0. } => false.
98
+ { ?d :pGiven [ :p ?p ]. ?p math:greaterThan 1. } => false.
99
+
100
+ { ?t a :Therapy. ?t :adverse ?p. ?p math:lessThan 0. } => false.
101
+ { ?t a :Therapy. ?t :adverse ?p. ?p math:greaterThan 1. } => false.
102
+
103
+ { ?t a :Therapy. ?t :successByDisease ?ps.
104
+ ?ps list:iterate ( ?i ?p ).
105
+ ?p math:lessThan 0.
106
+ } => false.
107
+
108
+ { ?t a :Therapy. ?t :successByDisease ?ps.
109
+ ?ps list:iterate ( ?i ?p ).
110
+ ?p math:greaterThan 1.
111
+ } => false.
112
+
113
+ # ------------------------------------------
114
+ # 5) HELPERS (all written as backward rules)
115
+ # ------------------------------------------
116
+
117
+ # pairList(d, (x1 x2 ...)) -> ((d x1) (d x2) ...)
118
+ { ( ?d () ) :pairList () } <= true.
119
+
120
+ { ( ?d ?xs ) :pairList ?pairs } <= {
121
+ ?xs list:firstRest ( ?x ?rest ).
122
+ ( ?d ?rest ) :pairList ?tailPairs.
123
+ ?pairs list:firstRest ( ( ?d ?x ) ?tailPairs ).
124
+ }.
125
+
126
+ # factor(d, evidenceItem) -> probability factor for that symptom
127
+ { ( ?d ?ev ) :factor ?p } <= {
128
+ ?ev :symptom ?s.
129
+ ?ev :present true.
130
+ ?d :pGiven [ :symptom ?s; :p ?p ].
131
+ }.
132
+
133
+ { ( ?d ?ev ) :factor ?q } <= {
134
+ ?ev :symptom ?s.
135
+ ?ev :present false.
136
+ ?d :pGiven [ :symptom ?s; :p ?p ].
137
+ ( 1 ?p ) math:difference ?q.
138
+ }.
139
+
140
+ # pairWithConst((x1 x2 ...), c) -> ((x1 c) (x2 c) ...)
141
+ { ( () ?c ) :pairWithConst () } <= true.
142
+
143
+ { ( ?xs ?c ) :pairWithConst ?pairs } <= {
144
+ ?xs list:firstRest ( ?x ?rest ).
145
+ ( ?rest ?c ) :pairWithConst ?tailPairs.
146
+ ?pairs list:firstRest ( ( ?x ?c ) ?tailPairs ).
147
+ }.
148
+
149
+ # zip((a1 a2 ...), (b1 b2 ...)) -> ((a1 b1) (a2 b2) ...)
150
+ { ( () () ) :zip () } <= true.
151
+
152
+ { ( ?as ?bs ) :zip ?pairs } <= {
153
+ ?as list:firstRest ( ?a ?arest ).
154
+ ?bs list:firstRest ( ?b ?brest ).
155
+ ( ?arest ?brest ) :zip ?tailPairs.
156
+ ?pairs list:firstRest ( ( ?a ?b ) ?tailPairs ).
157
+ }.
158
+
159
+ # Helpers for list:map
160
+ { ?pair :mul ?p } <= { ?pair math:product ?p }.
161
+ { ?pair :quot2 ?q } <= { ?pair math:quotient ?q }.
162
+
163
+ # ---------------------------------------------------------
164
+ # 6) DIAGNOSIS: unnormalized scores + normalized posteriors
165
+ # ---------------------------------------------------------
166
+
167
+ # score(d) = prior(d) * Π_e factor(d,e)
168
+ { ?d :scoreFor ?score } <= {
169
+ ?d :prior ?prior.
170
+ :Case :evidence ?evs.
171
+ ( ?d ?evs ) :pairList ?pairs.
172
+ ( ?pairs :factor ) list:map ?factors.
173
+ ?factors math:product ?likelihood.
174
+ ( ?prior ?likelihood ) math:product ?score.
175
+ }.
176
+
177
+ # Compute the score list, total evidence, and posterior list once.
178
+ {
179
+ :Case :diseases ?ds.
180
+ ( ?ds :scoreFor ) list:map ?scores.
181
+ ?scores math:sum ?total.
182
+
183
+ ( ?scores ?total ) :pairWithConst ?scorePairs.
184
+ ( ?scorePairs :quot2 ) list:map ?posteriors.
185
+ } => {
186
+ :Case :scores ?scores;
187
+ :evidenceTotal ?total;
188
+ :posteriors ?posteriors.
189
+ }.
190
+
191
+ # Attach each disease posterior to the disease term (no blank nodes in output)
192
+ {
193
+ :Case :diseases ?ds.
194
+ :Case :posteriors ?posts.
195
+
196
+ ?ds list:iterate ( ?i ?d ).
197
+ ( ?posts ?i ) list:memberAt ?p.
198
+ } => {
199
+ ?d :posterior ?p.
200
+ }.
201
+
202
+ # -------------------------------------------------------
203
+ # 7) THERAPY SCORING: expected success + expected utility
204
+ # -------------------------------------------------------
205
+
206
+ # expectedSuccess(t) = Σ_i post[i] * successByDisease[i]
207
+ {
208
+ :Case :posteriors ?posts.
209
+ ?t a :Therapy.
210
+ ?t :successByDisease ?succ.
211
+
212
+ ( ?posts ?succ ) :zip ?pairs.
213
+ ( ?pairs :mul ) list:map ?terms.
214
+ ?terms math:sum ?expectedSuccess.
215
+
216
+ ?t :adverse ?adverse.
217
+ :Model :benefitWeight ?bw.
218
+ :Model :harmWeight ?hw.
219
+
220
+ ( ?bw ?expectedSuccess ) math:product ?benefit.
221
+ ( ?hw ?adverse ) math:product ?harmCost.
222
+ ( ?benefit ?harmCost ) math:difference ?utility.
223
+ } => {
224
+ ?t :expectedSuccess ?expectedSuccess;
225
+ :expectedAdverse ?adverse;
226
+ :utility ?utility.
227
+ }.
228
+
229
+ # --------------------------------------------------------------
230
+ # 8) RECOMMENDATION: pick max-utility therapy from the case list
231
+ # --------------------------------------------------------------
232
+
233
+ # betterOf(t1,t2) chooses t1 if utility(t1) >= utility(t2), else t2.
234
+ { ( ?t1 ?t2 ) :betterOf ?t1 } <= {
235
+ ?t1 :utility ?u1.
236
+ ?t2 :utility ?u2.
237
+ ?u1 math:notLessThan ?u2.
238
+ }.
239
+ { ( ?t1 ?t2 ) :betterOf ?t2 } <= {
240
+ ?t1 :utility ?u1.
241
+ ?t2 :utility ?u2.
242
+ ?u1 math:lessThan ?u2.
243
+ }.
244
+
245
+ # bestTherapy((t)) = t
246
+ { ( ?ts ) :bestTherapy ?t } <= {
247
+ ?ts list:firstRest ( ?t () ).
248
+ }.
249
+
250
+ # bestTherapy((head rest...)) = betterOf(head, bestTherapy(rest))
251
+ { ( ?ts ) :bestTherapy ?best } <= {
252
+ ?ts list:firstRest ( ?head ?rest ).
253
+ ?rest list:firstRest ( ?_ ?__ ). # ensure rest is non-empty
254
+ ( ?rest ) :bestTherapy ?bestRest.
255
+ ( ?head ?bestRest ) :betterOf ?best.
256
+ }.
257
+
258
+ # Emit a single recommendation for this case.
259
+ {
260
+ :Case :therapies ?ts.
261
+ ( ?ts ) :bestTherapy ?best.
262
+ } => {
263
+ :Case :recommendedTherapy ?best.
264
+ }.
@@ -0,0 +1,117 @@
1
+ # ==================
2
+ # Collatz conjecture
3
+ # ==================
4
+ #
5
+ # Collatz iteration:
6
+ # - if n is even: n ↦ n/2
7
+ # - if n is odd : n ↦ 3n + 1
8
+ #
9
+ # The conjecture states that starting from any positive integer n,
10
+ # repeated application of the rule eventually reaches 1.
11
+ #
12
+ # This N3 program:
13
+ # 1) enumerates test inputs N = 1000, 999, ..., 1 via a backward :repeat relation
14
+ # 2) computes the full Collatz trajectory for each N as an RDF list
15
+ # 3) materializes only the final results as :collatzTrajectory triples
16
+ #
17
+ # See https://en.wikipedia.org/wiki/Collatz_conjecture
18
+
19
+ @prefix : <http://example.org/collatz#> .
20
+ @prefix math: <http://www.w3.org/2000/10/swap/math#> .
21
+ @prefix list: <http://www.w3.org/2000/10/swap/list#> .
22
+
23
+ # -----------------------------------------
24
+ # Query / materialization of the test suite
25
+ # -----------------------------------------
26
+ #
27
+ # Generate N in {1000..1} and ask the backward-defined :collatz predicate
28
+ # for the full trajectory list ?M.
29
+ #
30
+ # Note: we derive N as (1000 - N0) so that N0 ranges over 0..999.
31
+ # This keeps :repeat as a generic “0..N-1” generator.
32
+ #
33
+ # Output predicate is renamed to :collatzTrajectory to keep results separate
34
+ # from the intensional (backward) definition of :collatz.
35
+ {
36
+ 1000 :repeat ?N0 .
37
+ (1000 ?N0) math:difference ?N .
38
+ ?N :collatz ?M .
39
+ }
40
+ =>
41
+ {
42
+ ?N :collatzTrajectory ?M .
43
+ } .
44
+
45
+ # ------------------------
46
+ # Backward range generator
47
+ # ------------------------
48
+ #
49
+ # ?N :repeat ?I enumerates all integers I in the half-open interval:
50
+ # I ∈ [0 .. N-1]
51
+
52
+ # Base case: repeat(1) = {0}
53
+ { ?N :repeat 0 }
54
+ <=
55
+ { ?N math:equalTo 1. } .
56
+
57
+ # For N>1, also include the last value (N-1)
58
+ { ?N :repeat ?I }
59
+ <=
60
+ {
61
+ ?N math:greaterThan 1.
62
+ (?N 1) math:difference ?I. # I = N - 1
63
+ } .
64
+
65
+ # And for N>1, inherit everything from repeat(N-1)
66
+ # (this is the recursive “rest of the range”)
67
+ { ?N :repeat ?I }
68
+ <=
69
+ {
70
+ ?N math:greaterThan 1.
71
+ (?N 1) math:difference ?N1. # N1 = N - 1
72
+ ?N1 :repeat ?I.
73
+ } .
74
+
75
+ # -------------------------
76
+ # Backward Collatz relation
77
+ # -------------------------
78
+ #
79
+ # ?N0 :collatz ?M relates a start value ?N0 to its full Collatz trajectory ?M.
80
+ # The trajectory is represented as an RDF list:
81
+ # - for N0=1: (1)
82
+ # - for N0>1: (N0 ... 1)
83
+ #
84
+ # The recursion is guarded with “N0 > 1” so the base case terminates cleanly.
85
+
86
+ # Base case: 1 terminates immediately
87
+ { ?N0 :collatz (1) }
88
+ <=
89
+ { ?N0 math:equalTo 1. } .
90
+
91
+ # Even step: N0 > 1 and N0 mod 2 = 0 => N1 = N0/2
92
+ # The resulting list is constructed as: (N0) :: collatz(N1)
93
+ { ?N0 :collatz ?M }
94
+ <=
95
+ {
96
+ ?N0 math:greaterThan 1.
97
+ (?N0 2) math:remainder 0.
98
+ (?N0 2) math:integerQuotient ?N1. # N1 = floor(N0/2) (safe because even)
99
+
100
+ ?N1 :collatz ?J.
101
+ ?M list:firstRest (?N0 ?J). # M = [N0 | J]
102
+ } .
103
+
104
+ # Odd step: N0 > 1 and N0 mod 2 = 1 => N1 = 3*N0 + 1
105
+ # Again: (N0) :: collatz(N1)
106
+ { ?N0 :collatz ?M }
107
+ <=
108
+ {
109
+ ?N0 math:greaterThan 1.
110
+ (?N0 2) math:remainder 1.
111
+ (3 ?N0) math:product ?T.
112
+ (?T 1) math:sum ?N1. # N1 = 3*N0 + 1
113
+
114
+ ?N1 :collatz ?J.
115
+ ?M list:firstRest (?N0 ?J). # M = [N0 | J]
116
+ } .
117
+