eyeling 1.14.3 → 1.14.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
@@ -1555,12 +1555,6 @@ If `i` is out of range, `out` is the original string.
1555
1555
 
1556
1556
  Returns the number of differing positions between `a` and `b`. Fails if the two strings have different lengths.
1557
1557
 
1558
- #### `string:mutateSelectBest`
1559
-
1560
- **Shape:** `( samples current target mutProb seedState ) string:mutateSelectBest ( best bestScore seedState2 )`
1561
-
1562
- Generates `samples` mutated variants of `current` (mutating each character with probability `mutProb` percent), scores each candidate against `target` (Hamming distance), and returns the best candidate (lowest score). RNG is a deterministic 31-bit LCG, threaded via `seedState` → `seedState2`, so runs are reproducible.
1563
-
1564
1558
  ### Containment and prefix/suffix tests
1565
1559
 
1566
1560
  - `string:contains`
@@ -0,0 +1,111 @@
1
+ # ========================
2
+ # Fast exponentiation demo
3
+ # ========================
4
+ #
5
+ # Shows why using math:exponentiation (BigInt-capable) matters.
6
+ # The naive :powSlow definition uses repeated multiplication and should
7
+ # only be used for small exponents.
8
+
9
+ @prefix : <http://example.org/fastpow#>.
10
+ @prefix math: <http://www.w3.org/2000/10/swap/math#>.
11
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
12
+
13
+ # ----------------------
14
+ # Fast power via builtin
15
+ # ----------------------
16
+ # pow(base, exp) = base^exp
17
+ {
18
+ (?B ?E) :pow ?R.
19
+ } <= {
20
+ (?B ?E) math:exponentiation ?R.
21
+ }.
22
+
23
+ # --------------------------------
24
+ # Slow power via repeated multiply
25
+ # --------------------------------
26
+ # powSlow(base, 0) = 1
27
+ {
28
+ (?B 0) :powSlow 1.
29
+ } <= {
30
+ 0 math:equalTo 0.
31
+ }.
32
+
33
+ # powSlow(base, E>0) = powSlow(base, E-1) * base
34
+ {
35
+ (?B ?E) :powSlow ?R.
36
+ } <= {
37
+ ?E math:greaterThan 0.
38
+ (?E 1) math:difference ?E1.
39
+ (?B ?E1) :powSlow ?Prev.
40
+ (?Prev ?B) math:product ?R.
41
+ }.
42
+
43
+ # ----------------------------------
44
+ # Power tower (tetration) using :pow
45
+ # ----------------------------------
46
+ # tower(base, 0) = 1
47
+ {
48
+ (?B 0) :tower 1.
49
+ } <= {
50
+ 0 math:equalTo 0.
51
+ }.
52
+
53
+ # tower(base, H>0) = base ^ tower(base, H-1)
54
+ {
55
+ (?B ?H) :tower ?R.
56
+ } <= {
57
+ ?H math:greaterThan 0.
58
+ (?H 1) math:difference ?H1.
59
+ (?B ?H1) :tower ?Prev.
60
+ (?B ?Prev) math:exponentiation ?R.
61
+ }.
62
+
63
+ # -----------------------------------------
64
+ # “Projection” helpers to keep output small
65
+ # -----------------------------------------
66
+ # powMod1e6(base, exp) = (base^exp) mod 1,000,000
67
+ {
68
+ (?B ?E) :powMod1e6 ?M.
69
+ } <= {
70
+ (?B ?E) :pow ?R.
71
+ (?R 1000000) math:remainder ?M.
72
+ }.
73
+
74
+ # towerMod1e6(base, height) = tower(base,height) mod 1,000,000
75
+ {
76
+ (?B ?H) :towerMod1e6 ?M.
77
+ } <= {
78
+ (?B ?H) :tower ?R.
79
+ (?R 1000000) math:remainder ?M.
80
+ }.
81
+
82
+ # ----
83
+ # test
84
+ # ----
85
+ {
86
+ # small sanity: fast and slow should agree
87
+ (2 10) :pow ?P10.
88
+ (2 10) :powSlow ?S10.
89
+
90
+ # big exponent, but only output mod 1e6 (so results stay short)
91
+ (2 10000) :powMod1e6 ?M1.
92
+ (3 10000) :powMod1e6 ?M2.
93
+
94
+ # power tower (2 ↑↑ 4 = 65536), and a slightly larger one modulo 1e6
95
+ (2 4) :tower ?T4.
96
+ (2 5) :towerMod1e6 ?TM5.
97
+
98
+ # WARNING: uncommenting this will be slow / memory-hungry (repeat multiply)
99
+ # (2 10000) :powSlow ?Boom.
100
+ }
101
+ =>
102
+ {
103
+ :test :is {
104
+ (2 10) :pow ?P10.
105
+ (2 10) :powSlow ?S10.
106
+ (2 10000) :powMod1e6 ?M1.
107
+ (3 10000) :powMod1e6 ?M2.
108
+ (2 4) :tower ?T4.
109
+ (2 5) :towerMod1e6 ?TM5.
110
+ }.
111
+ }.
@@ -1,19 +1,17 @@
1
1
  # ==========================================================================================
