複雑な待ち行列問題

#############################
# 複雑な待ち行列問題
#      coded by Y.Suganuma
#############################

#####################
# クラスInletの定義
#####################

class Inlet

	###########################################################
	# コンストラクタ
	#      name1 : 入り口名
	#      a_t1   # = -n : 到着する客の人数を負の値にしたもの
	#             # =0 : 指数分布
	#             # =1 : 一定時間間隔
	#      m_a: 到着時間間隔またはその平均
	#      que1 : 客の到着時刻リスト
	#      name2 : 待ち行列名
	###########################################################

	def initialize(name1, a_t1, m_a, que1, name2)
	
		@_name = name1   # 入り口名
		@_out  = name2   # 待ち行列名
		@_a_t  = a_t1   # = -n : 到着する客の人数を負の値にしたもの
		                # =0 : 指数分布
		                # =1 : 一定時間間隔
		@_mean = m_a   # 到着時間間隔またはその平均
		@_que  = que1   # 客の到着時刻リスト
		if @_a_t == 0
			@_arrive_time = -@_mean * Math.log(rand(0))   # 客の到着時刻(負:客がない)
		elsif @_a_t == 1
			@_arrive_time = 0
		else
			@_arrive_time = @_que.delete_at(0)
		end
		@_out_n = 0   # 待ち行列番号(Q_baseのコンストラクタで設定)
	end

	attr_accessor("_out_n", "_out", "_name", "_arrive_time", "_a_t", "_mean", "_que")
end

#####################
# クラスQueueの定義
#####################

class Queue

	#############################################
	# コンストラクタ
	#      name1 : 待ち行列名
	#      n1 : =0 : 入り口から入る
	#           >0 : 複数の窓口から入る(窓口数)
	#      in1 : 入り口名,または,窓口名
	#      m1 : 処理する窓口数
	#      out1 : 窓口名
	#############################################

	def initialize(name1, n1, in1, m1, out1)
	
		@_name    = name1   # 待ち行列名
		@_n       = n1   # =0 : 入り口から入る
		                 # >0 : 複数の窓口から入る(窓口数)
		@_inn     = in1   # 入り口名,または,窓口名(入り口)
		@_m       = m1   # 処理する窓口数
		@_out     = out1   # 窓口名(出口)
		@_nc      = 0   # 待ち行列への到着客数カウンタ
		@_max_q_l = 0   # 最大待ち行列長
		@_c_ql    = 0.0   # 待ち行列長にその長さが継続した時間を乗じた値の累計
		@_ql_t    = 0.0   # 現在の待ち行列長になった時間
		@_max_wt  = 0.0   # 最大待ち時間
		@_c_wt    = 0.0   # 待ち時間の累計
		@_in_n    = Array.new()   # 入り口番号,または,窓口番号(Q_baseのコンストラクタで設定)
		@_out_n   = Array.new()   # 窓口番号(Q_baseのコンストラクタで設定)
		@_que     = Array.new()   # 待ち行列
	end

	attr_accessor("_name", "_n", "_m", "_inn", "_in_n", "_out", "_out_n", "_nc", "_c_ql", "_que", "_ql_t", "_max_q_l", "_c_wt", "_max_wt")
end

######################
# クラスEntityの定義
######################

class Entity

	########################################
	# コンストラクタ
	#      name1 : 窓口名
	#      s_t1   # =0 : 指数分布
	#             # =1 : 一定時間
	#      m_s:サービス時間またはその平均
	#      name2 : 待ち行列(入力)の名前
	#      sw : =0 : システムの外に出る
	#           =1 : 待ち行列に入る
	#      name3 : 待ち行列(出力)の名前
	########################################

	def initialize(name1, s_t1, m_s, name2, sw, name3)
	
		@_name = name1   # 窓口名
		@_to   = sw   # =0 : システムの外に出る
		              # =1 : 待ち行列に入る
		@_inn  = name2   # 待ち行列(入力)の名前
		if @_to > 0
			@_out = name3   # 待ち行列(出力)の名前
		end
		@_end_time = -1.0   # サービス終了時刻(負:何も処理していない)
		@_s_t      = s_t1   # =0 : 指数分布
		                    # =1 : 一定時間
		@_mean     = m_s   # 平均サービス時間
		@_service  = 0   # サービス中の客番号(0のときは無し)
		@_busy_t   = -1.0   # 窓口がふさがった時刻
		@_c_busy   = 0.0   # 窓口がふさがっている時間の累計
		@_in_n     = -1   # 待ち行列(入力)番号(Q_baseのコンストラクタで設定)
		@_out_n    = -1   # 待ち行列(出力)番号(Q_baseのコンストラクタで設定)
	end

	attr_accessor("_service", "_name", "_inn", "_in_n", "_to", "_out", "_out_n", "_end_time", "_busy_t", "_s_t", "_c_busy", "_mean")
