relax4 1.0.5 → 1.1.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.
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: