relax4 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -6,7 +6,8 @@ http://github.com/jdleesmiller/relax4_ruby
6
6
 
7
7
  == DESCRIPTION
8
8
 
9
- Ruby interface for the RELAX IV code for the minimum cost network flow problem.
9
+ Ruby interface for the RELAX IV code for minimum cost network flow problems,
10
+ which include transportation problems and assignment problems.
10
11
  In the words of the code's authors:
11
12
 
12
13
  PURPOSE - THIS ROUTINE IMPLEMENTS THE RELAXATION METHOD
@@ -34,7 +35,7 @@ In the words of the code's authors:
34
35
  FOR THE MINIMUM COST FLOW PROBLEM", LIDS REPORT P-2146, MIT, NOV. 1992.
35
36
 
36
37
  The original source can be downloaded from:
37
- * http://elib.zib.de/pub/Packages/mathprog/mincost/relax-4/
38
+ * http://elib.zib.de/pub/Packages/mathprog/mincost/relax-4
38
39
 
39
40
  See also:
40
41
  * http://www-neos.mcs.anl.gov/neos/solvers/lno:RELAX4/RELAX4.html
@@ -44,7 +45,10 @@ See also:
44
45
 
45
46
  require 'rubygems'
46
47
  require 'relax4'
47
-
48
+
49
+ A general network flow problem:
50
+
51
+ #
48
52
  # From Figure 29.3 of Cormen, Leiserson, Rivest and Stein (2001).
49
53
  # Introduction to Algorithms, 2nd Edition.
50
54
  #
@@ -65,6 +69,32 @@ See also:
65
69
 
66
70
  A lower-level interface is also available; see the docs for the Relax4 module.
67
71
 
72
+ A transportation problem:
73
+
74
+ #
75
+ # From http://www.me.utexas.edu/~jensen/models/network/net8.html
76
+ # A nil cost means that the flow is not allowed.
77
+ #
78
+ Relax4.solve_transportation_problem(
79
+ :costs => [[ 3, 1, nil],
80
+ [ 4, 2, 4],
81
+ [ nil, 3, 3]],
82
+ :demands => [ 7, 3, 5],
83
+ :supplies => [ 5, 7, 3]) #=> [[2, 3, 0], [5, 0, 2], [0, 0, 3]]
84
+
85
+ An assignment problem:
86
+
87
+ #
88
+ # From http://www.me.utexas.edu/~jensen/models/network/net9.html
89
+ # A nil cost means that the assignment is not allowed.
90
+ #
91
+ Relax4.solve_assignment_problem(:costs =>
92
+ [[ nil, 8, 6, 12, 1],
93
+ [ 15, 12, 7, nil, 10],
94
+ [ 10, nil, 5, 14, nil],
95
+ [ 12, nil, 12, 16, 15],
96
+ [ 18, 17, 14, nil, 13]]) #=> [4, 2, 3, 0, 1]
97
+
68
98
  == REQUIREMENTS
69
99
 
70
100
  Has been tested on:
@@ -98,10 +128,13 @@ and run
98
128
 
99
129
  * there are some hard-coded parameters that could be exposed
100
130
  * problem state is global (cf. FORTRAN), so can have only one instance at a time
101
- * add some wrappers for transportation and assignment problems
102
131
 
103
132
  == HISTORY
104
133
 
134
+ <em>1.1.0</em>
135
+ * added wrappers for transportation and assignment problems
136
+ * updated docs
137
+
105
138
  <em>1.0.5</em>
106
139
  * fix in previous version didn't quite solve the problem; new fix applied
107
140
 
@@ -138,7 +171,7 @@ following user guidelines:
138
171
  AND THE RELAXATION METHOD. THEY SHOULD ALSO REGISTER WITH THE
139
172
  AUTHORS TO RECEIVE UPDATES AND SUBSEQUENT RELEASES.
140
173
 
141
- The author of the RELAX IV code is:
174
+ The main author of the RELAX IV code is:
142
175
 
143
176
  DIMITRI P. BERTSEKAS
144
177
  LABORATORY FOR INFORMATION AND DECISION SYSTEMS
data/lib/relax4.rb CHANGED
@@ -158,6 +158,119 @@ module Relax4
158
158
  end
159
159
  end
160
160
 
