# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
from pysimple import Element, Parameter, Variable, Problem, NuoptStatus, Sum
from pysimple.typing import Table
[ドキュメント]class ColumnGenerator(metaclass=ABCMeta):
"""列生成法のための抽象基底クラス"""
iternum = property(lambda self: self.__iternum)
patternnum = property(lambda self: self.__pn0 + self.__iternum)
[ドキュメント] @abstractmethod
def create_init_pattern(self, *, pattern: dict, b: float | dict=1, c: float | dict=1) -> None:
"""set self.pattern, self.b, self.c"""
self.pattern = pattern
self.b = b
self.c = c
self.__iternum = 0
self.__pn0 = len(pattern)
[ドキュメント] def create_lambda(self, *, silent: bool=True) -> Table:
"""select_pattern の双対問題"""
pi = Element(value=self.pattern)
i = Element(set=pi(1).set)
b = Parameter(index=pi(1), value=self.b)
c = Parameter(index=pi(0), value=self.c)
pattern = Parameter(index=pi, value=self.pattern)
lmb = Variable(index=pi(1), lb=0, name='λ')
prb = Problem(type=max)
prb += Sum(pattern[pi]*lmb[pi(1)], pi(1)) <= c[pi(0)]
prb += Sum(b[i]*lmb[i])
prb.solve(silent=silent)
assert prb.status == NuoptStatus.OPTIMAL
return lmb.val
[ドキュメント] @abstractmethod
def create_new_pattern(self, lmbval: Table, *, silent: bool=True) -> tuple[Table, Table]:
"""組合せを 1 つ生成する"""
# z[z[i].val>0].val, prb.objective.val
[ドキュメント] def update_pattern(self, zval: Table) -> None:
"""update self.pattern, self.c"""
self.pattern.update({(self.patternnum, i): v for (i,), v in zval.items()})
# type は create_new_pattern の変数 type と同じ
[ドキュメント] def select_pattern(self, *, vtype=bin, silent: bool=True) -> tuple[Table, Table]: # 主問題
"""生成されたパターンから b を満たす組合せを選択する"""
pi = Element(value=self.pattern)
p = Element(set=pi(0).set)
b = Parameter(index=pi(1), value=self.b)
c = Parameter(index=pi(0), value=self.c)
pattern = Parameter(index=pi, value=self.pattern) # パターン p にアイテム i を含むか(0-1)
z = Variable(index=p, type=vtype, lb=0) # パターン p を選択するか(type=bin), 幾つ選択するか(type=int)
prb = Problem()
prb += Sum(pattern[pi]*z[pi(0)], pi(0)) >= b[pi(1)], 'cons' # アイテム i は何れかのパターンで選ぶ
prb += Sum(c[p]*z[p])
prb.solve(silent=silent)
assert prb.status == NuoptStatus.OPTIMAL
return z[z[p].val>0].val, prb['cons'].dual
[ドキュメント] def solve(self, *, eps: float=0.0, maxiter: int=100) -> Table:
"""列生成法で問題を解く"""
self.create_init_pattern() # 初期パターン
objval = eps + 1e-6 # eps より大きい適当な値
while objval > eps and self.iternum < maxiter:
self.__iternum += 1
# dual を求解
lmbval = self.create_lambda() # 双対問題を使う
# _, lmbval = self.select_pattern(type=float) # 主問題の双対変数を使う
# 新しいパターンの生成
zval, objval = self.create_new_pattern(lmbval)
# print('update:', dict(zval.items()))
self.update_pattern(zval)
print(f'{self.iternum}: {objval}', flush=True)
# 実行可能解取得
zval, _ = self.select_pattern()
return zval