lib/bayesnet/factor.rb in bayesnet-0.0.2 vs lib/bayesnet/factor.rb in bayesnet-0.0.3

- old
+ new

@@ -6,85 +6,89 @@ factor.instance_eval(&block) factor end # Specifies variable name together with its values - def var(var_name_to_values) - @vars.merge!(var_name_to_values) + def scope(var_name_to_values) + @scope.merge!(var_name_to_values) end - # Specifies function values for args. Latest args is an function value, all previous are argument values - def val(*args) - args = args[0] if args.size == 1 && args[0].is_a?(Array) - @vals[args[0..-2]] = args[-1] + # Specifies value for a scope context. Value is the last element in `context_and_val` + def val(*context_and_val) + if context_and_val.size == 1 && context_and_val[0].is_a?(Array) + context_and_val = context_and_val[0] + end + @vals[context_and_val[0..-2]] = context_and_val[-1] end def var_names - @vars.keys + @scope.keys end - def [](*args) - key = if args.size == 1 && args[0].is_a?(Hash) - args[0].slice(*var_names).values + def [](*context) + key = if context.size == 1 && context[0].is_a?(Hash) + context[0].slice(*var_names).values else - args + context end @vals[key] end def self.from_distribution(var_distribution) self.class.new(var_distribution.keys, var_distribution.values.map(&:to_a)) end - def args(*var_names) + def contextes(*var_names) return [] if var_names.empty? - @vars[var_names[0]].product(*var_names[1..].map { |var_name| @vars[var_name] }) + @scope[var_names[0]].product(*var_names[1..].map { |var_name| @scope[var_name] }) end def values @vals.values end def normalize vals = @vals.clone norm_factor = vals.map(&:last).sum * 1.0 vals.each { |k, v| vals[k] /= norm_factor } - self.class.new(@vars.clone, vals) + self.class.new(@scope.clone, vals) end - def limit_by(evidence) + def reduce_to(context) # todo: use Hash#except when Ruby 2.6 support no longer needed - evidence_keys_set = evidence.keys.to_set - vars = @vars.reject { |k, _| evidence_keys_set.include?(k) } + context_keys_set = context.keys.to_set + scope = @scope.reject { |k, _| context_keys_set.include?(k) } - evidence_vals = evidence.values - indices = evidence.keys.map { |k| index_by_var_name[k] } - vals = @vals.select { |k, v| indices.map { |i| k[i] } == evidence_vals } - vals.transform_keys! { |k| k - evidence_vals } + context_vals = context.values + indices = context.keys.map { |k| index_by_var_name[k] } + vals = @vals.select { |k, v| indices.map { |i| k[i] } == context_vals } + vals.transform_keys! { |k| k - context_vals } - self.class.new(vars, vals) + self.class.new(scope, vals) end - def reduce(over_vars) - vars = @vars.slice(*over_vars) - indices = vars.keys.map { |k| index_by_var_name[k] } - vals = @vals.group_by { |args, val| indices.map { |i| args[i] } } + # groups by `var_names` having same context and sum out values. + def marginalize(var_names) + scope = @scope.slice(*var_names) + + indices = scope.keys.map { |k| index_by_var_name[k] } + vals = @vals.group_by { |context, val| indices.map { |i| context[i] } } vals.transform_values! { |v| v.map(&:last).sum } - reduced = self.class.new(vars, vals) - reduced.normalize + + self.class.new(scope, vals) end private - def initialize(vars = {}, vals = {}) - @vars = vars + def initialize(scope = {}, vals = {}) + @scope = scope @vals = vals end def index_by_var_name return @index_by_var_name if @index_by_var_name @index_by_var_name = {} - @vars.each_with_index { |(k, v), i| @index_by_var_name[k] = i } + @scope.each_with_index { |(k, v), i| @index_by_var_name[k] = i } @index_by_var_name end end end