rb_probdsl 0.0.1

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.
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2010, Steffen Siering
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of Steffen Siering nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
data/examples/alarm.rb ADDED
@@ -0,0 +1,149 @@
1
+
2
+ require 'rubygems'
3
+
4
+ require 'probdsl'
5
+ include ProbDSL
6
+
7
+ # Alarm example from "Artificial Intelligence - A Modern Approach" by Russel
8
+ # and Norvig Page 493 cc.
9
+ #
10
+ # Suppose you have a new fairly reliable burglar alarm at home but occasionally
11
+ # it responds to minor earthquakes. You also have two neighbors John and Mary,
12
+ # who have promised to call you at work when they hear the alarm. John always
13
+ # calls when he hears the alarm, but sometimes confuses the telephone ringing
14
+ # with the alarm and calls then, too. Mary, on the other hand, is too much in
15
+ # loud music and sometimes misses the alarm altogether.
16
+ #
17
+ # So the bayesian network will be:
18
+ #
19
+ # B E
20
+ # \ /
21
+ # _\| |/_
22
+ # A
23
+ # / \
24
+ # |/_ _\|
25
+ # J M
26
+ #
27
+ # with probabilities:
28
+ # P(B) = 0.001
29
+ # P(E) = 0.002
30
+ #
31
+ # P(A| B=true, E=true) = 0.95
32
+ # P(A| B=true, E=false) = 0.94
33
+ # P(A| B=false, E=true) = 0.29
34
+ # P(A| B=false, E=false) = 0.001
35
+ #
36
+ # P(J| A=true) = 0.9
37
+ # P(J| A=false) = 0.05
38
+ #
39
+ # P(M| A=true) = 0.7
40
+ # P(M| A=false) = 0.01
41
+ #
42
+ # where B = burglar, E = earthquake, A = alarm, J = John calls and
43
+ # M = Mary calls
44
+ #
45
+
46
+ # first let's encode the probabilities from the network
47
+ # P(B)
48
+ def p_burglary
49
+ flip(0.001, :B, :notB)
50
+ end
51
+
52
+ # P(E)
53
+ def p_earthquake
54
+ flip(0.002, :E, :notE)
55
+ end
56
+
57
+ # P(A|B = b,E = e)
58
+ def p_alarm(b,e)
59
+ pAlarmTable = {
60
+ [:B, :E] => 0.95,
61
+ [:B, :notE] => 0.94,
62
+ [:notB, :E] => 0.29,
63
+ [:notB, :notE] => 0.001
64
+ }
65
+ flip(pAlarmTable[[b,e]], :A, :notA)
66
+ end
67
+
68
+ # P(J|A = a)
69
+ def p_john(a)
70
+ flip( a == :A ? 0.9 : 0.05, :J, :notJ)
71
+ end
72
+
73
+ # P(M|A = a)
74
+ def p_mary(a)
75
+ flip( a == :A ? 0.7 : 0.01, :M, :notM)
76
+ end
77
+
78
+ p "joint probability:"
79
+ p(prob do
80
+ b = p_burglary
81
+ e = p_earthquake
82
+ a = p_alarm(b,e)
83
+ [b,e,a,p_john(a), p_mary(a)]
84
+ end)
85
+
86
+ p "P(B|John=true, Mary=true):"
87
+ p(normalizedProb do
88
+ b = p_burglary
89
+ e = p_earthquake
90
+ a = p_alarm(b,e)
91
+ if (p_john(a) == :J && p_mary(a) == :M)
92
+ b
93
+ else
94
+ nil
95
+ end
96
+ end)
97
+
98
+ p "P(A|John=true, Mary=true)"
99
+ p(normalizedProb do
100
+ b = p_burglary
101
+ e = p_earthquake
102
+ a = p_alarm(b, e)
103
+ if p_john(a) == :J && p_mary(a) == :M
104
+ a
105
+ else
106
+ nil
107
+ end
108
+ end)
109
+
110
+ # john and mary tell us for sure, the alarm went of and we know
111
+ # that is true...
112
+ p "P(B|John=true, Mary=true, Alarm=true)"
113
+ p(normalizedProb do
114
+ b = p_burglary
115
+ e = p_earthquake
116
+ a = p_alarm(b,e)
117
+ if (a == :A && p_john(a) == :J && p_mary(a) == :M)
118
+ b
119
+ else
120
+ nil
121
+ end
122
+ end)
123
+
124
+ # what is the probability john will call, if mary called?
125
+ p "P(John|Mary=true)"
126
+ p(normalizedProb do
127
+ b = p_burglary
128
+ e = p_earthquake
129
+ a = p_alarm(b,e)
130
+ if (p_mary(a) == :M)
131
+ p_john(a)
132
+ else
133
+ nil
134
+ end
135
+ end)
136
+
137
+ # and probability mary will call, if john did
138
+ p "P(Mary|John=true)"
139
+ p(normalizedProb do
140
+ b = p_burglary
141
+ e = p_earthquake
142
+ a = p_alarm(b,e)
143
+ if (p_john(a) == :J)
144
+ p_mary(a)
145
+ else
146
+ nil
147
+ end
148
+ end)
149
+
@@ -0,0 +1,62 @@
1
+
2
+ require 'rubygems'
3
+
4
+ require 'probdsl'
5
+ include ProbDSL
6
+
7
+ #
8
+ # Problem:
9
+ # Given a positive or negative test for a specific illness we want to know the
10
+ # probability for being ill or healthy.
11
+ #
12
+ # Suppose the random variables I and T are given with I = {Ill, Healthy}
13
+ # being the health status and T = {Negative, Positive} the test result.
14
+ #
15
+ # It is known that the probability of being 'ill' is 1 in a 1000,
16
+ # thus:
17
+ # P(I = Ill) = 0.001 and P(I = Healthy) = 0.999
18
+ #
19
+ # Furthermore we do know that the test has an accuracy of 99%, thus
20
+ # P(T = Positive | I = Ill ) = 0.99
21
+ # P(T = Negative | I = Ill ) = 0.01
22
+ # P(T = Positive | I = Healthy ) = 0.01
23
+ # P(T = Negative | I = Healthy ) = 0.99
24
+ #
25
+ # Task:
26
+ # compute the probability of being 'ill', given the test was positive.
27
+ # Using bayes rule:
28
+ #
29
+ # P(T, I) = P(T|I) * P(I) = P(I|T) * P(T)
30
+ #
31
+ # =>
32
+ #
33
+ # P(T |I) * P(I)
34
+ # P(I|T) = ---------------- = < P(T|I) * P(I) >
35
+ # P(T)
36
+ #
37
+ #
38
+
39
+ PFalseNegative = 0.01 # constant for P( T | I = Ill)
40
+ PFalsePositive = 0.01 # constant for P( T | I = Healthy)
41
+
42
+ # define: P(I)
43
+ def p_disease
44
+ flip 0.001, :ILL, :HEALTHY
45
+ end
46
+
47
+ # P(T|I)
48
+ def p_test(i)
49
+ flip(i == :ILL ? PFalseNegative : 1 - PFalsePositive,
50
+ :Negative, :Positive)
51
+ end
52
+
53
+ p "P(I|T=Positive)"
54
+ p normalizedProb {
55
+ i = p_disease
56
+ if p_test(i) == :Positive
57
+ i
58
+ else
59
+ nil
60
+ end
61
+ }
62
+
@@ -0,0 +1,105 @@
1
+
2
+ require 'rubygems'
3
+
4
+ require 'probdsl'
5
+ include ProbDSL
6
+
7
+ # the monty hall problem is a simple game show based probability puzzle with
8
+ # a puzzling outcome :)
9
+ #
10
+ # Suppose you are on a game show and you are given the choice of 3 doors.
11
+ # Behind one of these doors is the price and behind the others a goat. Only the
12
+ # moderator knows behind which door the price is and will open one door with a
13
+ # goat after you did your first choice. Next you can choose if you want to
14
+ # switch doors or not.
15
+ #
16
+ # Question:
17
+ # What is the best strategie? Stay or switch?
18
+ # What are the probabilities of winning for each of these strategies?
19
+ #
20
+
21
+ # first we want to encode our state.
22
+ #
23
+ # these are the doors one can choose from:
24
+ $doors = [:A, :B, :C]
25
+
26
+ # and this the game's current state
27
+ class State
28
+ attr_accessor :prize, # door the prize is behind
29
+ :open, # door opened by host
30
+ :chosen # door currently chosen by player
31
+
32
+ def initialize(p,c,o)
33
+ @prize = p
34
+ @chosen = c
35
+ @open = o
36
+ end
37
+
38
+ def testWinner
39
+ if @prize == @chosen
40
+ :Winner
41
+ else
42
+ :Looser
43
+ end
44
+ end
45
+ end
46
+
47
+ # Let us encode the problem with random variables:
48
+ #
49
+ # P = doors : door prize was put behind
50
+ # C1 = doors : the door chosen in the first round by player
51
+ # O = doors : the door opened by show's host
52
+ #
53
+
54
+ # first step: let's hide the price
55
+ # P(P = A) = 1/3
56
+ # P(P = B) = 1/3
57
+ # P(P = C) = 1/3
58
+ def hide
59
+ uniform $doors
60
+ end
61
+
62
+ # and then let the player choose one door:
63
+ # P(C1 = A) = 1/3
64
+ # P(C1 = B) = 1/3
65
+ # P(C1 = C) = 1/3
66
+ def choose
67
+ uniform $doors
68
+ end
69
+
70
+ # compute probability distribution of host opening a specific door
71
+ # given the event P and C1:
72
+ # P(O|C1,P)
73
+ # with O != C1 and O != P
74
+ def open(hidden, chosen)
75
+ uniform($doors - [hidden, chosen])
76
+ end
77
+
78
+ # play the first round (until game host will open a door)
79
+ def firstRound
80
+ p = hide
81
+ c = choose
82
+ State.new p, c, open(p,c)
83
+ end
84
+
85
+ # finally implement strategie 'stay'
86
+ def stay(s)
87
+ s
88
+ end
89
+
90
+ # and strategy 'switch' choosing a door C2 with
91
+ # C2 != O and C2 != C1.
92
+ # find P(C2|O, C1, P)
93
+ def switch(s)
94
+ uniform(($doors - [s.open, s.chosen]).map {|d|
95
+ State.new s.prize, d, s.open
96
+ })
97
+ end
98
+
99
+ # print some results
100
+ p 'strategy stay:'
101
+ p(prob { stay(firstRound).testWinner })
102
+
103
+ p 'strategy switch:'
104
+ p(prob { switch(firstRound).testWinner })
105
+
@@ -0,0 +1,42 @@
1
+
2
+ require 'rubygems'
3
+ require 'probdsl'
4
+ include ProbDSL
5
+
6
+ #
7
+ # found here: http://heath.hrsoftworks.net/archives/000036.html
8
+ #
9
+
10
+ def die
11
+ uniform 1..6
12
+ end
13
+
14
+ puts <<HERE
15
+ Two dice are rolled simultaneously. Given that one die shows a "4", what is
16
+ the probability that the total on the uppermost faces of the two dice is "7"?
17
+
18
+ Answear (2/11):
19
+ HERE
20
+
21
+ p normalizedProb {
22
+ d1 = die; d2 = die
23
+ if d1 == 4 || d2 == 4
24
+ d1 + d2 == 7
25
+ else
26
+ nil
27
+ end
28
+ }.probability(true)
29
+
30
+ puts <<HERE
31
+
32
+ The same experiment using a simulation (t = 10s):
33
+ HERE
34
+ p collecting(loop_t 10) {
35
+ d1 = die; d2 = die
36
+ if d1 == 4 || d2 == 4
37
+ d1 + d2 == 7
38
+ else
39
+ nil
40
+ end
41
+ }.normalize.probability(true)
42
+
data/examples/test.rb ADDED
@@ -0,0 +1,68 @@
1
+
2
+ require 'rubygems'
3
+
4
+ require 'probdsl'
5
+ include ProbDSL
6
+
7
+ p 'test1'
8
+
9
+ p(prob {
10
+ d = uniform [1,2,3,4,5,6]
11
+ d
12
+ })
13
+
14
+ p 'test2'
15
+
16
+ def die
17
+ uniform 1..6
18
+ end
19
+
20
+ def dice
21
+ d1 = die
22
+ d2 = die
23
+ [d1, d2]
24
+ end
25
+
26
+ p(prob {
27
+ d1 = die
28
+ d2 = die
29
+ d1 + d2
30
+ })
31
+
32
+ p 'test3'
33
+
34
+ p(prob{ die + die })
35
+
36
+ p 'test4'
37
+
38
+ p(pick {
39
+ dice
40
+ })
41
+
42
+ p 'test5'
43
+
44
+ p(prob {
45
+ d1, d2 = dice
46
+ d1 + d2
47
+ })
48
+
49
+ p 'test6'
50
+ tmp = prob{ dice }
51
+
52
+ p(prob {
53
+ d1, d2 = dist(tmp) # this time use already evaluated distribution
54
+ d1 + d2
55
+ })
56
+
57
+ p 'test7'
58
+
59
+ p(collecting(loop_k 1000) {
60
+ die + die
61
+ })
62
+
63
+ p 'test8'
64
+
65
+ p(collecting(loop_t 30) {
66
+ die + die
67
+ })
68
+
data/lib/probdsl.rb ADDED
@@ -0,0 +1,129 @@
1
+
2
+ require 'rubygems'
3
+
4
+ require 'delimcc'
5
+ require 'prob'
6
+
7
+ module ProbDSL
8
+ include DelimCC
9
+ include Probably
10
+
11
+ class PValue
12
+ def initialize(v)
13
+ @value = v
14
+ end
15
+
16
+ def reify
17
+ Probably.mkState @value
18
+ end
19
+
20
+ def pick
21
+ @value
22
+ end
23
+ end
24
+
25
+ class PChoice
26
+ def initialize(&blk)
27
+ @fn = blk
28
+ end
29
+
30
+ def reify
31
+ d = @fn.call
32
+ d.dep do |m|
33
+ k = m[0]
34
+ v = m[1]
35
+ tmp = k.call(v)
36
+ tmp.reify
37
+ end
38
+ end
39
+
40
+ def pick
41
+ d = @fn.call
42
+ m,p = d.pick
43
+ k = m[0]
44
+ v = m[1]
45
+ tmp = k.call v
46
+ tmp.pick
47
+ end
48
+ end
49
+
50
+ def run_prob(&blk)
51
+ reset {
52
+ v = blk.call
53
+ PValue.new v
54
+ }
55
+ end
56
+
57
+ def prob(&blk)
58
+ run_prob(&blk).reify
59
+ end
60
+
61
+ def normalizedProb(&blk)
62
+ prob(&blk).normalize
63
+ end
64
+
65
+ def pick(&blk)
66
+ run_prob(&blk).pick
67
+ end
68
+
69
+ def collect(pred, tree)
70
+ m = Hash.new(0)
71
+ while (pred.call)
72
+ x = tree.pick
73
+ m[x] += 1.0
74
+ end
75
+ Distribution.new :MAP, m
76
+ end
77
+
78
+ def collecting(pred, &blk)
79
+ collect(pred, run_prob(&blk))
80
+ end
81
+
82
+ def loop_k(k)
83
+ tmp = k
84
+ proc {
85
+ r = tmp > 0
86
+ tmp-=1
87
+ r
88
+ }
89
+ end
90
+
91
+ def loop_t(s)
92
+ start = Time.now
93
+ proc {
94
+ (Time.now - start) < s
95
+ }
96
+ end
97
+
98
+ def dist(data)
99
+ shift { |k|
100
+ PChoice.new do
101
+ m = Hash.new(0)
102
+ data.each do |p,d|
103
+ tmp = [k,d]
104
+ m[tmp] += p
105
+ end
106
+
107
+ Distribution.new :MAP, m
108
+ end
109
+ }
110
+ end
111
+
112
+ def uniform(data)
113
+ dist(data.map {|x| [1, x]})
114
+ end
115
+
116
+ def flip(x, *data)
117
+ case data.length
118
+ when 0
119
+ dist [[x, true], [1 - x, false]]
120
+ when 1
121
+ dist [[x, data[0]], [1 - x, nil]]
122
+ when 2
123
+ dist [[x, data[0]], [1 - x, data[1]]]
124
+ else
125
+ raise 'illegal number of arguments'
126
+ end
127
+ end
128
+ end
129
+
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rb_probdsl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Steffen Siering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-23 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rb_prob
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rb_delimcc
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: steffen <dot> siering -> gmail <dot> com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/probdsl.rb
45
+ - examples/alarm.rb
46
+ - examples/diagnosis.rb
47
+ - examples/montyhall.rb
48
+ - examples/paradox.rb
49
+ - examples/test.rb
50
+ - LICENSE
51
+ has_rdoc: true
52
+ homepage: http://github.com/urso/rb_probdsl
53
+ licenses:
54
+ - BDS3
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project:
75
+ rubygems_version: 1.3.5
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: do probabilistic programming in ruby
79
+ test_files: []
80
+