2
2
  # Genetic algorithm demo
3
3
  #
4
- # Genetic algorithm (GA) in a nutshell:
5
- # - Start with a random candidate string of the same length as the target.
6
- # - Repeatedly create a batch of “children” by copying the current best string and randomly
7
- # mutating each character with a small probability.
8
- # - Score each child by how many positions differ from the target (Hamming distance).
9
- # - Keep the best-scoring child as the new current string and iterate until the score is 0.
10
- # This is a simple hill-climbing GA (selection + mutation, no crossover), using a seeded RNG
11
- # so runs are reproducible.
4
+ # We "evolve" a genome (a bitstring) that encodes a target integer.
12
5
  #
13
- # The GA is still expressed as backward rules (<=) for the outer evolution loop,
14
- # but the expensive inner loop ("take N mutated samples and keep the best") is
15
- # handled by a single string builtin to avoid huge proof trees and OOM.
6
+ # - Genome: a string of '0'/'1' bits (same length as :weights).
7
+ # - Phenotype: sum of selected weights where the genome bit is '1'.
8
+ # - Fitness: remaining distance to the target, with a heavy penalty for overshoot:
9
+ # if sum <= target: fitness = target - sum
10
+ # if sum > target: fitness = 1_000_000 + (sum - target)
11
+ # - Variation: single-point mutation (flip one bit).
12
+ # - Selection: pick the candidate with the lowest fitness (ties keep the earlier candidate).
16
13
  #
14
+ # With powers-of-two weights, this deterministic hill-climbing GA converges quickly.
17
15
  # ==========================================================================================
18
16
 
19
17
  @prefix : <urn:ga:>.
@@ -23,155 +21,200 @@
23
21
  @prefix string: <http://www.w3.org/2000/10/swap/string#>.
24
22
 
25
23
  :cfg
26
- :mutationProbability 5;
27
- :samples 80;
28
- :seed 100;
24
+ :target 2026;
25
+ :maxGenerations 64;
29
26
 
30
- # Debug knobs:
31
- :traceEvery 1; # 0 disables; 1 traces every generation
32
- :maxGenerations 0; # 0 means unlimited
27
+ # weights align with bit positions (index 0 is the left-most bit):
28
+ # genome = b0 b1 ... b10
29
+ # value = b0*1024 + b1*512 + ... + b10*1
30
+ :weights (1024 512 256 128 64 32 16 8 4 2 1).
33
31
 
34
- :alphabet (" " "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M"
35
- "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z").
32
+ # -------------
33
+ # small helpers
34
+ # -------------
36
35
 
37
- # ------------------------
38
- # Small arithmetic helpers
39
- # ------------------------
40
-
41
- { ( ?N 1 ) :dec ?N1 } <= { ( ?N 1 ) math:difference ?N1 }.
42
36
  { ( ?N 1 ) :inc ?N1 } <= { ( ?N 1 ) math:sum ?N1 }.
43
37
 
44
- # ----------------------------------------------
45
- # Seeded RNG (LCG) and JS Math.round(rnd() * N)
46
- # (Used only for the initial random chromosome.)
47
- # ----------------------------------------------
38
+ # build a string of N zeros
39
+ { ( 0 ) :zeros "" } <= true.
40
+
41
+ { ( ?N ) :zeros ?S } <= {
42
+ ?N math:greaterThan 0 .
43
+ ( ?N 1 ) math:difference ?N1 .
44
+ ( ?N1 ) :zeros ?Rest .
45
+ ( "%s%s" "0" ?Rest ) string:format ?S
46
+ }.
47
+
48
+ # flip a bit at index i in a 0/1 string
49
+ { ( ?S ?I ) :flipBit ?Out } <= {
50
+ ( ?S ?I ) string:charAt "0" .
51
+ ( ?S ?I "1" ) string:setCharAt ?Out
52
+ }.
53
+
54
+ { ( ?S ?I ) :flipBit ?Out } <= {
55
+ ( ?S ?I ) string:charAt "1" .
56
+ ( ?S ?I "0" ) string:setCharAt ?Out
57
+ }.
58
+
59
+ # generate all single-bit mutants of S: [flip(0), flip(1), ..., flip(len-1)]
60
+ { ( ?S ?Len ) :mutants ?Out } <= {
61
+ ( ?S 0 ?Len ) :mutantsFrom ?Out
62
+ }.
63
+
64
+ { ( ?S ?I ?Len ) :mutantsFrom () } <= {
65
+ ?I math:equalTo ?Len
66
+ }.
67
+
68
+ { ( ?S ?I ?Len ) :mutantsFrom ?Out } <= {
69
+ ?I math:lessThan ?Len .
70
+ ( ?S ?I ) :flipBit ?Cand .
71
+ ( ?I 1 ) :inc ?I1 .
72
+ ( ?S ?I1 ?Len ) :mutantsFrom ?Rest .
73
+ ( ( ?Cand ) ?Rest ) list:append ?Out
74
+ }.
75
+
76
+ # ------------------------
77
+ # decode genome -> integer
78
+ # ------------------------
48
79
 
