bayesnet 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f17178cdfa472aea14e7769f1831f1903ceee2613b6e7b71e1135990dc974ab
4
- data.tar.gz: c55cb95134d4dbb479e89839be7a4634b6bc6a8be52c5c066f1cc1ad21c584a7
3
+ metadata.gz: ca0c7b763a97d4516fb893a7c79716bd4f7279be3fba1b0a9d55f43279aa95cf
4
+ data.tar.gz: 55da869a7d0436bd7eb08a8787bc5d19d56e71e90ac283f4568931c391d3f407
5
5
  SHA512:
6
- metadata.gz: fda426d9dd319b2ad8300a8ba59acae511740cc271729f40b36ce7a7765d8019a901d9d83a2e955ccd964901ee604a3ad96fba092ce41394da5980a77d1bb198
7
- data.tar.gz: 03d4beb66b11ba684007dd49f67ef2ae085ecd5c6e5ca9b8b63327eee109ada3b1f389731543fa6d970bff110230231bc1533022640367a906f57dec1c41bc6d
6
+ metadata.gz: 203475b19fc5bfa4151b4586243ebedce938fcf6a5c5ed3d561f7553ea011e3c8cc797d45360be790f691cecf7462623a0e179160d770454a5bc5cd8ed1a77a2
7
+ data.tar.gz: fd014b35e9f98908694b5a25655f279b8885f01e59e30edf17c34a433758dca7d5c271b6318b5d67df921ce39a63c0e643fb84de241064d8bf8ec95e9a39b94d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bayesnet (0.0.2)
4
+ bayesnet (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
Binary file
@@ -8,25 +8,27 @@ module Bayesnet
8
8
  end
9
9
 
10
10
  # Specifies variable name together with its values
11
- def var(var_name_to_values)
12
- @vars.merge!(var_name_to_values)
11
+ def scope(var_name_to_values)
12
+ @scope.merge!(var_name_to_values)
13
13
  end
14
14
 
15
- # Specifies function values for args. Latest args is an function value, all previous are argument values
16
- def val(*args)
17
- args = args[0] if args.size == 1 && args[0].is_a?(Array)
18
- @vals[args[0..-2]] = args[-1]
15
+ # Specifies value for a scope context. Value is the last element in `context_and_val`
16
+ def val(*context_and_val)
17
+ if context_and_val.size == 1 && context_and_val[0].is_a?(Array)
18
+ context_and_val = context_and_val[0]
19
+ end
20
+ @vals[context_and_val[0..-2]] = context_and_val[-1]
19
21
  end
20
22
 
21
23
  def var_names
22
- @vars.keys
24
+ @scope.keys
23
25
  end
24
26
 
25
- def [](*args)
26
- key = if args.size == 1 && args[0].is_a?(Hash)
27
- args[0].slice(*var_names).values
27
+ def [](*context)
28
+ key = if context.size == 1 && context[0].is_a?(Hash)
29
+ context[0].slice(*var_names).values
28
30
  else
29
- args
31
+ context
30
32
  end
31
33
  @vals[key]
32
34
  end
@@ -35,9 +37,9 @@ module Bayesnet
35
37
  self.class.new(var_distribution.keys, var_distribution.values.map(&:to_a))
36
38
  end
37
39
 
38
- def args(*var_names)
40
+ def contextes(*var_names)
39
41
  return [] if var_names.empty?
40
- @vars[var_names[0]].product(*var_names[1..].map { |var_name| @vars[var_name] })
42
+ @scope[var_names[0]].product(*var_names[1..].map { |var_name| @scope[var_name] })
41
43
  end
42
44
 
43
45
  def values
@@ -48,42 +50,44 @@ module Bayesnet
48
50
  vals = @vals.clone
49
51
  norm_factor = vals.map(&:last).sum * 1.0
50
52
  vals.each { |k, v| vals[k] /= norm_factor }
51
- self.class.new(@vars.clone, vals)
53
+ self.class.new(@scope.clone, vals)
52
54
  end
53
55
 
54
- def limit_by(evidence)
56
+ def reduce_to(context)
55
57
  # todo: use Hash#except when Ruby 2.6 support no longer needed
56
- evidence_keys_set = evidence.keys.to_set
57
- vars = @vars.reject { |k, _| evidence_keys_set.include?(k) }
58
+ context_keys_set = context.keys.to_set
59
+ scope = @scope.reject { |k, _| context_keys_set.include?(k) }
58
60
 
59
- evidence_vals = evidence.values
60
- indices = evidence.keys.map { |k| index_by_var_name[k] }
61
- vals = @vals.select { |k, v| indices.map { |i| k[i] } == evidence_vals }
62
- vals.transform_keys! { |k| k - evidence_vals }
61
+ context_vals = context.values
62
+ indices = context.keys.map { |k| index_by_var_name[k] }
63
+ vals = @vals.select { |k, v| indices.map { |i| k[i] } == context_vals }
64
+ vals.transform_keys! { |k| k - context_vals }
63
65
 
64
- self.class.new(vars, vals)
66
+ self.class.new(scope, vals)
65
67
  end
66
68
 
67
- def reduce(over_vars)
68
- vars = @vars.slice(*over_vars)
69
- indices = vars.keys.map { |k| index_by_var_name[k] }
70
- vals = @vals.group_by { |args, val| indices.map { |i| args[i] } }
69
+ # groups by `var_names` having same context and sum out values.
70
+ def marginalize(var_names)
71
+ scope = @scope.slice(*var_names)
72
+
73
+ indices = scope.keys.map { |k| index_by_var_name[k] }
74
+ vals = @vals.group_by { |context, val| indices.map { |i| context[i] } }
71
75
  vals.transform_values! { |v| v.map(&:last).sum }
72
- reduced = self.class.new(vars, vals)
73
- reduced.normalize
76
+
77
+ self.class.new(scope, vals)
74
78
  end
75
79
 
76
80
  private
77
81
 
78
- def initialize(vars = {}, vals = {})
79
- @vars = vars
82
+ def initialize(scope = {}, vals = {})
83
+ @scope = scope
80
84
  @vals = vals
81
85
  end
82
86
 
83
87
  def index_by_var_name
84
88
  return @index_by_var_name if @index_by_var_name
85
89
  @index_by_var_name = {}
86
- @vars.each_with_index { |(k, v), i| @index_by_var_name[k] = i }
90
+ @scope.each_with_index { |(k, v), i| @index_by_var_name[k] = i }
87
91
  @index_by_var_name
88
92
  end
89
93
  end
@@ -20,14 +20,16 @@ module Bayesnet
20
20
  end
21
21
 
22
22
  def distribution(over: [], evidence: {})
23
- limited = joint_distribution.limit_by(evidence)
24
- limited.reduce(over)
23
+ joint_distribution
24
+ .reduce_to(evidence)
25
+ .marginalize(over)
26
+ .normalize
25
27
  end
26
28
 
27
29
  # This is MAP query, i.e. Maximum a Posteriory
28
30
  def most_likely_value(var_name, evidence:)
29
31
  posterior_distribution = distribution(over: [var_name], evidence: evidence)
30
- mode = posterior_distribution.args(var_name).zip(posterior_distribution.values).max_by(&:last)
32
+ mode = posterior_distribution.contextes(var_name).zip(posterior_distribution.values).max_by(&:last)
31
33
  mode.first.first
32
34
  end
33
35
 
@@ -47,15 +49,15 @@ module Bayesnet
47
49
 
48
50
  factor = Factor.new
49
51
  @nodes.each do |node_name, node|
50
- factor.var node_name => node.values
52
+ factor.scope node_name => node.values
51
53
  end
52
54
 
53
- factor.args(*var_names).each do |args|
54
- val_by_name = var_names.zip(args).to_h
55
+ factor.contextes(*var_names).each do |context|
56
+ val_by_name = var_names.zip(context).to_h
55
57
  val = nodes.values.reduce(1.0) do |prob, node|
56
58
  prob * node.factor[val_by_name]
57
59
  end
58
- factor.val args + [val]
60
+ factor.val context + [val]
59
61
  end
60
62
  @joint_distribution = factor.normalize
61
63
  end
data/lib/bayesnet/node.rb CHANGED
@@ -16,7 +16,7 @@ module Bayesnet
16
16
  @values = hash_or_array.keys
17
17
  node = self
18
18
  @factor = Factor.build do
19
- var node.name => node.values
19
+ scope node.name => node.values
20
20
  hash_or_array.each do |value, probability|
21
21
  val [value, probability]
22
22
  end
@@ -26,9 +26,9 @@ module Bayesnet
26
26
  @values = hash_or_array
27
27
  node = self
28
28
  @factor = Factor.build do
29
- var node.name => node.values
29
+ scope node.name => node.values
30
30
  node.parent_nodes.each do |parent_node_name, parent_node|
31
- var parent_node_name => parent_node.values
31
+ scope parent_node_name => parent_node.values
32
32
  end
33
33
  end
34
34
  instance_eval(&block)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bayesnet
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bayesnet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aleksandr Furmanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-28 00:00:00.000000000 Z
11
+ date: 2021-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: m