Python

SCIPとは?Apache-2.0の最適化ソルバーをPythonで使う手順【SCIP 10.0】

SCIPは、ドイツのZuse Institute Berlin(ZIB)が2002年から開発している数理最適化ソルバーです。混合整数計画(MIP)と混合整数非線形計画(MINLP)を扱え、バージョン8.0.3でライセンスがApache-2.0になり、商用製品への組み込みも許諾料なしで可能になりました。導入手順から実行例、CBCとの速度比較まで、すべて実機で動かした結果で示します。「SCIP」という綴りが指すものは最適化ソルバーだけではないため、まず3つの意味の見分け方から整理します。

まとめ

SCIPソルバーの要点は5つです。第一に、SCIPは8.0.3以降Apache-2.0であり、商用利用も再配布も可能です。第二に、Pythonから使うなら pip install pyscipopt の1行で足ります。この1行でSCIP本体(実測ではSCIP 10.0.2)とLPソルバーSoPlex、非線形用のIpoptまでwheelに同梱された状態で入るため、公式サイトからSCIPOptSuiteを別途ダウンロードする必要はありません。第三に、SCIP 10.0の目玉である厳密解モードはPyPI版のビルドに含まれておらず、有効化しようとするとエラーになります。第四に、実行不能なモデルの原因究明にはSCIP 10.0で入った generateIIS() が使え、矛盾している制約だけを機械的に絞り込めます。第五に、「無料ソルバーならSCIPがCBCより速い」は手元の実測では成り立ちませんでした。以下、それぞれの根拠と手順を見ていきます。

SCIPという略語が指す3つの対象と見分け方

「SCIP」は分野のまったく異なる3つのものに使われています。取り違えたまま資料を読み進めると時間を無駄にするため、最初に切り分けます。

最適化ソルバーのSCIP(ZIB・Solving Constraint Integer Programs)

この記事が扱うSCIPです。Solving Constraint Integer Programsの略で、開発元はZIB。起動時のバナーには Copyright (c) 2002-2026 Zuse Institute Berlin (ZIB) と表示されます。分枝限定法にカット生成、ヒューリスティック、制約伝播を組み合わせた解法エンジンで、単体のソルバーというより、独自の制約ハンドラやプライサーを差し込めるフレームワークとしての性格が強いのが特徴です。関連語としては pyscipoptscipoptsuitesoplex が一緒に出てきます。

コード索引フォーマットのSCIP(Sourcegraph・SCIP Code Intelligence Protocol)

Sourcegraphが策定した、ソースコードの索引データを受け渡すためのプロトコルです。公式リポジトリのREADMEには読み方が pronunciation: "skip" と明記されています。以前のLSIFを置き換える形式で、言語ごとのインデクサとして scip-typescriptscip-java といったツールが公開されています。検索語に typescriptjavaindexerlsif が混ざっていれば、まず間違いなくこちらです。最適化とは無関係です。

化学物質データベースのSCIP(ECHA・欧州化学品庁)

Substances of Concern In articles as such or in complex objects (Products) の略で、EUの廃棄物枠組み指令(2008/98/EC)に基づく届出データベースです。製品中に高懸念物質(SVHC)が重量比0.1%を超えて含まれる場合、EU域内で物品を供給する事業者(製造者・輸入者・組立者・流通業者など。消費者へのみ供給する小売は除外)に届出義務があり、2021年1月5日から運用されています。検索語に echasvhcreachdatabase が付く場合はこちらで、ソフトウェアではなく規制対応の話です。

SCIPソルバーの現在地:Apache-2.0への移行とSCIP 10.0系

SCIPで性能より先に確認すべきなのはライセンスと入手経路です。ここは過去数年で変わった部分で、古い解説記事の記述が残っている領域でもあります。

ライセンスがApache-2.0になった時期と、商用利用の可否

SCIPは長らくZIB Academic Licenseで配布され、商用利用には別途の許諾が必要でした。これがバージョン8.0.3から変わり、現在の scipopt/scip リポジトリのLICENSEファイルはApache License 2.0です。PySCIPOptのREADMEにも「Starting from v8.0.3, SCIP uses the Apache2.0 license」と明記されており、それ以前のバージョンを使う場合のみ旧ライセンスの制限を確認するよう案内されています。8.0.3はGitHub上で2022年12月13日にタグ付けされた版です。現行版であれば、社内システムへの組み込みもSCIPを内包した製品の配布も、Apache-2.0の条件(著作権表示とライセンス文の同梱、変更点の明示)を満たす限り可能です。商用ソルバーのGurobiのようなライセンスキーの発行・更新管理も不要です。なお、Pythonインターフェースである PySCIPOpt 自体のライセンスはMITで、SCIP本体とは別物である点だけ押さえておいてください。