49
- { ?S0 :lcgNext ?S1 } <= {
50
- ( ?S0 1103515245 ) math:product ?P .
51
- ( ?P 12345 ) math:sum ?T .
52
- ( ?T 2147483648 ) math:remainder ?S1
80
+ { ( ?Bits ?Weights ) :value ?Sum } <= {
81
+ ?Bits string:length ?Len .
82
+ ( ?Bits ?Weights 0 ?Len 0 ) :valueAcc ?Sum
53
83
  }.
54
84
 
55
- { ( ?S0 ?N ) :randRound ( ?R ?S1 ) } <= {
56
- ?S0 :lcgNext ?S1 .
57
- ( ?S1 ?N ) math:product ?A .
58
- ( ?A ?A ) math:sum ?TwoA .
59
- ( ?TwoA 2147483648 ) math:sum ?Num .
60
- ( 2147483648 2 ) math:product ?Den .
61
- ( ?Num ?Den ) math:integerQuotient ?R
85
+ { ( ?Bits ?Weights ?I ?Len ?Acc ) :valueAcc ?Acc } <= {
86
+ ?I math:equalTo ?Len
62
87
  }.
63
88
 
64
- { ?S0 :randomAlpha ( ?Ch ?S1 ) } <= {
65
- :cfg :alphabet ?Alphabet .
66
- ( ?S0 26 ) :randRound ( ?Idx ?S1 ) .
67
- ( ?Alphabet ?Idx ) list:memberAt ?Ch
89
+ { ( ?Bits ?Weights ?I ?Len ?Acc ) :valueAcc ?Sum } <= {
90
+ ?I math:lessThan ?Len .
91
+ ( ?Weights ?I ) list:memberAt ?W .
92
+
93
+ ( ?Bits ?I ) string:charAt "1" .
94
+ ( ?Acc ?W ) math:sum ?Acc1 .
95
+ ( ?I 1 ) :inc ?I1 .
96
+ ( ?Bits ?Weights ?I1 ?Len ?Acc1 ) :valueAcc ?Sum
68
97
  }.
69
98
 
70
- { ( 0 ?S0 ) :randomText ( "" ?S0 ) } <= true.
99
+ { ( ?Bits ?Weights ?I ?Len ?Acc ) :valueAcc ?Sum } <= {
100
+ ?I math:lessThan ?Len .
101
+ ( ?Weights ?I ) list:memberAt ?W .
71
102
 
72
- { ( ?Len ?S0 ) :randomText ( ?Out ?S2 ) } <= {
73
- ?Len math:greaterThan 0 .
74
- ( ?Len 1 ) :dec ?Len1 .
75
- ?S0 :randomAlpha ( ?Ch ?S1 ) .
76
- ( ?Len1 ?S1 ) :randomText ( ?Rest ?S2 ) .
77
- ( "%s%s" ?Ch ?Rest ) string:format ?Out
103
+ ( ?Bits ?I ) string:charAt "0" .
104
+ ( ?I 1 ) :inc ?I1 .
105
+ ( ?Bits ?Weights ?I1 ?Len ?Acc ) :valueAcc ?Sum
78
106
  }.
79
107
 
80
108
  # -------------------------
81
- # tracing helper (optional)
109
+ # fitness (lower is better)
82
110
  # -------------------------
83
111
 
84
- { ( 0 ?Gen ?Score ?Value ) :traceMaybe true } <= true.
112
+ { ( ?Sum ?Target ) :fitness ?F } <= {
113
+ ?Sum math:notGreaterThan ?Target .
114
+ ( ?Target ?Sum ) math:difference ?F
115
+ }.
116
+
117
+ { ( ?Sum ?Target ) :fitness ?F } <= {
118
+ ?Sum math:greaterThan ?Target .
119
+ ( ?Sum ?Target ) math:difference ?Over .
120
+ ( 1000000 ?Over ) math:sum ?F
121
+ }.
122
+
123
+ { ( ?Bits ?Target ?Weights ) :candidateScore ( ?Fit ?Sum ) } <= {
124
+ ( ?Bits ?Weights ) :value ?Sum .
125
+ ( ?Sum ?Target ) :fitness ?Fit
126
+ }.
127
+
128
+ # pick best of two candidates by fitness (ties keep the left candidate)
129
+ { ( ?C1 ?S1 ?Sum1 ?C2 ?S2 ?Sum2 ) :pickBest ( ?C1 ?S1 ?Sum1 ) } <= {
130
+ ?S1 math:notGreaterThan ?S2
131
+ }.
132
+
133
+ { ( ?C1 ?S1 ?Sum1 ?C2 ?S2 ?Sum2 ) :pickBest ( ?C2 ?S2 ?Sum2 ) } <= {
134
+ ?S1 math:greaterThan ?S2
135
+ }.
136
+
137
+ # best candidate from a list
138
+ { ( ?Target ?Weights ?L ) :bestMutant ( ?Best ?BestScore ?BestSum ) } <= {
139
+ ?L list:length 1 .
140
+ ?L list:first ?Best .
141
+ ( ?Best ?Target ?Weights ) :candidateScore ( ?BestScore ?BestSum )
142
+ }.
143
+
144
+ { ( ?Target ?Weights ?L ) :bestMutant ( ?Best ?BestScore ?BestSum ) } <= {
145
+ ?L list:length ?N .
146
+ ?N math:greaterThan 1 .
147
+
148
+ ?L list:first ?H .
149
+ ?L list:rest ?T .
150
+
151
+ ( ?Target ?Weights ?T ) :bestMutant ( ?B2 ?S2 ?Sum2 ) .
152
+ ( ?H ?Target ?Weights ) :candidateScore ( ?S1 ?Sum1 ) .
85
153
 
86
- { ( ?Every ?Gen ?Score ?Value ) :traceMaybe true } <= {
87
- ?Every math:greaterThan 0 .
88
- ( ?Gen ?Every ) math:remainder ?R .
89
- ?R math:equalTo 0 .
90
- ?Gen log:trace ( ?Score ?Value )
154
+ ( ?H ?S1 ?Sum1 ?B2 ?S2 ?Sum2 ) :pickBest ( ?Best ?BestScore ?BestSum )
91
155
  }.
92
156
 
93
157
  # --------------
94
158
  # evolution loop
95
159
  # --------------
96
160
 
97
- # Stop when score == 0
98
- { ( ?Current 0 ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
99
- :evolve ( ?Current ?Gen ?S0 ) } <= true.
100
-
101
- # Stop when generation cap is reached (only if MaxGen > 0)
102
- { ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
103
- :evolve ( ?Current ?Gen ?S0 ) } <= {
104
- ?Score math:notEqualTo 0 .
105
- ?MaxGen math:greaterThan 0 .
106
- ?Gen math:equalTo ?MaxGen
161
+ # solved
162
+ { ( ?Bits ?Target ?Weights ?Gen ?MaxGen ) :evolve ( ?Bits ?Gen ) } <= {
163
+ ( ?Bits ?Target ?Weights ) :candidateScore ( 0 ?Sum )
107
164
  }.
108
165
 
109
- # Unlimited run (MaxGen == 0): keep evolving
110
- { ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 0 ?TraceEvery )
111
- :evolve ( ?Final ?FinalGen ?S2 ) } <= {
112
- ?Score math:notEqualTo 0 .
113
- ( ?Samples ?Current ?Target ?MutProb ?S0 )
114
- string:mutateSelectBest
115
- ( ?Best ?BestScore ?S1 ) .
116
- ( ?Gen 1 ) :inc ?Gen1 .
117
- ( ?TraceEvery ?Gen1 ?BestScore ?Best ) :traceMaybe true .
118
- ( ?Best ?BestScore ?Target ?MutProb ?Samples ?Gen1 ?S1 0 ?TraceEvery )
119
- :evolve ( ?Final ?FinalGen ?S2 )
166
+ # generation cap (safety)
167
+ { ( ?Bits ?Target ?Weights ?Gen ?MaxGen ) :evolve ( ?Bits ?Gen ) } <= {
168
+ ?Gen math:equalTo ?MaxGen
120
169
  }.
121
170
 
122
- # Bounded run (MaxGen > 0): keep evolving while Gen < MaxGen
123
- { ( ?Current ?Score ?Target ?MutProb ?Samples ?Gen ?S0 ?MaxGen ?TraceEvery )
124
- :evolve ( ?Final ?FinalGen ?S2 ) } <= {
125
- ?Score math:notEqualTo 0 .
126
- ?MaxGen math:greaterThan 0 .
171
+ # stop if best doesn't improve
172
+ { ( ?Bits ?Target ?Weights ?Gen ?MaxGen ) :evolve ( ?Bits ?Gen ) } <= {
127
173
  ?Gen math:lessThan ?MaxGen .
128
- ( ?Samples ?Current ?Target ?MutProb ?S0 )
129
- string:mutateSelectBest
130
- ( ?Best ?BestScore ?S1 ) .
131
- ( ?Gen 1 ) :inc ?Gen1 .
132
- ( ?TraceEvery ?Gen1 ?BestScore ?Best ) :traceMaybe true .
133
- ( ?Best ?BestScore ?Target ?MutProb ?Samples ?Gen1 ?S1 ?MaxGen ?TraceEvery )
134
- :evolve ( ?Final ?FinalGen ?S2 )
135
- }.
174
+ ( ?Bits ?Target ?Weights ) :candidateScore ( ?Score ?Sum ) .
136
175
 
137
- # ----------------------------------
138
- # Public solver wrapper (string API)
139
- # ----------------------------------
176
+ ?Bits string:length ?Len .
177
+ ( ?Bits ?Len ) :mutants ?Ms .
178
+ ( ( ?Bits ) ?Ms ) list:append ?Candidates .
140
179
 
141
- { ?TargetString :solveResult ( ?Gen 0 ?Value ?Seed ) } <= {
142
- :cfg :mutationProbability ?MutProb;
143
- :samples ?Samples;
144
- :seed ?Seed;
145
- :traceEvery ?TraceEvery;
146
- :maxGenerations ?MaxGen.
147
-
148
- ?TargetString string:length ?Len .
180
+ ( ?Target ?Weights ?Candidates ) :bestMutant ( ?Best ?BestScore ?BestSum ) .
181
+ ?BestScore math:equalTo ?Score
182
+ }.
149
183
 
150
- ( ?Len ?Seed ) :randomText ( ?Current0 ?S1 ) .
184
+ # keep evolving while improvement exists
185
+ { ( ?Bits ?Target ?Weights ?Gen ?MaxGen ) :evolve ( ?Final ?FinalGen ) } <= {
186
+ ?Gen math:lessThan ?MaxGen .
187
+ ( ?Bits ?Target ?Weights ) :candidateScore ( ?Score ?Sum ) .
151
188
 
152
- # initial score using string:hammingDistance
153
- ( ?Current0 ?TargetString ) string:hammingDistance ?Score0 .
189
+ ?Bits string:length ?Len .
190
+ ( ?Bits ?Len ) :mutants ?Ms .
191
+ ( ( ?Bits ) ?Ms ) list:append ?Candidates .
154
192
 
155
- ( ?Current0 ?Score0 ?TargetString ?MutProb ?Samples 0 ?S1 ?MaxGen ?TraceEvery )
156
- :evolve
157
- ( ?Value ?Gen ?S2 ) .
193
+ ( ?Target ?Weights ?Candidates ) :bestMutant ( ?Best ?BestScore ?BestSum ) .
194
+ ?BestScore math:notEqualTo ?Score .
158
195
 
159
- # Succeeds only when solved:
160
- ( ?Value ?TargetString ) string:hammingDistance 0 .
161
- ?Value log:equalTo ?TargetString .
162
- ?S2 log:equalTo ?_FinalSeedState
196
+ ( ?Gen 1 ) :inc ?Gen1 .
197
+ ( ?Best ?Target ?Weights ?Gen1 ?MaxGen ) :evolve ( ?Final ?FinalGen )
163
198
  }.
164
199
 
165
- { ?TargetString :solved true } <= {
166
- ?TargetString :solveResult ( ?Gen 0 ?TargetString ?Seed )
167
- }.
200
+ # --------------
201
+ # query / output
202
+ # --------------
203
+
204
+ {
205
+ :cfg :target ?T;
206
+ :maxGenerations ?Max;
207
+ :weights ?W.
208
+
209
+ ?W list:length ?Len .
210
+ ( ?Len ) :zeros ?Start .
168
211
 
169
- # --------------------------------------------------------
170
- # Requested output: solve('METHINKS IT IS LIKE A WEASEL').
171
- # --------------------------------------------------------
212
+ ( ?Start ?T ?W 0 ?Max ) :evolve ( ?Best ?Gen ) .
213
+ ( ?Best ?W ) :value ?Sum .
214
+ ?Sum math:equalTo ?T .
172
215
 
173
- { "METHINKS IT IS LIKE A WEASEL" :solved true .
174
- ( "solve('%s').\n" "METHINKS IT IS LIKE A WEASEL" ) string:format ?Line
216
+ ( "GA evolved %s -> %s in %s generations.\n" ?Best ?Sum ?Gen )
217
+ string:format ?Line
175
218
  }
176
219
  log:query
177
220
  { 1 log:outputString ?Line }.
@@ -0,0 +1,87 @@
1
+ # ===============================================
2
+ # Fast modular exponentiation (repeated squaring)
3
+ # ===============================================
4
+
5
+ @prefix math: <http://www.w3.org/2000/10/swap/math#>.
6
+ @prefix : <https://eyereasoner.github.io/ns#>.
7
+
8
+ # (a * b) mod m
9
+ {
10
+ (?A ?B ?M) :modMul ?R.
11
+ } <= {
12
+ (?A ?B) math:product ?P.
13
+ (?P ?M) math:remainder ?R.
14
+ }.
15
+
16
+ # modPow(b, 0, m) = 1 mod m
17
+ {
18
+ (?B 0 ?M) :modPow ?R.
19
+ } <= {
20
+ ?M math:greaterThan 0.
21
+ (1 ?M) math:remainder ?R.
22
+ }.
23
+
24
+ # modPow(b, 1, m) = b mod m
25
+ {
26
+ (?B 1 ?M) :modPow ?R.
27
+ } <= {
28
+ ?M math:greaterThan 0.
29
+ (?B ?M) math:remainder ?R.
30
+ }.
31
+
32
+ # even exponent: modPow(b, e, m) = modPow((b*b mod m), e/2, m)
33
+ {
34
+ (?B ?E ?M) :modPow ?R.
35
+ } <= {
36
+ ?M math:greaterThan 0.
37
+ ?E math:greaterThan 1.
38
+ (?E 2) math:remainder 0.
39
+
40
+ (?B ?B ?M) :modMul ?B2.
41
+ (?E 2) math:integerQuotient ?Half.
42
+ (?B2 ?Half ?M) :modPow ?R.
43
+ }.
44
+
45
+ # odd exponent: modPow(b, e, m) = (b * modPow((b*b mod m), (e-1)/2, m)) mod m
46
+ {
47
+ (?B ?E ?M) :modPow ?R.
48
+ } <= {
49
+ ?M math:greaterThan 0.
50
+ ?E math:greaterThan 1.
51
+ (?E 2) math:remainder 1.
52
+
53
+ (?B ?B ?M) :modMul ?B2.
54
+ (?E 1) math:difference ?E1.
55
+ (?E1 2) math:integerQuotient ?Half.
56
+
57
+ (?B2 ?Half ?M) :modPow ?T.
58
+ (?B ?T ?M) :modMul ?R.
59
+ }.
60
+
61
+ # -----------------
62
+ # tests / showcases
63
+ # -----------------
64
+ {
65
+ # sanity check against a small naive computation:
66
+ (7 13) math:exponentiation ?PowSmall.
67
+ (?PowSmall 97) math:remainder ?NaiveSmall.
68
+ (7 13 97) :modPow ?FastSmall.
69
+
70
+ # “impressive” case: 7^1,000,000,000 mod 1,000,000,007
71
+ (7 1000000000 1000000007) :modPow ?R1.
72
+
73
+ # exponent itself computed via exponentiation: 2^25 = 33,554,432
74
+ (2 25) math:exponentiation ?E25.
75
+ (3 ?E25 1000000007) :modPow ?R2.
76
+
77
+ # last 12 digits of 2^(2^20)
78
+ (2 20) math:exponentiation ?E20.
79
+ (2 ?E20 1000000000000) :modPow ?Last12.
80
+ }
81
+ =>
82
+ {
83
+ :test :smallCheck [ :naive ?NaiveSmall; :fast ?FastSmall ].
84
+ :test :sevenPow1e9Mod1e9p7 ?R1.
85
+ :test :threePow2Pow25Mod1e9p7 ?R2.
86
+ :test :last12DigitsOf2Pow2Pow20 ?Last12.
87
+ }.
@@ -0,0 +1,10 @@
1
+ @prefix : <http://example.org/fastpow#> .
2
+
3
+ :test :is {
4
+ (2 10) :pow 1024 .
5
+ (2 10) :powSlow 1024 .
6
+ (2 10000) :powMod1e6 709376 .
7
+ (3 10000) :powMod1e6 200001 .
8
+ (2 4) :tower 65536 .
9
+ (2 5) :towerMod1e6 156736 .
10
+ } .
@@ -1,3 +1,3 @@
1
1
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
2
2
 
3
- 1 log:outputString "solve('METHINKS IT IS LIKE A WEASEL').\n" .
3
+ 1 log:outputString "GA evolved 11111101010 -> 2026 in 8 generations.\n" .
@@ -0,0 +1,8 @@
1
+ @prefix : <https://eyereasoner.github.io/ns#> .
2
+
3
+ :test :smallCheck _:sk_0 .
4
+ _:sk_0 :naive 38 .
5
+ _:sk_0 :fast 38 .
6
+ :test :sevenPow1e9Mod1e9p7 312556845 .
7
+ :test :threePow2Pow25Mod1e9p7 849572438 .
8
+ :test :last12DigitsOf2Pow2Pow20 940335579136 .
@@ -346,5 +346,3 @@ string:setCharAt a ex:Builtin ; ex:kind ex:Function ;
346
346
  string:hammingDistance a ex:Builtin ; ex:kind ex:Function ;
347
347
  rdfs:comment "Function: subject is a 2-item list (a b). Binds/unifies object with the number of differing positions (Hamming distance). Fails if lengths differ." .
348
348
 
349
- string:mutateSelectBest a ex:Builtin ; ex:kind ex:Function ;
350
- rdfs:comment "Shape: (samples current target mutProb seedState) string:mutateSelectBest (best bestScore seedState2). Generates samples mutated variants of current (mutating each character with probability mutProb percent), scores each candidate against target (Hamming distance), and returns the best candidate (lowest score). RNG is a deterministic 31-bit LCG, threaded via seedState → seedState2, so runs are reproducible." .
package/eyeling.js CHANGED
@@ -3610,89 +3610,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
3610
3610
  return s2 !== null ? [s2] : [];
3611
3611
  }
3612
3612
 
3613
- // string:mutateSelectBest
3614
- // Schema:
3615
- // ( $samples $current $target $mutProb $seedState )
3616
- // string:mutateSelectBest
3617
- // ( $best $bestScore $seedState2 )
3618
- //
3619
- // Deterministic 31-bit LCG (same as the GA demo):
3620
- // state = (1103515245 * state + 12345) % 2^31
3621
- // and randRound(N) = Math.round((state/2^31) * N) with state advanced once per draw.
3622
- if (pv === STRING_NS + 'mutateSelectBest') {
3623
- if (!(g.s instanceof ListTerm) || g.s.elems.length !== 5) return [];
3624
-
3625
- const samplesNum = parseNum(g.s.elems[0]);
3626
- const current = termToJsXsdStringNoLang(g.s.elems[1]);
3627
- const target = termToJsXsdStringNoLang(g.s.elems[2]);
3628
- const mutProbNum = parseNum(g.s.elems[3]);
3629
- const seedNum = parseNum(g.s.elems[4]);
3630
-
3631
- if (samplesNum === null || current === null || target === null || mutProbNum === null || seedNum === null)
3632
- return [];
3633
- if (current.length !== target.length) return [];
3634
-
3635
- const samples = Math.max(1, Math.trunc(samplesNum));
3636
- const mutProb = Math.max(0, Math.trunc(mutProbNum));
3637
- const seed0 = Math.trunc(seedNum);
3638
-
3639
- let state = BigInt(seed0);
3640
- const MOD = 2147483648n; // 2^31
3641
- const A = 1103515245n;
3642
- const C = 12345n;
3643
-
3644
- function rnd() {
3645
- state = (A * state + C) % MOD;
3646
- return Number(state) / 2147483648;
3647
- }
3648
-
3649
- function randRound(N) {
3650
- return Math.round(rnd() * N);
3651
- }
3652
-
3653
- function randomAlpha() {
3654
- const p = randRound(26);
3655
- return p === 0 ? ' ' : String.fromCharCode(64 + p);
3656
- }
3657
-
3658
- function mutate(str) {
3659
- let out = '';
3660
- for (let i = 0; i < str.length; i++) {
3661
- const p = randRound(100);
3662
- if (p > mutProb) out += str[i];
3663
- else out += randomAlpha();
3664
- }
3665
- return out;
3666
- }
3667
-
3668
- function score(str) {
3669
- let diffs = 0;
3670
- for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
3671
- return diffs;
3672
- }
3673
-
3674
- let best = '';
3675
- let bestScore = Infinity;
3676
-
3677
- for (let i = 0; i < samples; i++) {
3678
- const cand = mutate(current);
3679
- const candScore = score(cand);
3680
- if (candScore < bestScore) {
3681
- best = cand;
3682
- bestScore = candScore;
3683
- }
3684
- }
3685
-
3686
- const outList = new ListTerm([
3687
- makeStringLiteral(best),
3688
- internLiteral(String(bestScore)),
3689
- internLiteral(state.toString()),
3690
- ]);
3691
-
3692
- const s2 = unifyTerm(g.o, outList, subst);
3693
- return s2 !== null ? [s2] : [];
3694
- }
3695
-
3696
3613
  // Unknown builtin
3697
3614
  return [];
3698
3615
  }
@@ -3962,7 +3879,7 @@ function main() {
3962
3879
  ` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
3963
3880
  ` -h, --help Show this help and exit.\n` +
3964
3881
  ` -p, --proof-comments Enable proof explanations.\n` +
3965
- ` -r, --strings Print log:outputString strings (ordered by key) instead of N3 output.\n` +
3882
+ ` -r, --strings Print log:outputString strings (ordered by key), including via log:query.\n` +
3966
3883
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
3967
3884
  ` -t, --stream Stream derived triples as soon as they are derived.\n` +
3968
3885
  ` -v, --version Print version and exit.\n`;
@@ -4072,8 +3989,21 @@ function main() {
4072
3989
  // If requested, print log:outputString values (ordered by subject key) and exit.
4073
3990
  // Note: log:outputString values may depend on derived facts, so we must saturate first.
4074
3991
  if (outputStringsMode) {
4075
- engine.forwardChain(facts, frules, brules);
4076
- const out = engine.collectOutputStringsFromFacts(facts, prefixes);
3992
+ const hasQueries = Array.isArray(qrules) && qrules.length;
3993
+
3994
+ // If log:query directives are present, the intended output may not be part of the
3995
+ // saturated fact store (queries are output-selection statements). In that case,
3996
+ // collect log:outputString triples from the instantiated query conclusions.
3997
+ let outTriples;
3998
+ if (hasQueries) {
3999
+ const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules);
4000
+ outTriples = res.queryTriples;
4001
+ } else {
4002
+ engine.forwardChain(facts, frules, brules);
4003
+ outTriples = facts;
4004
+ }
4005
+
4006
+ const out = engine.collectOutputStringsFromFacts(outTriples, prefixes);
4077
4007
  if (out) process.stdout.write(out);
4078
4008
  process.exit(0);
4079
4009
  }
package/lib/builtins.js CHANGED
@@ -3598,89 +3598,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
3598
3598
  return s2 !== null ? [s2] : [];
3599
3599
  }
3600
3600
 
3601
- // string:mutateSelectBest
3602
- // Schema:
3603
- // ( $samples $current $target $mutProb $seedState )
3604
- // string:mutateSelectBest
3605
- // ( $best $bestScore $seedState2 )
3606
- //
3607
- // Deterministic 31-bit LCG (same as the GA demo):
3608
- // state = (1103515245 * state + 12345) % 2^31
3609
- // and randRound(N) = Math.round((state/2^31) * N) with state advanced once per draw.
3610
- if (pv === STRING_NS + 'mutateSelectBest') {
3611
- if (!(g.s instanceof ListTerm) || g.s.elems.length !== 5) return [];
3612
-
3613
- const samplesNum = parseNum(g.s.elems[0]);
3614
- const current = termToJsXsdStringNoLang(g.s.elems[1]);
3615
- const target = termToJsXsdStringNoLang(g.s.elems[2]);
3616
- const mutProbNum = parseNum(g.s.elems[3]);
3617
- const seedNum = parseNum(g.s.elems[4]);
3618
-
3619
- if (samplesNum === null || current === null || target === null || mutProbNum === null || seedNum === null)
3620
- return [];
3621
- if (current.length !== target.length) return [];
3622
-
3623
- const samples = Math.max(1, Math.trunc(samplesNum));
3624
- const mutProb = Math.max(0, Math.trunc(mutProbNum));
3625
- const seed0 = Math.trunc(seedNum);
3626
-
3627
- let state = BigInt(seed0);
3628
- const MOD = 2147483648n; // 2^31
3629
- const A = 1103515245n;
3630
- const C = 12345n;
3631
-
3632
- function rnd() {
3633
- state = (A * state + C) % MOD;
3634
- return Number(state) / 2147483648;
3635
- }
3636
-
3637
- function randRound(N) {
3638
- return Math.round(rnd() * N);
3639
- }
3640
-
3641
- function randomAlpha() {
3642
- const p = randRound(26);
3643
- return p === 0 ? ' ' : String.fromCharCode(64 + p);
3644
- }
3645
-
3646
- function mutate(str) {
3647
- let out = '';
3648
- for (let i = 0; i < str.length; i++) {
3649
- const p = randRound(100);
3650
- if (p > mutProb) out += str[i];
3651
- else out += randomAlpha();
3652
- }
3653
- return out;
3654
- }
3655
-
3656
- function score(str) {
3657
- let diffs = 0;
3658
- for (let i = 0; i < target.length; i++) if (str[i] !== target[i]) diffs++;
3659
- return diffs;
3660
- }
3661
-
3662
- let best = '';
3663
- let bestScore = Infinity;
3664
-
3665
- for (let i = 0; i < samples; i++) {
3666
- const cand = mutate(current);
3667
- const candScore = score(cand);
3668
- if (candScore < bestScore) {
3669
- best = cand;
3670
- bestScore = candScore;
3671
- }
3672
- }
3673
-
3674
- const outList = new ListTerm([
3675
- makeStringLiteral(best),
3676
- internLiteral(String(bestScore)),
3677
- internLiteral(state.toString()),
3678
- ]);
3679
-
3680
- const s2 = unifyTerm(g.o, outList, subst);
3681
- return s2 !== null ? [s2] : [];
3682
- }
3683
-
3684
3601
  // Unknown builtin
3685
3602
  return [];
3686
3603
  }
package/lib/cli.js CHANGED
@@ -71,7 +71,7 @@ function main() {
71
71
  ` -e, --enforce-https Rewrite http:// IRIs to https:// for log dereferencing builtins.\n` +
72
72
  ` -h, --help Show this help and exit.\n` +
73
73
  ` -p, --proof-comments Enable proof explanations.\n` +
74
- ` -r, --strings Print log:outputString strings (ordered by key) instead of N3 output.\n` +
74
+ ` -r, --strings Print log:outputString strings (ordered by key), including via log:query.\n` +
75
75
  ` -s, --super-restricted Disable all builtins except => and <=.\n` +
76
76
  ` -t, --stream Stream derived triples as soon as they are derived.\n` +
77
77
  ` -v, --version Print version and exit.\n`;
@@ -181,8 +181,21 @@ function main() {
181
181
  // If requested, print log:outputString values (ordered by subject key) and exit.
182
182
  // Note: log:outputString values may depend on derived facts, so we must saturate first.
183
183
  if (outputStringsMode) {
184
- engine.forwardChain(facts, frules, brules);
185
- const out = engine.collectOutputStringsFromFacts(facts, prefixes);
184
+ const hasQueries = Array.isArray(qrules) && qrules.length;
185
+
186
+ // If log:query directives are present, the intended output may not be part of the
187
+ // saturated fact store (queries are output-selection statements). In that case,
188
+ // collect log:outputString triples from the instantiated query conclusions.
189
+ let outTriples;
190
+ if (hasQueries) {
191
+ const res = engine.forwardChainAndCollectLogQueryConclusions(facts, frules, brules, qrules);
192
+ outTriples = res.queryTriples;
193
+ } else {
194
+ engine.forwardChain(facts, frules, brules);
195
+ outTriples = facts;
196
+ }
197
+
198
+ const out = engine.collectOutputStringsFromFacts(outTriples, prefixes);
186
199
  if (out) process.stdout.write(out);
187
200
  process.exit(0);
188
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.14.3",
3
+ "version": "1.14.4",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [