スタートページJavaScriptライブラリ目次

数理計画法 JavaScript関数ライブラリ(mp.js)の利用解説書

ご利用にあたって

線形計画法や動的計画法などの分野での基礎的な計算手法を JavaScript のライブラリとしてまとめたものです。
別シリーズ「Webテキスト」での活用を目的としたもので、小規模の素直な問題に限定されています。
単に結果を戻す「Return]タイプと、結果までの処理経過を示す「Trace」タイプがあります。

ソースコードを公開しています。ご利用は自由ですが、信頼性や効率性への保証はありません。

採録関数

mpLP線形計画法 シンプレックス法による最適解、レジューズドコスト、レンジ
mpTLP輸送問題 北西隅の方法により初期解を得て、U-V法により解の改善を行い、最適解を求める
mpALP割当問題 ハンガリー法をあきらめ、n!の組合せの中から最大/最小のものを探索する
mpSaitekiHaibun最適資金配分問題 動的計画法により、制約条件:Q=x0 + x1 + x2 + ・・・ + xn ≦ Qmax  0 ≦ xi ≦ xmaxi の下で
目的関数:Rx = f(x0) + f(x1) + f2(x2) + ・・・ + fn(xn) を最大化する xi を求める。
mpKnapsackナップザック問題/td> 個数制約:0 ≦ x[i] ≦ 上限個数[i] 容量制約:∑w[i]*x[i] ≦ ナップザック容量 の制約で
目的関数:∑f[i]*x[i] を最大化する x[i] を求める問題。動的計画法

線形計画法(シンプレックス法)
  mpLPTrace(表示場所, モデル, 変数名, 制約名) 
  rtn = mpLP(モデル, 変数名, 制約名) 

ごく基本的なLP(線形計画法)問題をシンプレクス法により解を求めます。
・変数および制約式の個数は、それぞれ10個以下を想定しています。
 計算誤差の影響は一切考慮していません。
・制約式≦定数項、0<定数項 でなければなりません。

単に基底解を求めるだけでなく、シャドウプライスやレンジも求めます。

ケース1

右図のモデルを対象にします。
それを下の入力例の形式で入力すると、出力例が得られます。

入力例
              製品
         定数項  A  B
    var モデル = [ [  0,   3,  2 ],   // 目的関数:定数項=0、製品の価格
                   [ 72,   4,  1 ],    // 原料制約:4*製品A + 1*製品B ≦ 72
                   [ 48,   2,  2 ],    // 労力制約:2*製品A + 2*製品B ≦ 48
                   [ 48,   1,  3 ] ];  // 電力制約:1*製品A + 3*製品B ≦ 48
    var 変数名 = ["製品A", "製品B"];
    var 制約名 = ["max", "原料","労力","電力"];
出力例  var rtn = mpLP(モデル, 変数名. 制約名) の結果
    rtn["解A"][k][j]  最終タブロー
       k  [k][0]  [k][1]  [k][2]  [k][3]  [k][4]  [k][5]  [k][6]
       0   基底   定数項  製品A  製品B   原料    労力    電力
       1   min      64      0       0     0.333    0.833     0
       2   製品A   16      1       0     0.333   -0.167     0
       3   製品B    8      0       1    -0.333    0.667     0
       4   電力      8      0       0     0.667   -1.833     1
    rtn["解B"][j][*] 感度分析
       j  [j][0]  [j][1]  [j][2]  [j][3]    [j][4]  [j][5]
       0  変数名   価格   解の値  シャドウ  レンジ レンジ
                  定数項          プライス   下限   上限
       1  製品A     3      16      0         2      8
       2  製品B     2       8      0         0.75   3
       3  原料      72       0      0.333    60     96
       4  労力      48       0      0.833    36     52.364
       5  電力      48       8      0        -0.5    0.455

ケース2

↓制約名変数名→
←目的関数
↓制約条件

輸送問題
  mpTLPTrace(表示場所, 費用表, 供給地名, 供給量, 需要地名, 需要量); 
  rtn = mpTLP(費用表, 供給地名, 供給量, 需要地名, 需要量); 

供給地iの供給量と需要地jの需要量と、i→jの単位輸送費用を与えて、総輸送費用を最小化するように、i→jの最適輸送量を計算します。
 北西隅の方法により初期解を得て、U-V法により解の改善を行い、最適解を求めます。
 すべての入力値は正の整数で、Σ供給量 = Σ需要量 でなければなりません。