SCIP 10.0で追加された機能と、PyPI版で使える範囲

SCIP 10.0.0は2025年11月24日に公開され、その後10.0.1が2026年2月3日、10.0.2が同年4月2日、10.0.3が同年7月6日にリリースされています。10.0系の目玉は、浮動小数点の丸め誤差を介さずに有理数のままMILPを解く厳密解モードと、実行不能の理由を説明する既約矛盾部分系(IIS)の抽出機能です。

ただし、この2つはPyPIのwheelでの扱いが分かれます。macOS x86_64版のwheelで enableExactSolving(True) を呼んだところ、SCIP was compiled without exact solve support というエラーで拒否されました。厳密解モードを使うには、その機能を有効にしてビルドしたSCIPが必要で、pipで入る配布物には含まれていません。一方でIISの方はpip版のまま動作します(後述)。「SCIP 10で厳密に解ける」という記述を見てpip版を入れても、そのままでは有効にできない点に注意してください。

PySCIPOptの導入:pip install 1行で入るものと入らないもの

かつては公式サイトからSCIPOptSuiteを入手し、CMakeでビルドしてからPythonバインディングを繋ぐ必要がありました。現在その工程は不要です。

インストール手順と、実際にインストールされる中身

macOS 26.6.1(x86_64)とPython 3.9.6の環境で、仮想環境を作って次の1行だけを実行しました。

python3 -m venv scipenv
source scipenv/bin/activate
pip install pyscipopt

入ったのはPySCIPOpt 6.2.1(2026年5月16日リリース)です。パッケージ内の .dylibs ディレクトリを確認すると、約25.8MBの libscip.10.0.dylib が同梱されていました。つまりSCIP本体がwheelに含まれており、別途の入手は要りません。実際にバージョンを表示させると次のようになります。

from pyscipopt import Model

m = Model()
m.printVersion()
# 実行結果(外部ライブラリの一覧は抜粋)
# SCIP version 10.0.2 [precision: 8 byte] [memory: block] [mode: optimized] [LP solver: SoPlex 8.0.2]
# Copyright (c) 2002-2026 Zuse Institute Berlin (ZIB)
# External libraries:
#   SoPlex 8.0.2    Linear programming solver developed at Zuse Institute Berlin
#   Ipopt 3.14.19   Interior Point Optimizer developed by A. Waechter et.al.
#   Nauty 2.8.8     Computing Graph Automorphism Groups by Brendan D. McKay

print(m.version())   # 10.0

LP緩和を解くSoPlexと、非線形計画を解くIpoptまで同梱されているため、線形から非線形まで追加設定なしで扱えます。この1行導入はmacOSに限りません。PySCIPOpt 6.2.1が配布する41ファイルにはWindows(win_amd64)とLinux(manylinux・x86_64およびaarch64)のwheelが含まれ、対応するPythonは3.8から3.14までです。実際にPython 3.9.6と3.14.6の両方で同じ手順が通ることを確認しています。

注意点として、version() が返すのは 10.0 という浮動小数点値でパッチ番号を含みません。同梱されているのが10.0.2なのか10.0.3なのかを確かめたい場合は printVersion() を使ってください。実測時点でPyPI版に入っていたのは10.0.2で、SCIP本体の最新リリース10.0.3より1パッチ後ろでした。

SCIPOptSuiteの個別導入が必要になる場面

pipで入るのはPythonから呼ぶための共有ライブラリだけで、scip という対話シェルの実行ファイルは仮想環境のbin配下に置かれません。read model.lpoptimize をシェルで打つ従来の使い方をしたい場合、列生成向けのGCGや並列分枝限定のUGを使いたい場合、そして前述の厳密解モードを有効にしたい場合は、公式サイトからSCIP Optimization Suiteを導入するかソースからビルドする必要があります。逆に言えば、Pythonでモデルを書いて解くだけならpipのみで完結します。

PySCIPOptの記述例:線形・整数・非線形の3パターン

以下のコードはすべて上記の環境で実行し、出力を確認したものです。モデルの構成要素である決定変数・目的関数・制約条件の関係が曖昧な場合は、決定変数と目的関数の決め方を先に押さえておくと読み進めやすくなります。

線形計画問題の記述と実行結果

変数を addVar で作り、addCons で制約を、setObjective で目的関数を与えて optimize を呼ぶ、という流れが基本形です。

