RCPSP求解プログラムの枠組み

rcpsp30.py


001
002

003
004
005
006
007
008

009
010
011
012
013

#rcpsp30.py
from optseq import *

data=["A01",5,10] #[名前,期間,納期]
prob=Model()
act=prob.addActivity(name=data[0])
#act=prob.addActivity(name=data[0],duedate=data[2]-1,backward=True)
mode=Mode("Mode",duration=data[1])
act.addModes(mode)

prob.Params.Makespan=True
prob.Params.TimeLimit=1
#prob.Params.OutputFlag=True
prob.optimize()
prob.write("rcpsp30.txt")

【rcpsp30.txt】


001
002
003
004
005
006

  activity    mode   duration 12345
------------------------------------
   A01       Mode       5     =====
------------------------------------
   resource usage/capacity     
------------------------------------

005
006

#act=prob.addActivity(name=data[0])
act=prob.addActivity(name=data[0],duedate=data[2]-1,backward=True) 

【rcpsp30.txt】


001
002
003
004
005
00

  activity    mode   duration 123456789
------------------------------------------------
   A01       Mode       5         =====
------------------------------------------------
   resource usage/capacity     
------------------------------------------------

rcpsp31.py


001
002
003
004
005
006
007
008
009

#rcpsp31.py
from optseq import *
#====データセット 
#i:[名前,期間,後続,資源]
data={
 1:["A01",5,[2],1],
 2:["A02",1,[0],0]
}
date10=10 #納期日

010
011
012
013
014
015

#====アクティビティ
prob=Model()
act={}
for i in data:
   #act[i]=prob.addActivity(name=data[i][0])   
    act[i]=prob.addActivity(name=data[i][0],duedate=date10,backward=True)  

010
011
012
013
014
015
016
017

#====先行制約
for i in data:
    for j in data[i][2]:
        if j>0: prob.addTemporal(act[i],act[j]) 
#----A02固定
i==2
prob.addTemporal("source",act[i],tempType="CC",delay= date10)   
prob.addTemporal(act[i],"source",tempType="CC",delay=-date10)     

018
019
020
021
022
023
024

#====資源制約  
res=prob.addResource("worker",capacity={(0,"inf"):1}) 
mode={}  
for i in data:
    mode[i]=Mode("M{0:02d}".format(i),duration=data[i][1])
    mode[i].addResource(res,requirement=data[i][3])
    act[i].addModes(mode[i])   

025
026
027
028
029
030

#====求解
prob.Params.Makespan=True
prob.Params.TimeLimit=1
#prob.Params.OutputFlag=True
prob.optimize()
prob.write("rcpsp31.txt") 

【rcpsp31.txt】


001
002
003
004
005
006
007
008
009
010

  activity    mode   duration  1 2 3 4 5 6 7 8 910
------------------------------------------------------------
   A01       M01        5             ==========  
   A02       M02        1                       ==
------------------------------------------------------------
   resource usage/capacity     
------------------------------------------------------------
            worker             0 0 0 0 1 1 1 1 1 0
                               1 1 1 1 1 1 1 1 1 1
------------------------------------------------------------

rcpsp32.py


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016

#rcpsp32.py
from optseq import *
#====データセット
#i:[名前,期間,後続,資源]
data={
 1:["A01",3,[4,6],3],
 2:["A02",4,[5,7],1],
 3:["A03",6,[8]  ,1],
 4:["A04",4,[5,7],4],
 5:["A05",5,[8]  ,4],
 6:["A06",5,[9]  ,4],
 7:["A07",4,[10] ,5],
 8:["A08",2,[10] ,2],
 9:["A09",2,[10] ,5],
10:["A10",5,[0]  ,3],
}

017
018
019
020
021

#====アクティビティ
prob=Model()
act={}
for i in data:
    act[i]=prob.addActivity(data[i][0])

022
023
024
025

#====先行制約
for i in data:
    for j in data[i][2]:
        if j>0: prob.addTemporal(act[i],act[j])  

026
027
028
029
030

031
032

#====資源制約  
res=prob.addResource("Resource",capacity={(0,"inf"):10}) 
mode={}  
for i in data:
    mode[i]=Mode("M{0:02d}_{1:02d}".format(i,data[i][3]),
                                    duration=data[i][1])
    mode[i].addResource(res,requirement=data[i][3])
    act[i].addModes(mode[i])

033
034
035
036
037
038
039

#====求解
prob.Params.Makespan=True
prob.Params.TimeLimit=1
#prob.Params.OutputFlag=True
prob.optimize()
prob.write("rcpsp32.txt")
prob.writeExcel("rcpsp32.csv")

【rcpsp32.txt】


001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018

  activity    mode   duration  1 2 3 4 5 6 7 8 910111213141516171819
