porolog 0.0.8 → 1.0.0

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.
@@ -152,22 +152,6 @@ describe 'Porolog' do
152
152
 
153
153
  end
154
154
 
155
- describe '.builtin' do
156
-
157
- it 'should incremently return Predicates' do
158
- iota1 = Predicate.builtin :iota
159
- zeta1 = Predicate.builtin :zeta
160
- iota2 = Predicate.builtin :iota
161
- zeta2 = Predicate.builtin :zeta
162
-
163
- assert_Predicate iota1, :_iota_1, []
164
- assert_Predicate zeta1, :_zeta_1, []
165
- assert_Predicate iota2, :_iota_2, []
166
- assert_Predicate zeta2, :_zeta_2, []
167
- end
168
-
169
- end
170
-
171
155
  describe '#initialize' do
172
156
 
173
157
  it 'can create predicates in different scopes' do
@@ -340,6 +324,84 @@ describe 'Porolog' do
340
324
 
341
325
  end
342
326
 
327
+ describe '#builtin?' do
328
+
329
+ it 'should return false for normal predicates' do
330
+ p = predicate :normal
331
+
332
+ assert_equal false, p.builtin?
333
+ end
334
+
335
+ it 'should return true for builtin predicates' do
336
+ p = builtin :append
337
+
338
+ assert_equal true, p.builtin?
339
+ end
340
+
341
+ end
342
+
343
+ describe '#satisfy_builtin' do
344
+
345
+ module Porolog
346
+ class Predicate
347
+ module Builtin
348
+ def user_defined(goal, block, *args, &arg_block)
349
+ puts 'Called user_defined'
350
+ block.call(goal, *args) || false
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ let(:predicate1) { Predicate.new :user_defined, builtin: true }
357
+ let(:arguments1) { predicate1.arguments(:m,:n) }
358
+ let(:goal) { arguments1.goal }
359
+
360
+ it 'should satisfy builtin predicate' do
361
+ called = false
362
+
363
+ assert_output "Called user_defined\n" do
364
+ predicate1.satisfy_builtin(goal) {|*args|
365
+ called = args
366
+ }
367
+ end
368
+
369
+ assert_equal [goal, goal[:m], goal[:n]], called
370
+ end
371
+
372
+ end
373
+
374
+ describe '.call_builtin' do
375
+
376
+ it 'should raise an exception when no such builtin predicate exists' do
377
+ assert_raises NameError do
378
+ Predicate.call_builtin(:non_existent)
379
+ end
380
+
381
+ end
382
+
383
+ it 'should call a builtin predicate' do
384
+ module Porolog
385
+ class Predicate
386
+ module Builtin
387
+ def custom(goal, block, *args, &arg_block)
388
+ block.call(goal, *args) || false
389
+ end
390
+ end
391
+ end
392
+ end
393
+ called = false
394
+
395
+ goal = new_goal :a, :b, :c
396
+ block = ->(goal, *args){
397
+ called = [goal] + args
398
+ }
399
+ Porolog::Predicate.call_builtin(:custom, goal, block, goal[:X], [1,2,3]) { 1 }
400
+ assert_equal [goal, goal[:X], [1,2,3]], called
401
+ end
402
+
403
+ end
404
+
343
405
  end
344
406
 
345
407
  end
@@ -304,6 +304,25 @@ describe 'Porolog' do
304
304
  assert_equal [goal, subgoal, [false]], rule_spy.calls[0].args
305
305
  end
306
306
 
307
+ it 'should handle nil expression' do
308
+ rule = Rule.new args1, []
309
+
310
+ goal.expects(:terminate!).with().times(0)
311
+ rule_spy = Spy.on(rule, :satisfy_conjunction).and_call_through
312
+ called = false
313
+
314
+ result = rule.satisfy_conjunction(goal, subgoal, rule.definition) do |solution_goal|
315
+ #:nocov:
316
+ called = true
317
+ #:nocov:
318
+ end
319
+
320
+ assert result, 'satisfy_conjunction should not succeed'
321
+ assert called, 'the satisfy block should not be called'
322
+ assert_equal 1, rule_spy.calls.size
323
+ assert_equal [goal, subgoal, []], rule_spy.calls[0].args
324
+ end
325
+
307
326
  it 'should evaluate conjunctions until a fail' do
308
327
  conjunction = [true, true, true, false, true, :CUT, true]
309
328
  rule = Rule.new args1, conjunction
@@ -122,6 +122,7 @@ describe 'Porolog' do
122
122
 
123
123
  it 'should return variable from instantiated variables' do
124
124
  variable = Variable.new 'v', goal1
125
+ variable.instantiate 'string value'
125
126
 
126
127
  assert_equal :variable, variable.type
127
128
  end
@@ -310,7 +311,13 @@ describe 'Porolog' do
310
311
  assert_equal headtail, goal1.value_of(:x)
311
312
  assert_equal headtail, goal1.value_of(:y)
312
313
  assert_equal headtail, goal1.value_of(:z)
313
- assert_Goal_variables goal1, { m: nil, n: nil, x: headtail, y: headtail, z: headtail }, [
314
+ assert_Goal_variables goal1, {
315
+ m: nil,
316
+ n: nil,
317
+ x: [nil, UNKNOWN_TAIL],
318
+ y: [nil, UNKNOWN_TAIL],
319
+ z: [nil, UNKNOWN_TAIL]
320
+ }, [
314
321
  'Goal1.:m',
315
322
  ' Goal1.:y[:head]',
316
323
  ' Goal1.:x',
@@ -450,59 +457,6 @@ describe 'Porolog' do
450
457
  ].join("\n")
451
458
  end
452
459
 
453
- it 'should raise an exception when multiple values are detected' do
454
- skip 'because multiple unequal values are avoided'
455
-
456
- # :nocov:
457
- variable1 = Variable.new :x, goal1
458
- variable2 = Variable.new :y, goal2
459
- variable3 = Variable.new :z, goal3
460
-
461
- value1 = Value.new [1,2,3], goal2
462
- value2 = Value.new 'word', goal3
463
-
464
- i1 = variable1.instantiate variable2
465
- i2 = variable1.instantiate variable3
466
- i3 = variable2.instantiate value1
467
- i4 = variable3.instantiate value2
468
-
469
- assert_Instantiation i1, variable1, variable2, nil, nil
470
- assert_Instantiation i2, variable1, variable3, nil, nil
471
- assert_Instantiation i3, variable2, value1, nil, nil
472
- assert_Instantiation i4, variable3, value2, nil, nil
473
-
474
- assert_raises Variable::MultipleValuesError do
475
- assert_equal value1.value, variable1.value
476
- assert_equal value2.value, variable1.value
477
- end
478
-
479
- assert_Goal_variables goal1, { m: nil, n: nil, x: [1,2,3] }, [
480
- 'Goal1.:m',
481
- 'Goal1.:n',
482
- 'Goal1.:x',
483
- ' Goal2.:y',
484
- ' Goal2.[1, 2, 3]',
485
- ' Goal3.:z',
486
- ].join("\n")
487
- assert_Goal_variables goal2, { m: nil, n: nil, y: [1,2,3] }, [
488
- 'Goal2.:m',
489
- 'Goal2.:n',
490
- 'Goal2.:y',
491
- ' Goal1.:x',
492
- ' Goal3.:z',
493
- ' Goal2.[1, 2, 3]',
494
- ].join("\n")
495
- assert_Goal_variables goal3, { m: nil, n: nil, z: [1,2,3] }, [
496
- 'Goal3.:m',
497
- 'Goal3.:n',
498
- 'Goal3.:z',
499
- ' Goal1.:x',
500
- ' Goal2.:y',
501
- ' Goal2.[1, 2, 3]',
502
- ].join("\n")
503
- # :nocov:
504
- end
505
-
506
460
  it 'should not raise an exception when multiple values are equal' do
507
461
  variable1 = Variable.new :x, goal1
508
462
  variable2 = Variable.new :y, goal2
@@ -571,6 +525,42 @@ describe 'Porolog' do
571
525
  assert_equal :variable1, variable1.value
572
526
  end
573
527
 
528
+ it 'should unify a matching flathead and flattail pair' do
529
+ variable1 = Variable.new :x, goal1
530
+
531
+ variable1.values << [1,2,UNKNOWN_TAIL]
532
+ variable1.values << [UNKNOWN_TAIL,3,4]
533
+
534
+ assert_equal [1,2,3,4], variable1.value
535
+ end
536
+
537
+ it 'should unify a matching flattail and flathead pair' do
538
+ variable1 = Variable.new :x, goal1
539
+
540
+ variable1.values << [UNKNOWN_TAIL,3,4]
541
+ variable1.values << [1,2,UNKNOWN_TAIL]
542
+
543
+ assert_equal [1,2,3,4], variable1.value
544
+ end
545
+
546
+ it 'should return nil when the values are incompatible' do
547
+ variable1 = Variable.new :x, goal1
548
+
549
+ variable1.values << [1,3,4]
550
+ variable1.values << [1,2,UNKNOWN_TAIL]
551
+
552
+ assert_nil variable1.value
553
+ end
554
+
555
+ it 'should unify a special case' do
556
+ variable1 = Variable.new :x, goal1
557
+
558
+ variable1.values << [1,[3,4]]
559
+ variable1.values << goal1[:h]/goal1[:t]
560
+
561
+ assert_equal [1,[3,4]], variable1.value
562
+ end
563
+
574
564
  end
575
565
 
576
566
  describe '#instantiate' do
@@ -671,7 +661,6 @@ describe 'Porolog' do
671
661
  # -- Assert Values --
672
662
  assert_equal 13, variable1.value
673
663
  assert_equal [7,11,13,23,29], variable2.value
674
- #assert_nil variable3.value
675
664
  assert_equal variable3, variable3.value
676
665
 
677
666
  # -- Assert instantiations --
@@ -0,0 +1,277 @@
1
+ #
2
+ # test/samples_test.rb - Test Suite for Porolog::Predicate
3
+ #
4
+ # Luis Esteban 13 July 2020
5
+ # created
6
+ #
7
+
8
+ require_relative 'test_helper'
9
+
10
+ describe 'Porolog' do
11
+
12
+ before(:all) do
13
+ reset
14
+ end
15
+
16
+ it 'implements delete first predicate' do
17
+ predicate :delete
18
+
19
+ delete(:X, [:X]/:T, :T).fact!
20
+
21
+ assert_solutions delete(9, [1,2,3,4], [2,3,4]), []
22
+ assert_solutions delete(1, [1,2,3,4], [2,3,4]), [{}]
23
+
24
+ assert_solutions delete(:Removed, [1,2,3,4], [2,3,4]), [{ Removed: 1 }]
25
+ assert_solutions delete(1, :Original, [2,3,4]), [{ Original: [1,2,3,4] }]
26
+ assert_solutions delete(1, [1,2,3,4], :Result), [{ Result: [2,3,4] }]
27
+ assert_solutions delete(1, [:First,2,3,4], [2,3,4]), [{ First: 1 }]
28
+ assert_solutions delete(1, [1,:Second,3,4], [2,3,4]), [{ Second: 2 }]
29
+ assert_solutions delete(1, [1,2,:Third,4], [2,3,4]), [{ Third: 3 }]
30
+ assert_solutions delete(1, [1,2,3,4], [:Second,3,4]), [{ Second: 2 }]
31
+ assert_solutions delete(1, [1,2,3,4], [:A, :B, :C]), [{ A: 2, B: 3, C: 4 }]
32
+ assert_solutions delete(1, [:A,2,3,:D], [:B,3,4]), [{ A: 1, B: 2, D: 4 }]
33
+ end
34
+
35
+ it 'implements the delete predicate' do
36
+ builtin :write
37
+ predicate :delete
38
+
39
+ delete(:X, [:X]/:T, :T).fact!
40
+ delete(:X, [:H]/:T, [:H]/:NT) << [
41
+ delete(:X, :T, :NT),
42
+ ]
43
+
44
+ assert_solutions delete(1, [1,2,3,4], [1,2,4]), [], goals: 9
45
+ assert_solutions delete(3, [1,2,3,4], [1,2,4]), [{}]
46
+ assert_solutions delete(4, [1,2,3,4], [1,2,3]), [{}]
47
+ assert_solutions delete(4, [1,2,3,4], [1,2,:X]), [{ X: 3 }]
48
+
49
+ assert_solutions delete(:Removed, [1,2,3,4], [1,2,4]), [
50
+ { Removed: 3 }
51
+ ]
52
+
53
+ assert_solutions delete(:Removed, [1,2,3,4], [1,2,3]), [
54
+ { Removed: 4 }
55
+ ]
56
+
57
+ assert_solutions delete(3, :Original, [1,2,4]), [
58
+ { Original: [3, 1, 2, 4] },
59
+ { Original: [1, 3, 2, 4] },
60
+ { Original: [1, 2, 3, 4] },
61
+ { Original: [1, 2, 4, 3] },
62
+ ]
63
+
64
+ assert_solutions delete(:X, [1,2,3,4], :L), [
65
+ { X: 1, L: [2, 3, 4] },
66
+ { X: 2, L: [1, 3, 4] },
67
+ { X: 3, L: [1, 2, 4] },
68
+ { X: 4, L: [1, 2, 3] },
69
+ ]
70
+
71
+ assert_solutions delete(:X, [1,2,3,4], [:A,:B,:C]), [
72
+ { X: 1, A: 2, B: 3, C: 4 },
73
+ { X: 2, A: 1, B: 3, C: 4 },
74
+ { X: 3, A: 1, B: 2, C: 4 },
75
+ { X: 4, A: 1, B: 2, C: 3 },
76
+ ]
77
+ end
78
+
79
+ it 'implements the permutation predicate' do
80
+ warn name
81
+ predicate :delete, :permutation
82
+
83
+ permutation([],[]).fact!
84
+ permutation(:List, [:H]/:Permutation) << [
85
+ delete(:H, :List, :Rest),
86
+ permutation(:Rest, :Permutation)
87
+ ]
88
+
89
+ delete(:X, [:X]/:T, :T).fact!
90
+ delete(:X, [:H]/:T, [:H]/:NT) << [
91
+ delete(:X, :T, :NT),
92
+ ]
93
+
94
+ assert_solutions permutation([1,2,3,4], [1,2,3,4]), [{}]
95
+ assert_solutions permutation([3,2,1,4], [1,2,3,4]), [{}]
96
+ assert_solutions permutation([3,2,:A,4], [1,2,3,4]), [
97
+ { A: 1 }
98
+ ]
99
+ assert_solutions permutation([3,2,:A,:B], [1,2,3,4]), [
100
+ { A: 1, B: 4 },
101
+ { A: 4, B: 1 },
102
+ ]
103
+ assert_solutions permutation([3,2,:A,4], [1,2,:C,4]), [
104
+ { A: 1, C: 3 }
105
+ ]
106
+ assert_solutions permutation([2,3,1], :L), [
107
+ { L: [2, 3, 1] },
108
+ { L: [2, 1, 3] },
109
+ { L: [3, 2, 1] },
110
+ { L: [3, 1, 2] },
111
+ { L: [1, 2, 3] },
112
+ { L: [1, 3, 2] }
113
+ ]
114
+ end
115
+
116
+ it 'solves the Einstein / Zebra riddle' do
117
+ warn name
118
+ builtin :is, :member, :is_noteq
119
+ predicate :same, :not_same, :left, :beside, :houses
120
+
121
+ same(:X,:X).fact!
122
+
123
+ not_same(:X,:X).cut_falicy!
124
+ not_same(:X,:Y).fact!
125
+
126
+ (1..5).to_a.each_cons(2) do |left, right|
127
+ left(left,right).fact!
128
+ beside(left,right).fact!
129
+ beside(right,left).fact!
130
+ end
131
+
132
+ HOUSES = [1, 2, 3, 4, 5 ]
133
+
134
+ PEOPLE = [:Brit, :Dane, :Swede, :German, :Norwegian]
135
+ SMOKES = [:Blend, :Pallmall, :Dunhill, :Winfield, :Rothmans ]
136
+ PETS = [:Dog, :Cats, :Fish, :Birds, :Horses ]
137
+ COLOURS = [:Red, :Blue, :Green, :White, :Yellow ]
138
+ DRINKS = [:Tea, :Beer, :Milk, :Water, :Coffee ]
139
+
140
+ FISH_INDEX = PETS.index(:Fish)
141
+
142
+ houses(:People, :Smokes, :Pets, :Colours, :Drinks) << [
143
+ same(:Milk, 3), # 8. The man living in the house right in the center drinks milk.
144
+ same(:Norwegian, 1), # 9. The Norwegian lives in the first house.
145
+ left(:Green, :White), # 4. The green house is on the left of the white house.
146
+ beside(:Norwegian, :Blue), # 14. The Norwegian lives next to the blue house.
147
+ not_same(:Blue, :White),
148
+ beside(:Blend, :Water), # 15. The man who smokes Blend has a neighbour who drinks water.
149
+ not_same(:Milk, :Water),
150
+ beside(:Horses, :Dunhill), # 11. The man who keeps horses lives next to the man who smokes Dunhill.
151
+ same(:Yellow, :Dunhill), # 7. The owner of the yellow house smokes Dunhill.
152
+ same(:Green, :Coffee), # 5. The green house owner drinks coffee.
153
+ not_same(:Milk, :Coffee),
154
+ not_same(:Water, :Coffee),
155
+ is_noteq(:Red, HOUSES, :Green, :White, :Blue, :Yellow),
156
+ same(:Brit, :Red), # 1. The Brit lives in a red house.
157
+ not_same(:Brit, :Norwegian),
158
+ beside(:Blend, :Cats), # 10. The man who smokes Blend lives next to the one who keeps cats.
159
+ not_same(:Horses, :Cats),
160
+ is_noteq(:Tea, HOUSES, :Coffee, :Milk, :Water),
161
+ same(:Dane, :Tea), # 3. The Dane drinks tea.
162
+ is_noteq(:Beer, HOUSES, :Tea, :Coffee, :Milk, :Water),
163
+ same(:Winfield, :Beer), # 12. The owner who smokes Winfield drinks beer.
164
+ is_noteq(:German, HOUSES, :Norwegian, :Dane, :Brit),
165
+ is_noteq(:Swede, HOUSES, :German, :Norwegian, :Dane, :Brit),
166
+ same(:Swede, :Dog), # 2. The Swede keeps a dog.
167
+ is_noteq(:Pallmall, HOUSES, :Dunhill, :Blend, :Winfield),
168
+ is_noteq(:Rothmans, HOUSES, :Pallmall, :Dunhill, :Blend, :Winfield),
169
+ same(:Pallmall, :Birds), # 6. The person who smokes Pall Mall keeps birds.
170
+ same(:German, :Rothmans), # 13. The German smokes Rothmans.
171
+ is_noteq(:Fish, HOUSES, :Dog, :Birds, :Cats, :Horses),
172
+
173
+ same(:People, PEOPLE),
174
+ same(:Smokes, SMOKES),
175
+ same(:Pets, PETS),
176
+ same(:Colours, COLOURS),
177
+ same(:Drinks, DRINKS),
178
+ ]
179
+
180
+ solutions = assert_solutions houses(:People, :Smokes, :Pets, :Colours, :Drinks), [
181
+ {
182
+ People: [3, 2, 5, 4, 1],
183
+ Smokes: [2, 3, 1, 5, 4],
184
+ Pets: [5, 1, 4, 3, 2],
185
+ Colours: [3, 2, 4, 5, 1],
186
+ Drinks: [2, 5, 3, 1, 4]
187
+ }
188
+ ], goals: 1873
189
+
190
+ solution = solutions.first
191
+
192
+ assert_equal :German, PEOPLE[solution[:People].index(solution[:Pets][FISH_INDEX])]
193
+
194
+ def house_details(solution, h)
195
+ [
196
+ PEOPLE[solution[:People].index(h)],
197
+ SMOKES[solution[:Smokes].index(h)],
198
+ PETS[solution[:Pets].index(h)],
199
+ COLOURS[solution[:Colours].index(h)],
200
+ DRINKS[solution[:Drinks].index(h)]
201
+ ]
202
+ end
203
+
204
+ assert_equal [:Norwegian, :Dunhill, :Cats, :Yellow, :Water ], house_details(solution, 1)
205
+ assert_equal [:Dane, :Blend, :Horses, :Blue, :Tea ], house_details(solution, 2)
206
+ assert_equal [:Brit, :Pallmall, :Birds, :Red, :Milk ], house_details(solution, 3)
207
+ assert_equal [:German, :Rothmans, :Fish, :Green, :Coffee], house_details(solution, 4)
208
+ assert_equal [:Swede, :Winfield, :Dog, :White, :Beer ], house_details(solution, 5)
209
+ end
210
+
211
+ it 'instantiates lists using member and length builtin predicates' do
212
+ builtin :member, :length
213
+ predicate :lists
214
+
215
+ lists(:L) << [
216
+ member(:N,[1,2,3,4,5]),
217
+ length(:L, :N),
218
+ member(:N, :L)
219
+ ]
220
+
221
+ assert_solutions lists(:L), [
222
+ { L: [1] },
223
+ { L: [2, nil] },
224
+ { L: [nil, 2] },
225
+ { L: [3, nil, nil] },
226
+ { L: [nil, 3, nil] },
227
+ { L: [nil, nil, 3] },
228
+ { L: [4, nil, nil, nil] },
229
+ { L: [nil, 4, nil, nil] },
230
+ { L: [nil, nil, 4, nil] },
231
+ { L: [nil, nil, nil, 4] },
232
+ { L: [5, nil, nil, nil, nil] },
233
+ { L: [nil, 5, nil, nil, nil] },
234
+ { L: [nil, nil, 5, nil, nil] },
235
+ { L: [nil, nil, nil, 5, nil] },
236
+ { L: [nil, nil, nil, nil, 5] },
237
+ ], goals: 13
238
+ end
239
+
240
+ it 'implements a prime number search' do
241
+ builtin :gtr, :is, :noteq, :between
242
+ predicate :prime, :search_prime
243
+
244
+ prime(2).fact!
245
+ prime(3).fact!
246
+ prime(:X) << [
247
+ between(:X, 4, 20000),
248
+ is(:X_mod_2, :X){|x| x % 2 },
249
+ noteq(:X_mod_2, 0),
250
+ search_prime(:X, 3),
251
+ ]
252
+
253
+ search_prime(:X, :N) << [
254
+ is(:N_squared, :N){|n| n ** 2 },
255
+ gtr(:N_squared, :X),
256
+ :CUT
257
+ ]
258
+
259
+ search_prime(:X, :N) << [
260
+ is(:X_mod_N, :X, :N){|x,n| x % n },
261
+ noteq(:X_mod_N, 0),
262
+ is(:M, :N){|n| n + 2 },
263
+ :CUT,
264
+ search_prime(:X, :M),
265
+ ]
266
+
267
+ known_primes = [
268
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
269
+ 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199,
270
+ 211, 223, 227, 229
271
+ ]
272
+
273
+ assert_equal known_primes, prime(:number).solve_for(:number, max_solutions: 50)
274
+ assert_equal 3016, Goal.goal_count
275
+ end
276
+
277
+ end