from pyscipopt import Model

m = Model("lp")
x = m.addVar("x", lb=0)
y = m.addVar("y", lb=0)
m.addCons(x + 2 * y <= 10)
m.addCons(2 * x + y <= 10)
m.setObjective(x + y, "maximize")

m.hideOutput()
m.optimize()
print(m.getStatus(), m.getObjVal(), m.getVal(x), m.getVal(y))
# optimal 6.666666666666666 3.333333333333333 3.3333333333333335

連続変数として宣言しているため、最適解は2本の制約の交点である x = y = 10/3 になり、目的関数値は6.6667です。hideOutput() を挟まないと探索ログが標準出力に流れるので、実運用のスクリプトでは呼んでおくと扱いやすくなります。

混合整数計画への切り替えとvtypeの指定

整数条件を課すには addVarvtype を渡すだけです。"C" が連続、"I" が整数、"B" が0-1のバイナリを表します。ナップサック問題のように「入れるか入れないか」を決める問題では、次のようにバイナリ変数の集合として書きます。

from pyscipopt import Model, quicksum

profit = [60, 100, 120]
weight = [10, 20, 30]
capacity = 50

m = Model("knapsack")
x = [m.addVar(vtype="B", name="x%d" % j) for j in range(3)]
m.addCons(quicksum(weight[j] * x[j] for j in range(3)) <= capacity)
m.setObjective(quicksum(profit[j] * x[j] for j in range(3)), "maximize")

m.hideOutput()
m.optimize()
print(m.getObjVal(), [m.getVal(v) for v in x])
# 220.0 [0.0, 1.0, 1.0]

重量20と30の品目を選んで容量50をちょうど使い切り、価値の合計は220になります。総和を書くときは組み込みの sum ではなく quicksum を使ってください。変数の数が増えたときの式構築が速くなります。

非線形制約の書き方と、生成コードに頻出する誤り

SCIPはIpoptを同梱しているため、二次以上の項を含む制約もそのまま書けます。ここで頻出する誤りが、二乗を x2 のように書いてしまうパターンです。実際に m.addCons(x2 + y2 <= 1) を実行すると NameError: name 'x2' is not defined で止まります。Pythonのべき乗演算子は ** なので、正しくは次のように書きます。

from pyscipopt import Model

m = Model("nl")
x = m.addVar("x", lb=0)
y = m.addVar("y", lb=0)
m.addCons(x ** 2 + y ** 2 <= 1)
m.setObjective(x + y, "maximize")

m.hideOutput()
m.optimize()
print(m.getStatus(), round(m.getObjVal(), 6))
# optimal 1.414214

単位円の内側で x + y を最大化するので、解は x = y = 0.707107、目的関数値は 2 の平方根である1.414214になります。理論値と一致するかどうかは、非線形モデルが意図どおり組めているかの手軽な検算になります。

打ち切り条件の設定:limits/time と limits/gap の効果

整数計画は最適性の証明に時間の大半を使うため、実務では「十分よい解で打ち切る」設定が効きます。setParam で秒数の上限と許容ギャップを指定できます。

m.setParam("limits/time", 10)    # 10秒で打ち切り
m.setParam("limits/gap", 0.01)   # 上界と下界の差が1%以内になったら停止
m.hideOutput()
m.optimize()
print(m.getStatus(), m.getObjVal(), round(m.getGap(), 4), round(m.getSolvingTime(), 2))
# gaplimit 4690.0 0.0071 0.83  ← 末尾の所要時間だけは実行ごとに変動します

後述の実測で使った変数150本のナップサック問題に limits/gap を1%で与えた結果がこの出力です。最適性を証明しきった場合の目的関数値4694に対して4690、差は0.09%にとどまる一方、所要時間は183.8秒から0.83秒へ短縮されました。約220分の1です。停止理由は getStatus()gaplimit を返すことで判別でき、証明済みの最適解かどうかを取り違える心配はありません。

解けないモデルの原因特定:IISによる矛盾制約の絞り込み

ステータスが infeasible で返ってきたとき、制約が数百本あるモデルからどれとどれが矛盾しているかを目視で探すのは現実的ではありません。SCIP 10.0で入ったIIS(既約矛盾部分系)の抽出機能を使うと、これ以上減らせない最小の矛盾集合まで機械的に絞り込めます。PyPI版のPySCIPOptでそのまま動きます。

from pyscipopt import Model

