Ruby で TOC

最近書店のベストセラー・コーナーで見かける本に、ザ・ゴールという小説があります。裏表紙の宣伝文をそのまま引用すると、

主人公アレックス・ロゴは、ある機械メーカーの工場長。長引く採算悪化を理由に、突然、本社から工場閉鎖を告げられる。残された時間は、わずかに三か月。それまでに収益体制を改善しなければ、工場は閉鎖され、多くの人が職を失ってしまうことになる。半ば諦めかけていた彼だったが、学生時代の恩師ジョナに偶然再開したことをきっかけに、工場再建へ向けて意欲を燃やし始める。
ジョナは、これまでの生産現場での常識を覆す考え方で、アレックスの工場が抱える諸問題を次々に科学的に解明していく。そのヒントをもとに、アレックスは工場の仲間たちとたゆまぬ努力を続け、超多忙な日々を過ごす。だが、あまりにも家庭を犠牲にしたため、妻のジュリーは姿を消してしまう。仕事ばかりか、別居、離婚という家庭崩壊の危機にもさらされたアレックスは・・・・。

という内容です。この本の面白いところは、この小説がそのまま、著者エリヤフ・ゴールドブラットの考え出した、TOC( The Theory Of Constraints )-- 制約理論--という工程管理の理論の解説書になっていることです。また、この本を読んで実際の工場に適用して生産力を倍増させた会社が続出したということです。

ザ・ゴールで紹介されているのは、主にTOCの工程管理における適用法です。TOCを工場の工程管理に適用するにはふたつの柱があります。ひとつはスループット会計という新しい生産性の管理会計法です。もうひとつはドラム・バッファー・ロープという工程管理の方法です。

スループット会計と言うのは、生産性の指標に従来のようなコスト会計を用いるのではなく、スループット(販売高)と在庫と作業経費の3つの指標を使う考え方です。個々の製品に複雑な原価計算を行って利益率を計算するのではなく、工場全体の生産性を最適化する考え方です。この3つの指標は通常の会計の用語とは定義が異ります。また、これらの指標には全てに「お金」という言葉が含まれています。すなわち、スループットは販売量だから、入ってくるお金だし、在庫は現在製造プロセスの中に溜っているお金、作業経費は出て行くお金のことです。在庫について言えば工場全体が在庫です。つまり、工場にあるもので売ることができるものは、販売前の製品であれ、原材料であれ、工場の機械であれ全て在庫なのです。また、作業経費には人件費や電力、燃料、減価償却費など外に出て行くお金全てが含まれます。人件費を在庫に割り振らないのは従業員は個別の製品毎に賃金をもらっている訳ではないからです。会計の役割は工場の生産性の指標を与えることです。いままでのコスト会計では生産性を上げるどころか阻害する場合もあることが強調されています。スループット会計を生産の指標とすることで確実にキャッシュを増やす方向が示されます。

さて、工程管理のほうですが、これは、ドラム・バッファー・ロープ方式で行います。ドラムとは太鼓のことで、システムの工程のうち処理能力の一番劣るボトルネック(ビール瓶の首の細いところに例えて作業能率の最も遅い工程をそう呼ぶ)の処理スピードに他の工程のスピードを合わせてしまうことです。他の機械が遊んでしまうが、その方がいいのです。たとえ他の機械を目一杯働かせたところでボトルネックで処理できないのだから、仕掛品が増えるだけで、結局在庫が増え、スループットからの利益が減ってしまいます。

バッファーを跳ばしてロープの方の意味を説明すると、これは、入口の原材料をボトルネックの作業量に合わせて、調節する方法です。これによって過剰な材料の投入を防ぎ、在庫を圧縮することができるのです。

最後にバッファーについてですが、これは、工程の間に適当な仕掛品のストックを置くことによって、各工程の作業能率の統計的な変動を吸収させる方法です。長々と TOC の説明が続きましたが、このバッファーの効果を Ruby で実験してみようと言うのがこのページのテーマです。

モデルの概要は次のようになります。製品は工程 A、工程 B、工程 C の3つの工程を経て完成品となります。各工程の作業能率にはバラツキがあり、単位時間あたりランダムに1〜6個の製品を仕上げることができます。したがって平均 3.5 個の完成品を作ることができるはずですが、前工程で仕上った仕掛品が少ない場合は、次の工程の能率が良くても、その工程で仕上げる製品の数が制限されてしまいます。これを依存関係といいます。それが積み重なるとそのシステムのスループットは各工程のスループットの平均より小さい値になってしまいます。したがって前後の工程の間にバッファーを設けていつでも仕掛品を利用できるようにするとスループットを上げることができるのです。このモデルの Ruby のプログラム toc.rb は次のようになります。

srand

class Unit
  def initialize( buffer )
    @buffer = buffer
  end
  attr_reader :buffer
  def addbuffer( parts )
    @buffer += parts
  end
  def process
    @dice = rand(6) + 1
    if @dice <= @buffer
      out = @dice
      @buffer -= out
    else
      out = @buffer 
      @buffer = 0
    end
    out
  end
  attr_reader :dice
end

a = Unit.new( 100 )
b = Unit.new( 0 )
c = Unit.new( 0 )

total = 0
n = 15
for i in 1..n
  a.addbuffer(0)
  print 'A:', '(', a.buffer, ')'
  a_out = a.process
  print a.dice, '-', a_out, '(', a.buffer, ') -> '
  b.addbuffer( a_out )
  print 'B:', '(', b.buffer, ')'
  b_out = b.process
  print b.dice, '-', b_out, '(', b.buffer, ') -> '
  c.addbuffer( b_out )
  print 'C:', '(', c.buffer, ')'
  c_out = c.process
  print c.dice, '-', c_out, '(', c.buffer, ')', "\n"
  total += c_out
