Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/numerical/knapsack.py
4057 views
1
r"""
2
Knapsack Problems
3
4
This module implements a number of solutions to various knapsack problems,
5
otherwise known as linear integer programming problems. Solutions to the
6
following knapsack problems are implemented:
7
8
- Solving the subset sum problem for super-increasing sequences.
9
- General case using Linear Programming
10
11
AUTHORS:
12
13
- Minh Van Nguyen (2009-04): initial version
14
- Nathann Cohen (2009-08): Linear Programming version
15
16
Definition of Knapsack problems
17
-------------------------------
18
19
You have already had a knapsack problem, so you should know, but in case you
20
do not, a knapsack problem is what happens when you have hundred of items to
21
put into a bag which is too small for all of them.
22
23
When you formally write it, here is your problem:
24
25
* Your bag can contain a weight of at most `W`.
26
* Each item `i` you have has a weight `w_i`.
27
* Each item `i` has a usefulness of `u_i`.
28
29
You then want to maximize the usefulness of the items you will store into
30
your bag, while keeping sure the weight of the bag will not go over `W`.
31
32
As a linear program, this problem can be represented this way
33
(if you define `b_i` as the binary variable indicating whether
34
the item `i` is to be included in your bag):
35
36
.. MATH::
37
38
\mbox{Maximize: }\sum_i b_i u_i \\
39
\mbox{Such that: }
40
\sum_i b_i w_i \leq W \\
41
\forall i, b_i \mbox{ binary variable}
42
43
(For more information, cf. http://en.wikipedia.org/wiki/Knapsack_problem.)
44
45
Examples
46
--------
47
48
If your knapsack problem is composed of three items (weight, value)
49
defined by (1,2), (1.5,1), (0.5,3), and a bag of maximum weight 2,
50
you can easily solve it this way::
51
52
sage: from sage.numerical.knapsack import knapsack
53
sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2)
54
[5.0, [(1, 2), (0.500000000000000, 3)]]
55
56
Super-increasing sequences
57
--------------------------
58
59
We can test for whether or not a sequence is super-increasing::
60
61
sage: from sage.numerical.knapsack import Superincreasing
62
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
63
sage: seq = Superincreasing(L)
64
sage: seq
65
Super-increasing sequence of length 8
66
sage: seq.is_superincreasing()
67
True
68
sage: Superincreasing().is_superincreasing([1,3,5,7])
69
False
70
71
Solving the subset sum problem for a super-increasing sequence
72
and target sum::
73
74
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
75
sage: Superincreasing(L).subset_sum(98)
76
[69, 21, 5, 2, 1]
77
"""
78
79
#*****************************************************************************
80
# Copyright (C) 2009 Minh Van Nguyen <[email protected]>
81
#
82
# Distributed under the terms of the GNU General Public License (GPL)
83
#
84
# http://www.gnu.org/licenses/
85
#
86
# This program is free software; you can redistribute it and/or modify
87
# it under the terms of the GNU General Public License as published by
88
# the Free Software Foundation; either version 2 of the License, or
89
# (at your option) any later version.
90
#
91
# This program is distributed in the hope that it will be useful,
92
# but WITHOUT ANY WARRANTY; without even the implied warranty of
93
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
94
# GNU General Public License for more details.
95
#*****************************************************************************
96
97
from sage.misc.latex import latex
98
from sage.rings.integer import Integer
99
from sage.structure.sage_object import SageObject
100
101
class Superincreasing(SageObject):
102
r"""
103
A class for super-increasing sequences.
104
105
Let `L = (a_1, a_2, a_3, \dots, a_n)` be a non-empty sequence of
106
non-negative integers. Then `L` is said to be super-increasing if
107
each `a_i` is strictly greater than the sum of all previous values.
108
That is, for each `a_i \in L` the sequence `L` must satisfy the property
109
110
.. MATH::
111
112
a_i > \sum_{k=1}^{i-1} a_k
113
114
in order to be called a super-increasing sequence, where `|L| \geq 2`.
115
If `L` has only one element, it is also defined to be a
116
super-increasing sequence.
117
118
If ``seq`` is ``None``, then construct an empty sequence. By
119
definition, this empty sequence is not super-increasing.
120
121
122
INPUT:
123
124
- ``seq`` -- (default: ``None``) a non-empty sequence.
125
126
127
EXAMPLES::
128
129
sage: from sage.numerical.knapsack import Superincreasing
130
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
131
sage: Superincreasing(L).is_superincreasing()
132
True
133
sage: Superincreasing().is_superincreasing([1,3,5,7])
134
False
135
sage: seq = Superincreasing(); seq
136
An empty sequence.
137
sage: seq = Superincreasing([1, 3, 6]); seq
138
Super-increasing sequence of length 3
139
sage: seq = Superincreasing(list([1, 2, 5, 21, 69, 189, 376, 919])); seq
140
Super-increasing sequence of length 8
141
"""
142
143
def __init__(self, seq=None):
144
r"""
145
Constructing a super-increasing sequence object from ``seq``.
146
147
If ``seq`` is ``None``, then construct an empty sequence. By
148
definition, this empty sequence is not super-increasing.
149
150
151
INPUT:
152
153
- ``seq`` -- (default: ``None``) a non-empty sequence.
154
155
156
EXAMPLES::
157
158
sage: from sage.numerical.knapsack import Superincreasing
159
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
160
sage: Superincreasing(L).is_superincreasing()
161
True
162
sage: Superincreasing().is_superincreasing([1,3,5,7])
163
False
164
"""
165
# argument seq is None, so construct an empty sequence
166
if seq is None:
167
self._seq = None
168
# now seq is known to be not None, so try to construct a
169
# super-increasing sequence from seq
170
else:
171
if self.is_superincreasing(seq):
172
self._seq = seq
173
else:
174
raise ValueError, "seq must be a super-increasing sequence"
175
176
def __cmp__(self, other):
177
r"""
178
Comparing ``self`` to ``other``.
179
180
181
TESTS::
182
183
sage: from sage.numerical.knapsack import Superincreasing
184
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
185
sage: seq = Superincreasing(L)
186
sage: seq == loads(dumps(seq))
187
True
188
"""
189
return cmp(self._seq, other._seq)
190
191
def __repr__(self):
192
r"""
193
Return a string representation of this super-increasing
194
sequence object.
195
196
197
EXAMPLES::
198
199
sage: from sage.numerical.knapsack import Superincreasing
200
sage: seq = Superincreasing(); seq
201
An empty sequence.
202
sage: seq = Superincreasing([1, 3, 6]); seq
203
Super-increasing sequence of length 3
204
sage: seq = Superincreasing(list([1, 2, 5, 21, 69, 189, 376, 919])); seq
205
Super-increasing sequence of length 8
206
"""
207
if self._seq is None:
208
return "An empty sequence."
209
else:
210
return "Super-increasing sequence of length %s" % len(self._seq)
211
212
def largest_less_than(self, N):
213
r"""
214
Return the largest integer in the sequence ``self`` that is less than
215
or equal to ``N``.
216
217
This function narrows down the candidate solution using a binary trim,
218
similar to the way binary search halves the sequence at each iteration.
219
220
221
INPUT:
222
223
- ``N`` -- integer; the target value to search for.
224
225
226
OUTPUT:
227
228
The largest integer in ``self`` that is less than or equal to ``N``. If
229
no solution exists, then return ``None``.
230
231
232
EXAMPLES:
233
234
When a solution is found, return it::
235
236
sage: from sage.numerical.knapsack import Superincreasing
237
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
238
sage: Superincreasing(L).largest_less_than(207)
239
179
240
sage: L = (2, 3, 7, 25, 67, 179, 356, 819)
241
sage: Superincreasing(L).largest_less_than(2)
242
2
243
244
But if no solution exists, return ``None``::
245
246
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
247
sage: Superincreasing(L).largest_less_than(-1) == None
248
True
249
250
TESTS:
251
252
The target ``N`` must be an integer::
253
254
sage: from sage.numerical.knapsack import Superincreasing
255
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
256
sage: Superincreasing(L).largest_less_than(2.30)
257
Traceback (most recent call last):
258
...
259
TypeError: N (= 2.30000000000000) must be an integer.
260
261
The sequence that ``self`` represents must also be non-empty::
262
263
sage: Superincreasing([]).largest_less_than(2)
264
Traceback (most recent call last):
265
...
266
ValueError: seq must be a super-increasing sequence
267
sage: Superincreasing(list()).largest_less_than(2)
268
Traceback (most recent call last):
269
...
270
ValueError: seq must be a super-increasing sequence
271
"""
272
from sage.functions.other import Function_floor
273
floor = Function_floor()
274
# input error handling
275
if len(self._seq) == 0:
276
raise TypeError, "self must be a non-empty list of integers."
277
if (not isinstance(N, Integer)) and (not isinstance(N, int)):
278
raise TypeError, "N (= %s) must be an integer." % N
279
280
# halving the list at each iteration, just like binary search
281
# TODO: some error handling to ensure that self only contains integers?
282
low = 0
283
high = len(self._seq) - 1
284
while low <= high:
285
mid = floor((low + high) / 2)
286
if N == self._seq[mid]:
287
return self._seq[mid]
288
if N < self._seq[mid]:
289
high = mid - 1
290
else:
291
low = mid + 1
292
if N >= self._seq[high]:
293
return self._seq[high]
294
else:
295
return None
296
297
def _latex_(self):
298
r"""
299
Return LaTeX representation of ``self``.
300
301
302
EXAMPLES::
303
304
sage: from sage.numerical.knapsack import Superincreasing
305
sage: latex(Superincreasing())
306
\left[\right]
307
sage: seq = Superincreasing([1, 2, 5, 21, 69, 189, 376, 919])
308
sage: latex(seq)
309
<BLANKLINE>
310
\left[1,
311
2,
312
5,
313
21,
314
69,
315
189,
316
376,
317
919\right]
318
"""
319
if self._seq is None:
320
return latex([])
321
else:
322
return latex(self._seq)
323
324
def is_superincreasing(self, seq=None):
325
r"""
326
Determine whether or not ``seq`` is super-increasing.
327
328
If ``seq=None`` then determine whether or not ``self`` is
329
super-increasing.
330
331
Let `L = (a_1, a_2, a_3, \dots, a_n)` be a non-empty sequence of
332
non-negative integers. Then `L` is said to be super-increasing if
333
each `a_i` is strictly greater than the sum of all previous values.
334
That is, for each `a_i \in L` the sequence `L` must satisfy the
335
property
336
337
.. MATH::
338
339
a_i > \sum_{k=1}^{i-1} a_k
340
341
in order to be called a super-increasing sequence, where `|L| \geq 2`.
342
If `L` has exactly one element, then it is also defined to be a
343
super-increasing sequence.
344
345
346
INPUT:
347
348
- ``seq`` -- (default: ``None``) a sequence to test
349
350
351
OUTPUT:
352
353
- If ``seq`` is ``None``, then test ``self`` to determine whether or
354
not it is super-increasing. In that case, return ``True`` if
355
``self`` is super-increasing; ``False`` otherwise.
356
357
- If ``seq`` is not ``None``, then test ``seq`` to determine whether
358
or not it is super-increasing. Return ``True`` if ``seq`` is
359
super-increasing; ``False`` otherwise.
360
361
362
EXAMPLES:
363
364
By definition, an empty sequence is not super-increasing::
365
366
sage: from sage.numerical.knapsack import Superincreasing
367
sage: Superincreasing().is_superincreasing([])
368
False
369
sage: Superincreasing().is_superincreasing()
370
False
371
sage: Superincreasing().is_superincreasing(tuple())
372
False
373
sage: Superincreasing().is_superincreasing(())
374
False
375
376
But here is an example of a super-increasing sequence::
377
378
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
379
sage: Superincreasing(L).is_superincreasing()
380
True
381
sage: L = (1, 2, 5, 21, 69, 189, 376, 919)
382
sage: Superincreasing(L).is_superincreasing()
383
True
384
385
A super-increasing sequence can have zero as one of its elements::
386
387
sage: L = [0, 1, 2, 4]
388
sage: Superincreasing(L).is_superincreasing()
389
True
390
391
A super-increasing sequence can be of length 1::
392
393
sage: Superincreasing([randint(0, 100)]).is_superincreasing()
394
True
395
396
397
TESTS:
398
399
The sequence must contain only integers::
400
401
sage: from sage.numerical.knapsack import Superincreasing
402
sage: L = [1.0, 2.1, pi, 21, 69, 189, 376, 919]
403
sage: Superincreasing(L).is_superincreasing()
404
Traceback (most recent call last):
405
...
406
TypeError: Element e (= 1.00000000000000) of seq must be a non-negative integer.
407
sage: L = [1, 2.1, pi, 21, 69, 189, 376, 919]
408
sage: Superincreasing(L).is_superincreasing()
409
Traceback (most recent call last):
410
...
411
TypeError: Element e (= 2.10000000000000) of seq must be a non-negative integer.
412
"""
413
# argument seq is None, so test self for super-increasing
414
if seq is None:
415
# self must be a non-empty sequence
416
if (self._seq is None) or len(self._seq) == 0:
417
return False
418
# so now self is known to represent a non-empty sequence
419
if (not isinstance(self._seq[0], Integer)) and (not isinstance(self._seq[0], int)):
420
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % self._seq[0]
421
if self._seq[0] < 0:
422
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % self._seq[0]
423
cumSum = self._seq[0] # the cumulative sum of the sequence represented by self
424
for e in self._seq[1:]:
425
if (not isinstance(e, Integer)) and (not isinstance(e, int)):
426
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % e
427
if e < 0:
428
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % e
429
if e <= cumSum:
430
return False
431
cumSum += e
432
return True
433
# now we know that seq is not None, so test seq for super-increasing
434
else:
435
# seq must be a non-empty sequence
436
if len(seq) == 0:
437
return False
438
# so now seq is known to represent a non-empty sequence
439
if (not isinstance(seq[0], Integer)) and (not isinstance(seq[0], int)):
440
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % seq[0]
441
if seq[0] < 0:
442
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % seq[0]
443
cumSum = seq[0] # the cumulative sum of the sequence seq
444
for e in seq[1:]:
445
if (not isinstance(e, Integer)) and (not isinstance(e, int)):
446
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % e
447
if e < 0:
448
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % e
449
if e <= cumSum:
450
return False
451
cumSum += e
452
return True
453
454
def subset_sum(self, N):
455
r"""
456
Solving the subset sum problem for a super-increasing sequence.
457
458
Let `S = (s_1, s_2, s_3, \dots, s_n)` be a non-empty sequence of
459
non-negative integers, and let `N \in \ZZ` be non-negative. The
460
subset sum problem asks for a subset `A \subseteq S` all of whose
461
elements sum to `N`. This method specializes the subset sum problem
462
to the case of super-increasing sequences. If a solution exists, then
463
it is also a super-increasing sequence.
464
465
.. NOTE::
466
467
This method only solves the subset sum problem for
468
super-increasing sequences. In general, solving the subset sum
469
problem for an arbitrary sequence is known to be computationally
470
hard.
471
472
473
INPUT:
474
475
- ``N`` -- a non-negative integer.
476
477
478
OUTPUT:
479
480
- A non-empty subset of ``self`` whose elements sum to ``N``. This
481
subset is also a super-increasing sequence. If no such subset
482
exists, then return the empty list.
483
484
485
ALGORITHMS:
486
487
The algorithm used is adapted from page 355 of [HPS08]_.
488
489
490
EXAMPLES:
491
492
Solving the subset sum problem for a super-increasing sequence
493
and target sum::
494
495
sage: from sage.numerical.knapsack import Superincreasing
496
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
497
sage: Superincreasing(L).subset_sum(98)
498
[69, 21, 5, 2, 1]
499
500
501
TESTS:
502
503
The target ``N`` must be a non-negative integer::
504
505
sage: from sage.numerical.knapsack import Superincreasing
506
sage: L = [0, 1, 2, 4]
507
sage: Superincreasing(L).subset_sum(-6)
508
Traceback (most recent call last):
509
...
510
TypeError: N (= -6) must be a non-negative integer.
511
sage: Superincreasing(L).subset_sum(-6.2)
512
Traceback (most recent call last):
513
...
514
TypeError: N (= -6.20000000000000) must be a non-negative integer.
515
516
The sequence that ``self`` represents must only contain non-negative
517
integers::
518
519
sage: L = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1]
520
sage: Superincreasing(L).subset_sum(1)
521
Traceback (most recent call last):
522
...
523
TypeError: Element e (= -10) of seq must be a non-negative integer.
524
525
526
REFERENCES:
527
528
.. [HPS08] J. Hoffstein, J. Pipher, and J.H. Silverman. *An
529
Introduction to Mathematical Cryptography*. Springer, 2008.
530
"""
531
# input error handling
532
if not self.is_superincreasing():
533
raise TypeError, "self is not super-increasing. Only super-increasing sequences are currently supported."
534
if (not isinstance(N, Integer)) and (not isinstance(N, int)):
535
raise TypeError, "N (= %s) must be a non-negative integer." % N
536
if N < 0:
537
raise TypeError, "N (= %s) must be a non-negative integer." % N
538
539
# solve subset sum problem for super-increasing sequence
540
candidates = []
541
a = self.largest_less_than(N)
542
while a is not None:
543
candidates.append(a)
544
a = self.largest_less_than(N - sum(candidates))
545
546
lst = list(set(candidates)) # removing any duplicate elements
547
if len(lst) != len(candidates):
548
return []
549
if sum(candidates) == N:
550
return candidates
551
else:
552
return []
553
554
def knapsack(seq, binary=True, max=1, value_only=False):
555
r"""
556
Solves the knapsack problem
557
558
Knapsack problems:
559
560
You have already had a knapsack problem, so you should know,
561
but in case you do not, a knapsack problem is what happens
562
when you have hundred of items to put into a bag which is
563
too small for all of them.
564
565
When you formally write it, here is your problem:
566
567
* Your bag can contain a weight of at most `W`.
568
* Each item `i` you have has a weight `w_i`.
569
* Each item `i` has a usefulness of `u_i`.
570
571
You then want to maximize the usefulness of the items you
572
will store into your bag, while keeping sure the weight of
573
the bag will not go over `W`.
574
575
As a linear program, this problem can be represented this way
576
(if you define `b_i` as the binary variable indicating whether
577
the item `i` is to be included in your bag):
578
579
.. MATH::
580
581
\mbox{Maximize: }\sum_i b_i u_i \\
582
\mbox{Such that: }
583
\sum_i b_i w_i \leq W \\
584
\forall i, b_i \mbox{ binary variable} \\
585
586
(For more information,
587
cf. http://en.wikipedia.org/wiki/Knapsack_problem.)
588
589
EXAMPLE:
590
591
If your knapsack problem is composed of three
592
items (weight, value) defined by (1,2), (1.5,1), (0.5,3),
593
and a bag of maximum weight 2, you can easily solve it this way::
594
595
sage: from sage.numerical.knapsack import knapsack
596
sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2)
597
[5.0, [(1, 2), (0.500000000000000, 3)]]
598
599
sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True)
600
5.0
601
602
In the case where all the values (usefulness) of the items
603
are equal to one, you do not need embarrass yourself with
604
the second values, and you can just type for items
605
`(1,1), (1.5,1), (0.5,1)` the command::
606
607
sage: from sage.numerical.knapsack import knapsack
608
sage: knapsack([1,1.5,0.5], max=2, value_only=True)
609
2.0
610
611
INPUT:
612
613
- ``seq`` -- Two different possible types:
614
615
- A sequence of pairs (weight, value).
616
- A sequence of reals (a value of 1 is assumed).
617
618
- ``binary`` -- When set to True, an item can be taken 0 or 1 time.
619
When set to False, an item can be taken any amount of
620
times (while staying integer and positive).
621
622
- ``max`` -- Maximum admissible weight.
623
624
- ``value_only`` -- When set to True, only the maximum useful
625
value is returned. When set to False, both the maximum useful
626
value and an assignment are returned.
627
628
OUTPUT:
629
630
If ``value_only`` is set to True, only the maximum useful value
631
is returned. Else (the default), the function returns a pair
632
``[value,list]``, where ``list`` can be of two types according
633
to the type of ``seq``:
634
635
- A list of pairs `(w_i, u_i)` for each object `i` occurring
636
in the solution.
637
- A list of reals where each real is repeated the number
638
of times it is taken into the solution.
639
"""
640
reals = not isinstance(seq[0], tuple)
641
if reals:
642
seq = [(x,1) for x in seq]
643
644
from sage.numerical.mip import MixedIntegerLinearProgram, Sum
645
p = MixedIntegerLinearProgram(maximization=True)
646
present = p.new_variable()
647
p.set_objective(Sum([present[i] * seq[i][1] for i in range(len(seq))]))
648
p.add_constraint(Sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max)
649
650
if binary:
651
p.set_binary(present)
652
else:
653
p.set_integer(present)
654
655
if value_only:
656
return p.solve(objective_only=True)
657
658
else:
659
objective = p.solve()
660
present = p.get_values(present)
661
662
val = []
663
664
if reals:
665
[val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq))]
666
else:
667
[val.extend([seq[i]] * int(present[i])) for i in range(len(seq))]
668
669
return [objective,val]
670
671