m = Model("infeas")
x = m.addVar("x", lb=0)
y = m.addVar("y", lb=0)
m.addCons(x + y <= 1, name="c1")
m.addCons(x >= 3, name="c2")
m.addCons(y >= 0.5, name="c3")

m.hideOutput()
m.optimize()
print(m.getStatus())   # infeasible

m.hideOutput(False)
iis = m.generateIIS()
# IIS Status            : irreducible infeasible subsystem (IIS) found
# IIS irreducible       : yes
# Num. Cons. in IIS     : 2

この例では制約が3本ありますが、矛盾しているのは c1c2 の2本だけです。x が3以上でありながら x + y が1以下という条件は同時に満たせません。c3 は無関係なので、出力は「IISに含まれる制約は2本」と報告します。なお実際の画面では、Python側の print とSCIPのC側出力でバッファが別なので、infeasible の表示位置はIISのブロックより後ろにずれます。

IISの生成自体にも探索コストがかかるため大規模モデルでは所要時間を見ておく必要がありますが、小規模から中規模であれば、上限値を少しずつ緩めて試す従来のやり方より確実に速く原因へ到達できます。

SCIP・CBC・python-mip・PuLP・Gurobiの選び分け

Pythonから最適化を行う選択肢は、モデリング用のライブラリと実際に解くソルバーが別レイヤだと押さえると整理できます。数理最適化の3要素とアルゴリズムを踏まえたうえで、位置づけは次のとおりです。

名称 役割 ライセンス 非線形
SCIP ソルバー Apache-2.0 対応
PySCIPOpt モデラー(SCIP同梱) MIT 対応
CBC ソルバー EPL-2.0 非対応
Gurobi ソルバー(商用) 有償 対応
PuLP モデラー MIT 非対応
python-mip モデラー EPL-2.0 非対応

モデラーであるPuLPやpython-mipは、それ自体は解きません。書いたモデルを裏側のソルバーへ渡す役目です。PuLPで最小化問題を解く手順と比べると、PySCIPOptはモデラーとソルバーが一体で提供される分、依存関係が単純になります。表の非線形の欄について補足すると、Gurobiが多変数の合成非線形関数を直接記述できるようになったのは2024年11月のバージョン12.0からで、それ以前も指数や対数といった一変数の関数制約は区分線形近似で扱えていました。CBCは線形専用なので、非線形項が入った時点で選択肢から外れます。

python-mipからSCIPを呼べない理由と、無言のCBCフォールバック

「python-mipでSCIPを使いたい」という要望は成立しません。python-mipのREADMEに挙げられている対応ソルバーはCBCとGurobiの2つだけで、パッケージ内にもCBC用とGurobi用のバックエンドしか存在しないためです。実際にwheelの中身を見ると cbc-c-darwin-x86-64.dylib のようなCBCの共有ライブラリが同梱されており、追加インストールなしでCBCが動く構成になっています。

ここで厄介なのが、mip.SCIP という定数だけは実在することです。値は文字列の 'SCIP' で、Model(solver_name=mip.SCIP) と書いてもエラーは出ません。手元で実行したところ、そのままCBCのバナーが表示され、内部で使われるのは SolverCbc でした。つまりSCIPを指定したつもりで無言のうちにCBCで解かれ、結果をSCIPのものと誤認する事故が起こり得ます。SCIPを使いたい場合はPySCIPOptを直接使うか、複数ソルバーを切り替えたいならPyomoのような別のモデラーを選んでください。なおpython-mipの最新版は1.17.6(2026年3月23日)で、Python 3.10以上が必要です。

SCIPとCBCの速度実測と、再現用スクリプト

無料ソルバーの比較では「SCIPの方がCBCより速い」と説明されることが多いのですが、手元で測るとそうなりませんでした。多次元ナップサック問題を規模だけ変えて両者に解かせた結果が次のとおりです。既定パラメータ、同一マシン(macOS 26.6.1 x86_64、Python 3.14.6)、SCIPはPySCIPOpt 6.2.1(SCIP 10.0.2)経由、CBCはpython-mip 1.17.6同梱版(2026年3月23日ビルド)です。

規模 SCIP 10.0.2 CBC(2026-03-23版) 最適値
変数60・制約30 0.9秒 0.7秒 1803
変数100・制約30 7.1秒 4.8秒 3180
変数150・制約40 183.8秒 46.9秒 4694

