compsci 0.3.0.1 → 0.3.2.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.
- checksums.yaml +5 -5
- data/README.md +138 -79
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/compsci.gemspec +2 -2
- data/examples/binary_search_tree.rb +43 -8
- data/examples/complete_tree.rb +21 -22
- data/examples/flex_node.rb +117 -0
- data/examples/heap.rb +40 -2
- data/examples/heap_push.rb +7 -1
- data/examples/ternary_search_tree.rb +30 -0
- data/examples/tree.rb +27 -25
- data/lib/compsci/complete_tree.rb +6 -7
- data/lib/compsci/fit.rb +17 -0
- data/lib/compsci/flex_node.rb +90 -0
- data/lib/compsci/heap.rb +1 -2
- data/lib/compsci/names/pokemon.rb +62 -0
- data/lib/compsci/node.rb +109 -59
- data/lib/compsci/simplex/parse.rb +10 -6
- data/lib/compsci/simplex.rb +19 -20
- data/lib/compsci/timer.rb +61 -1
- data/lib/compsci.rb +10 -1
- data/test/bench/fibonacci.rb +29 -128
- data/test/bench/flex_node.rb +30 -0
- data/test/complete_tree.rb +16 -14
- data/test/compsci.rb +25 -0
- data/test/fibonacci.rb +6 -5
- data/test/fit.rb +84 -42
- data/test/flex_node.rb +226 -0
- data/test/heap.rb +46 -46
- data/test/names.rb +95 -56
- data/test/node.rb +177 -85
- data/test/simplex_parse.rb +23 -15
- data/test/timer.rb +10 -10
- metadata +17 -17
- data/examples/tree_push.rb +0 -72
- data/lib/compsci/binary_search_tree.rb +0 -86
- data/lib/compsci/tree.rb +0 -142
- data/test/bench/tree.rb +0 -31
- data/test/binary_search_tree.rb +0 -98
- data/test/tree.rb +0 -200
data/examples/tree.rb
CHANGED
@@ -1,42 +1,44 @@
|
|
1
1
|
require 'compsci/node'
|
2
|
-
require 'compsci/tree'
|
3
|
-
require 'compsci/timer'
|
4
2
|
|
5
3
|
include CompSci
|
6
4
|
|
7
5
|
puts <<EOF
|
6
|
+
|
8
7
|
#
|
9
|
-
#
|
8
|
+
# Fill up and display some trees
|
10
9
|
#
|
11
10
|
|
12
11
|
EOF
|
13
12
|
|
14
13
|
vals = Array.new(30) { rand 99 }
|
14
|
+
puts "vals: #{vals.inspect}"
|
15
15
|
|
16
|
-
[
|
17
|
-
# start with the same vals for each class
|
16
|
+
[2, 3, 4].each { |children|
|
18
17
|
my_vals = vals.dup
|
19
|
-
p my_vals
|
20
|
-
tree = tree_class.new(ChildFlexNode, my_vals.shift)
|
21
|
-
tree.push my_vals.shift until my_vals.empty?
|
22
|
-
p tree
|
23
|
-
puts tree.display(width: 80)
|
24
|
-
puts
|
25
|
-
visited = []
|
26
|
-
tree.df_search { |n|
|
27
|
-
visited << n
|
28
|
-
false # or n.value > 90
|
29
|
-
}
|
30
|
-
puts "df_search visited: %s" % visited.join(' ')
|
31
|
-
puts
|
32
|
-
puts
|
33
18
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
19
|
+
puts <<EOF
|
20
|
+
|
21
|
+
#
|
22
|
+
# Children: #{children}
|
23
|
+
#
|
24
|
+
|
25
|
+
EOF
|
26
|
+
|
27
|
+
|
28
|
+
root = Node.new my_vals.shift, children: children
|
29
|
+
nodes = [root]
|
30
|
+
until my_vals.empty?
|
31
|
+
new_nodes = []
|
32
|
+
nodes.each { |node|
|
33
|
+
children.times { |i|
|
34
|
+
node[i] = Node.new my_vals.shift, children: children
|
35
|
+
}
|
36
|
+
new_nodes += node.children
|
37
|
+
}
|
38
|
+
nodes = new_nodes
|
39
|
+
end
|
40
|
+
p root
|
40
41
|
puts
|
42
|
+
puts root.display(width: 80)
|
41
43
|
puts
|
42
44
|
}
|
@@ -1,10 +1,8 @@
|
|
1
1
|
module CompSci
|
2
|
-
# A
|
2
|
+
# A CompleteTree can very efficiently use an array for storage using
|
3
3
|
# simple arithmetic to determine parent child relationships.
|
4
4
|
#
|
5
|
-
|
6
|
-
#
|
7
|
-
class CompleteNaryTree
|
5
|
+
class CompleteTree
|
8
6
|
# integer math maps several children to one parent
|
9
7
|
def self.parent_idx(idx, n)
|
10
8
|
(idx-1) / n
|
@@ -67,6 +65,7 @@ module CompSci
|
|
67
65
|
puts "not yet"
|
68
66
|
end
|
69
67
|
|
68
|
+
# TODO: fixme
|
70
69
|
def display(width: 80)
|
71
70
|
str = ''
|
72
71
|
old_level = 0
|
@@ -89,19 +88,19 @@ module CompSci
|
|
89
88
|
alias_method :to_s, :display
|
90
89
|
end
|
91
90
|
|
92
|
-
class CompleteBinaryTree <
|
91
|
+
class CompleteBinaryTree < CompleteTree
|
93
92
|
def initialize(array: [])
|
94
93
|
super(array: array, child_slots: 2)
|
95
94
|
end
|
96
95
|
end
|
97
96
|
|
98
|
-
class CompleteTernaryTree <
|
97
|
+
class CompleteTernaryTree < CompleteTree
|
99
98
|
def initialize(array: [])
|
100
99
|
super(array: array, child_slots: 3)
|
101
100
|
end
|
102
101
|
end
|
103
102
|
|
104
|
-
class CompleteQuaternaryTree <
|
103
|
+
class CompleteQuaternaryTree < CompleteTree
|
105
104
|
def initialize(array: [])
|
106
105
|
super(array: array, child_slots: 4)
|
107
106
|
end
|
data/lib/compsci/fit.rb
CHANGED
@@ -153,5 +153,22 @@ module CompSci
|
|
153
153
|
|
154
154
|
return Math.exp(a), b, self.error(xys) { |x| (Math.exp(a) * (x ** b)) }
|
155
155
|
end
|
156
|
+
|
157
|
+
def self.predict(model, a, b, x)
|
158
|
+
case model
|
159
|
+
when :constant
|
160
|
+
a
|
161
|
+
when :logarithmic
|
162
|
+
a + b * Math.log(x)
|
163
|
+
when :linear
|
164
|
+
a + b * x
|
165
|
+
when :exponential
|
166
|
+
a * Math::E ** (b * x)
|
167
|
+
when :power
|
168
|
+
a * x ** b
|
169
|
+
else
|
170
|
+
raise("unknown model: #{model}")
|
171
|
+
end
|
172
|
+
end
|
156
173
|
end
|
157
174
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'compsci/node'
|
2
|
+
|
3
|
+
module CompSci
|
4
|
+
#
|
5
|
+
# FlexNodes accumulate children; no child gaps
|
6
|
+
#
|
7
|
+
|
8
|
+
# FlexNode API is #add_child, #add_parent, #new_child
|
9
|
+
|
10
|
+
class FlexNode < Node
|
11
|
+
def initialize(value, metadata: {})
|
12
|
+
@value = value
|
13
|
+
@children = []
|
14
|
+
@metadata = metadata
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_child(node)
|
18
|
+
@children << node
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_child(value)
|
22
|
+
self.add_child self.class.new(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_parent(node)
|
26
|
+
node.add_child(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def df_search(&blk)
|
30
|
+
return self if yield self
|
31
|
+
stop_node = nil
|
32
|
+
@children.each { |child|
|
33
|
+
stop_node = child.df_search(&blk)
|
34
|
+
break if stop_node
|
35
|
+
}
|
36
|
+
stop_node
|
37
|
+
end
|
38
|
+
|
39
|
+
def bf_search(&blk)
|
40
|
+
destinations = [self]
|
41
|
+
while !destinations.empty?
|
42
|
+
node = destinations.shift
|
43
|
+
return node if yield node
|
44
|
+
destinations += node.children
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def open_parent?(child_slots)
|
50
|
+
@children.size < child_slots
|
51
|
+
end
|
52
|
+
|
53
|
+
def open_parent(child_slots)
|
54
|
+
self.bf_search { |n| n.open_parent?(child_slots) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def push(value, child_slots)
|
58
|
+
self.open_parent(child_slots).new_child value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ChildFlexNode < FlexNode
|
63
|
+
attr_accessor :parent
|
64
|
+
|
65
|
+
def initialize(value, metadata: {})
|
66
|
+
super(value, metadata: metadata)
|
67
|
+
@parent = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
# O(log n) recursive
|
71
|
+
def gen
|
72
|
+
@parent ? @parent.gen + 1 : 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def siblings
|
76
|
+
@parent ? @parent.children : []
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_child(node)
|
80
|
+
node.parent ||= self
|
81
|
+
raise "node has a parent: #{node.parent}" if node.parent != self
|
82
|
+
@children << node
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_parent(node)
|
86
|
+
@parent = node
|
87
|
+
@parent.add_child(self)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/compsci/heap.rb
CHANGED
@@ -18,7 +18,7 @@ require 'compsci/complete_tree'
|
|
18
18
|
# swap nodes at each layer of the tree, and there are log(n, base b) layers
|
19
19
|
# to the tree.
|
20
20
|
#
|
21
|
-
class CompSci::Heap < CompSci::
|
21
|
+
class CompSci::Heap < CompSci::CompleteTree
|
22
22
|
# * defaults to a MaxHeap, with the largest node at the root
|
23
23
|
# * specify a minheap with minheap: true or cmp_val: -1
|
24
24
|
#
|
@@ -65,7 +65,6 @@ class CompSci::Heap < CompSci::CompleteNaryTree
|
|
65
65
|
#
|
66
66
|
def sift_up(idx)
|
67
67
|
return self if idx <= 0
|
68
|
-
# print '.'
|
69
68
|
pidx = self.class.parent_idx(idx, @child_slots)
|
70
69
|
if !self.heapish?(pidx, idx)
|
71
70
|
@array[idx], @array[pidx] = @array[pidx], @array[idx] # swap
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'compsci/names'
|
2
|
+
|
3
|
+
module CompSci::Names::Pokemon
|
4
|
+
DATAFILE = File.join(__dir__, 'pokemon.txt')
|
5
|
+
|
6
|
+
def self.array
|
7
|
+
@@array ||= self.read_file
|
8
|
+
@@array
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.hash
|
12
|
+
@@hash ||= self.read_hash
|
13
|
+
@@hash
|
14
|
+
end
|
15
|
+
|
16
|
+
# an array of all the names
|
17
|
+
def self.read_file(path: DATAFILE)
|
18
|
+
File.readlines(path).map { |n| n.chomp }
|
19
|
+
end
|
20
|
+
|
21
|
+
# return an array, possibly empty, if all: true
|
22
|
+
# return a string, possibly nil, if all: false
|
23
|
+
def self.grep(rgx, path: DATAFILE, all: false)
|
24
|
+
ary = []
|
25
|
+
File.open(path).each_line { |l|
|
26
|
+
if l.match rgx
|
27
|
+
ary << l.chomp
|
28
|
+
break unless all
|
29
|
+
end
|
30
|
+
}
|
31
|
+
all ? ary : ary.first
|
32
|
+
end
|
33
|
+
|
34
|
+
# a hash of all the names, keyed by the first letter
|
35
|
+
def self.read_hash(path: DATAFILE)
|
36
|
+
hsh = Hash.new { |h, k| h[k] = [] }
|
37
|
+
File.open(path).each_line { |line|
|
38
|
+
hsh[line.chr] << line.chomp
|
39
|
+
}
|
40
|
+
hsh
|
41
|
+
end
|
42
|
+
|
43
|
+
# convert 0-25 to a lowercase alpha
|
44
|
+
def self.key(val)
|
45
|
+
if val.is_a?(String)
|
46
|
+
if val.match(/^[0-9]/)
|
47
|
+
val = val[0..1].to_i
|
48
|
+
elsif val.match(/^[a-z]/i)
|
49
|
+
return val.downcase[0]
|
50
|
+
else
|
51
|
+
raise(ArgumentError, "can't handle #{val}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
CompSci::Names::ENGLISH_LOWER[val.to_i]
|
55
|
+
end
|
56
|
+
|
57
|
+
# return a pokemon sampled from those keyed by val
|
58
|
+
def self.sample(val)
|
59
|
+
ary = self.hash[self.key(val)]
|
60
|
+
ary.sample if ary
|
61
|
+
end
|
62
|
+
end
|
data/lib/compsci/node.rb
CHANGED
@@ -1,67 +1,141 @@
|
|
1
1
|
module CompSci
|
2
2
|
# has a value and an array of children; allows child gaps
|
3
3
|
class Node
|
4
|
-
|
4
|
+
def self.display_line(nodes: [], width: 80)
|
5
|
+
block_width = [width / nodes.size, 1].max
|
6
|
+
nodes.map { |node|
|
7
|
+
str = node ? node.to_s : '_'
|
8
|
+
space = [(block_width + str.size) / 2, str.size + 1].max
|
9
|
+
str.ljust(space, ' ').rjust(block_width, ' ')
|
10
|
+
}.join
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :value, :metadata
|
5
14
|
attr_reader :children
|
6
15
|
|
7
|
-
def initialize(value, children:
|
16
|
+
def initialize(value, children: 2, metadata: {})
|
8
17
|
@value = value
|
9
|
-
|
10
|
-
|
11
|
-
else
|
12
|
-
@children = children
|
13
|
-
end
|
14
|
-
# @metadata = {}
|
18
|
+
@children = Array.new(children)
|
19
|
+
@metadata = metadata
|
15
20
|
end
|
16
21
|
|
17
|
-
def
|
18
|
-
@
|
22
|
+
def [](idx)
|
23
|
+
@children[idx]
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
# of the Node API.
|
23
|
-
def set_child(idx, node)
|
26
|
+
def []=(idx, node)
|
24
27
|
@children[idx] = node
|
25
28
|
end
|
26
29
|
|
30
|
+
def to_s
|
31
|
+
@value.to_s
|
32
|
+
end
|
33
|
+
|
27
34
|
def inspect
|
28
35
|
"#<%s:0x%0xi @value=%s @children=[%s]>" %
|
29
|
-
[self.class,
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
[self.class, self.object_id, self, @children.join(', ')]
|
37
|
+
end
|
38
|
+
|
39
|
+
def display(width: 80)
|
40
|
+
lines = [self.class.display_line(nodes: [self], width: width)]
|
41
|
+
nodes = @children
|
42
|
+
while nodes.any? { |n| !n.nil? }
|
43
|
+
lines << self.class.display_line(nodes: nodes, width: width)
|
44
|
+
if nodes.size > 3**7
|
45
|
+
lines << "nodes.size = #{nodes.size}; abort render"
|
46
|
+
break
|
47
|
+
end
|
48
|
+
nodes = nodes.reduce([]) { |memo, n|
|
49
|
+
memo + Array.new(@children.size) { |i| n and n.children[i] }
|
50
|
+
}
|
51
|
+
end
|
52
|
+
lines.join("\n")
|
33
53
|
end
|
34
54
|
end
|
35
55
|
|
56
|
+
# TODO: implement key with @metadata !?!?!?!?
|
57
|
+
|
36
58
|
# adds a key to Node; often the key is used to place the node in the
|
37
59
|
# tree, independent of the value; e.g. key=priority, value=process_id
|
38
60
|
class KeyNode < Node
|
39
|
-
|
61
|
+
class DuplicateKey < RuntimeError; end
|
62
|
+
class SearchError < RuntimeError; end
|
63
|
+
|
64
|
+
attr_reader :key, :duplicates
|
65
|
+
|
66
|
+
def self.key_cmp_idx(new_key, key, child_slots: 2, duplicates: false)
|
67
|
+
if child_slots < 2
|
68
|
+
raise(ArgumentError, "child_slots: #{child_slots} too small")
|
69
|
+
elsif child_slots == 2
|
70
|
+
raise(DuplicateKey, "#{new_key}") if new_key == key and !duplicates
|
71
|
+
new_key < key ? 0 : 1
|
72
|
+
elsif child_slots == 3
|
73
|
+
(new_key <=> key) + 1
|
74
|
+
else
|
75
|
+
raise(ArgumentError: "child_slots: #{child_slots} too big")
|
76
|
+
end
|
77
|
+
end
|
40
78
|
|
41
|
-
def initialize(val, key: nil, children:
|
42
|
-
@key = key
|
79
|
+
def initialize(val, key: nil, children: 2, duplicates: false)
|
43
80
|
super(val, children: children)
|
81
|
+
@key, @duplicates = key, duplicates
|
44
82
|
end
|
45
83
|
|
46
84
|
def to_s
|
47
|
-
[key, value].join(':')
|
85
|
+
[@key, @value].join(':')
|
86
|
+
end
|
87
|
+
|
88
|
+
# which child idx should handle key?
|
89
|
+
def cidx(key)
|
90
|
+
self.class.key_cmp_idx(key, @key,
|
91
|
+
child_slots: @children.size,
|
92
|
+
duplicates: @duplicates)
|
48
93
|
end
|
49
|
-
end
|
50
94
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
95
|
+
# works for 2 or 3 children
|
96
|
+
def insert(key, val)
|
97
|
+
idx = self.cidx(key)
|
98
|
+
if @children[idx]
|
99
|
+
@children[idx].insert(key, val)
|
100
|
+
else
|
101
|
+
@children[idx] = self.class.new(val, key: key,
|
102
|
+
children: @children.size,
|
103
|
+
duplicates: @duplicates)
|
104
|
+
end
|
57
105
|
end
|
58
106
|
|
59
|
-
|
60
|
-
|
107
|
+
# returns a single node for binary search
|
108
|
+
# returns multiple nodes for ternary search
|
109
|
+
def search(key)
|
110
|
+
if @children.size == 2
|
111
|
+
self.search2(key)
|
112
|
+
elsif @children.size == 3
|
113
|
+
self.search3(key)
|
114
|
+
else
|
115
|
+
raise(SearchError, "can't search for @children.size children")
|
116
|
+
end
|
61
117
|
end
|
62
118
|
|
63
|
-
|
64
|
-
|
119
|
+
# returns a single node or nil
|
120
|
+
def search2(key)
|
121
|
+
return self if key == @key
|
122
|
+
child = @children[self.cidx(key)]
|
123
|
+
child.search(key) if child
|
124
|
+
end
|
125
|
+
|
126
|
+
# returns an array of nodes, possibly empty
|
127
|
+
def search3(key)
|
128
|
+
if key == @key
|
129
|
+
nodes = [self]
|
130
|
+
node = @children[1]
|
131
|
+
while node
|
132
|
+
nodes << node
|
133
|
+
node = node.children[1]
|
134
|
+
end
|
135
|
+
return nodes
|
136
|
+
end
|
137
|
+
child = @children[self.cidx(key)]
|
138
|
+
child ? child.search(key) : []
|
65
139
|
end
|
66
140
|
end
|
67
141
|
|
@@ -69,7 +143,7 @@ module CompSci
|
|
69
143
|
class ChildNode < Node
|
70
144
|
attr_accessor :parent
|
71
145
|
|
72
|
-
def initialize(value, children:
|
146
|
+
def initialize(value, children: 2)
|
73
147
|
@parent = nil
|
74
148
|
super(value, children: children)
|
75
149
|
end
|
@@ -83,35 +157,11 @@ module CompSci
|
|
83
157
|
@parent ? @parent.children : []
|
84
158
|
end
|
85
159
|
|
86
|
-
|
160
|
+
# update both sides of the relationship
|
161
|
+
def []=(idx, node)
|
87
162
|
node.parent ||= self
|
88
163
|
raise "node has a parent: #{node.parent}" if node.parent != self
|
89
164
|
@children[idx] = node
|
90
165
|
end
|
91
|
-
|
92
|
-
def set_parent(idx, node)
|
93
|
-
@parent = node
|
94
|
-
@parent.set_child(idx, self)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# ChildNode which accumulates children with no gaps
|
99
|
-
# It meets the FlexNode API but does not inherit from FlexNode since it
|
100
|
-
# needs to reimplement each method; instead get parent stuff from ChildNode
|
101
|
-
class ChildFlexNode < ChildNode
|
102
|
-
def add_child(node)
|
103
|
-
node.parent ||= self
|
104
|
-
raise "node has a parent: #{node.parent}" if node.parent != self
|
105
|
-
@children << node
|
106
|
-
end
|
107
|
-
|
108
|
-
def new_child(value)
|
109
|
-
self.add_child self.class.new(value)
|
110
|
-
end
|
111
|
-
|
112
|
-
def add_parent(node)
|
113
|
-
@parent = node
|
114
|
-
@parent.add_child(self)
|
115
|
-
end
|
116
166
|
end
|
117
167
|
end
|
@@ -82,18 +82,22 @@ class CompSci::Simplex
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
def self.problem(
|
85
|
+
def self.problem(**kwargs)
|
86
|
+
self.new(*self.get_params(**kwargs))
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.get_params(maximize: nil, constraints: [], **kwargs)
|
86
90
|
if maximize
|
87
|
-
|
91
|
+
expr, maximize = maximize, true
|
88
92
|
elsif kwargs[:minimize]
|
89
|
-
|
93
|
+
expr, maximize = kwargs[:minimize], false
|
90
94
|
else
|
91
95
|
raise(ArgumentError, "one of maximize/minimize expected")
|
92
96
|
end
|
93
|
-
unless
|
97
|
+
unless expr.is_a?(String)
|
94
98
|
raise(ArgumentError, "bad expr: #{expr} (#{expr.class})")
|
95
99
|
end
|
96
|
-
obj_cof = Parse.expression(
|
100
|
+
obj_cof = Parse.expression(expr)
|
97
101
|
|
98
102
|
c = [] # coefficients of objective expression
|
99
103
|
a = [] # array (per constraint) of the inequality's lhs coefficients
|
@@ -116,7 +120,7 @@ class CompSci::Simplex
|
|
116
120
|
a.push cofs
|
117
121
|
b.push rhs
|
118
122
|
}
|
119
|
-
|
123
|
+
[c, a, b]
|
120
124
|
end
|
121
125
|
|
122
126
|
def self.maximize(expression, *ineqs)
|
data/lib/compsci/simplex.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
require 'compsci'
|
2
|
-
|
3
1
|
# note, this work is based on https://github.com/rickhull/simplex
|
4
2
|
# which was forked in 2017 from https://github.com/danlucraft/simplex
|
5
3
|
# which had its last commit in 2013
|
6
4
|
|
5
|
+
module CompSci; end
|
6
|
+
|
7
7
|
class CompSci::Simplex
|
8
8
|
DEFAULT_MAX_PIVOTS = 10_000
|
9
9
|
|
@@ -26,35 +26,36 @@ class CompSci::Simplex
|
|
26
26
|
@max_pivots = DEFAULT_MAX_PIVOTS
|
27
27
|
|
28
28
|
# Problem dimensions; these never change
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
29
|
+
@num_free_vars = num_vars
|
30
|
+
@num_basic_vars = num_inequalities
|
31
|
+
@total_vars = @num_free_vars + @num_basic_vars
|
32
32
|
|
33
33
|
# Set up initial matrix A and vectors b, c
|
34
|
-
|
34
|
+
# store all input values as Rational (via #rationalize)
|
35
|
+
@c = c.map { |flt| -1 * flt.rationalize } + Array.new(@num_basic_vars, 0)
|
35
36
|
@a = a.map.with_index { |ary, i|
|
36
|
-
if ary.size != @
|
37
|
+
if ary.size != @num_free_vars
|
37
38
|
raise ArgumentError, "a is inconsistent"
|
38
39
|
end
|
39
|
-
#
|
40
|
-
ary
|
40
|
+
# add identity matrix
|
41
|
+
ary.map { |flt| flt.rationalize } +
|
42
|
+
Array.new(@num_basic_vars) { |ci| ci == i ? 1 : 0 }
|
41
43
|
}
|
42
|
-
@b = b
|
44
|
+
@b = b.map { |flt| flt.rationalize }
|
43
45
|
|
44
|
-
|
45
|
-
@basic_vars = (@num_non_slack_vars...@num_vars).to_a
|
46
|
+
@basic_vars = (@num_free_vars...@total_vars).to_a
|
46
47
|
self.update_solution
|
47
48
|
end
|
48
49
|
|
49
50
|
# does not modify vector / matrix
|
50
51
|
def update_solution
|
51
|
-
@x = Array.new(@
|
52
|
+
@x = Array.new(@total_vars, 0)
|
52
53
|
|
53
54
|
@basic_vars.each { |basic_var|
|
54
55
|
idx = nil
|
55
|
-
@
|
56
|
+
@num_basic_vars.times { |i|
|
56
57
|
if @a[i][basic_var] == 1
|
57
|
-
idx =i
|
58
|
+
idx = i
|
58
59
|
break
|
59
60
|
end
|
60
61
|
}
|
@@ -78,7 +79,7 @@ class CompSci::Simplex
|
|
78
79
|
end
|
79
80
|
|
80
81
|
def current_solution
|
81
|
-
@x[0...@
|
82
|
+
@x[0...@num_free_vars]
|
82
83
|
end
|
83
84
|
|
84
85
|
def can_improve?
|
@@ -121,7 +122,7 @@ class CompSci::Simplex
|
|
121
122
|
}
|
122
123
|
|
123
124
|
# update A and B
|
124
|
-
@
|
125
|
+
@num_basic_vars.times { |i|
|
125
126
|
next if i == pivot_row
|
126
127
|
r = @a[i][pivot_column]
|
127
128
|
@a[i] = @a[i].map.with_index { |val, j| val - r * @a[pivot_row][j] }
|
@@ -134,7 +135,7 @@ class CompSci::Simplex
|
|
134
135
|
def pivot_row(column_ix)
|
135
136
|
min_ratio = nil
|
136
137
|
idx = nil
|
137
|
-
@
|
138
|
+
@num_basic_vars.times { |i|
|
138
139
|
a, b = @a[i][column_ix], @b[i]
|
139
140
|
next if a == 0 or (b < 0) ^ (a < 0)
|
140
141
|
ratio = Rational(b, a)
|
@@ -168,6 +169,4 @@ class CompSci::Simplex
|
|
168
169
|
lines.insert(1, "-"*max_line_length)
|
169
170
|
lines.join("\n")
|
170
171
|
end
|
171
|
-
|
172
|
-
|
173
172
|
end
|