end

########################
# クラスCustomerの定義
########################

class Customer
	
	#####################
	# コンストラクタ
	#      s1,s2 : 状態
	#      t : 到着時刻
	#####################

	def initialize(s1, s2, t)
	
		@_time   = t   # 到着時刻
		@_state1 = s1   # 客の状態1
		                #   =0 : 待ち行列に入っている
		                #   =1 : サービスを受けている
		@_state2 = s2   # 客の状態2(待ち行列番号,または,窓口番号)
	end

	attr_accessor("_state1", "_state2", "_time")
end

#######################
# クラスQ_baseの定義
#######################

class Q_base

	########################################
	# コンストラクタ
	#      v_i : Inletオブジェクトリスト
	#      v_q : Queueオブジェクトリスト
	#      v_e : Entityオブジェクトリスト
	#      e : シミュレーション終了時刻
	########################################

	def initialize(v_i, v_q, v_e, e)

		print("\n")
				# 接続関係のチェック
		@_inl = v_i   # Inletオブジェクトリスト
		@_que = v_q   # Queueオブジェクトリスト
		@_ent = v_e   # Entityオブジェクトリスト
						# Inlet
		for i1 in 0 ... @_inl.length-1
			for i2 in i1+1 ... @_inl.length
				if @_inl[i1]._name == @_inl[i2]._name
					print("***error 同じ名前の入り口があります " + @_inl[i1]._name + "\n")
				end
			end
		end

		for i1 in 0 ... @_inl.length
			k = -1
			for i2 in 0 ... @_que.length
				if @_inl[i1]._out == @_que[i2]._name
					k = i2
					break
				end
			end
			if k >= 0
				@_inl[i1]._out_n = k
			else
				print("***error 入り口から入る待ち行列名が不適当です " + @_inl[i1]._out + "\n")
			end
		end
						# Queue
		for i1 in 0 ... @_que.length-1
			for i2 in i1+1 ... @_que.length
				if @_que[i1]._name == @_que[i2]._name
					print("***error 同じ名前の待ち行列があります " + @_que[i1]._name + "\n")
				end
			end
		end

		for i1 in 0 ... @_que.length
			if @_que[i1]._n == 0
				k = -1
				for i2 in 0 ... @_inl.length
					if @_que[i1]._inn[0] == @_inl[i2]._name
						k = i2
						break
					end
				end
				if k >= 0
					@_que[i1]._in_n.push(k)
				else
					print("***error 待ち行列に入る入り口名が不適当です " + @_que[i1]._inn[0] + "\n")
				end
			else
				for i2 in 0 ... @_que[i1]._n
					k = -1
					for i3 in 0 ... @_ent.length
						if @_que[i1]._inn[i2] == @_ent[i3]._name
							k = i3
							break
						end
					end
					if k >= 0
						@_que[i1]._in_n.push(k)
					else
						print("***error 待ち行列に入る窓口名が不適当です " + @_que[i1]._inn[i2] + "\n")
					end
				end
			end
			for i2 in 0 ... @_que[i1]._m
				k = -1
				for i3 in 0 ... @_ent.length
					if @_que[i1]._out[i2] == @_ent[i3]._name
						k = i3
						break
					end
				end
				if k >= 0
					@_que[i1]._out_n.push(k)
				else
					print("***error 待ち行列を処理する窓口名が不適当です " + @_que[i1]._out[i2])
				end
			end
		end
						# Entity
		for i1 in 0 ... @_ent.length-1
			k = -1
			for i2 in i1+1 ... @_ent.length
				if @_ent[i1]._name == @_ent[i2]._name
					print("***error 同じ名前の窓口があります " + @_ent[i1]._name + "\n")
				end
			end
		end

		for i1 in 0 ... @_ent.length
			k = -1
			for i2 in 0 ... @_que.length
				if @_ent[i1]._inn == @_que[i2]._name
					k = i2
					break
				end
			end
			if k >= 0
				@_ent[i1]._in_n = k
			else
				print("***error 窓口に入る待ち行列名が不適当です " + @_ent[i1]._inn + "\n")
			end
			if @_ent[i1]._to > 0
				k = -1
				for i2 in 0 ... @_que.length
					if @_ent[i1]._out == @_que[i2]._name
						k = i2
						break
					end
				end
				if k >= 0
					@_ent[i1]._out_n = k
				else
					print("***error 窓口の出口にある待ち行列名が不適当です " + @_ent[i1]._out)
				end
			end
		end
				# 初期設定
		@_p_time  = 0.0   # 現在時刻
		@_max_c   = 0   # 最大系内客数
		@_nc      = 0   # システムへの到着客数カウンタ
		@_now_c_t = 0.0   # 現在の系内客数になった時間
		@_c_now_c = 0.0   # 系内客数にその数が継続した時間を乗じた値の累計
		@_c_sys   = 0.0   # 滞在時間の累計
		@_max_sys = 0.0   # 最大滞在時間
		@_end     = e   # シミュレーション終了時間
		@_cus     = Hash.new()   # 系内にいる客のリスト
				# 乱数の初期設定
		srand()
	end
	
	#############
	# 全体の制御
	#############

	def Control()
	
		sw = [0, 0]

		while sw[0] > -2
			Next(sw)   # 次の処理の選択
			if sw[0] == -1
				sw[0] = End_o_s()   # シミュレーションの終了
			else
				if sw[0] == 0
					Arrive(sw[1])   # 客の到着処理
				else
					Service(sw[1])   # サービスの終了
				end
			end
		end
	end
	
	#############################################
	# 次の処理の決定
	#      sw[0] : =-1 : シミュレーションの終了
	#              =0  : 客の到着処理
	#              =1  : 窓口のサービス終了
	#        [1] : 入り口番号 or 窓口番号
	#############################################

	def Next(sw)
	
		tm    = @_end   # 次の処理時刻
		sw[0] = -1
						# 次の客の到着時刻
		for i1 in 0 ... @_inl.length
			x = @_inl[i1]
			if x._arrive_time >= 0.0 and x._arrive_time < tm
				sw[0] = 0
				sw[1] = i1
				tm    = x._arrive_time
			end
		end
						# サービス終了時刻
		for i1 in 0 ... @_ent.length
			x = @_ent[i1]
			if x._service > 0 and x._end_time <= tm
				sw[0] = 1
				sw[1] = i1
				tm    = x._end_time
			end
		end
	
		if sw[0] < 0
			@_end = @_p_time
		end
	end
	
	##################################
	# 終了処理
	#      return : =-1 : 終了前処理
	#               =-2 : 実際の終了
	##################################

	def End_o_s()
	
		sw       = -2
		@_p_time = @_end   # 現在時刻
	
		for i1 in 0 ... @_ent.length
			x = @_ent[i1]
			if x._service > 0   # サービス中の客はいるか?
				if sw == -2
					sw    = -1
					@_end = x._end_time   # 窓口i1のサービス終了時刻
				else
					if x._end_time > @_end
						@_end = x._end_time   # 窓口i1のサービス終了時刻
					end
				end
			end
		end
	
		return sw
	end
	
	########################
	# 客の到着処理
	#      kk : 入り口番号
	########################

	def Arrive(kk)
				# 客数の増加と次の客の到着時刻の設定
		@_nc      += 1   # 到着客数カウンタ
		@_p_time   = @_inl[kk]._arrive_time   # 現在時刻
		if @_inl[kk]._a_t == 0   # 次の客の到着時刻
			@_inl[kk]._arrive_time = @_p_time - @_inl[kk]._mean * Math.log(rand(0))
		elsif @_inl[kk]._a_t == 1
			@_inl[kk]._arrive_time = @_p_time + @_inl[kk]._mean   
		else
			if (@_inl[kk]._que).length < 1
				@_inl[kk]._arrive_time = -1.0
			else
				@_inl[kk]._arrive_time = @_inl[kk]._que.delete_at(0)
			end
		end
		if @_inl[kk]._arrive_time >= @_end
			@_inl[kk]._arrive_time = -1.0
		end
				# 系内客数の処理
		@_c_now_c += @_cus.length * (@_p_time - @_now_c_t)   # 系内客数にその数が継続した時間を乗じた値の累計
		@_now_c_t  = @_p_time   # 現在の系内客数になった時刻
		if @_cus.length+1 > @_max_c
			@_max_c = @_cus.length + 1   # 最大系内客数
		end
				# 空いている窓口を探す
		k1             = @_inl[kk]._out_n
		@_que[k1]._nc += 1
		k              = -1
		for i1 in 0 ... @_que[k1]._m
			k2 = @_que[k1]._out_n[i1]   # 処理窓口
			if @_ent[k2]._service == 0
				k = k2
				break
			end
		end
				# 窓口に空きがない場合
		if k < 0
			ct_p             = Customer.new(0, k1, @_p_time)
			@_cus[@_nc]      = ct_p   # 客の登録(系内客数)
			@_que[k1]._c_ql += (@_que[k1]._que).length * (@_p_time - @_que[k1]._ql_t)   # 待ち行列長にその長さが継続した時間を乗じた値の累計
			@_que[k1]._que.push(@_nc)   # 客の登録(待ち行列)
			@_que[k1]._ql_t = @_p_time   # 現在の待ち行列長になった時刻
			if (@_que[k1]._que).length > @_que[k1]._max_q_l
				@_que[k1]._max_q_l = (@_que[k1]._que).length   # 最大待ち行列長
			end
				# すぐサービスをうけられる場合
		else
			ct_p              = Customer.new(1, k, @_p_time)
			@_cus[@_nc]       = ct_p   # 客の登録(系内客数)
			@_ent[k]._service = @_nc   # サービスを受けている客番号
			@_ent[k]._busy_t  = @_p_time   # 窓口がふさがった時刻
			if @_ent[k]._s_t == 0   # 窓口のサービス終了時刻
				@_ent[k]._end_time = @_p_time - @_ent[k]._mean * Math.log(rand(0))
			else
				@_ent[k]._end_time = @_p_time + @_ent[k]._mean
			end
		end
	end
	
	###################################
	# サービス終了時の処理
	#      kk : サービス終了窓口番号
	###################################

	def Service(kk)
				# 時間の設定
		@_p_time            = @_ent[kk]._end_time   # 現在時刻
		@_ent[kk]._end_time = -1.0   # サービス終了時間
				# システムの外へ出る場合
		if @_ent[kk]._to == 0
				# 系内客数の処理
			@_c_now_c += @_cus.length * (@_p_time - @_now_c_t)   # 系内客数にその数が継続した時間を乗じた値の累計
			@_now_c_t  = @_p_time   # 現在の系内客数になった時刻
				# 滞在時間の処理
			it = @_cus[@_ent[kk]._service]   # サービス中の客
			x1 = @_p_time - it._time
			@_c_sys += x1   # 滞在時間の累計
			if x1 > @_max_sys
				@_max_sys = x1   # 最大滞在時間
			end
			@_cus.delete(@_ent[kk]._service)   # 客の削除(系内客数)
				# 他の窓口処理へ入る場合の処理
		else
			k1             = @_ent[kk]._out_n
			@_que[k1]._nc += 1
			sw             = 1
			k2             = 0
			if (@_que[k1]._que).length == 0
				for i1 in 0 ... @_que[k1]._m
					k2 = @_que[k1]._out_n[i1]   # 窓口
					if @_ent[k2]._service == 0
						sw = 0
						break
					end
				end
			end
				# 待ち行列がある場合
			if sw > 0
				@_que[k1]._c_ql += (@_que[k1]._que).length * (@_p_time - @_que[k1]._ql_t)   # 待ち行列長にその長さが継続した時間を乗じた値の累計
				@_que[k1]._que.push(@_ent[kk]._service)   # 客の登録(待ち行列)
				@_que[k1]._ql_t = @_p_time   # 現在の待ち行列長になった時刻
				if (@_que[k1]._que).length > @_que[k1]._max_q_l
					@_que[k1]._max_q_l = (@_que[k1]._que).length   # 最大待ち行列長
				end
				it         = @_cus[@_ent[kk]._service]
				it._state1 = 0   # 客の状態変更(待ち行列)
				it._state2 = @_ent[kk]._out_n   # 客の状態変更(待ち行列番号)
				# すぐサービスをうけられる場合
			else
				@_ent[k2]._service = @_ent[kk]._service   # サービスを受けている客番号
				@_ent[k2]._busy_t  = @_p_time   # 窓口がふさがった時刻
				if @_ent[k2]._s_t == 0   # 窓口のサービス終了時刻
					@_ent[k2]._end_time = @_p_time - @_ent[k2]._mean * Math.log(rand(0))
				else
					@_ent[k2]._end_time = @_p_time + @_ent[k2]._mean
				end
			end
		end
				# 窓口使用時間の処理
		@_ent[kk]._service  = 0   # 窓口を空き状態にする
		@_ent[kk]._c_busy  += (@_p_time - @_ent[kk]._busy_t)   # 窓口がふさがっている時間の累計
				# この窓口に対する待ち行列がある場合
		k3 = @_ent[kk]._in_n
		if (@_que[k3]._que).length > 0
			@_que[k3]._c_ql += (@_que[k3]._que).length * (@_p_time - @_que[k3]._ql_t)   # 待ち行列長にその長さが継続した時間を乗じた値の累計
			n                = @_que[k3]._que.delete_at(0)   # 待ち行列の先頭にいる客
			@_que[k3]._ql_t  = @_p_time   # 現在の待ち行列長になった時刻
			x1               = @_p_time - @_cus[n]._time
			@_que[k3]._c_wt += x1   # 待ち時間の累計
			if x1 > @_que[k3]._max_wt
				@_que[k3]._max_wt = x1   # 最大待ち時間
			end
			for i1 in 0 ... @_que[k3]._m
				k4 = @_que[k3]._out_n[i1]   # 窓口
				if @_ent[k4]._service == 0
					@_ent[k4]._service = n   # 窓口の客番号
					@_ent[k4]._busy_t  = @_p_time   # 窓口がふさがった時刻
					if @_ent[k4]._s_t == 0   # 窓口のサービス終了時刻
						@_ent[k4]._end_time = @_p_time - @_ent[k4]._mean * Math.log(rand(0))
					else
						@_ent[k4]._end_time = @_p_time + @_ent[k4]._mean
					end
					@_cus[n]._state1 = 1   # 客の状態変更(サービス中)
					@_cus[n]._state2 = k4   # 客の状態変更(窓口番号)
					break
				end
			end
		end
	end
	
	##########################
	# 統計量の計算と最終出力
	##########################

	def Output()
						# System
		printf("全客数 %d", @_nc)
		printf(" 最大系内客数 %d 最大滞在時間 %.3f\n", @_max_c, @_max_sys)
		printf("平均系内客数 %.3f", (@_c_now_c / @_p_time))
		printf(" 平均滞在時間 %.3f", (@_c_sys / @_nc))
		printf(" 終了時間 %.3f\n", @_p_time)
						# Entity
		for i1 in 0 ... @_ent.length
			e = @_ent[i1]
			printf("Entity " + e._name)
			printf(" 稼働率 %.3f\n", (e._c_busy / @_p_time))
		end
						# Queue
		for i1 in 0 ... @_que.length
			q = @_que[i1]
			printf("Queue " + q._name)
			printf(" 客数 %d", q._nc)
			printf("   最大待ち行列長 %d", q._max_q_l)
			printf("   最大待ち時間 %.3f\n", q._max_wt)
			printf("      平均待ち行列長 %.3f", (q._c_ql / @_p_time))
			printf("   平均待ち時間 %.3f\n", (q._c_wt / q._nc))
		end
	end
