hungarian_algorithm_c 0.0.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 +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
|