simplex 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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/simplex.rb +137 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 607708dc2b1019f3de26f43a8f714d7ba18a1d27
4
+ data.tar.gz: e05193d59e5237b419560bdb8aa65add6894697e
5
+ SHA512:
6
+ metadata.gz: 57ee4659de8ec4e8660bcc38b412fdd636f300903def27a9b13b1b5baed75a84ead979fb8f7bb84d297dbd17b0c2f8fd0ccfe664463460435a3039ed2b06fc95
7
+ data.tar.gz: 4915382d169cd3e9743ef3bba31f94ef2c3d89c6862d0849f361689435d1984b3ffeb9b118f8a983ee5d4af24668098452af29cd78a1dcfbbdcf957019963320
data/lib/simplex.rb ADDED
@@ -0,0 +1,137 @@
1
+ require 'matrix'
2
+
3
+ class Matrix
4
+ def []=(i, j, x)
5
+ @rows[i][j] = x
6
+ end
7
+ end
8
+
9
+ class Vector
10
+ public :[]=
11
+ end
12
+
13
+ class Simplex
14
+ DEFAULT_MAX_ITERATIONS = 1_000_000
15
+ attr_accessor :max_iterations
16
+
17
+ def initialize(c, a, b)
18
+ @max_iterations = DEFAULT_MAX_ITERATIONS
19
+ # Problem dimensions
20
+ @num_non_slack_vars = a.first.length
21
+ @num_constraints = b.length
22
+ @num_vars = @num_non_slack_vars + @num_constraints
23
+ @x = Array.new(@num_vars)
24
+
25
+ # Set up initial matrix A and vectors b, c
26
+ @c = Vector[*c.map {|c1| -1*c1 } + [0]*@num_constraints]
27
+ @a = Matrix[*a.map {|a1| a1.clone + [0]*@num_constraints}]
28
+ @b = Vector[*b.clone]
29
+ 0.upto(@num_constraints - 1) {|i| @a[i, @num_non_slack_vars + i] = 1 }
30
+
31
+ @basic_vars = ((@num_non_slack_vars)...(@num_vars)).to_a
32
+
33
+ # set initial solution: all non-slack variables = 0
34
+ update_solution
35
+ @solved = false
36
+ end
37
+
38
+ def solve
39
+ return if @solved
40
+ i = 0
41
+ while can_improve? and i < @max_iterations
42
+ i += 1
43
+ pivot_column = entering_variable_ix
44
+ pivot_row = minimum_coefficient_ratio_row_ix(pivot_column)
45
+ leaving_var = leaving_variable(pivot_row)
46
+ @basic_vars.delete(leaving_var)
47
+
48
+ # update objective
49
+ c_ratio = Rational(@c[pivot_column], @a[pivot_row, pivot_column])
50
+ @c = @c - (@a.row(pivot_row)*c_ratio)
51
+
52
+ # update pivot row
53
+ ratio = Rational(1, @a[pivot_row, pivot_column])
54
+ 0.upto(@a.column_count - 1) do |column_ix|
55
+ @a[pivot_row, column_ix] = ratio * @a[pivot_row, column_ix]
56
+ end
57
+ @b[pivot_row] = ratio * @b[pivot_row]
58
+
59
+ # update A and B
60
+ 0.upto(@a.row_count - 1) do |row_ix|
61
+ next if row_ix == pivot_row
62
+ ratio = @a[row_ix, pivot_column]
63
+ 0.upto(@a.column_count - 1) do |column_ix|
64
+ @a[row_ix, column_ix] = @a[row_ix, column_ix] - ratio*@a[pivot_row, column_ix]
65
+ end
66
+ @b[row_ix] = @b[row_ix] - ratio*@b[pivot_row]
67
+ end
68
+
69
+ @basic_vars << pivot_column
70
+ @basic_vars.sort!
71
+ update_solution
72
+ end
73
+ @solved = true
74
+ end
75
+
76
+ def update_solution
77
+ 0.upto(@num_vars - 1) {|i| @x[i] = 0 }
78
+ @basic_vars.each do |basic_var|
79
+ row_coeff_1 = nil
80
+ 0.upto(@a.row_count - 1) do |row_ix|
81
+ coeff = @a[row_ix, basic_var]
82
+ if coeff == 1
83
+ if row_coeff_1 == nil
84
+ row_coeff_1 = row_ix
85
+ end
86
+ end
87
+ end
88
+ @x[basic_var] = @b[row_coeff_1]
89
+ end
90
+ end
91
+
92
+ def solution
93
+ solve
94
+ @x.to_a[0...@num_non_slack_vars]
95
+ end
96
+
97
+ def can_improve?
98
+ !!entering_variable_ix
99
+ end
100
+
101
+ def entering_variable_ix
102
+ current_min_value = nil
103
+ current_min_index = nil
104
+ @c.each_with_index do |v, i|
105
+ if v < 0
106
+ if current_min_value == nil || v <= current_min_value
107
+ current_min_value = v
108
+ current_min_index = i
109
+ end
110
+ end
111
+ end
112
+ current_min_index
113
+ end
114
+
115
+ def leaving_variable(pivot_row)
116
+ 0.upto(@a.column_count - 1) do |column_ix|
117
+ if @a[pivot_row, column_ix] == 1 and @basic_vars.include?(column_ix)
118
+ return column_ix
119
+ end
120
+ end
121
+ end
122
+
123
+ def minimum_coefficient_ratio_row_ix(column_ix)
124
+ current_min_value = nil
125
+ current_min_index = nil
126
+ 0.upto(@a.row_count - 1) do |row_ix|
127
+ next if @a[row_ix, column_ix] == 0
128
+ ratio = Rational(@b[row_ix], @a[row_ix, column_ix])
129
+ if ratio > 0 && (!current_min_value || ratio < current_min_value)
130
+ current_min_value = ratio
131
+ current_min_index = row_ix
132
+ end
133
+ end
134
+ return current_min_index
135
+ end
136
+
137
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplex
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Lucraft
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Naive implementation of the simplex linear programming algorithm in pure
14
+ Ruby.
15
+ email: dan@fluentradical.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/simplex.rb
21
+ homepage:
22
+ licenses: []
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.0.6
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Simplex linear programming solver
44
+ test_files: []