161
+ #
162
+ # Solve a transportation problem.
163
+ #
164
+ # There are +m+ suppliers and +n+ consumers of some commodity. Each supplier
165
+ # has a given _supply_ which must be used, and each consumer has a given
166
+ # _demand_ which must be satisfied; it is assumed that the total supply equals
167
+ # the total demand. The cost of transporting one unit of the commodity from
168
+ # each supplier to each consumer is given, and the objective is to perform the
169
+ # transport with minimum cost. The output is a +m+ by +n+ matrix that gives
170
+ # the flow from each supplier to each consumer.
171
+ #
172
+ # @param [Hash] args named arguments
173
+ #
174
+ # @option args [Array<Array<Integer>>] :costs matrix with +m+ rows (one for
175
+ # each supplier) and +n+ columns (one for each consumer); a cost of +nil+
176
+ # means that there is no corresponding edge (transport is impossible);
177
+ # negative costs are allowed; may be non-square but must be rectangular (all
178
+ # rows must have the same size).
179
+ #
180
+ # @option args [Array<Integer>] :supplies for each supplier; size +m+; total
181
+ # demand should equal total supply, or else the problem will be infeasible
182
+ # (but you can add a dummy node to fix this).
183
+ #
184
+ # @option args [Array<Integer>] :demands for each consumer; size +n+; total
185
+ # demand should equal total supply, or else the problem will be infeasible
186
+ # (but you can add a dummy node to fix this).
187
+ #
188
+ # @option args [Boolean] :auction_init see {solve}
189
+ #
190
+ # @option args [Integer] :large see {solve}
191
+ #
192
+ # @option args [Integer] :max_cost see {solve}
193
+ #
194
+ # @return [Array<Array<Integer>>] optimal flows from each supplier to each
195
+ # consumer; size +m+ by +n+; +nil+ entries in the cost matrix will have flow 0
196
+ # here.
197
+ #
198
+ # @raise [ArgumentError] if problem inputs are invalid
199
+ #
200
+ # @raise [InfeasibleError] if problem is infeasible
201
+ #
202
+ def self.solve_transportation_problem args
203
+ args = args.dup
204
+ costs = get_arg(args, :costs)
205
+ supplies = get_arg(args, :supplies)
206
+ demands = get_arg(args, :demands)
207
+
208
+ m = supplies.size
209
+ n = demands.size
210
+
211
+ arcs = set_args_from_cost_matrix(args, costs, m, n)
212
+ args[:demands] = supplies.map{|x| -x} + demands
213
+
214
+ arc_flows = Relax4.solve(args)
215
+
216
+ # Extract output into matrix with same dims as input cost matrix.
217
+ flows = Array.new(m) { Array.new(n, 0) }
218
+ arcs.zip(arc_flows).each do |(i,j),fij|
219
+ flows[i][j] = fij
220
+ end
221
+ flows
222
+ end
223
+
224
+ #
225
+ # Solve a square assignment problem.
226
+ #
227
+ # There are +n+ agents and +n+ tasks, and each agent must be assigned to
228
+ # exactly one task. The cost of each assignment is given, and the objective is
229
+ # to find the assignment with the lowest cost.
230
+ #
231
+ # Some assignments can be made illegal by passing +nil+ in the relevant entry
232
+ # in the cost matrix. If you have more agents than tasks (or vice versa), you
233
+ # can add some columns (or rows) of zeros to the cost matrix.
234
+ #
235
+ # @param [Hash] args named arguments
236
+ #
237
+ # @option args [Array<Array<Integer>>] :costs square matrix with +n+ rows (one
238
+ # for each agent) and +n+ columns (one for each task); a cost of +nil+ means
239
+ # that the corresponding assignment is not allowed; negative costs are
240
+ # allowed.
241
+ #
242
+ # @option args [Boolean] :auction_init see {solve}
243
+ #
244
+ # @option args [Integer] :large see {solve}
245
+ #
246
+ # @option args [Integer] :max_cost see {solve}
247
+ #
248
+ # @return [Array<Integer>] zero-based index of task assigned to each agent.
249
+ #
250
+ # @raise [ArgumentError] if problem inputs are invalid
251
+ #
252
+ # @raise [InfeasibleError] if problem is infeasible
253
+ #
254
+ #
255
+ def self.solve_assignment_problem args
256
+ args = args.dup
257
+ costs = get_arg(args, :costs)
258
+
259
+ n = costs.size
260
+
261
+ arcs = set_args_from_cost_matrix(args, costs, n, n)
262
+ args[:demands] = [-1]*n + [1]*n
263
+
264
+ arc_flows = Relax4.solve(args)
265
+
266
+ # Find indexes.
267
+ assignment = Array.new(n)
268
+ arcs.zip(arc_flows).each do |(i,j),fij|
269
+ assignment[i] = j if fij > 0
270
+ end
271
+ assignment
272
+ end
273
+
161
274
  #
162
275
  # Raised to indicate that a problem is infeasible.
163
276
  #
