1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2 /* */
3 /* This file is part of the program and library */
4 /* SCIP --- Solving Constraint Integer Programs */
5 /* */
6 /* Copyright (C) 2002-2021 Konrad-Zuse-Zentrum */
7 /* fuer Informationstechnik Berlin */
8 /* */
9 /* SCIP is distributed under the terms of the ZIB Academic License. */
10 /* */
11 /* You should have received a copy of the ZIB Academic License */
12 /* along with SCIP; see the file COPYING. If not visit scipopt.org. */
13 /* */
14 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
15
16 /**@file solveknapsackexactly.c
17 * @brief unit tests for exact dynamic programming algorithm for knapsack problem
18 * @author Marc Pfetsch
19 * @author Gregor Hendel
20 */
21
22 #include "scip/cons_knapsack.h"
23 #include "scip/scip.h"
24
25 #include "include/scip_test.h"
26
27 #define EPS 1e-06
28 #define MAX_ARRAYLEN 1000
29
30 /* GLOBAL VARIABLES */
31 static SCIP* scip;
32 static SCIP_Longint weights[MAX_ARRAYLEN];
33 static int nitems;
34 static SCIP_Real profits[MAX_ARRAYLEN];
35 static int items[MAX_ARRAYLEN];
36 static int solitems[MAX_ARRAYLEN];
37 static int nonsolitems[MAX_ARRAYLEN];
38 static SCIP_Longint capacity;
39 static SCIP_Bool success;
40 static SCIP_Real solval;
41 static int nnonsolitems;
42 static int nsolitems = -1;
43
44 /* TEST SUITE */
45 static
setup(void)46 void setup(void)
47 {
48 int i;
49
50 SCIPcreate(&scip);
51
52 /* assign items */
53 for( i = 0; i < MAX_ARRAYLEN; ++i )
54 items[i] = i;
55 }
56
57 static
teardown(void)58 void teardown(void)
59 {
60 SCIPfree(&scip);
61 }
62
63 /** run exact knapsack algorithm on the given data */
64 static
solveKnapsack(void)65 SCIP_RETCODE solveKnapsack(
66 void
67 )
68 {
69 SCIP_CALL( SCIPsolveKnapsackExactly(scip, nitems, weights, profits, capacity, items, solitems, nonsolitems, &nsolitems, &nnonsolitems, &solval, &success) );
70
71 return SCIP_OKAY;
72 }
73
74 /** is set1 contained in set2? */
75 static
checkSetContainment(int * set1,int * set2,int len1,int len2)76 SCIP_Bool checkSetContainment(
77 int* set1,
78 int* set2,
79 int len1,
80 int len2
81 )
82 {
83 int j;
84 int sortedset2 [MAX_ARRAYLEN];
85
86 cr_assert(len1 <= MAX_ARRAYLEN);
87 cr_assert(len2 <= MAX_ARRAYLEN);
88
89 if( len1 > len2 )
90 return FALSE;
91
92 BMScopyMemoryArray(sortedset2, set2, len2);
93 SCIPsortInt(sortedset2, len2);
94
95 /* find each item of set 1 in set 2 */
96 for( j = 0; j < len1; ++j )
97 {
98 int pos;
99 if( ! SCIPsortedvecFindInt(sortedset2, set1[j], len2, &pos) )
100 return FALSE;
101 }
102
103 return TRUE;
104 }
105
106 TestSuite(solveknapsackexactly, .init = setup, .fini = teardown);
107
108 /* TESTS */
109
110 /*
111 * test trivial cases
112 */
113 Test(solveknapsackexactly, test1, .description="check whether the case that all items are redundant is caught correctly")
114 {
115 nitems = 2;
116 capacity = 1LL;
117 weights[0] = 2LL;
118 weights[1] = 1LL;
119 profits[0] = 1.0; /* should not be taken, since capacity is too large */
120 profits[1] = -1.0; /* should not be taken, since profit is negative */
121
122 solveKnapsack();
123
124 cr_assert( success );
125 cr_assert( nsolitems == 0 );
126 cr_assert( nnonsolitems == 2 );
127 cr_assert_float_eq(solval, 0.0, EPS);
128 }
129
130 Test(solveknapsackexactly, test2, .description="test whether the correct items are sorted out (weight == 0 or negative profit)")
131 {
132 /* also tests whether the case of all items fitting into the knapsack is caught correctly. */
133 nitems = 5;
134 capacity = 1LL;
135 weights[0] = 0LL;
136 weights[1] = 0LL;
137 weights[2] = 2LL;
138 weights[3] = 2LL;
139 weights[4] = 1LL;
140
141 profits[0] = 1.0; /* should take item 1, since weight is 0 */
142 profits[1] = -1.0; /* should not take item 1, since profit is negative, although weight is 0 */
143 profits[2] = 1.0; /* should not take item 2, since weight is too large */
144 profits[3] = -1.0; /* should not take item 3, since weight is too large and profit is negative */
145 profits[4] = 1.0; /* should take item */
146
147 solveKnapsack();
148
149 cr_assert( success );
150 cr_assert( nsolitems == 2 );
151 cr_assert( nnonsolitems == 3 );
152 cr_assert( solitems[0] == 0 );
153 cr_assert( solitems[1] == 4 );
154 cr_assert( nonsolitems[0] == 1 );
155 cr_assert( nonsolitems[1] == 2 );
156 cr_assert( nonsolitems[2] == 3 );
157 cr_assert_float_eq(solval, 2.0, EPS);
158 }
159
160 Test(solveknapsackexactly, test3, .description="test whether the case of all equal weights is handled correctly")
161 {
162 nitems = 4;
163 capacity = 4LL;
164 weights[0] = 2LL;
165 weights[1] = 2LL;
166 weights[2] = 2LL;
167 weights[3] = 2LL;
168
169 profits[0] = 1.0;
170 profits[1] = 2.0;
171 profits[2] = 3.0;
172 profits[3] = 4.0;
173
174 solveKnapsack();
175
176 cr_assert( success );
177 cr_assert( nsolitems == 2 );
178 cr_assert( nnonsolitems == 2 );
179 cr_assert( (solitems[0] == 2 && solitems[1] == 3) || (solitems[0] == 3 && solitems[1] == 2) );
180 cr_assert( (nonsolitems[0] == 0 && nonsolitems[1] == 1) || (nonsolitems[0] == 1 && nonsolitems[1] == 0) );
181 cr_assert_float_eq(solval, 7.0, EPS);
182 }
183
184 Test(solveknapsackexactly, test4, .description="test whether the case that only one item fits into the knapsack is handled correctly")
185 {
186 nitems = 3;
187 capacity = 3LL;
188 weights[0] = 2LL;
189 weights[1] = 3LL;
190 weights[2] = 2LL;
191
192 profits[0] = 3.0;
193 profits[1] = 2.0;
194 profits[2] = 1.0;
195
196 solveKnapsack();
197
198 cr_assert( success );
199 cr_assert( nsolitems == 1 );
200 cr_assert( nnonsolitems == 2 );
201 cr_assert( solitems[0] == 0 );
202 cr_assert( (nonsolitems[0] == 1 && nonsolitems[1] == 2) || (nonsolitems[0] == 2 && nonsolitems[1] == 1) );
203 cr_assert_float_eq(solval, 3.0, EPS);
204 }
205
206 Test(solveknapsackexactly, test_greedy1, .description="test greedy algorithm")
207 {
208 nitems = 3;
209 capacity = 3LL;
210 weights[0] = 1LL;
211 weights[1] = 2LL;
212 weights[2] = 1LL;
213
214 profits[0] = 3.0;
215 profits[1] = 2.0;
216 profits[2] = 0.5;
217
218 /* Due to the weights and profits, the ordering is just 0,1,2. The greedy solution will pick {0,1}. This is also
219 * optimal, since we used all the capacity and this solution is an integral optimal solution. So the greedy algorithm
220 * should allow to terminate the process. Note, however, that this cannot be seen from the outside, so, e.g., a
221 * debugger has to be used. */
222 solveKnapsack();
223
224 cr_assert( success );
225 cr_assert( nsolitems == 2 );
226 cr_assert( nnonsolitems == 1 );
227 cr_assert( (solitems[0] == 0 && solitems[1] == 1) || (solitems[0] == 1 && solitems[1] == 0) );
228 cr_assert( nonsolitems[0] == 2 );
229 cr_assert_float_eq(solval, 5, EPS);
230 }
231
232 Test(solveknapsackexactly, test_greedy2, .description="test whether greedy solution is equal to the rounded LP value")
233 {
234 nitems = 3;
235 capacity = 4LL;
236 weights[0] = 1LL;
237 weights[1] = 2LL;
238 weights[2] = 2LL;
239
240 profits[0] = 3.0;
241 profits[1] = 1.0;
242 profits[2] = 1.0;
243
244 solveKnapsack();
245
246 /* the solution packs two items and is optimal, since the LP optimal value is 4.5 */
247 cr_assert( success );
248 cr_assert( nsolitems == 2 );
249 cr_assert( nnonsolitems == 1 );
250 cr_assert_float_eq(solval, 4.0, EPS);
251 }
252
253 Test(solveknapsackexactly, test_general, .description="general test")
254 {
255 nitems = 6;
256 capacity = 13LL;
257 weights[0] = 7LL;
258 weights[1] = 2LL;
259 weights[2] = 7LL;
260 weights[3] = 5LL;
261 weights[4] = 1LL;
262 weights[5] = 3LL;
263
264 profits[0] = 1.0;
265 profits[1] = 1.0;
266 profits[2] = 1.0;
267 profits[3] = 1.0;
268 profits[4] = 1.0;
269 profits[5] = 1.0;
270
271 solveKnapsack();
272
273 cr_assert( success );
274 cr_assert( nsolitems == 4 );
275 cr_assert( nnonsolitems == 2 );
276 cr_assert( (nonsolitems[0] == 0 && nonsolitems[1] == 2) || (nonsolitems[0] == 2 && nonsolitems[1] == 0) );
277 cr_assert_float_eq(solval, 4.0, EPS);
278 }
279
280 /* large test */
281 Test(solveknapsackexactly, test_large, .description="large test")
282 {
283 int j;
284
285 nitems = 1000;
286 capacity = nitems + 1;
287
288 /* half of the items have a weight of 2 and a smaller profit */
289 for (j = 0; j < nitems/2; ++j)
290 {
291 weights[j] = 2LL;
292 profits[j] = j;
293 }
294
295 /* the remaining half of the items have a weight of 1 but a larger profit; all of them should be selected */
296 for (j = nitems/2; j < nitems; ++j)
297 {
298 weights[j] = 1LL;
299 profits[j] = j;
300 }
301
302 solveKnapsack();
303
304 cr_assert( success );
305 cr_assert( nsolitems == 750 ); /* the 500 latter items with higher profit, and 250 of the less profitable items */
306 cr_assert( nnonsolitems == nitems - 750 );
307 cr_assert( checkSetContainment(&items[nitems/2], solitems, nitems / 2, nsolitems) );
308 cr_assert( checkSetContainment(&items[250], solitems, 250, nsolitems) );
309 cr_assert( checkSetContainment(items, nonsolitems, 250, nnonsolitems) );
310 }
311