-----------------------------------------------------------------------------
   A01      M01_03      3     ======                                
   A02      M02_01      4     ========                              
   A03      M03_01      6     ============                          
   A04      M04_04      4           ========                        
   A05      M05_04      5                   ==========              
   A06      M06_04      5           ==========                      
   A07      M07_05      4                         ========          
   A08      M08_02      2                             ====          
   A09      M09_05      2                     ====                  
   A10      M10_03      5                                 ==========
-----------------------------------------------------------------------------
   resource usage/capacity     
-----------------------------------------------------------------------------
           Resource            5 5 510 9 9 8 8 9 9 9 9 7 7 3 3 3 3 3
                              10101010101010101010101010101010101010
-----------------------------------------------------------------------------

補遺2

[1] 例題1(図1.1)へのカレンダー挿入(図2.19の作成)

工程’sファイル「例題1.kzd」をコピーして、「ex1.kdz」とします。その編集期間とカレンダー(0:デフォールト)を次のように変更します。

カレンダーを上書きすると、次が得られます。

ここで先行関係を整えるために、「日程調整」

を行うと、次が得られます。

[2] 図2.12へのカレンダー挿入(図2.20の作成)
工程’sファイル「例題1a (開始日を10日シフト).kzd」をコピーして、「ex2.kdz」とします。その編集期間とカレンダー(0:デフォールト)をex1.kdzと同じように変更します。カレンダーを上書きすると、次が得られます。

ここで先行関係を整えるために、「日程調整」を行うと、次が得られます。

[3] 図2.13へのカレンダー挿入(図2.21の作成)
工程’sファイル「例題1b (2つの計画を並べる).kzd」をコピーして、「ex3.kdz」とします。そのカレンダー(0:デフォールト)と同じように変更します。カレンダーを上書きすると、次が得られます。

いま、コピーされた計画の先頭の3つのアクティビティについて、日程調整をかけても移動させない指定を行います(バーの右上にマークが表示されます)。

ここで先行関係を整えるために、「日程調整」を行うと、次が得られます。

カレンダーと通し日

一般に、計画は稼働日を用いて行い、あとで非稼働日を挿入します。

[1]カレンダー

例題1に、休みなし、休みあり、土曜出勤も3種類のカレンダーを付与します。

カレンダー編集

[2]通し日

工程’sでは稼働日にシリアル番号を振った「通し日」の設定できます。

通し日の設定

第1章の例題1を通し日を用いて表すと次のようになります。

データセットの作成支援

「工程’s」にはオプションで、エクスポート・インポート機能があって、工程データを12種類のTSV(Tab Separated Values)ファイルに出力できます。

例題1.kzdに対してエクスポートをかけます。

この操作を行うと、指定したフォルダに次のように13種類のtsvファイルが作成されます。

ここで重要なのが赤枠で囲んだ3つのファイルで、koutes.tsv、constraint.tsv、requirement.tsvはそれぞれ日程計画、先行制約、資源制約に関する情報を含んでいます。

tsvファイルを、EXCELにdrag-dropすれば、簡単に中身をチェックできます。いま適当に編集してtsvファイルに戻したとします。これらを次のようにインポートすれば、例題1.kzdが変更されます。

「工程’s」の基礎

第1章の例題1を「工程’s」を用いてstep-by-stepで作成してみます。

[1]準備
編集期間設定

現在日の設定

プロジェクト名称の設定

カレンダー編集(略)
通し日の設定(略)

[2]リソースの定義
資源マスター編集

[3]アクティビティの定義
WBSの設定
– グループの挿入

– グループプロパティの設定

– グループバーの表示・非表示(略)
– ツリービューの表示・非表示

アクティビティバーの設置

– IDの設定
– 名称の設定
– 必要資源の設定
– シンボル変更(略)

[4]先行制約の定義
先行制約の定義
山積み

– 山積みグラフの表示
– 供給資源量表示・非表示
– 供給資源量設定

補遺1

造船工程計画

造船工程計画とは、ブロック製作のために2種類のリソース(場所、作業員)のタイムライン化(時間軸に並べること)と言えます。2つの計画AB,BC,CAは比較的簡単に求まりますが、3つの計画A,B,Cの共通部分ABCを求める必要があります。

定盤計画の難しさ

問題の規模

例題2に対して、次式に基づいて、「問題の規模」を計算してみます。

\displaystyle{(1)\quad N(L,B,\ell,b)=(L-\ell+1)(B-b+1) }

L=4B=4だから、ブロックBLK1については

\displaystyle{(2)\quad N_{\rm BLK1}(4,4,1,2)=(4-1+1)(4-2+1)=4\times 3=12 }

ブロックBLK2については

\displaystyle{(3)\quad N_{\rm BLK2}(4,4,1,2)=(4-2+1)(4-1+1)=3\times 4=12 }

ブロックBLK3については

