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 +18 -13
- package/examples/bayes-therapy.n3 +264 -0
- package/examples/collatz.n3 +117 -0
- package/examples/digital-product-passport.n3 +316 -0
- package/examples/godel-incompleteness.n3 +107 -0
- package/examples/goldbach.n3 +117 -0
- package/examples/output/bayes-therapy.n3 +26 -0
- package/examples/output/collatz.n3 +1002 -0
- package/examples/output/digital-product-passport.n3 +10 -0
- package/examples/output/godel-incompleteness.n3 +9 -0
- package/examples/output/goldbach.n3 +669 -0
- package/examples/output/takeuchi.n3 +107 -0
- package/examples/output/void.n3 +11 -0
- package/examples/takeuchi.n3 +82 -0
- package/examples/void.n3 +160 -0
- package/eyeling-builtins.ttl +3 -3
- package/eyeling.js +149 -13
- package/lib/builtins.js +61 -6
- package/lib/engine.js +56 -6
- package/lib/parser.js +32 -1
- package/package.json +1 -1
- package/test/examples.test.js +4 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1977
|
+
In practice, that setting is reflected in several common features of N3-style rule writing:
|
|
1973
1978
|
|
|
1974
|
-
- **
|
|
1975
|
-
- **
|
|
1976
|
-
- **
|
|
1977
|
-
- **Rules can be
|
|
1978
|
-
- **
|
|
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
|
-
|
|
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
|
+
|