Class: Symian::TransitionMatrix

Inherits:
Object
  • Object
show all
Defined in:
lib/symian/transition_matrix.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ TransitionMatrix

Returns a new instance of TransitionMatrix.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/symian/transition_matrix.rb', line 13

def initialize(input)
  # allow filename, string, and IO objects as input
  if input.kind_of?(String)
    if File.exists?(input)
      input = File.new(input, 'r')
    else
      input = StringIO.new(input.strip.split("\n").collect{|l| l.strip }.join("\n"))
    end
  else
    raise RuntimeError unless input.respond_to?(:read)
  end

  @transition_probabilities = {}

  # process escalation matrix
  headers = nil
  CSV.parse(input.read, :headers => :first_row) do |row|
    headers ||= row.headers
    @sg_names ||= headers[1..-2]

    # make sure that support groups do not include the "In" virtual support group
    raise RuntimeError if @sg_names.include?("In")

    # make sure that last support group is the "Out" virtual support group
    raise RuntimeError unless headers[-1] == "Out"

    sg_name = row[0]  # the first row element is the support group name

    # make sure support group name is valid
    raise RuntimeError unless sg_name == "In" or @sg_names.include?(sg_name)

    # make sure we are not overwriting existing data
    raise RuntimeError if @transition_probabilities[sg_name]
    @transition_probabilities[sg_name] = []

    # prepare corresponding row in transition matrix
    2.upto(row.length) do |i|
      escalations = Integer(row[i-1]) # raises ArgumentError in case of errors
      if escalations > 0
        @transition_probabilities[sg_name] << { :sg_name => headers[i-1],
                                                :escalations => escalations }
      end
    end

    # calculate normalized probabilities
    normalize_probabilities(@transition_probabilities[sg_name])
  end

  # check that we have transition probabilities for each support group
  [ "In", *@sg_names].each do |name|
    raise RuntimeError unless @transition_probabilities.has_key?(name)
  end

  # TODO: make seeding of this thing configurable...
  @rng = ERV::RandomVariable.new(:distribution => :uniform, :min_value => 0.0, :max_value => 1.0)
end

Instance Attribute Details

#transition_probabilitiesObject (readonly)

this is mostly for testing purposes



11
12
13
# File 'lib/symian/transition_matrix.rb', line 11

def transition_probabilities
  @transition_probabilities
end

Instance Method Details

#escalation(from) ⇒ Object

Raises:

  • (ArgumentError)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/symian/transition_matrix.rb', line 71

def escalation(from)
  # raise error if source support group does not exist
  raise ArgumentError unless tps = @transition_probabilities[from]

  # get random value
  x = @rng.next

  # return name of first support group whose (cumulative)
  # transition probability is larger than x
  tps.each do |el|
    return el[:sg_name] if el[:probability] > x
  end

  # the destination support group was not found
  raise RuntimeError
end

#merge(sg1_name, sg2_name, new_name = nil) ⇒ Object

Raises:

  • (RuntimeError)


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/symian/transition_matrix.rb', line 89

def merge(sg1_name, sg2_name, new_name=nil)
  # raise error if support groups do not exist
  raise RuntimeError unless sg1_probs = @transition_probabilities.delete(sg1_name) and
                            sg2_probs = @transition_probabilities.delete(sg2_name)

  new_sg_name = new_name || "Merge_of_%s_and_%s" % [ sg1_name, sg2_name ]

  # recalculate escalations to new sg
  @transition_probabilities.each do |k,v|

    # add escalation information for new group
    escalations = 0
    v.each do |el|
      if el[:sg_name] == sg1_name or el[:sg_name] == sg2_name
        escalations += el[:escalations]
      end
    end

    v << { :sg_name => new_sg_name,
           :escalations => escalations }

    # remove old escalation information
    v.delete_if {|el| el[:sg_name] == sg1_name or el[:sg_name] == sg2_name }

    # recalculate normalized probabilities
    normalize_probabilities(v)
  end

  # update @sg_names
  @sg_names[@sg_names.index(sg1_name)] = new_name
  @sg_names.delete(sg2_name)

  # recalculate escalations from new sg
  total_escalation_info = sg1_probs + sg2_probs
  @transition_probabilities[new_sg_name] = []
  [ @sg_names, "Out" ].flatten!.each do |name|
    escalations = total_escalation_info.inject(0) do |sum,el|
      sum + (el[:sg_name] == name ? el[:escalations] : 0)
    end

    if escalations > 0
      @transition_probabilities[new_sg_name] << { :sg_name => name,
                                                  :escalations => escalations }
    end
  end

  # recalculate normalized probabilities
  normalize_probabilities(@transition_probabilities[new_sg_name])
end

#to_sObject



140
141
142
143
144
145
146
147
148
149
# File 'lib/symian/transition_matrix.rb', line 140

def to_s
  lines = [ "From/To,#{@sg_names.join(',')},Out" ]
  [ "In", *@sg_names ].each do |input_sg|
    escalations = [ @sg_names, "Out" ].flatten!.map do |output_sg|
      @transition_probabilities[input_sg].map{|x| x[:sg_name] == output_sg ? x[:escalations] : nil }.compact.first || 0
    end
    lines << "#{input_sg},#{escalations.join(',')}"
  end
  lines.join("\n")
end