💭
Pythonの最適化ライブラリPuLP をM1 Mac × Dockerで動かす
目的
M1 Mac上のDockerコンテナ内で、Pythonの数理最適化ライブラリPuLPを動かしたい。
事象
- M1 Mac上のDockerコンテナに、単にPuLPをインストールしてoptimizeメソッドを実行すると、以下のようなエラーが生じる。
OSError: [Errno 8] Exec format error: '/src/.venv/lib/python3.11/site-packages/pulp/solverdir/cbc/linux/64/cbc'
- Pythonの仮想環境venvでは、上記エラーは発生しなかった。
推定原因
- 上記エラーは、最適化ソルバーであるcbcがARMアーキテクチャに対応していないことを意味している。[1]
- 上記エラーコードにあるソルバーフォルダ
solverdir
には、linux
の他にもosx
があり、Mac os環境下(今回のvenv)ではおそらくそちらが実行されている。つまり、プログラム実行環境のos違いによって参照するソルバーのパスが異なると、推定される。 - Mac osの場合のソルバーはおそらくM1チップ環境下でも実行できるものであり、Linuxの場合のソルバーはM1チップに対応していないと考えられる。したがって、venv環境ではM1チップで実行可能なソルバーが実行され、Docker環境下ではM1チップで実行不可能なソルバーが実行されていると、推定される。
対応策
- Linux及びarm64環境下で実行可能な、cbcバイナリファイルを別でインストールし、PuLP実行時にソルバパスを指定することで、上記エラーは解消された。
- 具体的な手順は以下の通り。
- Dockerfileに以下の行を追加する。以下はLinux向けのcbcソルバをインストールするコマンドである。[2][3]コンテナ内では
/usr/bin/cbc
に配置される。RUN apt-get update && apt-get install -y coinor-cbc
- PythonスクリプトでPuLPを使用する際は、以下のようにソルバパスを設定する。[3:1]ただし、
problem
は最適化問題が定義されているオブジェクト。path_to_solver = '/usr/bin/cbc' solver = pulp.COIN_CMD(path=path_to_solver) problem.solve(solver)
- Dockerfileに以下の行を追加する。以下はLinux向けのcbcソルバをインストールするコマンドである。[2][3]コンテナ内では
サンプルコード
環境構築用コード
Dockerfile
#python3.11のイメージをダウンロード
FROM python:3.11-buster
WORKDIR /src
# Linux向けのcbcソルバをインストール
RUN apt-get update && apt-get install -y coinor-cbc
COPY requirements.txt /tmp/requirements.txt
RUN python3 -m pip install -r /tmp/requirements.txt
docker-compose.yaml
version: '3'
services:
demo-app:
build: .
volumes:
- .:/src
tty: true #docker compose up時にコンテナを起動し続ける設定。
requirements.txt
pulp==2.7.0
上記Dockerfile
, docker-compose.yaml
, requirements.txt
を用いて構築したコンテナ内で、以下のmain.py
が無事実行できれば良い。なお、main.py
は以下の最適化問題を解くためのプログラムである。
main.py
import pulp
# 問題を定義する。
problem = pulp.LpProblem( "demo", pulp.LpMaximize )
# 変数を定義する。
x = [ pulp.LpVariable( f"x_{i}", cat=pulp.LpBinary ) for i in range(3) ]
# 係数を設定する。
c = [ 1, 2, 3 ]
a1 = [ 4, 2, 1 ]
b1 = 3
a2 = [ 1, -2, 1 ]
b2 = 2
# 目的関数を設定する。
problem += pulp.lpDot( c, x )
# 制約条件を設定する。
problem += pulp.lpDot( a1, x ) <= b1
problem += pulp.lpDot( a2, x ) <= b2
# cbcソルバーのパスを設定する。
path_to_solver = '/usr/bin/cbc'
solver = pulp.COIN_CMD(path=path_to_solver)
# 最適化を実行する。
result = problem.solve(solver)
# 結果を出力する。
print(f"目的関数の値: {pulp.value(problem.objective)}")
print("最適解")
for i in range(len(x)):
print(f"x_{i} = {pulp.value(x[i])}")
実行結果は以下の通り。
目的関数の値: 5.0
最適解:
x_0 = 0.0
x_1 = 1.0
x_2 = 1.0
-
PuLPのgithubのissue(https://github.com/coin-or/pulp/issues/433) ↩︎
-
ubuntuへのcbcソルバインストール(https://howtoinstall.co/package/coinor-cbc) ↩︎
-
同様の事象に関するgithubのissue(https://github.com/davidusb-geek/emhass-add-on/issues/9) ↩︎ ↩︎
Discussion