3件とも最適値は一致しており、解の正しさに差はありません。速度は3規模ともCBCが上回り、変数150本では約3.9倍の開きが出ました。ただしこれは1系統の問題を1シードで測った結果であり、CBCが一般に優れていることを示すものではありません。実際、CBCを2021年ビルドの古い版(python-mip 1.15.0同梱)に差し替えて同じ測定を行うと、変数100本ではSCIPのほうが速いという逆の順位になりました。同じ問題でもソルバーのビルド時期だけで結果が入れ替わるということです。一般論としての速さ比較を根拠にソルバーを決めるのは避け、自分のモデルを両方に流して測ってください。上の問題は次のスクリプトで生成できます。

import random

def build(N, M, seed=42):
    random.seed(seed)
    profit = [random.randint(10, 100) for _ in range(N)]
    weight = [[random.randint(1, 40) for _ in range(N)] for _ in range(M)]
    cap = [int(sum(row) * 0.4) for row in weight]
    return profit, weight, cap

# 目的関数: sum(profit[j] * x[j]) を最大化
# 制約: 各 i について sum(weight[i][j] * x[j]) <= cap[i]、x[j] はバイナリ
profit, weight, cap = build(150, 40)

商用ソルバーへ切り替える判断基準

SCIPで足りなくなる分岐は主に3つです。1つ目は、上の実測のように既定パラメータで数分を超え、ギャップ設定やパラメータ調整でも要件を満たせない場合。2つ目は、求解を業務プロセスに組み込み、応答時間の上限をSLAとして約束する必要がある場合。3つ目は、ソルバー側の不具合に対してベンダーサポートを求められる体制が要件になっている場合です。逆に、社内の意思決定支援や試算のように再実行が許される用途であれば、Apache-2.0で費用がかからないSCIPで足ります。価格帯を含めた具体的な比較はGurobiの使い方と価格を参照してください。

よくある質問

SCIPの読み方は?

最適化ソルバーのSCIPについて、ZIBが読み方を規定した資料は見当たりません。Solving Constraint Integer Programsの頭字語です。一方、同じ綴りのSourcegraph版SCIPは公式リポジトリのREADMEに pronunciation: "skip" と明記されており、こちらは「スキップ」と読みます。読み方を調べて出てきた情報がコード索引やLSIFの話であれば、それは別物のSCIPを指しています。

SCIPは商用製品に組み込んで使えますか?

バージョン8.0.3以降であれば可能です。現行のSCIPはApache License 2.0で配布されており、著作権表示とライセンス文の同梱、変更を加えた場合の明示という条件を満たせば、商用利用も再配布も許諾されています。それ以前のバージョンはZIB Academic Licenseで商用利用に制限があったため、古い環境を引き継いでいる場合は同梱されているSCIPのバージョンを printVersion() で確認してください。PySCIPOpt自体のライセンスはMITです。

pip install scip でインストールできますか?

そのコマンド自体は成功しますが、入るのは別物です。PyPIには scip という名前の無関係なパッケージ(バージョン0.1.12・細胞画像処理のScalable Cytometry Image Processing)が登録されており、エラーが出ないまま静かにそちらがインストールされます。最適化ソルバーを入れるパッケージ名は pyscipopt です。pip install pyscipopt を実行すると、PythonインターフェースとSCIP本体(実測ではSCIP 10.0.2の共有ライブラリ)、SoPlex、Ipoptまでまとめて入ります。公式サイトからSCIPOptSuiteを別途ダウンロードする必要があるのは、対話シェルやGCG、UGといった付属ツールを使う場合に限られます。

SCIPとSciPyは何が違いますか?

綴りが似ているだけで別物です。SCIPはZIBが開発する混合整数計画向けのソルバーで、整数条件を含む問題を分枝限定法で解き、最適性を保証できます。SciPyはPythonの科学計算ライブラリで、最適化機能は scipy.optimize として連続変数の非線形最適化や最小二乗法を担当し、整数計画は守備範囲外です。整数変数が出てきたらSCIP、連続変数の関数最小化ならSciPyという切り分けになります。詳細はSciPyの使い方とNumPyとの違いで解説しています。

SCIPとCBCはどちらが速いですか?

問題によって変わるため、一律には決まりません。当方で多次元ナップサック問題(1系統・シード42のみ)を規模だけ変えて測ったところ、変数60本から150本の3規模すべてでCBCがSCIPより速く、最大で3.9倍の差が出ました。ただしCBCを2021年ビルドの古い版に差し替えると変数100本では順位が逆転しており、1つの結果から一般化はできません。いずれの測定でも最適値は一致しています。非線形制約を含むモデルはCBCでは扱えないため、その場合はSCIPの一択です。線形のみであれば両方で試して測るのが確実です。

関連記事

資料請求

RELATED POSTS 関連記事