@@ -220,5 +333,25 @@ module Relax4
220
333
  private
221
334
 
222
335
  def self.get_arg(args, key); args[key] or raise "#{key} is required" end
336
+
337
+ #
338
+ # Set start_nodes, end_nodes and costs based on cost matrix; nil entries in
339
+ # the cost matrix are not mapped to arcs.
340
+ #
341
+ # @return Array<Array<Integer>> array of pairs of (zero-based) indices into
342
+ # costs that correspond to the created arcs; size is <tt>m*n</tt> minus the
343
+ # number of nil entries in the costs matrix
344
+ #
345
+ def self.set_args_from_cost_matrix(args, costs, m, n)
346
+ # Filter out any nil costs.
347
+ arcs = (0...m).to_a.product((0...n).to_a).delete_if {|i,j| !costs[i][j]}
348
+
349
+ # Make problem inputs.
350
+ args[:start_nodes] = arcs.map{|i,j| i+1}
351
+ args[:end_nodes] = arcs.map{|i,j| m+j+1}
352
+ args[:costs] = arcs.map{|i,j| costs[i][j]}
353
+
354
+ arcs
355
+ end
223
356
  end
224
357
 
@@ -1,3 +1,3 @@
1
1
  module Relax4
2
- VERSION = '1.0.5'
2
+ VERSION = '1.1.0'
3
3
  end
data/test/test_relax4.rb CHANGED
@@ -56,6 +56,20 @@ class TestRelax4< Test::Unit::TestCase
56
56
  flows.zip(prob[:costs]).map{|f,c|f*c}.inject(:+)
57
57
  end
58
58
 
59
+ # Compute cost of flows when costs and flows are represented as matrices, and
60
+ # some of the costs may be nil.
61
+ def matrix_problem_cost prob, flows
62
+ flows = flows.flatten
63
+ costs = prob[:costs].flatten
64
+ flows.zip(costs).map{|f,c|f*c if c}.compact.inject(:+)
65
+ end
66
+
67
+ # Compute cost of given assignment.
68
+ def assignment_problem_cost prob, assignment
69
+ n = prob[:costs].size # assumed square
70
+ n.times.map{|i| prob[:costs][i][assignment[i]]}.inject(:+)
71
+ end
72
+
59
73
  def test_solve_3
60
74
  prob = problem_from_relax4_inp 'test/RELAX4.INP'
61
75
 
@@ -292,4 +306,139 @@ ARCS
292
306
  Relax4.solve(prob.merge(:auction_init => true))
293
307
  end
294
308
  end