end

					# 入り口
print("入り口(Inlet)の数は? ")
n_i = Integer(gets())
inl = Array.new()
for i1 in 0 ... n_i
	print(String(i1+1) + " 番目の入り口(Inlet)\n")
	print("     名前は? ")
	name1 = gets().strip()
	qq    = Array.new()
	print("     到着分布(=0:指数分布,=1:一定時間間隔,<0:指定,客数の負値)? ")
	n = Integer(gets())
	if n == 0
		print("          到着時間間隔の平均値は? ")
		m_a = Float(gets())
	elsif n == 1
		print("          到着時間間隔は? ")
		m_a = Float(gets())
	else
		for i2 in range(0, -n)
			print("          到着時間は? ")
			x = Float(gets())
			qq.push(x)
		end
	end
	print("     並ぶ待ち行列の名前は? ")
	name2 = gets().strip()
	inl_e = Inlet.new(name1, n, m_a, qq, name2)
	inl.push(inl_e)
end
				# 待ち行列
print("待ち行列(Queue)の数は? ")
n_q = Integer(gets())
que = Array.new()
for i1 in 0 ... n_q
	print(String(i1+1) + " 番目の待ち行列(Queue)\n")
	print("     名前は? ")
	name1 = gets().strip()
	print("     入り口(0),または,窓口(n>0,窓口の数)から? ")
	n   = Integer(gets())
	inn = Array.new()
	if n == 0
		print("          入り口の名前は? ")
		name2 = gets().strip()
		inn.push(name2)
	else
		for i2 in 0 ... n
			print("          " + String(i2+1) + " 番目の窓口の名前は? ")
			name3 = gets().strip()
			inn.push(name3)
		end
	end
	print("     処理する窓口の数は? ")
	m   = Integer(gets())
	out = Array.new()
	for i2 in 0 ... m
		print("          " +String(i2+1) + " 番目の窓口の名前は? ")
		name4 = gets().strip()
		out.push(name4)
	end
	que_e = Queue.new(name1, n, inn, m, out)
	que.push(que_e)
