第60回:R言語で始める Gurobi ソルバー実務マニュアル(ユーザー目線で理解する)

公開日:2025-12-23
R × Gurobi optimization

まず結論(最初にここだけ読めばOK)

この記事でわかること

Gurobiとは何か(ひとことで)

Gurobiは「条件を守りながら、一番よい選択を探す計算エンジン」です。

人間が総当たりで考えるのが不可能な組合せや、制約が複雑に絡み合う意思決定を、現実的な時間で解ける形にしてくれます。

モデルの基本は3点だけ

まずはこの3点を日本語で説明できるようになると、最適化が一気にわかりやすくなります。

R × Gurobi の基本的な流れ(最短ルート)

結果の読み方(ここで失敗しない)

代表的な問題パターン(実務で頻出)

1) 生産計画(線形計画)

2種類の製品を、限られた資源(原料・作業時間など)の中で、どう作れば利益が最大になるかを考える典型例です。

ポイントは、数式から入るのではなく、「何を決めて」「何を良しとして」「どんなルールを守るか」を先に固めることです。

2) ナップサック(0/1整数計画)

容量や予算に制限がある中で、品目を「選ぶ/選ばない」で最適な組合せを求めます。

3) 輸送(輸送コスト最小化)

供給地から需要地へ、どれだけ運ぶかを決め、総輸送コストを最小化します。

4) 割当(アサイン)問題

「誰にどの仕事を担当させるか」を、総時間(または総コスト)最小で決める組合せ最適化です。

例:割当(アサイン)問題を「数式なし」で理解する

割当問題は、ルールを日本語で言えることがすべてです。

結果の読み方(迷わない)

Gurobiの出力は、0/1(YES/NO)の形で返ってきます。

したがって、読む側は 「1になっている組合せだけを見る」 で割当結果を正しく把握できます。

割当問題は専用アルゴリズム(ハンガリアン法など)もありますが、Gurobiのような汎用MIPソルバーでも高速に解けるため、実務ではそのまま解いて問題になることはほとんどありません。

実務の注意点と落とし穴

エラーは悪いことではなく、モデルを改善するヒントです。原因になっている制約やデータの矛盾を潰していくと、理解が一気に深まります。

サンプル集

サンプル1:生産計画(線形計画)

記号は「以下(超えない)」「以上(下回らない)」「等号(ちょうど一致)」の3種類だけです。業務の言葉に直せれば、そのままモデルになります。

R(gurobi)での最小サンプル(利益は万円表記)

# 利益最大(A=40万円/単位, B=30万円/単位)
# 原料:Aは2、Bは1 消費(合計100まで)
# 作業時間:Aは1、Bは2 消費(合計80まで)

library(gurobi)

m <- list()
m$modelsense <- "max"
m$obj <- c(40, 30)  # 利益(万円): [A, B]

m$A <- matrix(c(
  2, 1,   # 原料
  1, 2    # 作業時間
), nrow = 2, byrow = TRUE)

m$rhs   <- c(100, 80)
m$sense <- c("<", "<")   # "<" は「以下(超えない)」
m$lb    <- c(0, 0)       # 0以上

res <- gurobi(m, list(OutputFlag = 0))
res$objval  # 最大利益(万円)
res$x       # [A, B] の最適生産量

サンプル2:ナップサック(0/1整数計画:選ぶ/選ばない)

# モデルの準備
knap_model <- list()
knap_model$modelsense <- "max"
knap_model$obj   <- c(60, 90, 120)         # 価値
knap_model$A     <- matrix(c(10, 20, 30),  # 重さ係数
                            nrow=1)        # 1行(容量制約)
knap_model$rhs   <- 50
knap_model$sense <- "<"
knap_model$vtype <- "B"    # 全品目を0-1変数に

# 最適化実行
knap_res <- gurobi(knap_model, list())
print(knap_res$objval)  # 最適総価値
print(knap_res$x)       # 選択された品物 (変数値)

サンプル3:輸送問題(配分の最適化)

R(gurobi)での最小サンプル

# 2供給地(S1,S2)→ 3需要地(T1,T2,T3)に配分して輸送コスト最小化
library(gurobi)

# 変数順:S1→T1,S1→T2,S1→T3,S2→T1,S2→T2,S2→T3(計6本)
cost <- c(2, 4, 5,
          3, 1, 7)

supply <- c(35, 50)         # S1=35, S2=50
demand <- c(30, 25, 30)     # T1=30, T2=25, T3=30

m <- list()
m$modelsense <- "min"
m$obj <- cost

A <- matrix(0, nrow = 5, ncol = 6)

# 供給制約(<=)
A[1, 1:3] <- 1
A[2, 4:6] <- 1

# 需要制約(>=)
A[3, c(1,4)] <- 1
A[4, c(2,5)] <- 1
A[5, c(3,6)] <- 1

m$A <- A
m$rhs   <- c(supply, demand)
m$sense <- c("<","<",  ">",">",">")  # "<" 以下 / ">" 以上
m$lb <- rep(0, 6)

res <- gurobi(m, list(OutputFlag = 0))
res$objval
res$x

サンプル4:割当問題(アサイン:0/1で決める)

読むコツ:0/1変数はスイッチです。1の場所だけ拾えば割当が確定します。

# 変数 9個: 順に x_A1,x_A2,x_A3, x_B1,x_B2,x_B3, x_C1,x_C2,x_C3
assign_model <- list()
assign_model$modelsense <- "min"
assign_model$obj <- c(8,4,3,  2,6,5,  7,8,4)

# 制約行列 A (6本: Aさん行, Bさん行, Cさん行, Job1列, Job2列, Job3列)
assign_model$A <- matrix(c(
  # 各人=1:
  1,1,1,  0,0,0,  0,0,0,   # Aさん担当数
  0,0,0,  1,1,1,  0,0,0,   # Bさん担当数
  0,0,0,  0,0,0,  1,1,1,   # Cさん担当数
  # 各仕事=1:
  1,0,0,  1,0,0,  1,0,0,   # Job1割当数
  0,1,0,  0,1,0,  0,1,0,   # Job2割当数
  0,0,1,  0,0,1,  0,0,1    # Job3割当数
), nrow=6, byrow=TRUE)

assign_model$rhs   <- c(1,1,1,  1,1,1)
assign_model$sense <- c("=", "=", "=",  "=", "=", "=")
assign_model$vtype <- "B"

assign_res <- gurobi(assign_model, list())
print(assign_res$objval)  # 最小総時間
print(assign_res$x)       # 各 x_ij の値

まとめ

← ブログ一覧へ戻る