LoginSignup
7
8

More than 5 years have passed since last update.

洗濯物を干しながら最適化してみた

Last updated at Posted at 2015-12-23

きっかけ

「洗濯物、干しておいて」と言われて、干しながら考えたもの。

洗濯物問題

座標 p=[-3, -2, -1, 0, 1, 2, 3] に重量 w=[7, 8, 9, 11, 13, 15, 17] の洗濯物を1つずつ順番に干した時に、重心位置の絶対値が最小になる干し方を求めよ

定式化

定式化の仕方については、組合せ最適化を使おうを参考のこと。








変数 $x_{ijk} \in {0, 1}$ $i$番目に位置$j$に洗濯物$k$を干すかどうか
$y$ 重心位置の絶対値
目的関数 $y$ $\rightarrow$ 最小
制約条件 $\sum^n_{i=0}{ \sum_j{ \sum_k{ p[j] ~ w[k] ~ x_{ijk}} } } \le y$ $\forall n \in {0, 1, \dots }$
毎回置くこと
全ての位置に置くこと
全ての洗濯物を置くこと

pythonで解いてみる

from pulp import * # pip install pulp
def addvar(lowBound=0, var_count=[0], *args, **kwargs):
    var_count[0] += 1
    return LpVariable('v%d' % var_count[0], lowBound=lowBound, *args, **kwargs)

p = [-3, -2, -1, 0, 1, 2, 3]
w = [5, 6, 7, 9, 10, 11, 12]
r = range(len(p))
m = LpProblem()
x = [[[addvar(cat=LpBinary) for _ in r] for _ in r] for _ in r]
y = addvar()
m += y
for n in r:
    m += lpSum(x[n][j][k] for j in r for k in r) == 1
    m += lpSum(x[i][n][k] for i in r for k in r) == 1
    m += lpSum(x[i][j][n] for i in r for j in r) == 1
    if n:
        m += lpSum(p[j] * w[k] * x[i][j][k]
                   for i in range(n+1) for j in r for k in r) <= y
        m += lpSum(-p[j] * w[k] * x[i][j][k]
                   for i in range(n+1) for j in r for k in r) <= y
m += lpSum(x[0][len(p) // 2][k] for k in r) == 1
m += lpSum(x[1][j][k] for j in range(len(p) // 2) for k in r) == 1
%time m.solve()
print(LpStatus[m.status], value(m.objective))
>>>
Wall time: 2 s
Optimal 10.0

位置座標0にはいつ置いてもいいので、最初に置くことにしている。また、次は左右どちらでもいいので、左に固定している。

結果表示
for i in r:
    for j in r:
        for k in r:
            if value(x[i][j][k]) > 0.5:
                print(i, j, k)
>>>
0 3 6
1 2 4
2 5 3
3 1 2
4 6 0
5 0 1
6 4 5

最適解は複数あるようなので、おそらく局所探索法などの近似解法の方が有効だろう。

追記

定式化において pandas を使うと下記のように見やすくなる。

py3
import pandas as pd
from pulp import * # pip install pulp
def addvar(lowBound=0, var_count=[0], *args, **kwargs):
    var_count[0] += 1
    return LpVariable('v%d' % var_count[0], lowBound=lowBound, *args, **kwargs)
def Σ(s, f=None):
    if not f:
        return lpSum(t.query(s.format(**globals())).x)
    return lpSum(t.query(s.format(**globals())).apply(f, axis=1))

p = [-3, -2, -1, 0, 1, 2, 3] # 座標
w = [5, 6, 7, 9, 10, 11, 12] # 重量
r = range(len(p)) # 範囲
m = LpProblem() # 数理モデル
t = pd.DataFrame([(i, j, k, addvar(cat=LpBinary))
    for i in r for j in r for k in r], columns=['順番', '位置', '重量', 'x'])
y = addvar() # 重心位置の絶対値
m += y # 目的関数
for n in r:
    m += Σ('順番=={n}') == 1 # 順番 n で置くこと
    m += Σ('位置=={n}') == 1 # 位置 n に置くこと
    m += Σ('重量=={n}') == 1 # 洗濯物 n を置くこと
    if n:
        # 重心位置の絶対値が y 以下
        m += Σ('順番<={n}', lambda q: p[q.位置] * w[q.重量] * q.x) <= y
        m += Σ('順番<={n}', lambda q: -p[q.位置] * w[q.重量] * q.x) <= y
m += Σ('順番==0 & 位置==3') == 1 # 順番 0 に 位置 3 に置くこと
m += Σ('順番==1 & 位置<=2') == 1 # 順番 1 に 位置が 2 以下に置くこと
m.solve()
print(LpStatus[m.status], value(m.objective))
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8