309
+
310
+ def test_transportation_problem_1
311
+ # From http://www.me.utexas.edu/~jensen/models/network/net8.html
312
+ prob = {
313
+ :costs => [[ 3, 1, nil],
314
+ [ 4, 2, 4],
315
+ [ nil, 3, 3]],
316
+ :demands => [ 7, 3, 5],
317
+ :supplies => [ 5, 7, 3]}
318
+ flows = Relax4.solve_transportation_problem(prob)
319
+ assert_equal 46, matrix_problem_cost(prob, flows)
320
+ assert_equal [[2, 3, 0],
321
+ [5, 0, 2],
322
+ [0, 0, 3]], flows
323
+ end
324
+
325
+ def test_transportation_problem_2
326
+ # From http://www.nios.ac.in/srsec311/opt-lp6.pdf (terminal questions)
327
+ prob = {
328
+ :costs => [[ 1, 2, 1, 4],
329
+ [ 3, 3, 2, 1],
330
+ [ 4, 2, 5, 9]],
331
+ :supplies => [ 30, 50, 20],
332
+ :demands => [ 20, 40, 30, 10]}
333
+ flows = Relax4.solve_transportation_problem(prob)
334
+ assert_equal 180, matrix_problem_cost(prob, flows)
335
+
336
+ prob = {
337
+ :costs => [[ 21, 16, 25, 13],
338
+ [ 17, 18, 14, 23],
339
+ [ 32, 17, 18, 41]],
340
+ :supplies => [ 11, 13, 19],
341
+ :demands => [ 6, 10, 12, 15]}
342
+ flows = Relax4.solve_transportation_problem(prob)
343
+ assert_equal 711, matrix_problem_cost(prob, flows)
344
+ end
345
+
346
+ def test_assignment_problem_1
347
+ # From http://www.me.utexas.edu/~jensen/models/network/net9.html
348
+ prob = {
349
+ :costs => [[ nil, 8, 6, 12, 1],
350
+ [ 15, 12, 7, nil, 10],
351
+ [ 10, nil, 5, 14, nil],
352
+ [ 12, nil, 12, 16, 15],
353
+ [ 18, 17, 14, nil, 13]]}
354
+ assignment = Relax4.solve_assignment_problem(prob)
355
+ assert_equal [4, 2, 3, 0, 1], assignment
356
+ end
357
+
358
+ def test_assignment_problem_2
359
+ # Test with no nils.
360
+ assert_equal [0, 1, 2],
361
+ Relax4.solve_assignment_problem(:costs => [[1, 2, 3],
362
+ [4, 5, 6],
363
+ [7, 8, 9]])
364
+
365
+ # Test with a row of nils; problem is infeasible.
366
+ assert_raise Relax4::InfeasibleError do
367
+ Relax4.solve_assignment_problem(:costs => [[nil, nil, nil],
368
+ [ 4, 5, 6],
369
+ [ 7, 8, 9]])
370
+ end
371
+
372
+ # Test with a column of nils; problem is infeasible.
373
+ assert_raise Relax4::InfeasibleError do
374
+ Relax4.solve_assignment_problem(:costs => [[nil, 2, 3],
375
+ [nil, 5, 6],
376
+ [nil, 8, 9]])
377
+ end
378
+ end
379
+
380
+ def test_assignment_problem_3
381
+ # From http://www.utdallas.edu/~scniu/OPRE-6201/documents/TP5-Assignment.pdf
382
+ prob = {
383
+ :costs => [[ 13, 4, 7, 6],
384
+ [ 1, 11, 5, 4],
385
+ [ 6, 7, 2, 8],
386
+ [ 1, 3, 5, 9]]}
387
+ assignment = Relax4.solve_assignment_problem(prob)
388
+ assert_equal [1, 3, 2, 0], assignment
389
+ assert_equal 11, assignment_problem_cost(prob, assignment)
390
+ end
391
+
392
+ def string_array_to_matrix xs, n
393
+ xs = xs.map{|x| x.to_i}
394
+ n.times.map{|i| xs[i*n,n]}
395
+ end
396
+
397
+ def test_assignment_problem_4
398
+ # From http://www.nios.ac.in/srsec311/opt-lp5.pdf
399
+ prob = {:costs => string_array_to_matrix(%w(
400
+ 140 110 155 170 180
401
+ 115 100 110 140 155
402
+ 120 90 135 150 165
403
+ 30 30 60 60 90
404
+ 35 15 50 60 85), 5)}
405
+ assignment = Relax4.solve_assignment_problem(prob)
406
+ assert_equal [4,2,1,0,3], assignment
407
+ assert_equal 470, assignment_problem_cost(prob, assignment)
408
+
409
+ prob = {:costs => string_array_to_matrix(%w(
410
+ 15 17 24 16
411
+ 10 15 12 13
412
+ 17 19 18 16
413
+ 13 20 16 14), 4)}
414
+ assignment = Relax4.solve_assignment_problem(prob)
415
+ assert_equal [1, 2, 3, 0], assignment
416
+ assert_equal 58, assignment_problem_cost(prob, assignment)
417
+
418
+ prob = {:costs => string_array_to_matrix(%w(
419
+ 8 26 17 11
420
+ 14 29 5 27
421
+ 40 21 20 17
422
+ 19 26 24 10), 4)}
423
+ assignment = Relax4.solve_assignment_problem(prob)
424
+ assert_equal [0, 2, 1, 3], assignment
425
+
426
+ prob = {:costs => string_array_to_matrix(%w(
427
+ 10 6 4 8 3
428
+ 2 11 7 7 6
429
+ 5 10 11 4 8
430
+ 6 5 3 2 5
431
+ 11 7 10 11 7), 5)}
432
+ assignment = Relax4.solve_assignment_problem(prob)
433
+ assert_equal [4, 0, 3, 2, 1], assignment
434
+
435
+ prob = {:costs => string_array_to_matrix(%w(
436
+ 8 9 10 11
437
+ 10 11 12 13
438
+ 13 14 15 13
439
+ 9 11 14 10), 4)}
440
+ assignment = Relax4.solve_assignment_problem(prob)
441
+ assert_equal 43, assignment_problem_cost(prob, assignment)
442
+ assert_equal 43, assignment_problem_cost(prob, [2, 1, 3, 0])
443
+ end
295
444
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relax4
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 5
10
- version: 1.0.5
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Lees-Miller
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-25 00:00:00 +01:00
18
+ date: 2010-10-30 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -52,7 +52,7 @@ rdoc_options:
52
52
  - --main
53
53
  - README.rdoc
54
54
  - --title
55
- - relax4-1.0.5
55
+ - relax4-1.1.0
56
56
  - --exclude
57
57
  - ext
58
58
  require_paths: