1. ナップサック問題#

1.1. 背景#

ナップサック問題は数理最適化の中でも特に基本的な問題である. 数理最適化による問題解決方法とはどういったものか,ということを述べるための導入としてもしばしば例に出される問題でもある.

1.2. 設定#

1.3. 定式化#

1.3.1. 集合と定数#

設定で与えられた表から,品物ごとにその品物の価値とサイズが与えられている. このことから「品物の集まり」が,与えられた設定での集合 \(GOODS\) となっている.

(1)#\[g\in GOODS := \{缶コーヒー,水入りペットボトル,バナナ,りんご,おにぎり,パン\}\]

そして価値とサイズはその要素を添字とする定数 \(VALUE\)\(SIZE\) として直ちに定式化できる.

(2)#\[VALUE[g],~ SIZE[g]\]

1.3.2. 変数#

変数は「何をいくつ詰め込むと良いか」と誘導があるため,素直に次の変数 \(x\) を考えることができる.

品物 \(g\) を詰め込む個数
(3)#\[x[g]\in\mathbb{Z}_{\geq0}\]

1.3.3. 制約条件#

制約条件はナップサックの容量上限のみであるから,次のように立式できる.

(4)#\[\sum_{g\in GOODS} SIZE[g]\cdot x[g] \leq U\]

ここで \(U\) はナップサックの容量上限値であり,与えられた問題設定の場合には \(U=65\) である.

1.3.4. 目的関数#

「品物の総価値を最大にすること」であったから,目的関数は次のように書ける.

(5)#\[\mathrm{Maximize:}~ \sum_{g\in GOODS} VALUE[g]\cdot x[g]\]

1.4. 求解#

あらためて定式化と対応する PySIMPLE をすべて書き出すと次のようになる.

(6)#\[\begin{split}& \mathrm{Maximize:}~ \sum_{g\in GOODS} VALUE[g]\cdot x[g] \\ & \mathrm{subject~to:}~ \\ & \sum_{g\in GOODS} SIZE[g]\cdot x[g] \leq U\end{split}\]
PySIMPLE Sample Code
 1def optimize(data):
 2    from pysimple import Problem, Set, Element, IntegerVariable, Sum, Parameter
 3
 4    # Sets and Parameters
 5    GOODS = Set(dim=1, value=['缶コーヒー','水入りペットボトル','バナナ','りんご','おにぎり','パン'])
 6    g = Element(set=GOODS)
 7    U = Parameter(value=data['CAPACITY'])
 8    VALUE = Parameter(index=g, value=data['VALUE'])
 9    SIZE = Parameter(index=g, value=data['SIZE'])
10
11    # Variables
12    x = IntegerVariable(index=g, lb=0)
13
14    # Constraint
15    prob = Problem(name='knapsack-problem', type=max)
16    prob += Sum(SIZE[g]*x[g]) <= U, 'CoCapacity'
17
18    # Objective
19    prob += Sum(VALUE[g]*x[g]), 'TotalValue'
20
21    # Solve
22    prob.solve()
23
24    # Print
25    print(prob.objective.val)
26    print(x.val)
27
28data = {
29    'CAPACITY': 65,
30    'VALUE': {
31        '缶コーヒー':    120,
32        '水入りペットボトル':    130,
33        'バナナ': 80,
34        'りんご': 100,
35        'おにぎり': 250,
36        'パン': 185
37    },
38    'SIZE': {
39        '缶コーヒー': 10,
40        '水入りペットボトル': 12,
41        'バナナ': 7,
42        'りんご': 9,
43        'おにぎり': 21,
44        'パン': 16,
45    },
46}
47optimize(data)

上記を実行すると次の結果を得る.

Result
 1[About Nuorium Optimizer]
 2Nuorium Optimizer 24.1.1 build:ff97c72
 3	 <with META-HEURISTICS engine "wcsp"/"rcpsp">
 4	 <with Netlib BLAS>
 5, Copyright (C) 1991 NTT DATA Mathematical Systems Inc.
 6
 7[Problem and Algorithm]
 8PROBLEM_NAME                                 knapsack-problem
 9NUMBER_OF_VARIABLES                                         6
