oakproof 0.7.1

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.
data/src/utilities.rb ADDED
@@ -0,0 +1,361 @@
1
+ require 'io/console'
2
+
3
+ class WordWrapper
4
+ def initialize
5
+ @indent = 2
6
+ @width = IO.console.winsize[1] - 1 # subtract 1 for portability
7
+ @position = 0
8
+ end
9
+
10
+ def clear?
11
+ @position == 0
12
+ end
13
+
14
+ def print s
15
+ s.to_s.each_line {|line|
16
+ if line.end_with? "\n"
17
+ puts line.chomp
18
+ else
19
+ word_wrap line
20
+ end
21
+ }
22
+ end
23
+
24
+ def puts s = ''
25
+ print s
26
+ $stdout.puts
27
+ @position = 0
28
+ end
29
+
30
+ def word_wrap s
31
+ if @position == :wrapping # waiting to go to next line and indent
32
+ s = s.lstrip
33
+ return if s.empty?
34
+ $stdout.puts
35
+ $stdout.print ' ' * @indent
36
+ @position = @indent
37
+ word_wrap s
38
+ elsif s.length <= @width-@position # fits on this line
39
+ $stdout.print s
40
+ @position += s.length
41
+ else # wrap to next line
42
+ i = s.rindex ' ', @width-@position
43
+ if i # break it at the space
44
+ $stdout.print s[0...i].rstrip
45
+ s = s[i..-1]
46
+ elsif @position <= @indent # no choice but to truncate it
47
+ $stdout.print s[0...@width-@position]
48
+ s = s[@width-@position..-1]
49
+ end
50
+ @position = :wrapping
51
+ word_wrap s
52
+ end
53
+ end
54
+ end
55
+
56
+ def bind_variables variables, body, quantifier
57
+ if body
58
+ to_bind = variables & body.free_variables # only bind those that need it
59
+ else
60
+ raise unless quantifier == :for_some
61
+ to_bind = variables
62
+ end
63
+ return body if to_bind.empty?
64
+ variables_tree = Tree.new to_bind, []
65
+ Tree.new quantifier, [variables_tree, body].compact
66
+ end
67
+
68
+ def conjuncts trees
69
+ # seems to have no effect on the prover, and is easier to read!
70
+ trees.collect {|tree|
71
+ next [tree] unless tree.operator == :and
72
+ conjuncts tree.subtrees
73
+ }.flatten
74
+ end
75
+
76
+ def conjunction_tree trees
77
+ trees = conjuncts trees
78
+ return nil if trees.empty?
79
+ return trees[0] if trees.size == 1
80
+ Tree.new :and, trees
81
+ end
82
+
83
+ def contains_quantifiers? tree
84
+ Tree::Quantifiers.any? {|quantifier| tree.contains? quantifier}
85
+ end
86
+
87
+ def equal_up_to_variable_names? tree1, tree2, refs1 = {}, refs2 = {}, level = 0
88
+ # check whether tree1 and tree2 are equal up to variable renaming
89
+ return false unless tree1.operator.class == tree2.operator.class
90
+ if tree1.operator.is_a? Symbol
91
+ return false unless tree1.operator == tree2.operator
92
+ return false unless tree1.subtrees.size == tree2.subtrees.size
93
+ pairs = [tree1.subtrees, tree2.subtrees].transpose
94
+ case tree1.operator
95
+ when *Tree::Quantifiers
96
+ last1, last2 = {}, {}
97
+ zipped = tree1.subtrees[0].operator.zip tree2.subtrees[0].operator
98
+ zipped.each_with_index {|(var1, var2), i|
99
+ refs1[var1], last1[var1] = [level,i], refs1[var1]
100
+ refs2[var2], last2[var2] = [level,i], refs2[var2]
101
+ }
102
+ result = pairs[1..-1].all? {|subtree1, subtree2|
103
+ equal_up_to_variable_names? subtree1, subtree2, refs1, refs2, level+1
104
+ }
105
+ zipped.each_with_index {|(var1, var2), i|
106
+ last1[var1] ? (refs1[var1] = last1[var1]) : (refs1.delete var1)
107
+ last2[var2] ? (refs2[var2] = last2[var2]) : (refs2.delete var2)
108
+ }
109
+ result
110
+ when :not, :and, :or, :implies, :iff, :equals, :predicate
111
+ pairs.all? {|subtree1, subtree2|
112
+ equal_up_to_variable_names? subtree1, subtree2, refs1, refs2, level+1
113
+ }
114
+ else raise
115
+ end
116
+ elsif tree1.operator.is_a? String
117
+ if refs1[tree1.operator] and refs2[tree2.operator]
118
+ refs1[tree1.operator] == refs2[tree2.operator] # both variables
119
+ elsif refs1[tree1.operator] or refs2[tree2.operator]
120
+ false # one is a constant, the other is a variable
121
+ else
122
+ tree1.operator == tree2.operator # both constants
123
+ end
124
+ else
125
+ raise
126
+ end
127
+ end
128
+
129
+ def first_orderize tree, by_arity = false
130
+ subtrees = tree.subtrees.collect {|subtree| first_orderize subtree, by_arity}
131
+ if tree.operator == :predicate
132
+ i = (by_arity ? subtrees.size.to_s : '')
133
+ Tree.new :predicate, [Tree.new("apply#{i}", []), *subtrees]
134
+ else
135
+ Tree.new tree.operator, subtrees
136
+ end
137
+ end
138
+
139
+ def implication_tree antecedents, consequent
140
+ antecedent = conjunction_tree antecedents.compact
141
+ return consequent if not antecedent
142
+ Tree.new :implies, [antecedent, consequent]
143
+ end
144
+
145
+ def new_name names, prefix = 'x'
146
+ # make a guess, for speed!
147
+ name = "#{prefix}_#{names.size}"
148
+ return name if not names.include? name
149
+ # otherwise, find the lowest available
150
+ options = (0..names.size).collect {|i| "#{prefix}_#{i}"}
151
+ available = options - names
152
+ raise if available.empty?
153
+ available.first
154
+ end
155
+
156
+ def new_names used, count, prefix = 'x'
157
+ used = used.dup
158
+ count.times.collect {
159
+ name = new_name used, prefix
160
+ used << name
161
+ name
162
+ }
163
+ end
164
+
165
+ def replace_empty_quantifiers tree
166
+ # we assume that the domain is not empty, and replace
167
+ # each occurrence of "there is an x" with a tautology
168
+ if tree.operator == :for_some and not tree.subtrees[1]
169
+ tree_for_true
170
+ else
171
+ subtrees = tree.subtrees.collect {|subtree|
172
+ replace_empty_quantifiers subtree
173
+ }
174
+ Tree.new tree.operator, subtrees
175
+ end
176
+ end
177
+
178
+ def replace_for_at_most_one tree
179
+ # for at most one a,b,c, p[a,b,c]
180
+ # becomes
181
+ # for some a',b',c',
182
+ # for all a,b,c,
183
+ # p[a,b,c] implies (a=a' and b=b' and c=c')
184
+ subtrees = tree.subtrees.collect {|subtree| replace_for_at_most_one subtree}
185
+ if tree.operator == :for_at_most_one
186
+ variables, body = tree.subtrees[0].operator, tree.subtrees[1]
187
+ used = tree.free_variables + variables
188
+ primes = new_names used, variables.size, 'at_most_one'
189
+ equalities = conjunction_tree variables.each_index.collect {|i|
190
+ Tree.new :equals, [Tree.new(variables[i],[]), Tree.new(primes[i],[])]
191
+ }
192
+ tree = body ? Tree.new(:implies, [body, equalities]) : equalities
193
+ tree = Tree.new :for_all, [Tree.new(variables,[]), tree]
194
+ Tree.new :for_some, [Tree.new(primes,[]), tree]
195
+ else
196
+ Tree.new tree.operator, subtrees
197
+ end
198
+ end
199
+
200
+ def self_implication? tree
201
+ tree.operator == :implies and equal_up_to_variable_names? *tree.subtrees
202
+ end
203
+
204
+ def substitute tree, substitution, bound = [] # faster than Set for some reason
205
+ # performs substitution on tree, where substitution keys are strings and
206
+ # values are trees. does not substitute again within the values.
207
+ case tree.operator
208
+ when *Tree::Quantifiers
209
+ return tree if tree.subtrees.size == 1 # empty quantifier body
210
+ variables = tree.subtrees[0].operator
211
+ subtree = substitute tree.subtrees[1], substitution, (bound + variables)
212
+ if subtree.equal? tree.subtrees[1]
213
+ tree
214
+ else
215
+ Tree.new tree.operator, [tree.subtrees[0], subtree]
216
+ end
217
+ when :and, :or, :not, :implies, :iff, :equals, :predicate
218
+ subtrees = tree.subtrees.collect {|subtree|
219
+ substitute subtree, substitution, bound
220
+ }
221
+ if subtrees.each_index.all? {|i| subtrees[i].equal? tree.subtrees[i]}
222
+ tree
223
+ else
224
+ Tree.new tree.operator, subtrees
225
+ end
226
+ when String
227
+ return tree if not substitution.has_key? tree.operator
228
+ return tree if bound.include? tree.operator
229
+ replacement = substitution[tree.operator]
230
+ conflict = (bound & replacement.free_variables).first
231
+ # error if substitution contains quantified variable
232
+ raise SubstituteException, conflict if conflict
233
+ replacement
234
+ else
235
+ raise "unexpected operator #{tree.operator.inspect}"
236
+ end
237
+ end
238
+
239
+ def tree_for_false
240
+ Tree.new :and, [
241
+ Tree.new('false', []),
242
+ Tree.new(:not, [Tree.new('false', [])])
243
+ ]
244
+ end
245
+
246
+ def tree_for_true
247
+ Tree.new :or, [
248
+ Tree.new('true', []),
249
+ Tree.new(:not, [Tree.new('true', [])])
250
+ ]
251
+ end
252
+
253
+ def find_minimal_subsets_from_results array, results = {}
254
+ # find the minimal subsets of array which are true in results, assuming that
255
+ # if a given set is false, all of its subsets are false
256
+ set = array.to_set
257
+ raise unless results.has_key? set
258
+ result = results[set]
259
+ return [] if result == false
260
+ subarrays = array.combination array.size-1
261
+ minimal = subarrays.collect {|subarray|
262
+ find_minimal_subsets_from_results subarray, results
263
+ }.flatten(1).uniq
264
+ (minimal.empty? and result == true) ? [array] : minimal
265
+ end
266
+
267
+ def find_minimal_subsets input_array
268
+ # find the minimal subsets for which yield returns true, assuming that if it
269
+ # returns false for a given set, it will return false for all of its subsets,
270
+ # and that order and duplicates do not matter
271
+ input_array = input_array.uniq
272
+ pending, results = [input_array], {}
273
+ until pending.empty?
274
+ array = pending.shift
275
+ set = array.to_set
276
+ next if results.has_key? set
277
+ if results.any? {|key, result| result == false and set.subset? key}
278
+ results[set] = false
279
+ else
280
+ results[set] = yield array
281
+ end
282
+ next if results[set] == false
283
+ array.combination(array.size-1).each {|subarray| pending << subarray}
284
+ end
285
+ find_minimal_subsets_from_results input_array, results
286
+ end
287
+
288
+ def find_valid_elements array
289
+ # find all elements for which the block returns :valid, assuming that if it
290
+ # returns :invalid on a subset, then no element in that subset is valid
291
+ result = yield array
292
+ return [] if result == :invalid
293
+ if array.size == 1
294
+ case result
295
+ when :valid then return array
296
+ when :unknown then return []
297
+ else raise
298
+ end
299
+ end
300
+ part1, part2 = array[0...array.size/2], array[array.size/2..-1]
301
+ results1 = find_valid_elements(part1) {|subset| yield subset}
302
+ results2 = find_valid_elements(part2) {|subset| yield subset}
303
+ results1 + results2
304
+ end
305
+
306
+ def seek_valid_subset array
307
+ # try to find a subset for which the block returns :valid, assuming that if it
308
+ # returns :invalid on a subset, then no subset of that subset is valid. if
309
+ # unsuccessful, guarantees that there is no valid single-element subset.
310
+ queue = [array]
311
+ until queue.empty?
312
+ subset = queue.shift
313
+ result = yield subset
314
+ return subset if result == :valid
315
+ next if result == :invalid
316
+ next if subset.size == 1
317
+ queue << subset[0...subset.size/2]
318
+ queue << subset[subset.size/2..-1]
319
+ end
320
+ nil
321
+ end
322
+
323
+ def reduce_subset array
324
+ # assuming that the block is truthy for the array, finds a subset for which
325
+ # the block is still truthy, such that no element can be removed from the
326
+ # subset without losing the truthiness of the block.
327
+ targets = 1 # start by assuming there is one target
328
+ target_ratio = 1 / Math::E
329
+ hits, tries = 0, 0
330
+ while true
331
+ sample_fraction = 1 / Math::E ** (1.0 / targets)
332
+ sample_size = (sample_fraction * array.size).round
333
+ # print "hits, tries = #{hits},#{tries} targets = #{targets} "
334
+ # puts "array size = #{array.size} sample size = #{sample_size}"
335
+ if sample_size >= array.size - 1
336
+ sample = array.combination(array.size-1).find {|sample|
337
+ tries += 1
338
+ if yield sample
339
+ hits += 1
340
+ targets -= 1 if hits / tries.to_f > target_ratio and targets > 1
341
+ sample
342
+ else
343
+ targets += 1 if hits / tries.to_f < target_ratio
344
+ nil
345
+ end
346
+ }
347
+ return array if not sample
348
+ array = sample
349
+ else
350
+ sample = array.sample sample_size
351
+ tries += 1
352
+ if yield sample
353
+ array = sample
354
+ hits += 1
355
+ targets -= 1 if hits / tries.to_f > target_ratio and targets > 1
356
+ else
357
+ targets += 1 if hits / tries.to_f < target_ratio
358
+ end
359
+ end
360
+ end
361
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oakproof
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Smith
8
+ autorequire:
9
+ bindir: "."
10
+ cert_chain: []
11
+ date: 2024-06-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ executables:
16
+ - oak
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - "./oak"
21
+ - eprover/eprover-2.3-linux
22
+ - eprover/eprover-2.3-mac
23
+ - eprover/eprover-2.3-windows.exe
24
+ - eprover/eprover.txt
25
+ - src/bindings.rb
26
+ - src/commands.rb
27
+ - src/external prover.rb
28
+ - src/grammar rules.rb
29
+ - src/grammar.rb
30
+ - src/oak.rb
31
+ - src/parser.rb
32
+ - src/proof.rb
33
+ - src/schema.rb
34
+ - src/utilities.rb
35
+ homepage: http://oakproof.org/
36
+ licenses:
37
+ - AGPL-3.0
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.4.22
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: A proof checker focused on simplicity, readability, and ease of use.
58
+ test_files: []