end
				# 窓口
print("窓口(Entity)の数は? ")
n_e = Integer(gets())
ent = Array.new()
for i1 in 0 ... n_e
	print(String(i1+1) + " 番目の窓口(Entity)\n")
	print("     名前は? ")
	name1 = gets().strip()
	print("     サービス分布(=0:指数分布,=1:一定時間)? ")
	s_t = Integer(gets())
	if s_t == 0
		print("          サービス時間の平均値は? ")
		m_s = Float(gets())
	else
		print("          サービス時間は? ")
		m_s = Float(gets())
	end
	print("     待ち行列(入力)の名前は? ")
	name2 = gets().strip()
	print("     終了後,外部(0),または,待ち行列(1)? ")
	sw    = Integer(gets())
	name3 = ""
	if sw > 0
		print("          待ち行列(出力)の名前は? ")
		name3 = gets().strip()
	end
	ent_e = Entity.new(name1, s_t, m_s, name2, sw, name3)
	ent.push(ent_e)
end
				# 全体の制御を行うクラス
print("シミュレーション終了時間? ")
stp  = Float(gets())
base = Q_base.new(inl, que, ent, stp)   # 全体の制御を行うクラス
				# 実行
base.Control()
				# 出力
base.Output()

=begin
------------入力例(簡単な場合)-----------
1
  Inlet
    0
    5
    Queue
1
  Queue
    0
      Inlet
    2
      Entity1
      Entity2
2
  Entity1
    0
      4
    Queue
    0
  Entity2
    0
      4
    Queue
    0
10000

------------入力例(複雑な場合)-----------
2
  Inlet1
    0
      5
    Queue1
  Inlet2
    0
      5
    Queue2
3
  Queue1
    0
      Inlet1
    1
      Entity1
  Queue2
    0
      Inlet2
    1
      Entity2
  Queue3
    2
      Entity1
      Entity2
    2
      Entity3
      Entity4
4
  Entity1
    0
      4
    Queue1
    1
      Queue3
  Entity2
    0
      4
    Queue2
    1
      Queue3
  Entity3
    0
      3
    Queue3
    0
  Entity4
    0
      3
    Queue3
    0
10000
=end