サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

gurobipy エラー

gurobipy とは GUROBI optimizerが提供するpythonインターフェイスです. pip で installできます.

gurobipyをクラス内で用いるときの注意点;

下記のプログラムは2変数(x, y)からなる簡単な最適化問題を求解するもの.
solve()関数で問題の定式化と求解を行なっています. self.xとself.yにgurobipyの変数オブジェクト(gurobipy.Var)を格納しています;

import gurobipy


class Problem:
    def __init__(self):
        self.x = None
        self.y = None

    def solve(self):
        model = gurobipy.Model()

        self.x = model.addVar(name = "x")
        self.y = model.addVar(name = "y")

        model.addConstr(-3*self.x + self.y <= 6)
        model.addConstr(-self.x - 2*self.y >= -4)
        model.setObjective(-self.x + 4*self.y)

        model.optimize()

        print(f"solve::obj:{model.ObjVal}")
        print(f"solve::x:{self.x.X}")
        print(f"solve::y:{self.y.X}")

    def print_solution(self):
        print(f"print_solution::x:{self.x.X}")
        print(f"print_solution::y:{self.y.X}")


if __name__ == "__main__":
    problem = Problem()
    problem.solve()
    problem.print_solution()

これを実行すると, 下記のエラーが発生します.

Set parameter GURO_PAR_SPECIAL
Set parameter TokenServer to value "hogehoge"
Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)
Thread count: 48 physical cores, 96 logical processors, using up to 32 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0x14dc6dc8
Coefficient statistics:
  Matrix range     [1e+00, 3e+00]
  Objective range  [1e+00, 4e+00]
  Bounds range     [0e+00, 0e+00]
  RHS range        [4e+00, 6e+00]
Presolve removed 2 rows and 2 columns
Presolve time: 0.00s
Presolve: All rows and columns removed
Iteration    Objective       Primal Inf.    Dual Inf.      Time
       0   -4.0000000e+00   0.000000e+00   0.000000e+00      0s

Solved in 0 iterations and 0.00 seconds (0.00 work units)
Optimal objective -4.000000000e+00
solve::obj:-4.0
solve::x:4.0
solve::y:0.0

Traceback (most recent call last):
  File "/home/hogehoge/test.py", line 33, in <module>
    problem.print_solution()
  File "/home/hogehoge/test.py", line 26, in print_solution
    print(f"print_solution::x:{self.x.X}")
  File "src/gurobipy/var.pxi", line 125, in gurobipy.Var.__getattr__
  File "src/gurobipy/var.pxi", line 153, in gurobipy.Var.getAttr
  File "src/gurobipy/attrutil.pxi", line 35, in gurobipy.__getattr
  File "src/gurobipy/attrutil.pxi", line 23, in gurobipy.__getattrinfo
AttributeError: 'gurobipy.Var' object has no attribute 'X'

solve()関数内でのself.x.Xがきちんと表示されているのに, print_solution()関数ではself.xのX属性がなくなっています.
gurobipyの中身のコードをチェックしたいのですが, バイナリでパッケージが配られているので, 詳しいことは分かりませんが, (おそらく)solve()関数スコープを抜ける際にmodelオブジェクトのメモリが解放されて, 求解情報がクリアされている模様.

これを防ぐにはmodelオブジェクトのメモリ解放を防げばいいので, 例えばクラス内属性として保存しておけば良いです. このような、スコープから外れて変数のメモリが解放されることを防ぐために、より大きなスコープに変数を紐づけておくことを( hook ) と言ったりします。フックに変数を引っ掛けるようなイメージなんですかね。

import gurobipy


class Problem:
    def __init__(self):
+       self.model = None
        self.x = None
        self.y = None

    def solve(self):
        model = gurobipy.Model()

        self.x = model.addVar(name = "x")
        self.y = model.addVar(name = "y")

        model.addConstr(-3*self.x + self.y <= 6)
        model.addConstr(-self.x - 2*self.y >= -4)
        model.setObjective(-self.x + 4*self.y)

        model.optimize()
+       self.model = model

        print(f"solve::obj:{model.ObjVal}")
        print(f"solve::x:{self.x.X}")
        print(f"solve::y:{self.y.X}")

    def print_solution(self):
        print(f"print_solution::x:{self.x.X}")
        print(f"print_solution::y:{self.y.X}")


if __name__ == "__main__":
    problem = Problem()
    problem.solve()
    problem.print_solution()

これで実行してみると、

Set parameter GURO_PAR_SPECIAL
Set parameter TokenServer to value "hogehoge"
Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)
Thread count: 48 physical cores, 96 logical processors, using up to 32 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0x14dc6dc8
Coefficient statistics:
  Matrix range     [1e+00, 3e+00]
  Objective range  [1e+00, 4e+00]
  Bounds range     [0e+00, 0e+00]
  RHS range        [4e+00, 6e+00]
Presolve removed 2 rows and 2 columns
Presolve time: 0.00s
Presolve: All rows and columns removed
Iteration    Objective       Primal Inf.    Dual Inf.      Time
       0   -4.0000000e+00   0.000000e+00   0.000000e+00      0s

Solved in 0 iterations and 0.00 seconds (0.00 work units)
Optimal objective -4.000000000e+00
solve::obj:-4.0
solve::x:4.0
solve::y:0.0
print_solution::x:4.0
print_solution::y:0.0

うまくいきました。


補足 - python: v3.9.10 - gurobipy: v9.5.1