Skip to content

Commit ba18a88

Browse files
author
Andrii Dmytrenko
committed
Initial commit
0 parents  commit ba18a88

File tree

3 files changed

+327
-0
lines changed

3 files changed

+327
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
/target
3+
**/*.rs.bk
4+
Cargo.lock

Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "lapjv"
3+
version = "0.1.0"
4+
authors = ["Andrii Dmytrenko <[email protected]>"]
5+
6+
[dependencies]
7+
ndarray = "*"

src/lib.rs

+316
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
extern crate ndarray;
2+
3+
pub type Matrix<T> = ndarray::Array2<T>;
4+
5+
/// @brief Jonker-Volgenant algorithm.
6+
/// @param dim in problem size
7+
/// @param assign_cost in cost matrix
8+
/// @param verbose in indicates whether to report the progress to stdout
9+
/// @param rowsol out column assigned to row in solution / size dim
10+
/// @param colsol out row assigned to column in solution / size dim
11+
/// @param u out dual variables, row reduction numbers / size dim
12+
/// @param v out dual variables, column reduction numbers / size dim
13+
/// @return achieved minimum assignment cost
14+
15+
pub fn lapjv(matrix: &Matrix<f64>) -> (Vec<isize>, Vec<isize>) {
16+
{
17+
use std::io::Write;
18+
let mut f = std::fs::File::create("matrix.txt").unwrap();
19+
f.write_all(format!("{:?}", matrix).as_bytes()).unwrap();
20+
f.flush().unwrap();
21+
}
22+
let dim = matrix.dim().0;
23+
let mut free = vec![0; dim]; // list of unassigned rows.
24+
let mut collist = vec![0; dim]; // list of columns to be scanned in various ways.
25+
let mut matches = vec![0; dim]; // counts how many times a row could be assigned.
26+
let mut d = vec![0f64; dim]; // 'cost-distance' in augmenting path calculation. // cost
27+
let mut pred = vec![0; dim]; // row-predecessor of column in augmenting/alternating path.
28+
29+
let mut v = vec![0f64; dim];
30+
31+
let mut in_row = vec![0isize; dim];
32+
let mut in_col = vec![0isize; dim];
33+
34+
// COLUMN REDUCTION
35+
for j in (0..dim).into_iter().rev() { // reverse order gives better results.
36+
let mut min = matrix[(0, j)];
37+
let mut imin = 0;
38+
for i in 1..dim {
39+
if matrix[(i, j)] < min {
40+
min = matrix[(i,j)];
41+
imin = i;
42+
}
43+
}
44+
45+
v[j] = min;
46+
matches[imin] += 1;
47+
48+
if matches[imin] == 1 {
49+
// init assignment if minimum row assigned for first time.
50+
in_row[imin] = j as isize;
51+
in_col[j] = imin as isize;
52+
} else {
53+
in_col[j] = -1; // row already assigned, column not assigned.
54+
}
55+
}
56+
println!("lapjv: column reduction finished");
57+
58+
// REDUCTION TRANSFER
59+
let mut numfree = 0;
60+
for i in 0..dim {
61+
if matches[i] == 0 { // fill list of unassigned 'free' rows.
62+
free[numfree] = i;
63+
numfree +=1;
64+
} else if matches[i] == 1 { // transfer reduction from rows that are assigned once.
65+
let j1 = in_row[i] as usize;
66+
let mut min = std::f64::MAX;
67+
for j in 0..dim {
68+
if j != j1 && matrix[(i,j)] - v[j] < min {
69+
min = matrix[(i,j)] - v[j];
70+
}
71+
}
72+
v[j1] -= min;
73+
}
74+
}
75+
76+
println!("lapjv: REDUCTION TRANSFER finished");
77+
78+
// AUGMENTING ROW REDUCTION
79+
for loopcmt in 0..2 { // loop to be done twice.
80+
// scan all free rows.
81+
// in some cases, a free row may be replaced with another one to be scanned next.
82+
let mut k = 0;
83+
let prvnumfree = numfree;
84+
numfree = 0; // start list of rows still free after augmenting row reduction.
85+
86+
while k < prvnumfree {
87+
let i = free[k];
88+
k += 1;
89+
// find minimum and second minimum reduced cost over columns.
90+
// let (umin, usubmin, j1, j2) = find_umins_plain(matrix, i, &v);
91+
let mut umin = matrix[[i,0]] - v[0];
92+
let mut usubmin = std::f64::MAX;
93+
let mut j1 = 0;
94+
let mut j2 = 0;
95+
let mut i0;
96+
97+
for j in 1..dim {
98+
let h = matrix[(i,j)] - v[j];
99+
if h < usubmin {
100+
if h >= umin {
101+
usubmin = h;
102+
j2 = j;
103+
} else {
104+
usubmin = umin;
105+
umin = h;
106+
j2 = j1;
107+
j1 = j;
108+
}
109+
}
110+
}
111+
112+
i0 = in_col[j1];
113+
if umin < usubmin {
114+
v[j1] = v[j1] - (usubmin - umin);
115+
} else if i0 >= 0 {
116+
j1 = j2;
117+
i0 = in_col[j2];
118+
}
119+
120+
// (re-)assign i to j1, possibly de-assigning an i0.
121+
in_row[i] = j1 as isize;
122+
in_col[j1 as usize] = i as isize;
123+
if i0 >= 0 { // minimum column j1 assigned earlier.
124+
if umin < usubmin {
125+
// put in current k, and go back to that k.
126+
// continue augmenting path i - j1 with i0.
127+
k -= 1;
128+
free[k] = i0 as usize;
129+
} else {
130+
// no further augmenting reduction possible.
131+
// store i0 in list of free rows for next phase.
132+
free[numfree] = i0 as usize;
133+
numfree += 1;
134+
}
135+
}
136+
}
137+
println!("lapjv: augmenting row reduction: {}/{}", loopcmt, 2);
138+
}
139+
140+
for f in 0..numfree {
141+
let freerow = free[f];
142+
let mut endofpath = 0;
143+
for j in 0..dim {
144+
d[j] = matrix[(freerow, j)] - v[j];
145+
pred[j] = freerow;
146+
collist[j] = j;
147+
}
148+
149+
let mut low = 0;
150+
let mut up = 0;
151+
let mut unassignedfound = false;
152+
153+
while !unassignedfound {
154+
let mut min = 0f64;
155+
let mut last = 0;
156+
if up == low {
157+
last = low - 1;
158+
min = d[collist[up]];
159+
up += 1;
160+
161+
for k in up..dim {
162+
let j = collist[k];
163+
let h = d[j];
164+
if h <= min {
165+
if h < min {
166+
up = low;
167+
min = h;
168+
}
169+
collist[k] = collist[up];
170+
collist[up] = j;
171+
up += 1;
172+
}
173+
}
174+
175+
for k in low..up {
176+
if in_col[collist[k]] < 0 {
177+
endofpath = collist[k];
178+
unassignedfound = true;
179+
break;
180+
}
181+
}
182+
}
183+
184+
if !unassignedfound {
185+
let j1 = collist[low];
186+
low += 1;
187+
let i = in_col[j1] as usize;
188+
let h = matrix[(i, j1)] - v[j1] - min;
189+
190+
for k in up..dim {
191+
let j = collist[k];
192+
let v2 = matrix[(i, j)] - v[j] - h;
193+
194+
if v2 < d[j] {
195+
pred[j] = i;
196+
197+
if v2 == min {
198+
if in_col[j] < 0 {
199+
endofpath = j;
200+
unassignedfound = true;
201+
break;
202+
} else {
203+
collist[k] = collist[up];
204+
collist[up] = j;
205+
up += 1;
206+
}
207+
}
208+
209+
d[j] = v2;
210+
}
211+
}
212+
}
213+
214+
for k in 0..last {
215+
let j1 = collist[k];
216+
v[j1] += d[j1] - min;
217+
}
218+
219+
let mut i = freerow + 1;
220+
while i != freerow {
221+
i = pred[endofpath];
222+
in_col[endofpath] = i as isize;
223+
let j1 = endofpath;
224+
endofpath = in_row[i] as usize;
225+
in_row[i] = j1 as isize;
226+
}
227+
}
228+
}
229+
(in_row, in_col)
230+
}
231+
232+
#[inline(always)]
233+
fn find_umins_plain_(dim: usize, idx: usize, assign_cost: &[f64], v: &[f64]) -> (f64, f64, isize, isize) {
234+
let local_cost = &assign_cost[idx * dim..];
235+
let mut umin = local_cost[0] - v[0];
236+
let mut j1 = 0isize;
237+
let mut j2 = -1isize;
238+
let mut usubmin = std::f64::MAX;
239+
for j in 1..dim {
240+
let h = local_cost[j] - v[j];
241+
if h < usubmin {
242+
if h >= umin {
243+
usubmin = h;
244+
j2 = j as isize;
245+
} else {
246+
usubmin = umin;
247+
umin = h;
248+
j2 = j1;
249+
j1 = j as isize;
250+
}
251+
}
252+
}
253+
(umin, usubmin, j1, j2)
254+
}
255+
256+
#[inline(always)]
257+
fn find_umins_plain(matrix: &Matrix<f64>, row: usize, v: &[f64]) -> (f64, f64, isize, isize) {
258+
let local_cost = matrix.row(row);
259+
let mut umin = local_cost[0] - v[0];
260+
let mut j1 = 0isize;
261+
let mut j2 = -1isize;
262+
let mut usubmin = std::f64::MAX;
263+
for j in 1..local_cost.dim() {
264+
let h = local_cost[j] - v[j];
265+
if h < usubmin {
266+
if h >= umin {
267+
usubmin = h;
268+
j2 = j as isize;
269+
} else {
270+
usubmin = umin;
271+
umin = h;
272+
j2 = j1;
273+
j1 = j as isize;
274+
}
275+
}
276+
}
277+
(umin, usubmin, j1, j2)
278+
}
279+
280+
#[cfg(test)]
281+
mod tests {
282+
use super::*;
283+
#[test]
284+
fn it_works() {
285+
let m = Matrix::from_shape_vec((3,3), vec![1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]).unwrap();
286+
let result = lapjv(&m);
287+
assert_eq!(result.0, vec![2, 0, 1]);
288+
assert_eq!(result.1, vec![1, 2, 0]);
289+
}
290+
291+
#[test]
292+
fn test_solve_random10() {
293+
const N: usize = 10;
294+
let c = vec![
295+
612, 643, 717, 2, 946, 534, 242, 235, 376, 839,
296+
224, 141, 799, 180, 386, 745, 592, 822, 421, 42,
297+
241, 369, 831, 67, 258, 549, 615, 529, 458, 524,
298+
231, 649, 287, 910, 12, 820, 31, 92, 217, 555,
299+
912, 81, 568, 241, 292, 653, 417, 652, 630, 788,
300+
32, 822, 788, 166, 122, 690, 304, 568, 449, 214,
301+
441, 469, 584, 633, 213, 414, 498, 500, 317, 391,
302+
798, 581, 183, 420, 16, 748, 35, 516, 639, 356,
303+
351, 921, 67, 33, 592, 775, 780, 335, 464, 788,
304+
771, 455, 950, 25, 22, 576, 969, 122, 86, 74,
305+
].iter().map(|x| *x as f64).collect();
306+
let m = Matrix::from_shape_vec((N,N), c).unwrap();
307+
let result = lapjv(&m);
308+
let cost = cost(&m, (&result.0, &result.1));
309+
assert_eq!(cost, 1071.0);
310+
assert_eq!(result.0, vec![7,9,3,4,1,0,5,6,2,8]);
311+
}
312+
313+
fn cost(input: &Matrix<f64>, (rows, _cols): (&[isize], &[isize])) -> f64 {
314+
(0..rows.len()).into_iter().fold(0.0, |acc, i| acc + input[(i, rows[i] as usize)])
315+
}
316+
}

0 commit comments

Comments
 (0)