10(#INTEGER/DISCRETE)                                         6
11NUMBER_OF_FUNCTIONS                                         2
12PROBLEM_TYPE                                     MAXIMIZATION
13METHOD                                                SIMPLEX
14
15[Progress]
16<preprocess begin>.........<preprocess end>
17<iteration begin>
18Coefficient Statistics (original problem)
19  Coefficient range         [min,max] : [7.00e+00,2.10e+01]
20  RHS and bounds            [min,max] : [6.50e+01,6.50e+01]
21  Objective                 [min,max] : [8.00e+01,2.50e+02]
22Coefficient Statistics (after scaling)
23  Coefficient range         [min,max] : [5.99e-01,1.80e+00]
24  RHS and bounds            [min,max] : [5.56e+00,5.56e+00]
25  Objective                 [min,max] : [5.55e-01,1.73e+00]
26  Row scaling range         [min,max] : [-6.94e-03,0.00e+00]
27  Column scaling range      [min,max] : [1.00e+00,1.00e+00]
28#sol         upper         lower   gap(%)  time(s)  list  mem(MiB)
29#1            +inf            -0     +inf      0.0     0       413  sol: init 
30
31<<wcsp tabu search begin>>
32  number of column singleton : 0
33  skip search . . .
34<<wcsp tabu search end>>
35
36
37        1.2
38
39               770            -0  100.000      0.0     1       413  cut: 2 
40               770            -0  100.000      0.0     1       413  cut: 2 
41#2             770           760    1.299      0.0     4       413  sol: relax 
42#3             770           765    0.649      0.0     3       413  sol: relax 
43#4             770           770    0.000      0.0     1       413  sol: relax 
44               770           770    0.000      0.0     0       413
45
46<iteration end>
47
48[Result]
49STATUS                                                OPTIMAL
50VALUE_OF_OBJECTIVE                                        770
51SIMPLEX_PIVOT_COUNT                                        24
52PARTIAL_PROBLEM_COUNT                                      10
53ELAPSED_TIME(sec.)                                       0.00
54TotalValue.val=770
55x[缶コーヒー].val=3.0
56x[水入りペットボトル].val=0.0
57x[バナナ].val=2.0
58x[りんご].val=0.0
59x[おにぎり].val=1.0
60x[パン].val=0.0

1.4.1. コメント#

設定された問題を定式化することは以下の点で容易になっている.

  • 表形式にデータを整理 (整然データへと整理) するだけで,定式化に必要な集合であるとか,定数や変数が容易に見出せる点

  • 表の各列について,列に並ぶ値の総和が上下限値の制約や目的関数と直接に関連している点

これらのことから,ナップサック問題は数理最適化の定式化を述べる上で,よく引用されるのだが, ここで暗黙に仮定したことは線形性であることには注意する.

注釈

例えば目的関数は価値の最大化であるが,ナップサックに詰め込む品物組み合わせが人によっては, 単純に価値の総和とは限らない.「缶コーヒー」と「おにぎり」は口に合うだろうか. また制約条件についても,ナップサックや品物の形状によっては単純な容量の足し上げで正しいとは限らない. つまり,このような非線形性は考慮に入れていないのである. 「この組み合わせが最もコスパがいい!」と無批判に結果を受け止めてはいけないのだ!

教科書的な問題の説明では,こういった線形性の仮定を疑うことは意図することではないが, 実務ではこの仮定が理想的すぎて,上で述べた単純なナップサック問題としてそのまま適用できると目論むことは希望的観測となりやすい.

設定された問題ではナップサックの容量や品物のサイズは与えられているものの,その「形」については与えられていなかった. よってナップサック問題を実務に応用する際には,「形」という要素が関係してこないように努めなければならない. 例えば,品物は流体のように容器に自在に形を変えることができたり, もしくはサイズは規格化されていて,仮に正方形だとしたとき,サイズ \(n\) とは単位正方形が \(n\) 個寄せ集まったものを意味して容器に個数分詰め合わせるだけで済むといったことである. 他にも品物の個別の形はあるものの,ナップサックは柔軟に伸縮できて,容量の上限さえ満たされていれば済むというものである.

こういった事柄が定式化の特徴としてあることも理解しておくと, 実際のオペレーションが「形」を考慮する必要があるかどうかが気になるようになり, 一段抽象的に見れるようになってくる.

1.4.2. アレンジ#

ナップサックに詰め込める品物は種類ごとに一つまでだと改題した場合を考えよう. この制限を加えると \(0\)-\(1\) ナップサック問題という典型的な \(0\)-\(1\) 整数計画問題となる. つまり整数変数がバイナリ変数となるのだが,このために PySIMPLEでは BinaryVariable と書き換えればよい.

x = BinaryVariable(index=g)

以上の点についてモデルファイルを書き換えると次のとおり.

PySIMPLE Sample Code
 1def optimize(data):
 2    from pysimple import Problem, Set, Element, BinaryVariable, Sum, Parameter
 3
 4    # Sets and Parameters
 5    GOODS = Set(dim=1, value=['缶コーヒー','水入りペットボトル','バナナ','りんご','おにぎり','パン'])
 6    g = Element(set=GOODS)
 7    U = Parameter(value=data['CAPACITY'])
 8    VALUE = Parameter(index=g, value=data['VALUE'])
 9    SIZE = Parameter(index=g, value=data['SIZE'])
10
11    # Variables
12    x = BinaryVariable(index=g)
13
14    # Constraint
15    prob = Problem(name='knapsack-problem', type=max)
16    prob += Sum(SIZE[g]*x[g]) <= U, 'CoCapacity'
17
18    # Objective
19    prob += Sum(VALUE[g]*x[g]), 'TotalValue'
20
21    # Solve
22    prob.solve()
23
24    # Print
25    print(prob.objective.val)
26    print(x.val)
27
28data = {
29    'CAPACITY': 65,
30    'VALUE': {
31        '缶コーヒー':    120,
32        '水入りペットボトル':    130,
33        'バナナ': 80,
34        'りんご': 100,
35        'おにぎり': 250,
36        'パン': 185
37    },
38    'SIZE': {
39        '缶コーヒー': 10,
40        '水入りペットボトル': 12,
41        'バナナ': 7,
42        'りんご': 9,
43        'おにぎり': 21,
44        'パン': 16,
45    },
46}
47optimize(data)

上記を実行すると次の結果を得る.

Result
 1[About Nuorium Optimizer]
 2Nuorium Optimizer 26.1.0 build:f2d78da8
 3	 <with META-HEURISTICS engine "wcsp"/"rcpsp">
 4	 <with Netlib BLAS>
 5, Copyright (C) 1991 NTT DATA Mathematical Systems Inc.
 6
 7[Problem and Algorithm]
 8PROBLEM_NAME                                 knapsack-problem
 9NUMBER_OF_VARIABLES                                         6
10(#INTEGER/DISCRETE)                                         6
11NUMBER_OF_FUNCTIONS                                         2
12PROBLEM_TYPE                                     MAXIMIZATION
13METHOD                                                SIMPLEX
14
15[Progress]
16<preprocess begin>.........<preprocess end>
17<iteration begin>
18Coefficient Statistics (original problem)
19  Coefficient range         [min,max] : [7.00e+00,2.10e+01]
20  RHS and bounds            [min,max] : [1.00e+00,6.50e+01]
21  Objective                 [min,max] : [8.00e+01,2.50e+02]
22Coefficient Statistics (after scaling)
23  Coefficient range         [min,max] : [5.99e-01,1.80e+00]
24  RHS and bounds            [min,max] : [1.00e+00,5.56e+00]
25  Objective                 [min,max] : [5.55e-01,1.73e+00]
26  Row scaling range         [min,max] : [-6.94e-03,0.00e+00]
27  Column scaling range      [min,max] : [1.00e+00,1.00e+00]
28#sol         upper         lower   gap(%)  time(s)  list  mem(MiB)
29#1            +inf            -0     +inf      0.0     0       749  sol: init 
30
31<<wcsp tabu search begin>>
32  number of column singleton : 0
33  number of column selection : 0
34  Modify coefficients 
35  search target : -173
36<preprocess begin>..<preprocess end>
37preprocessing time: 0(s) 
38<iteration begin>
39--- TryCount = 1 ---
40# random seed = 1
41(hard/semihard/soft) penalty= 0/0/87, time= 0.00(s)
42<greedyupdate begin>......<greedyupdate end>
43greedyupdate time= 0(s)
44(hard/semihard/soft) penalty= 0/0/24, time= 0.00(s), iteration= 0
45--- End Phase-I iteration ---
46# (hard/semihard/soft) penalty= 0/0/24
47# time = 0.00/0.00(s)
48# iteration = 0/1000
49<iteration end>
50# (hard/soft) = 0/24
51# iteration = 1000
52# time =  0.00 (s), succ = 1
53<<wcsp tabu search end>>
54
55#2            +inf           745     +inf      0.0     0       749  sol: wcsp 
56               745           745    0.000      0.0     0       749
57
58<iteration end>
59
60[Result]
61STATUS                                                OPTIMAL
62VALUE_OF_OBJECTIVE                                        745
63SIMPLEX_PIVOT_COUNT                                         0
64PARTIAL_PROBLEM_COUNT                                       1
65ELAPSED_TIME(sec.)                                       0.00
66TotalValue.val=745
67x[缶コーヒー].val=0.0
68x[水入りペットボトル].val=1.0
69x[バナナ].val=1.0
70x[りんご].val=1.0
71x[おにぎり].val=1.0
72x[パン].val=1.0

引用書式

BibTeX