hungarian_algorithm_c 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +9 -0
- data/Gemfile +2 -0
- data/README.md +49 -0
- data/Rakefile +8 -0
- data/ext/hungarian_algorithm_c/extconf.rb +3 -0
- data/ext/hungarian_algorithm_c/hungarian_algorithm_c.c +69 -0
- data/ext/hungarian_algorithm_c/libhungarian/Makefile +20 -0
- data/ext/hungarian_algorithm_c/libhungarian/README.md +11 -0
- data/ext/hungarian_algorithm_c/libhungarian/hungarian.c +419 -0
- data/ext/hungarian_algorithm_c/libhungarian/hungarian.h +74 -0
- data/ext/hungarian_algorithm_c/libhungarian/hungarian_test.c +83 -0
- data/hungarian_algorithm_c.gemspec +31 -0
- data/lib/hungarian_algorithm_c.rb +22 -0
- data/lib/hungarian_algorithm_c/version.rb +3 -0
- data/spec/hungarian_algorithm_c_spec.rb +64 -0
- data/spec/spec_helper.rb +5 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3131f481cd59f14347ab0881138cebeee3e426df
|
4
|
+
data.tar.gz: 8d5f3506ffe58e10ccb2bb5b550aaec1d3296a18
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3f41e80045ae12207881e794037d078e890e60bae9045c363cd86c3716f148318204ed06bdd67b2f0ef0630ede36cf83cc3a048a7edcb7cf1a83dbd8b0f5a957
|
7
|
+
data.tar.gz: ea61a05071cab151bb5e867e535b6127351889f610671800e8c800996ad432fbb20151e2a86333a5bb9179457f33c47c2a46133b29b6815db03ad4010f9892cc
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# HungarianAlgorithmC
|
2
|
+
|
3
|
+
A Ruby gem that computes the [Hungarian Algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm) in C.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the following to your [Gemfile](http://tosbourn.com/what-is-the-gemfile/):
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Gemfile
|
11
|
+
gem 'hungarian_algorithm_c', github: 'deliveroo/hungarian_algorithm_c'
|
12
|
+
```
|
13
|
+
|
14
|
+
Run `bundle install`.
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
Find the best pairings for elements with costs like so:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
costs = [
|
22
|
+
[4, 3],
|
23
|
+
[3, 0]
|
24
|
+
]
|
25
|
+
|
26
|
+
HungarianAlgorithmC.find_pairings(costs)
|
27
|
+
|
28
|
+
#=> [[0, 0], [1, 1]]
|
29
|
+
```
|
30
|
+
|
31
|
+
The output will be an array of arrays; each sub-array will have the first element as the row index and the second element as the column index for the `costs` matrix. The indices together will select the lowest-cost combination.
|
32
|
+
|
33
|
+
Another example:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
costs = [
|
37
|
+
[2, 3, 3],
|
38
|
+
[3, 3, 2],
|
39
|
+
[3, 2, 3]
|
40
|
+
]
|
41
|
+
|
42
|
+
HungarianAlgorithmC.find_pairings(costs)
|
43
|
+
|
44
|
+
#=> [[0, 0], [1, 2], [2, 1]]
|
45
|
+
```
|
46
|
+
|
47
|
+
## Acknowledgements
|
48
|
+
|
49
|
+
The C code uses the implementation by [Cyrill Stachniss](ext/hungarian_algorithm_c/libhungarian).
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#include "libhungarian/hungarian.c"
|
3
|
+
#include "libhungarian/hungarian_test.c"
|
4
|
+
|
5
|
+
// Define namespace / module name
|
6
|
+
VALUE HungarianAlgorithmC = Qnil;
|
7
|
+
|
8
|
+
// function declarations
|
9
|
+
void Init_hungarian_algorithm_c();
|
10
|
+
static VALUE indices_array(hungarian_problem_t* p, int row_size);
|
11
|
+
VALUE pairs(VALUE self, VALUE flattened_array_ruby, VALUE row_size_val);
|
12
|
+
|
13
|
+
// function to initialize module
|
14
|
+
void Init_hungarian_algorithm_c() {
|
15
|
+
HungarianAlgorithmC = rb_define_module("HungarianAlgorithmC");
|
16
|
+
rb_define_singleton_method(HungarianAlgorithmC, "pairs", pairs, 2);
|
17
|
+
}
|
18
|
+
|
19
|
+
// function to extract assignment indices from a hungarian_problem_t struct to a '2D' Ruby array
|
20
|
+
static VALUE indices_array(hungarian_problem_t* p, int row_size) {
|
21
|
+
int** values = p->assignment;
|
22
|
+
VALUE array = rb_ary_new2(row_size);
|
23
|
+
|
24
|
+
int i;
|
25
|
+
for (i = 0; i < row_size; i++) {
|
26
|
+
VALUE row = rb_ary_new2(2);
|
27
|
+
|
28
|
+
int j;
|
29
|
+
for (j = 0; j < row_size; j++) {
|
30
|
+
int value = values[i][j];
|
31
|
+
if (value == 1) {
|
32
|
+
rb_ary_push(row, INT2NUM(i));
|
33
|
+
rb_ary_push(row, INT2NUM(j));
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
rb_ary_push(array, row);
|
38
|
+
}
|
39
|
+
|
40
|
+
return array;
|
41
|
+
}
|
42
|
+
|
43
|
+
// function to find the optimal assignments from a flattend Ruby array
|
44
|
+
// array must be flatted from an array representation of a rectangular matrix
|
45
|
+
// i.e. the number of rows should equal the number of columns
|
46
|
+
VALUE pairs(VALUE self, VALUE flattened_array_ruby, VALUE row_size_val) {
|
47
|
+
VALUE output;
|
48
|
+
hungarian_problem_t p;
|
49
|
+
int** matrix;
|
50
|
+
int row_size = NUM2INT(row_size_val);
|
51
|
+
int array_size = row_size * row_size;
|
52
|
+
int array_c[array_size];
|
53
|
+
|
54
|
+
int index;
|
55
|
+
for (index = 0; index < array_size; index++) {
|
56
|
+
double element = 100 * NUM2DBL(rb_ary_entry(flattened_array_ruby, index));
|
57
|
+
int rounded_element = element;
|
58
|
+
array_c[index] = rounded_element;
|
59
|
+
}
|
60
|
+
|
61
|
+
matrix = array_to_matrix(array_c, row_size, row_size);
|
62
|
+
|
63
|
+
hungarian_init(&p, matrix, row_size, row_size, 0);
|
64
|
+
hungarian_solve(&p);
|
65
|
+
output = indices_array(&p, row_size);
|
66
|
+
hungarian_free(&p);
|
67
|
+
|
68
|
+
return output;
|
69
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
CC = gcc
|
2
|
+
AR = ar
|
3
|
+
|
4
|
+
CFLAGS = -O3 -Wall -I.
|
5
|
+
LDFLAGS = -L. -lhungarian
|
6
|
+
|
7
|
+
all: libhungarian.a hungarian_test
|
8
|
+
|
9
|
+
hungarian_test: hungarian_test.c $(HUNGARIANLIB)
|
10
|
+
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
|
11
|
+
|
12
|
+
|
13
|
+
libhungarian.a: hungarian.o
|
14
|
+
$(AR) cr $@ hungarian.o
|
15
|
+
|
16
|
+
%.o: %.c %.h
|
17
|
+
$(CC) $(CFLAGS) -c $<
|
18
|
+
|
19
|
+
clean:
|
20
|
+
rm -f *.o *.a hungarian_test
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# C Implementation of the Hungarian Method
|
2
|
+
|
3
|
+
[Download libhungarian-v0.1.3.tgz (21.09.2015)](http://www2.informatik.uni-freiburg.de/~stachnis/misc/libhungarian-v0.1.3.tgz)
|
4
|
+
|
5
|
+
From [homepage](http://www2.informatik.uni-freiburg.de/~stachnis/misc.html):
|
6
|
+
|
7
|
+
> C-implementation of the Hungarian Method: finding the optimal assignment (assigning a set of jobs to a set of machines) in O(n^3), where n=max{#jobs, #machines}. The implementation is a sligntly enhanced version of the implementation provided by the Stanford GraphBase. See also: Stanford GraphBase, Hungarian Method by Brian Gerkey.
|
8
|
+
|
9
|
+
From [implementation file](hungarian.c):
|
10
|
+
|
11
|
+
> This file may be freely copied and distributed!
|
@@ -0,0 +1,419 @@
|
|
1
|
+
/********************************************************************
|
2
|
+
********************************************************************
|
3
|
+
**
|
4
|
+
** libhungarian by Cyrill Stachniss, 2004
|
5
|
+
**
|
6
|
+
**
|
7
|
+
** Solving the Minimum Assignment Problem using the
|
8
|
+
** Hungarian Method.
|
9
|
+
**
|
10
|
+
** ** This file may be freely copied and distributed! **
|
11
|
+
**
|
12
|
+
** Parts of the used code was originally provided by the
|
13
|
+
** "Stanford GraphGase", but I made changes to this code.
|
14
|
+
** As asked by the copyright node of the "Stanford GraphGase",
|
15
|
+
** I hereby proclaim that this file are *NOT* part of the
|
16
|
+
** "Stanford GraphGase" distrubition!
|
17
|
+
**
|
18
|
+
** This file is distributed in the hope that it will be useful,
|
19
|
+
** but WITHOUT ANY WARRANTY; without even the implied
|
20
|
+
** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
21
|
+
** PURPOSE.
|
22
|
+
**
|
23
|
+
********************************************************************
|
24
|
+
********************************************************************/
|
25
|
+
|
26
|
+
|
27
|
+
#include <stdio.h>
|
28
|
+
#include <stdlib.h>
|
29
|
+
#include "hungarian.h"
|
30
|
+
|
31
|
+
#define INF (0x7FFFFFFF)
|
32
|
+
#define verbose (0)
|
33
|
+
|
34
|
+
#define hungarian_test_alloc(X) do {if ((void *)(X) == NULL) fprintf(stderr, "Out of memory in %s, (%s, line %d).\n", __FUNCTION__, __FILE__, __LINE__); } while (0)
|
35
|
+
|
36
|
+
|
37
|
+
void hungarian_print_matrix(int** C, int rows, int cols) {
|
38
|
+
int i,j;
|
39
|
+
fprintf(stderr , "\n");
|
40
|
+
for(i=0; i<rows; i++) {
|
41
|
+
fprintf(stderr, " [");
|
42
|
+
for(j=0; j<cols; j++) {
|
43
|
+
fprintf(stderr, "%5d ",C[i][j]);
|
44
|
+
}
|
45
|
+
fprintf(stderr, "]\n");
|
46
|
+
}
|
47
|
+
fprintf(stderr, "\n");
|
48
|
+
}
|
49
|
+
|
50
|
+
void hungarian_print_assignment(hungarian_problem_t* p) {
|
51
|
+
hungarian_print_matrix(p->assignment, p->num_rows, p->num_cols) ;
|
52
|
+
}
|
53
|
+
|
54
|
+
void hungarian_print_costmatrix(hungarian_problem_t* p) {
|
55
|
+
hungarian_print_matrix(p->cost, p->num_rows, p->num_cols) ;
|
56
|
+
}
|
57
|
+
|
58
|
+
void hungarian_print_status(hungarian_problem_t* p) {
|
59
|
+
|
60
|
+
fprintf(stderr,"cost:\n");
|
61
|
+
hungarian_print_costmatrix(p);
|
62
|
+
|
63
|
+
fprintf(stderr,"assignment:\n");
|
64
|
+
hungarian_print_assignment(p);
|
65
|
+
|
66
|
+
}
|
67
|
+
|
68
|
+
int hungarian_imax(int a, int b) {
|
69
|
+
return (a<b)?b:a;
|
70
|
+
}
|
71
|
+
|
72
|
+
int hungarian_init(hungarian_problem_t* p, int** cost_matrix, int rows, int cols, int mode) {
|
73
|
+
|
74
|
+
int i,j, org_cols, org_rows;
|
75
|
+
int max_cost;
|
76
|
+
max_cost = 0;
|
77
|
+
|
78
|
+
org_cols = cols;
|
79
|
+
org_rows = rows;
|
80
|
+
|
81
|
+
// is the number of cols not equal to number of rows ?
|
82
|
+
// if yes, expand with 0-cols / 0-cols
|
83
|
+
rows = hungarian_imax(cols, rows);
|
84
|
+
cols = rows;
|
85
|
+
|
86
|
+
p->num_rows = rows;
|
87
|
+
p->num_cols = cols;
|
88
|
+
|
89
|
+
p->cost = (int**)calloc(rows,sizeof(int*));
|
90
|
+
hungarian_test_alloc(p->cost);
|
91
|
+
p->assignment = (int**)calloc(rows,sizeof(int*));
|
92
|
+
hungarian_test_alloc(p->assignment);
|
93
|
+
|
94
|
+
for(i=0; i<p->num_rows; i++) {
|
95
|
+
p->cost[i] = (int*)calloc(cols,sizeof(int));
|
96
|
+
hungarian_test_alloc(p->cost[i]);
|
97
|
+
p->assignment[i] = (int*)calloc(cols,sizeof(int));
|
98
|
+
hungarian_test_alloc(p->assignment[i]);
|
99
|
+
for(j=0; j<p->num_cols; j++) {
|
100
|
+
p->cost[i][j] = (i < org_rows && j < org_cols) ? cost_matrix[i][j] : 0;
|
101
|
+
p->assignment[i][j] = 0;
|
102
|
+
|
103
|
+
if (max_cost < p->cost[i][j])
|
104
|
+
max_cost = p->cost[i][j];
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
|
109
|
+
if (mode == HUNGARIAN_MODE_MAXIMIZE_UTIL) {
|
110
|
+
for(i=0; i<p->num_rows; i++) {
|
111
|
+
for(j=0; j<p->num_cols; j++) {
|
112
|
+
p->cost[i][j] = max_cost - p->cost[i][j];
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
else if (mode == HUNGARIAN_MODE_MINIMIZE_COST) {
|
117
|
+
// nothing to do
|
118
|
+
}
|
119
|
+
else
|
120
|
+
fprintf(stderr,"%s: unknown mode. Mode was set to HUNGARIAN_MODE_MINIMIZE_COST !\n", __FUNCTION__);
|
121
|
+
|
122
|
+
return rows;
|
123
|
+
}
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
void hungarian_free(hungarian_problem_t* p) {
|
129
|
+
int i;
|
130
|
+
for(i=0; i<p->num_rows; i++) {
|
131
|
+
free(p->cost[i]);
|
132
|
+
free(p->assignment[i]);
|
133
|
+
}
|
134
|
+
free(p->cost);
|
135
|
+
free(p->assignment);
|
136
|
+
p->cost = NULL;
|
137
|
+
p->assignment = NULL;
|
138
|
+
}
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
void hungarian_solve(hungarian_problem_t* p)
|
143
|
+
{
|
144
|
+
int i, j, m, n, k, l, s, t, q, unmatched, cost;
|
145
|
+
int* col_mate;
|
146
|
+
int* row_mate;
|
147
|
+
int* parent_row;
|
148
|
+
int* unchosen_row;
|
149
|
+
int* row_dec;
|
150
|
+
int* col_inc;
|
151
|
+
int* slack;
|
152
|
+
int* slack_row;
|
153
|
+
|
154
|
+
cost=0;
|
155
|
+
m =p->num_rows;
|
156
|
+
n =p->num_cols;
|
157
|
+
|
158
|
+
col_mate = (int*)calloc(p->num_rows,sizeof(int));
|
159
|
+
hungarian_test_alloc(col_mate);
|
160
|
+
unchosen_row = (int*)calloc(p->num_rows,sizeof(int));
|
161
|
+
hungarian_test_alloc(unchosen_row);
|
162
|
+
row_dec = (int*)calloc(p->num_rows,sizeof(int));
|
163
|
+
hungarian_test_alloc(row_dec);
|
164
|
+
slack_row = (int*)calloc(p->num_rows,sizeof(int));
|
165
|
+
hungarian_test_alloc(slack_row);
|
166
|
+
|
167
|
+
row_mate = (int*)calloc(p->num_cols,sizeof(int));
|
168
|
+
hungarian_test_alloc(row_mate);
|
169
|
+
parent_row = (int*)calloc(p->num_cols,sizeof(int));
|
170
|
+
hungarian_test_alloc(parent_row);
|
171
|
+
col_inc = (int*)calloc(p->num_cols,sizeof(int));
|
172
|
+
hungarian_test_alloc(col_inc);
|
173
|
+
slack = (int*)calloc(p->num_cols,sizeof(int));
|
174
|
+
hungarian_test_alloc(slack);
|
175
|
+
|
176
|
+
for (i=0;i<p->num_rows;i++) {
|
177
|
+
col_mate[i]=0;
|
178
|
+
unchosen_row[i]=0;
|
179
|
+
row_dec[i]=0;
|
180
|
+
slack_row[i]=0;
|
181
|
+
}
|
182
|
+
for (j=0;j<p->num_cols;j++) {
|
183
|
+
row_mate[j]=0;
|
184
|
+
parent_row[j] = 0;
|
185
|
+
col_inc[j]=0;
|
186
|
+
slack[j]=0;
|
187
|
+
}
|
188
|
+
|
189
|
+
for (i=0;i<p->num_rows;++i)
|
190
|
+
for (j=0;j<p->num_cols;++j)
|
191
|
+
p->assignment[i][j]=HUNGARIAN_NOT_ASSIGNED;
|
192
|
+
|
193
|
+
// Begin subtract column minima in order to start with lots of zeroes 12
|
194
|
+
if (verbose)
|
195
|
+
fprintf(stderr, "Using heuristic\n");
|
196
|
+
for (l=0;l<n;l++)
|
197
|
+
{
|
198
|
+
s=p->cost[0][l];
|
199
|
+
for (k=1;k<m;k++)
|
200
|
+
if (p->cost[k][l]<s)
|
201
|
+
s=p->cost[k][l];
|
202
|
+
cost+=s;
|
203
|
+
if (s!=0)
|
204
|
+
for (k=0;k<m;k++)
|
205
|
+
p->cost[k][l]-=s;
|
206
|
+
}
|
207
|
+
// End subtract column minima in order to start with lots of zeroes 12
|
208
|
+
|
209
|
+
// Begin initial state 16
|
210
|
+
t=0;
|
211
|
+
for (l=0;l<n;l++)
|
212
|
+
{
|
213
|
+
row_mate[l]= -1;
|
214
|
+
parent_row[l]= -1;
|
215
|
+
col_inc[l]=0;
|
216
|
+
slack[l]=INF;
|
217
|
+
}
|
218
|
+
for (k=0;k<m;k++)
|
219
|
+
{
|
220
|
+
s=p->cost[k][0];
|
221
|
+
for (l=1;l<n;l++)
|
222
|
+
if (p->cost[k][l]<s)
|
223
|
+
s=p->cost[k][l];
|
224
|
+
row_dec[k]=s;
|
225
|
+
for (l=0;l<n;l++)
|
226
|
+
if (s==p->cost[k][l] && row_mate[l]<0)
|
227
|
+
{
|
228
|
+
col_mate[k]=l;
|
229
|
+
row_mate[l]=k;
|
230
|
+
if (verbose)
|
231
|
+
fprintf(stderr, "matching col %d==row %d\n",l,k);
|
232
|
+
goto row_done;
|
233
|
+
}
|
234
|
+
col_mate[k]= -1;
|
235
|
+
if (verbose)
|
236
|
+
fprintf(stderr, "node %d: unmatched row %d\n",t,k);
|
237
|
+
unchosen_row[t++]=k;
|
238
|
+
row_done:
|
239
|
+
;
|
240
|
+
}
|
241
|
+
// End initial state 16
|
242
|
+
|
243
|
+
// Begin Hungarian algorithm 18
|
244
|
+
if (t==0)
|
245
|
+
goto done;
|
246
|
+
unmatched=t;
|
247
|
+
while (1)
|
248
|
+
{
|
249
|
+
if (verbose)
|
250
|
+
fprintf(stderr, "Matched %d rows.\n",m-t);
|
251
|
+
q=0;
|
252
|
+
while (1)
|
253
|
+
{
|
254
|
+
while (q<t)
|
255
|
+
{
|
256
|
+
// Begin explore node q of the forest 19
|
257
|
+
{
|
258
|
+
k=unchosen_row[q];
|
259
|
+
s=row_dec[k];
|
260
|
+
for (l=0;l<n;l++)
|
261
|
+
if (slack[l])
|
262
|
+
{
|
263
|
+
int del;
|
264
|
+
del=p->cost[k][l]-s+col_inc[l];
|
265
|
+
if (del<slack[l])
|
266
|
+
{
|
267
|
+
if (del==0)
|
268
|
+
{
|
269
|
+
if (row_mate[l]<0)
|
270
|
+
goto breakthru;
|
271
|
+
slack[l]=0;
|
272
|
+
parent_row[l]=k;
|
273
|
+
if (verbose)
|
274
|
+
fprintf(stderr, "node %d: row %d==col %d--row %d\n",
|
275
|
+
t,row_mate[l],l,k);
|
276
|
+
unchosen_row[t++]=row_mate[l];
|
277
|
+
}
|
278
|
+
else
|
279
|
+
{
|
280
|
+
slack[l]=del;
|
281
|
+
slack_row[l]=k;
|
282
|
+
}
|
283
|
+
}
|
284
|
+
}
|
285
|
+
}
|
286
|
+
// End explore node q of the forest 19
|
287
|
+
q++;
|
288
|
+
}
|
289
|
+
|
290
|
+
// Begin introduce a new zero into the matrix 21
|
291
|
+
s=INF;
|
292
|
+
for (l=0;l<n;l++)
|
293
|
+
if (slack[l] && slack[l]<s)
|
294
|
+
s=slack[l];
|
295
|
+
for (q=0;q<t;q++)
|
296
|
+
row_dec[unchosen_row[q]]+=s;
|
297
|
+
for (l=0;l<n;l++)
|
298
|
+
if (slack[l])
|
299
|
+
{
|
300
|
+
slack[l]-=s;
|
301
|
+
if (slack[l]==0)
|
302
|
+
{
|
303
|
+
// Begin look at a new zero 22
|
304
|
+
k=slack_row[l];
|
305
|
+
if (verbose)
|
306
|
+
fprintf(stderr,
|
307
|
+
"Decreasing uncovered elements by %d produces zero at [%d,%d]\n",
|
308
|
+
s,k,l);
|
309
|
+
if (row_mate[l]<0)
|
310
|
+
{
|
311
|
+
for (j=l+1;j<n;j++)
|
312
|
+
if (slack[j]==0)
|
313
|
+
col_inc[j]+=s;
|
314
|
+
goto breakthru;
|
315
|
+
}
|
316
|
+
else
|
317
|
+
{
|
318
|
+
parent_row[l]=k;
|
319
|
+
if (verbose)
|
320
|
+
fprintf(stderr, "node %d: row %d==col %d--row %d\n",t,row_mate[l],l,k);
|
321
|
+
unchosen_row[t++]=row_mate[l];
|
322
|
+
}
|
323
|
+
// End look at a new zero 22
|
324
|
+
}
|
325
|
+
}
|
326
|
+
else
|
327
|
+
col_inc[l]+=s;
|
328
|
+
// End introduce a new zero into the matrix 21
|
329
|
+
}
|
330
|
+
breakthru:
|
331
|
+
// Begin update the matching 20
|
332
|
+
if (verbose)
|
333
|
+
fprintf(stderr, "Breakthrough at node %d of %d!\n",q,t);
|
334
|
+
while (1)
|
335
|
+
{
|
336
|
+
j=col_mate[k];
|
337
|
+
col_mate[k]=l;
|
338
|
+
row_mate[l]=k;
|
339
|
+
if (verbose)
|
340
|
+
fprintf(stderr, "rematching col %d==row %d\n",l,k);
|
341
|
+
if (j<0)
|
342
|
+
break;
|
343
|
+
k=parent_row[j];
|
344
|
+
l=j;
|
345
|
+
}
|
346
|
+
// End update the matching 20
|
347
|
+
if (--unmatched==0)
|
348
|
+
goto done;
|
349
|
+
// Begin get ready for another stage 17
|
350
|
+
t=0;
|
351
|
+
for (l=0;l<n;l++)
|
352
|
+
{
|
353
|
+
parent_row[l]= -1;
|
354
|
+
slack[l]=INF;
|
355
|
+
}
|
356
|
+
for (k=0;k<m;k++)
|
357
|
+
if (col_mate[k]<0)
|
358
|
+
{
|
359
|
+
if (verbose)
|
360
|
+
fprintf(stderr, "node %d: unmatched row %d\n",t,k);
|
361
|
+
unchosen_row[t++]=k;
|
362
|
+
}
|
363
|
+
// End get ready for another stage 17
|
364
|
+
}
|
365
|
+
done:
|
366
|
+
|
367
|
+
// Begin doublecheck the solution 23
|
368
|
+
for (k=0;k<m;k++)
|
369
|
+
for (l=0;l<n;l++)
|
370
|
+
if (p->cost[k][l]<row_dec[k]-col_inc[l])
|
371
|
+
exit(0);
|
372
|
+
for (k=0;k<m;k++)
|
373
|
+
{
|
374
|
+
l=col_mate[k];
|
375
|
+
if (l<0 || p->cost[k][l]!=row_dec[k]-col_inc[l])
|
376
|
+
exit(0);
|
377
|
+
}
|
378
|
+
k=0;
|
379
|
+
for (l=0;l<n;l++)
|
380
|
+
if (col_inc[l])
|
381
|
+
k++;
|
382
|
+
if (k>m)
|
383
|
+
exit(0);
|
384
|
+
// End doublecheck the solution 23
|
385
|
+
// End Hungarian algorithm 18
|
386
|
+
|
387
|
+
for (i=0;i<m;++i)
|
388
|
+
{
|
389
|
+
p->assignment[i][col_mate[i]]=HUNGARIAN_ASSIGNED;
|
390
|
+
/*TRACE("%d - %d\n", i, col_mate[i]);*/
|
391
|
+
}
|
392
|
+
for (k=0;k<m;++k)
|
393
|
+
{
|
394
|
+
for (l=0;l<n;++l)
|
395
|
+
{
|
396
|
+
/*TRACE("%d ",p->cost[k][l]-row_dec[k]+col_inc[l]);*/
|
397
|
+
p->cost[k][l]=p->cost[k][l]-row_dec[k]+col_inc[l];
|
398
|
+
}
|
399
|
+
/*TRACE("\n");*/
|
400
|
+
}
|
401
|
+
for (i=0;i<m;i++)
|
402
|
+
cost+=row_dec[i];
|
403
|
+
for (i=0;i<n;i++)
|
404
|
+
cost-=col_inc[i];
|
405
|
+
if (verbose)
|
406
|
+
fprintf(stderr, "Cost is %d\n",cost);
|
407
|
+
|
408
|
+
|
409
|
+
free(slack);
|
410
|
+
free(col_inc);
|
411
|
+
free(parent_row);
|
412
|
+
free(row_mate);
|
413
|
+
free(slack_row);
|
414
|
+
free(row_dec);
|
415
|
+
free(unchosen_row);
|
416
|
+
free(col_mate);
|
417
|
+
}
|
418
|
+
|
419
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
/********************************************************************
|
2
|
+
********************************************************************
|
3
|
+
**
|
4
|
+
** libhungarian by Cyrill Stachniss, 2004
|
5
|
+
**
|
6
|
+
**
|
7
|
+
** Solving the Minimum Assignment Problem using the
|
8
|
+
** Hungarian Method.
|
9
|
+
**
|
10
|
+
** ** This file may be freely copied and distributed! **
|
11
|
+
**
|
12
|
+
** Parts of the used code was originally provided by the
|
13
|
+
** "Stanford GraphGase", but I made changes to this code.
|
14
|
+
** As asked by the copyright node of the "Stanford GraphGase",
|
15
|
+
** I hereby proclaim that this file are *NOT* part of the
|
16
|
+
** "Stanford GraphGase" distrubition!
|
17
|
+
**
|
18
|
+
** This file is distributed in the hope that it will be useful,
|
19
|
+
** but WITHOUT ANY WARRANTY; without even the implied
|
20
|
+
** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
21
|
+
** PURPOSE.
|
22
|
+
**
|
23
|
+
********************************************************************
|
24
|
+
********************************************************************/
|
25
|
+
|
26
|
+
#ifndef HUNGARIAN_H
|
27
|
+
#define HUNGARIAN_H
|
28
|
+
|
29
|
+
#ifdef __cplusplus
|
30
|
+
extern "C" {
|
31
|
+
#endif
|
32
|
+
|
33
|
+
#define HUNGARIAN_NOT_ASSIGNED 0
|
34
|
+
#define HUNGARIAN_ASSIGNED 1
|
35
|
+
|
36
|
+
#define HUNGARIAN_MODE_MINIMIZE_COST 0
|
37
|
+
#define HUNGARIAN_MODE_MAXIMIZE_UTIL 1
|
38
|
+
|
39
|
+
typedef struct {
|
40
|
+
int num_rows;
|
41
|
+
int num_cols;
|
42
|
+
int** cost;
|
43
|
+
int** assignment;
|
44
|
+
} hungarian_problem_t;
|
45
|
+
|
46
|
+
/** This method initialize the hungarian_problem structure and init
|
47
|
+
* the cost matrices (missing lines or columns are filled with 0).
|
48
|
+
* It returns the size of the quadratic(!) assignment matrix. **/
|
49
|
+
int hungarian_init(hungarian_problem_t* p,
|
50
|
+
int** cost_matrix,
|
51
|
+
int rows,
|
52
|
+
int cols,
|
53
|
+
int mode);
|
54
|
+
|
55
|
+
/** Free the memory allocated by init. **/
|
56
|
+
void hungarian_free(hungarian_problem_t* p);
|
57
|
+
|
58
|
+
/** This method computes the optimal assignment. **/
|
59
|
+
void hungarian_solve(hungarian_problem_t* p);
|
60
|
+
|
61
|
+
/** Print the computed optimal assignment. **/
|
62
|
+
void hungarian_print_assignment(hungarian_problem_t* p);
|
63
|
+
|
64
|
+
/** Print the cost matrix. **/
|
65
|
+
void hungarian_print_costmatrix(hungarian_problem_t* p);
|
66
|
+
|
67
|
+
/** Print cost matrix and assignment matrix. **/
|
68
|
+
void hungarian_print_status(hungarian_problem_t* p);
|
69
|
+
|
70
|
+
#ifdef __cplusplus
|
71
|
+
}
|
72
|
+
#endif
|
73
|
+
|
74
|
+
#endif
|
@@ -0,0 +1,83 @@
|
|
1
|
+
/********************************************************************
|
2
|
+
********************************************************************
|
3
|
+
**
|
4
|
+
** libhungarian by Cyrill Stachniss, 2004
|
5
|
+
**
|
6
|
+
**
|
7
|
+
** Solving the Minimum Assignment Problem using the
|
8
|
+
** Hungarian Method.
|
9
|
+
**
|
10
|
+
** ** This file may be freely copied and distributed! **
|
11
|
+
**
|
12
|
+
** Parts of the used code was originally provided by the
|
13
|
+
** "Stanford GraphGase", but I made changes to this code.
|
14
|
+
** As asked by the copyright node of the "Stanford GraphGase",
|
15
|
+
** I hereby proclaim that this file are *NOT* part of the
|
16
|
+
** "Stanford GraphGase" distrubition!
|
17
|
+
**
|
18
|
+
** This file is distributed in the hope that it will be useful,
|
19
|
+
** but WITHOUT ANY WARRANTY; without even the implied
|
20
|
+
** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
21
|
+
** PURPOSE.
|
22
|
+
**
|
23
|
+
********************************************************************
|
24
|
+
********************************************************************/
|
25
|
+
|
26
|
+
|
27
|
+
#include <stdio.h>
|
28
|
+
#include <stdlib.h>
|
29
|
+
#include "hungarian.h"
|
30
|
+
|
31
|
+
int** array_to_matrix(int* m, int rows, int cols) {
|
32
|
+
int i,j;
|
33
|
+
int** r;
|
34
|
+
r = (int**)calloc(rows,sizeof(int*));
|
35
|
+
for(i=0;i<rows;i++)
|
36
|
+
{
|
37
|
+
r[i] = (int*)calloc(cols,sizeof(int));
|
38
|
+
for(j=0;j<cols;j++)
|
39
|
+
r[i][j] = m[i*cols+j];
|
40
|
+
}
|
41
|
+
return r;
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
int main() {
|
46
|
+
|
47
|
+
hungarian_problem_t p;
|
48
|
+
|
49
|
+
/* an example cost matrix */
|
50
|
+
int r[4*3] = { 100, 1, 1,
|
51
|
+
100, 2, 2,
|
52
|
+
1, 0, 0,
|
53
|
+
0, 2, 0 };
|
54
|
+
int** m = array_to_matrix(r,4,3);
|
55
|
+
|
56
|
+
/* initialize the gungarian_problem using the cost matrix*/
|
57
|
+
int matrix_size = hungarian_init(&p, m , 4,3, HUNGARIAN_MODE_MINIMIZE_COST) ;
|
58
|
+
|
59
|
+
fprintf(stderr, "assignement matrix has a now a size %d rows and %d columns.\n\n", matrix_size,matrix_size);
|
60
|
+
|
61
|
+
/* some output */
|
62
|
+
fprintf(stderr, "cost-matrix:");
|
63
|
+
hungarian_print_costmatrix(&p);
|
64
|
+
|
65
|
+
/* solve the assignement problem */
|
66
|
+
hungarian_solve(&p);
|
67
|
+
|
68
|
+
/* some output */
|
69
|
+
fprintf(stderr, "assignment:");
|
70
|
+
hungarian_print_assignment(&p);
|
71
|
+
|
72
|
+
/* free used memory */
|
73
|
+
hungarian_free(&p);
|
74
|
+
|
75
|
+
int idx;
|
76
|
+
for (idx=0; idx < 4; idx+=1) {
|
77
|
+
free(m[idx]);
|
78
|
+
}
|
79
|
+
free(m);
|
80
|
+
|
81
|
+
return 0;
|
82
|
+
}
|
83
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require_relative './lib/hungarian_algorithm_c/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "hungarian_algorithm_c"
|
7
|
+
spec.version = HungarianAlgorithmC::VERSION
|
8
|
+
spec.authors = ["Syed Humza Shah"]
|
9
|
+
spec.email = ["humza.shah@deliveroo.co.uk"]
|
10
|
+
|
11
|
+
spec.summary = 'Evaluates the Hungarian algorithm in C'
|
12
|
+
spec.description = 'A Ruby gem that evaluates the Hungarian algorithm in C'
|
13
|
+
spec.homepage = 'https://github.com/deliveroo/hungarian_algorithm_c'
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
all_files = `git ls-files -z`.split("\x0")
|
19
|
+
test_file_regex = %r{^(test|spec|features)/}
|
20
|
+
spec.files = all_files.reject { |f| f.match(test_file_regex) }
|
21
|
+
spec.test_files = all_files.select { |f| f.match(test_file_regex) }
|
22
|
+
|
23
|
+
spec.has_rdoc = false
|
24
|
+
spec.extensions = %w[ext/hungarian_algorithm_c/extconf.rb]
|
25
|
+
|
26
|
+
spec.required_ruby_version = '>= 2.0.0'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'rake-compiler'
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'hungarian_algorithm_c.so'
|
2
|
+
|
3
|
+
module HungarianAlgorithmC
|
4
|
+
class << self
|
5
|
+
def find_pairings(array)
|
6
|
+
validate!(array)
|
7
|
+
pairs(array.flatten, array.size)
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!(array)
|
11
|
+
return if rectangular?(array)
|
12
|
+
raise ArgumentError.new('array must be rectangular')
|
13
|
+
end
|
14
|
+
|
15
|
+
def rectangular?(array)
|
16
|
+
row_size = array.size
|
17
|
+
array.all? { |column| row_size == column.size }
|
18
|
+
end
|
19
|
+
|
20
|
+
private :pairs, :validate!, :rectangular?
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative './spec_helper'
|
2
|
+
require_relative '../lib/hungarian_algorithm_c'
|
3
|
+
|
4
|
+
RSpec.describe HungarianAlgorithmC do
|
5
|
+
describe '.find_pairings' do
|
6
|
+
subject { described_class.find_pairings(matrix_with_costs) }
|
7
|
+
|
8
|
+
context 'unbalanced array / non-square matrix' do
|
9
|
+
let(:matrix_with_costs) { [1, 2] }
|
10
|
+
|
11
|
+
it 'raises ArgumentError' do
|
12
|
+
expect { subject }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context '2x2 array' do
|
17
|
+
let(:matrix_with_costs) { [
|
18
|
+
[4, 3],
|
19
|
+
[3, 0]
|
20
|
+
] }
|
21
|
+
|
22
|
+
it 'should output minimum cost pairs' do
|
23
|
+
should match_array([
|
24
|
+
[1, 1],
|
25
|
+
[0, 0]
|
26
|
+
])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context '3x3 array' do
|
31
|
+
let(:matrix_with_costs) { [
|
32
|
+
[2, 3, 3],
|
33
|
+
[3, 3, 2],
|
34
|
+
[3, 2, 3]
|
35
|
+
] }
|
36
|
+
|
37
|
+
it 'should output minimum cost pairs' do
|
38
|
+
should match_array([
|
39
|
+
[0, 0],
|
40
|
+
[1, 2],
|
41
|
+
[2, 1]
|
42
|
+
])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '4x4 array' do
|
47
|
+
let(:matrix_with_costs) { [
|
48
|
+
[2, 3, 1, 3],
|
49
|
+
[10, 2, 90, 6],
|
50
|
+
[10, 3, 34, 4],
|
51
|
+
[11, 13, 15, 17]
|
52
|
+
] }
|
53
|
+
|
54
|
+
it 'should output minimum cost pairs' do
|
55
|
+
should match_array([
|
56
|
+
[0, 2],
|
57
|
+
[1, 1],
|
58
|
+
[2, 3],
|
59
|
+
[3, 0]
|
60
|
+
])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hungarian_algorithm_c
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Syed Humza Shah
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake-compiler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A Ruby gem that evaluates the Hungarian algorithm in C
|
56
|
+
email:
|
57
|
+
- humza.shah@deliveroo.co.uk
|
58
|
+
executables: []
|
59
|
+
extensions:
|
60
|
+
- ext/hungarian_algorithm_c/extconf.rb
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- ext/hungarian_algorithm_c/extconf.rb
|
69
|
+
- ext/hungarian_algorithm_c/hungarian_algorithm_c.c
|
70
|
+
- ext/hungarian_algorithm_c/libhungarian/Makefile
|
71
|
+
- ext/hungarian_algorithm_c/libhungarian/README.md
|
72
|
+
- ext/hungarian_algorithm_c/libhungarian/hungarian.c
|
73
|
+
- ext/hungarian_algorithm_c/libhungarian/hungarian.h
|
74
|
+
- ext/hungarian_algorithm_c/libhungarian/hungarian_test.c
|
75
|
+
- hungarian_algorithm_c.gemspec
|
76
|
+
- lib/hungarian_algorithm_c.rb
|
77
|
+
- lib/hungarian_algorithm_c/version.rb
|
78
|
+
- spec/hungarian_algorithm_c_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
homepage: https://github.com/deliveroo/hungarian_algorithm_c
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 2.0.0
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 2.4.5.1
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: Evaluates the Hungarian algorithm in C
|
104
|
+
test_files:
|
105
|
+
- spec/hungarian_algorithm_c_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
has_rdoc: false
|