ケース1

問題
               需要地番号j    0         1         2         3
                          ┌───────────────────┐
                  │需要地A 需要地B 需要地C 需要地D│  需要地名
                          ├───────────────────┤
    供給地番号i        │  12    20     16     18  │ 需要量
        ┌────┬──┼───────────────────┤
      0 │供給地X│ 18 │  7    3     2     10    │
      1 │供給地Y│ 22 │  9    3     6     8    │ 費用表(単価)
      2 │供給地Y│ 26 │  8    7     8      6    │
       └────┴──┴───────────────────┘
        供給地名 └ 供給量

入力データ
    var 供給地名 = ["供給地X", "供給地Y", "供給地Z"];
    var 供給量 = [18, 22, 26];
    var 需要地名 = ["需要地A", "需要地B", "需要地C", "需要地D"];
    var 需要量 = [12, 20, 16, 18];
    var 費用表 = [ [ 7, 3, 2,10],
                   [ 9, 3, 6, 8],
                   [ 8, 7, 8, 6] ];
    var rtn = mpTLP(費用表, 供給地名, 供給量, 需要地名, 需要量)

出力データ
    rtn["輸送表"]
       0    1    2    3
        ┌──────────┐
      0 │ 2  0   16  0  │
      1 │ 2  20  0  0  │
      2 │ 8  0  0  18  │
       └──────────┘
    rtn["総費用"] = 296

            0    1    2    3
             ┌──────────┐
               │  7    1    2    5  │ rtn["v"]  需要量を1増加したたときの総費用の増加
        ┌──┼──────────┤
      0 │  0 │ 0    2    0    5  │
      1 │  2 │ 0    0    2    1  │ rtn["評価表"] 輸送量を1増加したたときの総費用の増加
      2 │  1 │ 0    5    5    0  │
       └──┴──────────┘
            └ rtn["u"]  供給量を1増加したたときの総費用の増加 

ケース2

需要地→
供給地供給・需要量

割当問題
  mpALP(表示場所, 金額表) 
  rtn = mpALP(金額表) 

    rtn["最大金額"]
    rtn["最大割当"]
    rtn["最小金額"]
    rtn["最小割当"]

割当問題とは、n人の担当者とn個の職務があり、それぞれの割当てによる効果あるいは費用の表が与えられたとき、総金額が最大及び最小の組合せを求める手法です。

割当問題の解法としてハンガリー法が有名です。参考(Webテキスト)割当問題
しかし、「0要素を網羅する最小個数の線を引く」ことをプログラムで実装することが私にはできないままです。

それでn!の組合せの中から最大/最小のものを探索するという、あまりにも姑息な手段を採用した。これでもn<8程度なら、計算時間を意識せずに解が求まります。

ケース1

入力
//                     職務
//                 0  1  2  3  4
    var 金額表 = [[1, 3, 4, 5, 2],  // 0
                  [3, 5, 2, 2, 6],  // 1 担
                  [1, 2, 5, 8, 5],  // 2 当
                  [2, 3, 4, 6, 2],  // 3 者
                  [2, 5, 1, 3, 4]]; // 4
    var rtn = mpALP(金額表);
出力
    rtn["最大金額"]= 25, rtn["最大割当"][k][j]= [[3,4,0,2,1],]
    rtn["最小金額"]= 8, rtn["最小割当"][k][j]= [[0,2,4,1,3],]

ケース2

n =
職務0職務1職務2職務3職務4
担当者0
担当者1
担当者2
担当者3
担当者4

動的計画法による最適資金配分
  mpSaitekiHaibunTrace(表示場所, 全資金, 利益表) 
  rtn = mpSaitekiHaibun(全資金, 利益表) 

動的計画法により、
   制約条件:Q=x0 + x1 + x2 + ・・・ + xn ≦ Qmax  0 ≦ xi ≦ xmaxi
  の下で
   目的関数:Rx = f(x0) + f(x1) + f2(x2) + ・・・ + fn(xn) →最大
を最大化する xi を求めます。

これは、「n個の投資案件があり、案件iに xi の資金を配分したときの利益が fi(xi) であるとしたとする。全投資のための資金の上限がQmaxであるとき、利益最大にする各投資への資金配分額を求めよ。」という意味です。

