Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/numerical/knapsack.py
8815 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 do
20
not, a knapsack problem is what happens when you have hundred of items to put
21
into a bag which is too small, and you want to pack the most useful 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` has a weight `w_i`.
27
* Each item `i` has a usefulness `u_i`.
28
29
You then want to maximize the total 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 (if you define
33
`b_i` as the binary variable indicating whether the item `i` is to be included
34
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, see the :wikipedia:`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
INPUT:
122
123
- ``seq`` -- (default: ``None``) a non-empty sequence.
124
125
EXAMPLES::
126
127
sage: from sage.numerical.knapsack import Superincreasing
128
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
129
sage: Superincreasing(L).is_superincreasing()
130
True
131
sage: Superincreasing().is_superincreasing([1,3,5,7])
132
False
133
sage: seq = Superincreasing(); seq
134
An empty sequence.
135
sage: seq = Superincreasing([1, 3, 6]); seq
136
Super-increasing sequence of length 3
137
sage: seq = Superincreasing(list([1, 2, 5, 21, 69, 189, 376, 919])); seq
138
Super-increasing sequence of length 8
139
"""
140
141
def __init__(self, seq=None):
142
r"""
143
Constructing a super-increasing sequence object from ``seq``.
144
145
If ``seq`` is ``None``, then construct an empty sequence. By
146
definition, this empty sequence is not super-increasing.
147
148
149
INPUT:
150
151
- ``seq`` -- (default: ``None``) a non-empty sequence.
152
153
154
EXAMPLES::
155
156
sage: from sage.numerical.knapsack import Superincreasing
157
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
158
sage: Superincreasing(L).is_superincreasing()
159
True
160
sage: Superincreasing().is_superincreasing([1,3,5,7])
161
False
162
"""
163
# argument seq is None, so construct an empty sequence
164
if seq is None:
165
self._seq = None
166
# now seq is known to be not None, so try to construct a
167
# super-increasing sequence from seq
168
else:
169
if self.is_superincreasing(seq):
170
self._seq = seq
171
else:
172
raise ValueError, "seq must be a super-increasing sequence"
173
174
def __cmp__(self, other):
175
r"""
176
Comparing ``self`` to ``other``.
177
178
179
TESTS::
180
181
sage: from sage.numerical.knapsack import Superincreasing
182
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
183
sage: seq = Superincreasing(L)
184
sage: seq == loads(dumps(seq))
185
True
186
"""
187
return cmp(self._seq, other._seq)
188
189
def __repr__(self):
190
r"""
191
Return a string representation of this super-increasing
192
sequence object.
193
194
195
EXAMPLES::
196
197
sage: from sage.numerical.knapsack import Superincreasing
198
sage: seq = Superincreasing(); seq
199
An empty sequence.
200
sage: seq = Superincreasing([1, 3, 6]); seq
201
Super-increasing sequence of length 3
202
sage: seq = Superincreasing(list([1, 2, 5, 21, 69, 189, 376, 919])); seq
203
Super-increasing sequence of length 8
204
"""
205
if self._seq is None:
206
return "An empty sequence."
207
else:
208
return "Super-increasing sequence of length %s" % len(self._seq)
209
210
def largest_less_than(self, N):
211
r"""
212
Return the largest integer in the sequence ``self`` that is less than
213
or equal to ``N``.
214
215
This function narrows down the candidate solution using a binary trim,
216
similar to the way binary search halves the sequence at each iteration.
217
218
219
INPUT:
220
221
- ``N`` -- integer; the target value to search for.
222
223
224
OUTPUT:
225
226
The largest integer in ``self`` that is less than or equal to ``N``. If
227
no solution exists, then return ``None``.
228
229
230
EXAMPLES:
231
232
When a solution is found, return it::
233
234
sage: from sage.numerical.knapsack import Superincreasing
235
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
236
sage: Superincreasing(L).largest_less_than(207)
237
179
238
sage: L = (2, 3, 7, 25, 67, 179, 356, 819)
239
sage: Superincreasing(L).largest_less_than(2)
240
2
241
242
But if no solution exists, return ``None``::
243
244
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
245
sage: Superincreasing(L).largest_less_than(-1) == None
246
True
247
248
TESTS:
249
250
The target ``N`` must be an integer::
251
252
sage: from sage.numerical.knapsack import Superincreasing
253
sage: L = [2, 3, 7, 25, 67, 179, 356, 819]
254
sage: Superincreasing(L).largest_less_than(2.30)
255
Traceback (most recent call last):
256
...
257
TypeError: N (= 2.30000000000000) must be an integer.
258
259
The sequence that ``self`` represents must also be non-empty::
260
261
sage: Superincreasing([]).largest_less_than(2)
262
Traceback (most recent call last):
263
...
264
ValueError: seq must be a super-increasing sequence
265
sage: Superincreasing(list()).largest_less_than(2)
266
Traceback (most recent call last):
267
...
268
ValueError: seq must be a super-increasing sequence
269
"""
270
from sage.functions.other import Function_floor
271
floor = Function_floor()
272
# input error handling
273
if len(self._seq) == 0:
274
raise TypeError, "self must be a non-empty list of integers."
275
if (not isinstance(N, Integer)) and (not isinstance(N, int)):
276
raise TypeError, "N (= %s) must be an integer." % N
277
278
# halving the list at each iteration, just like binary search
279
# TODO: some error handling to ensure that self only contains integers?
280
low = 0
281
high = len(self._seq) - 1
282
while low <= high:
283
mid = floor((low + high) / 2)
284
if N == self._seq[mid]:
285
return self._seq[mid]
286
if N < self._seq[mid]:
287
high = mid - 1
288
else:
289
low = mid + 1
290
if N >= self._seq[high]:
291
return self._seq[high]
292
else:
293
return None
294
295
def _latex_(self):
296
r"""
297
Return LaTeX representation of ``self``.
298
299
EXAMPLES::
300
301
sage: from sage.numerical.knapsack import Superincreasing
302
sage: latex(Superincreasing())
303
\left[\right]
304
sage: seq = Superincreasing([1, 2, 5, 21, 69, 189, 376, 919])
305
sage: latex(seq)
306
<BLANKLINE>
307
\left[1,
308
2,
309
5,
310
21,
311
69,
312
189,
313
376,
314
919\right]
315
"""
316
if self._seq is None:
317
return latex([])
318
else:
319
return latex(self._seq)
320
321
def is_superincreasing(self, seq=None):
322
r"""
323
Determine whether or not ``seq`` is super-increasing.
324
325
If ``seq=None`` then determine whether or not ``self`` is
326
super-increasing.
327
328
Let `L = (a_1, a_2, a_3, \dots, a_n)` be a non-empty sequence of
329
non-negative integers. Then `L` is said to be super-increasing if
330
each `a_i` is strictly greater than the sum of all previous values.
331
That is, for each `a_i \in L` the sequence `L` must satisfy the
332
property
333
334
.. MATH::
335
336
a_i > \sum_{k=1}^{i-1} a_k
337
338
in order to be called a super-increasing sequence, where `|L| \geq 2`.
339
If `L` has exactly one element, then it is also defined to be a
340
super-increasing sequence.
341
342
343
INPUT:
344
345
- ``seq`` -- (default: ``None``) a sequence to test
346
347
348
OUTPUT:
349
350
- If ``seq`` is ``None``, then test ``self`` to determine whether or
351
not it is super-increasing. In that case, return ``True`` if
352
``self`` is super-increasing; ``False`` otherwise.
353
354
- If ``seq`` is not ``None``, then test ``seq`` to determine whether
355
or not it is super-increasing. Return ``True`` if ``seq`` is
356
super-increasing; ``False`` otherwise.
357
358
359
EXAMPLES:
360
361
By definition, an empty sequence is not super-increasing::
362
363
sage: from sage.numerical.knapsack import Superincreasing
364
sage: Superincreasing().is_superincreasing([])
365
False
366
sage: Superincreasing().is_superincreasing()
367
False
368
sage: Superincreasing().is_superincreasing(tuple())
369
False
370
sage: Superincreasing().is_superincreasing(())
371
False
372
373
But here is an example of a super-increasing sequence::
374
375
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
376
sage: Superincreasing(L).is_superincreasing()
377
True
378
sage: L = (1, 2, 5, 21, 69, 189, 376, 919)
379
sage: Superincreasing(L).is_superincreasing()
380
True
381
382
A super-increasing sequence can have zero as one of its elements::
383
384
sage: L = [0, 1, 2, 4]
385
sage: Superincreasing(L).is_superincreasing()
386
True
387
388
A super-increasing sequence can be of length 1::
389
390
sage: Superincreasing([randint(0, 100)]).is_superincreasing()
391
True
392
393
394
TESTS:
395
396
The sequence must contain only integers::
397
398
sage: from sage.numerical.knapsack import Superincreasing
399
sage: L = [1.0, 2.1, pi, 21, 69, 189, 376, 919]
400
sage: Superincreasing(L).is_superincreasing()
401
Traceback (most recent call last):
402
...
403
TypeError: Element e (= 1.00000000000000) of seq must be a non-negative integer.
404
sage: L = [1, 2.1, pi, 21, 69, 189, 376, 919]
405
sage: Superincreasing(L).is_superincreasing()
406
Traceback (most recent call last):
407
...
408
TypeError: Element e (= 2.10000000000000) of seq must be a non-negative integer.
409
"""
410
# argument seq is None, so test self for super-increasing
411
if seq is None:
412
# self must be a non-empty sequence
413
if (self._seq is None) or len(self._seq) == 0:
414
return False
415
# so now self is known to represent a non-empty sequence
416
if (not isinstance(self._seq[0], Integer)) and (not isinstance(self._seq[0], int)):
417
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % self._seq[0]
418
if self._seq[0] < 0:
419
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % self._seq[0]
420
cumSum = self._seq[0] # the cumulative sum of the sequence represented by self
421
for e in self._seq[1:]:
422
if (not isinstance(e, Integer)) and (not isinstance(e, int)):
423
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % e
424
if e < 0:
425
raise TypeError, "Element e (= %s) of self must be a non-negative integer." % e
426
if e <= cumSum:
427
return False
428
cumSum += e
429
return True
430
# now we know that seq is not None, so test seq for super-increasing
431
else:
432
# seq must be a non-empty sequence
433
if len(seq) == 0:
434
return False
435
# so now seq is known to represent a non-empty sequence
436
if (not isinstance(seq[0], Integer)) and (not isinstance(seq[0], int)):
437
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % seq[0]
438
if seq[0] < 0:
439
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % seq[0]
440
cumSum = seq[0] # the cumulative sum of the sequence seq
441
for e in seq[1:]:
442
if (not isinstance(e, Integer)) and (not isinstance(e, int)):
443
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % e
444
if e < 0:
445
raise TypeError, "Element e (= %s) of seq must be a non-negative integer." % e
446
if e <= cumSum:
447
return False
448
cumSum += e
449
return True
450
451
def subset_sum(self, N):
452
r"""
453
Solving the subset sum problem for a super-increasing sequence.
454
455
Let `S = (s_1, s_2, s_3, \dots, s_n)` be a non-empty sequence of
456
non-negative integers, and let `N \in \ZZ` be non-negative. The
457
subset sum problem asks for a subset `A \subseteq S` all of whose
458
elements sum to `N`. This method specializes the subset sum problem
459
to the case of super-increasing sequences. If a solution exists, then
460
it is also a super-increasing sequence.
461
462
.. NOTE::
463
464
This method only solves the subset sum problem for
465
super-increasing sequences. In general, solving the subset sum
466
problem for an arbitrary sequence is known to be computationally
467
hard.
468
469
470
INPUT:
471
472
- ``N`` -- a non-negative integer.
473
474
475
OUTPUT:
476
477
- A non-empty subset of ``self`` whose elements sum to ``N``. This
478
subset is also a super-increasing sequence. If no such subset
479
exists, then return the empty list.
480
481
482
ALGORITHMS:
483
484
The algorithm used is adapted from page 355 of [HPS08]_.
485
486
487
EXAMPLES:
488
489
Solving the subset sum problem for a super-increasing sequence
490
and target sum::
491
492
sage: from sage.numerical.knapsack import Superincreasing
493
sage: L = [1, 2, 5, 21, 69, 189, 376, 919]
494
sage: Superincreasing(L).subset_sum(98)
495
[69, 21, 5, 2, 1]
496
497
498
TESTS:
499
500
The target ``N`` must be a non-negative integer::
501
502
sage: from sage.numerical.knapsack import Superincreasing
503
sage: L = [0, 1, 2, 4]
504
sage: Superincreasing(L).subset_sum(-6)
505
Traceback (most recent call last):
506
...
507
TypeError: N (= -6) must be a non-negative integer.
508
sage: Superincreasing(L).subset_sum(-6.2)
509
Traceback (most recent call last):
510
...
511
TypeError: N (= -6.20000000000000) must be a non-negative integer.
512
513
The sequence that ``self`` represents must only contain non-negative
514
integers::
515
516
sage: L = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1]
517
sage: Superincreasing(L).subset_sum(1)
518
Traceback (most recent call last):
519
...
520
TypeError: Element e (= -10) of seq must be a non-negative integer.
521
522
523
REFERENCES:
524
525
.. [HPS08] J. Hoffstein, J. Pipher, and J.H. Silverman. *An
526
Introduction to Mathematical Cryptography*. Springer, 2008.
527
"""
528
# input error handling
529
if not self.is_superincreasing():
530
raise TypeError, "self is not super-increasing. Only super-increasing sequences are currently supported."
531
if (not isinstance(N, Integer)) and (not isinstance(N, int)):
532
raise TypeError, "N (= %s) must be a non-negative integer." % N
533
if N < 0:
534
raise TypeError, "N (= %s) must be a non-negative integer." % N
535
536
# solve subset sum problem for super-increasing sequence
537
candidates = []
538
a = self.largest_less_than(N)
539
while a is not None:
540
candidates.append(a)
541
a = self.largest_less_than(N - sum(candidates))
542
543
lst = list(set(candidates)) # removing any duplicate elements
544
if len(lst) != len(candidates):
545
return []
546
if sum(candidates) == N:
547
return candidates
548
else:
549
return []
550
551
def knapsack(seq, binary=True, max=1, value_only=False, solver=None, verbose=0):
552
r"""
553
Solves the knapsack problem
554
555
For more information on the knapsack problem, see the documentation of the
556
:mod:`knapsack module <sage.numerical.knapsack>` or the
557
:wikipedia:`Knapsack_problem`.
558
559
INPUT:
560
561
- ``seq`` -- Two different possible types:
562
563
- A sequence of tuples ``(weight, value, something1, something2,
564
...)``. Note that only the first two coordinates (``weight`` and
565
``values``) will be taken into account. The rest (if any) will be
566
ignored. This can be useful if you need to attach some information to
567
the items.
568
569
- A sequence of reals (a value of 1 is assumed).
570
571
- ``binary`` -- When set to ``True``, an item can be taken 0 or 1 time.
572
When set to ``False``, an item can be taken any amount of times (while
573
staying integer and positive).
574
575
- ``max`` -- Maximum admissible weight.
576
577
- ``value_only`` -- When set to ``True``, only the maximum useful value is
578
returned. When set to ``False``, both the maximum useful value and an
579
assignment are returned.
580
581
- ``solver`` -- (default: ``None``) Specify a Linear Program (LP) solver to
582
be used. If set to ``None``, the default one is used. For more information
583
on LP solvers and which default solver is used, see the documentation of
584
class :class:`MixedIntegerLinearProgram
585
<sage.numerical.mip.MixedIntegerLinearProgram>`.
586
587
- ``verbose`` -- integer (default: ``0``). Sets the level of verbosity. Set
588
to 0 by default, which means quiet.
589
590
OUTPUT:
591
592
If ``value_only`` is set to ``True``, only the maximum useful value is
593
returned. Else (the default), the function returns a pair ``[value,list]``,
594
where ``list`` can be of two types according to the type of ``seq``:
595
596
- The list of tuples `(w_i, u_i, ...)` occurring in the solution.
597
598
- A list of reals where each real is repeated the number of times it is
599
taken into the solution.
600
601
EXAMPLES:
602
603
If your knapsack problem is composed of three items ``(weight, value)``
604
defined by ``(1,2), (1.5,1), (0.5,3)``, and a bag of maximum weight `2`, you
605
can easily solve it this way::
606
607
sage: from sage.numerical.knapsack import knapsack
608
sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2)
609
[5.0, [(1, 2), (0.500000000000000, 3)]]
610
611
sage: knapsack( [(1,2), (1.5,1), (0.5,3)], max=2, value_only=True)
612
5.0
613
614
Besides weight and value, you may attach any data to the items::
615
616
sage: from sage.numerical.knapsack import knapsack
617
sage: knapsack( [(1, 2, 'spam'), (0.5, 3, 'a', 'lot')])
618
[3.0, [(0.500000000000000, 3, 'a', 'lot')]]
619
620
In the case where all the values (usefulness) of the items are equal to one,
621
you do not need embarrass yourself with the second values, and you can just
622
type for items `(1,1), (1.5,1), (0.5,1)` the command::
623
624
sage: from sage.numerical.knapsack import knapsack
625
sage: knapsack([1,1.5,0.5], max=2, value_only=True)
626
2.0
627
"""
628
reals = not isinstance(seq[0], tuple)
629
if reals:
630
seq = [(x,1) for x in seq]
631
632
from sage.numerical.mip import MixedIntegerLinearProgram
633
p = MixedIntegerLinearProgram(maximization=True, solver=solver)
634
present = p.new_variable()
635
p.set_objective(p.sum([present[i] * seq[i][1] for i in range(len(seq))]))
636
p.add_constraint(p.sum([present[i] * seq[i][0] for i in range(len(seq))]), max=max)
637
638
if binary:
639
p.set_binary(present)
640
else:
641
p.set_integer(present)
642
643
if value_only:
644
return p.solve(objective_only=True, log=verbose)
645
646
else:
647
objective = p.solve(log=verbose)
648
present = p.get_values(present)
649
650
val = []
651
652
if reals:
653
[val.extend([seq[i][0]] * int(present[i])) for i in range(len(seq))]
654
else:
655
[val.extend([seq[i]] * int(present[i])) for i in range(len(seq))]
656
657
return [objective,val]
658
659