\displaystyle{(4)\quad N_{\rm BLK3}(4,4,2,2)=(4-2+1)(4-2+1)=3\times 3=9 }

したがって、例題2に対して問題の規模は

\displaystyle{(15)\quad N_{\rm BLK1}N_{\rm BLK2}N_{\rm BLK3}=12\times 12\times 9=1296 }

と計算できます。

本書では、すべてのRCPSPについて、問題の規模を次のコードで、モードの総数として求めていますす。

OptSeq

#====問題の規模:      
A=[]
for a in prob.act: A.append(a.name) 
print("アクティビティ:",A)  
N=len(prob.act)
print("アクティビティ総数(含待機数):",N)  
M=[]
for a in prob.act: M.append(len(a.modes)) 
print("モード数:",M)  
print("平均モード数:",round(sum(M)/N))
P=math.ceil(math.log10(round(sum(M)/N)**N))
print("問題の規模: 10**",P)   

ここで、RCPSPとして定式化されたモデルを{\bf prob}としています。{\bf prob.act}により、すべてのアクティビティ名のリスト{\bf A}を求めて、その長さを{\bf N}としています。また各アクティビティに付されたモードの個数を並べたリスト{\bf M}を求めています。平均モード数{\bf sum(M)/N}を求め、これを{\bf N}乗することで、問題の規模としています。

RCPSP法の枠組み

rcpsp11.py

OptSeq

#rcpsp11.py
from optseq import *
#====データセット
#i:[名前、期間、後続、資源]
data={
 1:["A01",3,[4,6],3],
 2:["A02",4,[5,7],1],
 3:["A03",6,[8]  ,1],
 4:["A04",4,[5,7],4],
 5:["A05",5,[8]  ,4],
 6:["A06",5,[9]  ,4],
 7:["A07",4,[10] ,5],
 8:["A08",2,[10] ,2],
 9:["A09",2,[10] ,5],
10:["A10",5,[0]  ,3],
}
#====アクティビティ
prob=Model()
act={}
for i in data:
    act[i]=prob.addActivity(data[i][0])
#====先行制約
for i in data:
    for j in data[i][2]:
        if j>0: prob.addTemporal(act[i],act[j])  
#====資源制約  
res=prob.addResource("Resource",capacity={(0,"inf"):10}) 
mode={}  
for i in data:
    mode[i]=Mode("M{0:02d}_{1:02d}".format(i,data[i][3]),\
                                    duration=data[i][1])
    mode[i].addResource(res,requirement=data[i][3])
    act[i].addModes(mode[i])
#====求解
prob.Params.Makespan=True
prob.Params.TimeLimit=1
prob.optimize()
prob.write("rcpsp11.txt")
prob.writeExcel("rcpsp11.csv")

rcpsp11.pyはPython言語で記述されています。

データセットはPythonの辞書データとして定義されており、keyがアクティビティ名となっていて、先行関係はkeyを参照して指定されていることに注意してください。ここでは簡単のために数字を用いていますが、文字列を用いることもできます。たとえばデータセットの定義は次のように行うこともできます。

OptSeq

#====データセット
data={
#"id":["名称",期間,["後続#1"," 後続#2"]、員数],
"A01":["A1(3人)",3,["A04"," A06"],3],
"A02":["A2(1人)",4,["A05"," A07"],1],
"A03":["A3(1人)",6,["A08"," 0"],1],
"A04":["A4(4人)",4,["A05"," A07"],4],
"A05":["A5(4人)",5,["A08"," 0"],4],
"A06":["A6(4人)",5,["A09"," 0"],4],
"A07":["A7(5人)",4,["A10"," 0"],5],
"A08":["A8(2人)",2,["A10"," 0"],2],
"A09":["A9(5人)",2,["A10"," 0"],5],
"A10":["A10(3人)",4,["0"," 0"],3],
}

その他のコードについては、別途詳しく説明します。

RCPSPとは

図1.1

図1.1は「工程’s」で作成されており、作成時のカレンダーが用いられています。ただ例題1は特定のカレンダーに依存しないので、「通し日」を用いて、次のように表すこともできます。

「工程’s」の基礎については、次にまとめています。

「工程’s」の基礎

CT-ERRATA

頁・式
p.3 おきたい思う おきたい思う
p.31 (a=\theta-\piだから)、\sin\theta\simeq \theta-\pi (a=\piだから)、\sin\theta\simeq -(\theta-\pi)
p.83 -x0) [-L 1]*x0)
(Program 52を修正・更新しています)
(8.4) =ke_v(t) =k_ve_v(t)
p.136 (8.15)が成り立つ (8.17)が成り立つ

●本書では、m行n列の行列のサイズを「m  \times n」と表記しています。たとえば(n-p)行n列の行列のサイズは「n-p\times n」となります。ここで、\timesは掛け算(の演算子)ではなく、行数と列数の区切りであることに注意してください。