end
print "total = #{total}  average = #{ total / n.to_f }\n"

toc.rb の出力は次のようになります。

A:(100)5-5(95) -> B:(5)3-3(2) -> C:(2)6-3(0)
A:(95)2-2(93) -> B:(4)6-4(0) -> C:(4)5-4(0)
A:(93)2-2(91) -> B:(2)1-1(1) -> C:(1)6-1(0)
A:(91)5-5(86) -> B:(6)6-6(0) -> C:(6)3-3(3)
A:(86)3-3(83) -> B:(3)3-3(0) -> C:(6)4-4(2)
A:(83)1-1(82) -> B:(1)1-1(0) -> C:(3)2-2(1)
A:(82)2-2(80) -> B:(2)6-2(0) -> C:(3)3-3(0)
A:(80)2-2(78) -> B:(2)2-2(0) -> C:(2)6-2(0)
A:(78)3-3(75) -> B:(3)2-2(1) -> C:(2)3-2(0)
A:(75)2-2(73) -> B:(3)4-3(0) -> C:(3)2-2(1)
A:(73)3-3(70) -> B:(3)3-3(0) -> C:(4)5-4(0)
A:(70)6-6(64) -> B:(6)3-3(3) -> C:(3)1-1(2)
A:(64)2-2(62) -> B:(5)1-1(4) -> C:(3)1-1(2)
A:(62)4-4(58) -> B:(8)5-5(3) -> C:(7)2-2(5)
A:(58)2-2(56) -> B:(5)2-2(3) -> C:(7)1-1(6)
total = 35  average = 2.333333333

1行目を例にしてこの表の見方を説明します。1行目の出力は次のようになります。

A:(100)5-5(95) -> B:(5)3-3(2) -> C:(2)6-3(0)

A: は工程Aを示します。(100)は工程Aのための仕掛品が100個あるという意味です。5-5 はこの工程の処理能力がこの単位時間では5個であり、処理されて出来上がる品も5個であるということを示します。(95)は処理後の仕掛品の個数で100から5個減っています。B:は工程Bを示します。この工程では仕掛品の数は A 工程で処理された5個です。この工程のこの単位時間での処理能力は3個で仕掛品は5個ですから処理品は3個作ることができます。C:は工程Cです。この工程では仕掛品は工程Bからの3個ですから処理能力は6個なのに仕掛品が3個しかないため3個の完成品しか作ることができません。このような仕掛品と工程の処理能力のミスマッチは他にも見つけることができます。

最後の行は10単位時間で完成させた製品の量と、単位時間あたりの平均値を表示しています。完成品の総数は35で、平均は約2.3で理論値の3.5には及びません。それでは工程Bと工程Cの入口に10個ずつの仕掛品のバッファーを置いたらどうなるでしょうか。toc.rb プログラムの次の部分

a = Unit.new( 100 )
b = Unit.new( 0 )
c = Unit.new( 0 )

を次のように変えて見ます。

a = Unit.new( 100 )
b = Unit.new( 10 )
c = Unit.new( 10 )

実行結果は次のようになります。

A:(100)1-1(99) -> B:(11)1-1(10) -> C:(10)4-4(7)
A:(99)3-3(96) -> B:(13)4-4(9) -> C:(11)6-6(5)
A:(96)4-4(92) -> B:(13)6-6(7) -> C:(11)2-2(9)
A:(92)5-5(87) -> B:(12)4-4(8) -> C:(13)3-3(10)
A:(87)5-5(82) -> B:(13)2-2(11) -> C:(12)4-4(8)
A:(82)5-5(77) -> B:(16)1-1(15) -> C:(9)1-1(8)
A:(77)1-1(76) -> B:(16)4-4(12) -> C:(12)3-3(9)
A:(76)1-1(75) -> B:(13)4-4(9) -> C:(13)2-2(11)
A:(75)5-5(70) -> B:(14)4-4(10) -> C:(15)2-2(13)
A:(70)4-4(66) -> B:(14)2-2(12) -> C:(12)5-5(10)
A:(66)3-3(63) -> B:(15)2-2(13) -> C:(12)6-6(6)
A:(63)1-1(62) -> B:(14)5-5(9) -> C:(11)6-6(5)
A:(62)2-2(60) -> B:(11)4-4(7) -> C:(9)3-3(6)
A:(60)2-2(58) -> B:(9)4-4(5) -> C:(10)4-4(6)
A:(58)6-6(52) -> B:(11)3-3(8) -> C:(9)5-5(4)
total = 56  average = 3.733333333

完成品の総数は56個で、平均は約3.7個になりシステム全体のスループットが上昇していることが分かります。バッファーがあるときは処理能力と仕掛品のミスマッチは見られません

この実験からも TOC の方法が、全体の生産の速度をボトルネックに合わせることで在庫の量を減らし、各工程の統計的変動をバッファーで吸収することでシステム全体の生産能率を上げ、生産のスピードと収益性を共に増加させる良い方法であることが分かります。ザ・ゴールには、この他にも非ボトルネックのバッチサイズを小さくすることで機械のアイドル時間を減らしたり、品質検査をボトルネックの前に持って来ることで処理速度を落すことなく不良品の数を減らす方法など盛り沢山のテクニックが惜しげもなく分かりやすく説明してあります。製造業に携わっていないものでも知的興奮を味わわせてくれる小説です。(2002/7/15)