ケース1

案件0,1,2の案件への資金配分による利益が右図のとおりだとします。
点線の部分は、これ以上配分しても利益は変わらない(無駄な配分)ことを示します。
それを、下の利益表の形式で記述します。
解は、最適解のように2次元表で得られます。
解は与えた全資金だけでなく、資金が0,1.2,…、全資金について得られます。
入力
    var 全資金 = 8;
                 //  type   x0,f0   x1,f1
    var 利益表 = [ ["step",  1, 8         ],   // 案件0
                   ["line",  1, 5,   3,10 ],   // 案件1
                   ["line",  3, 5,   4,15 ] ]; // 案件2
    var 最適解 = mpSaitekiHaibun(全資金, 利益表):

出力(最適解)
              案件0       案件1       案件2
           資金  利益  資金  利益  資金  利益
  q↓i→     0     1     2     3     4     5

    0       0     0     0     0     0     0
    1       1     8     0     0     0     0
    2       1     8     1     5     0     0
    3       1     8     2     7.5   0     0
    4       1     8     3    10     0     0
    5       1     8     0     0     4    15
    6       1     8     1     5     4    15
    7       1     8     2     7.5   4    15
    8       1     8     3    10     4    15

ケース2

全資金
案件type x0f0x1f1x2f2x3f3
0
1
2
3
4
5

ナップザック問題
  mpKnapsackTrace(表示場所, タイプ, 全容量, 体積, 利益[, 個数上限])  
  最適解 = mpKnapsack(タイプ, 全容量, 体積, 利益[, 個数上限])  

ナップザック問題とは、n個の品物について、体積 w[i] とナップザックに入れることの利益 f[i] を与えて、
   個数制約:0 ≦ x[i] ≦ 上限個数[i]
   容量制約:∑w[i]*x[i] ≦ ナップザック容量
 の制約で
   目的関数:∑f[i]*x[i]
を最大化する x[i] を求める問題です。

その代表的解法として最適性の原理による動的計画法があります。
何らかの方法で i-1 までの品物について、容量 q を配分したときの最適配分 p[i-1][q] が求められているとします。
i = 0 のとき、一つしか品物がないので p[0][q] = f[0]*x[0] です。
品物 i を追加したとき、
  a:品物 i を入れなかったとき、その利益は p[i-1][q] のままです。
  b:x[i] 個入れるときの利益は、次の合計になります。
      品物 i での利益は、f[i]*x[i] となり、w[i]*x[i] の容量を使います。
      品物 i-1 までの利益は、容量残 = q-w[i]*x[i] なので p[i-1][容量残] になります。
  aが大ならば、品物iを入れないので、以前の品物構成は変わらない。
  bが大ならば、品物iを入れる。以前の最適配分は p[i-1][容量残]
これにより、品物 i を追加したときの最適配分 p[i][q] が求められます。
このようにして、全部の品物を追加したときの解が求められます、
注意
  データは全て正整数で与えてください。
  数値は、なるべく小さい値になるよう単位を調整してください。
   全容量は20以下、体積は10以下を想定しています。
  タイプ3以外は個数上限は不要です。

ケース1

入力
    var タイプ = 3;   //  各品物の入れられる個数によるタイプ 1=1個、2=任意、3=上限指定
    var 全容量 = 8;
    var 体積 = [2, 4, 1, 3];
    var 利益 = [3, 7, 1, 8];
    var 個数上限 = [2, 2, 3, 1];
    var 表示場所 = "mpKnapsackTest1表示場所";
    // タイプ1・2のとき
    最適解 = mpKnapsack(タイプ, 全容量, 体積, 利益);
    // タイプ3のとき
    最適解 = mpKnapsack(タイプ, 全容量, 体積, 利益, 個数上限);

出力 最適解 = mpKnapsack(表示場所, 全容量, 体積, 利益, 個数上限) の戻り値

    q↓i→  [q][0] [q][1] [q][2] [q][3]
     [0]      0      0      0      0
     [1]      0      0      1      0
     [2]      1      0      0      0
     [3]      0      0      0      1
     [4]      0      0      1      1
     [5]      1      0      0      1
     [6]      0      0      3      1
     [7]      0      1      0      1
     [8]      0      1      0      1

ケース2

タイプ
全容量
品物体積利益個数上限
0
1
2
3
4
5
6