=begin Some noodling about what a node might contain in order to describe the joint probabilities. =end class Node attr_reader :variable, :parents def initialize(variable, *parents) @variable = Variable.infer(variable) raise ArgumentError, "A valid variable cannot be implied from #{variable}" unless @variable @parents = parents end def name self.variable.name end def inspect "Node: #{self.name} #{ self.parents.map{|p| p.name}.inspect }" end class << self def infer(obj, *parents) return obj if obj.is_a?(Node) end end end class Variable attr_reader :values, :name, :observations, :total def initialize(name, *values) values = [true, false] if values.empty? @name = name @values = values @observations = Array.new(@values.size, 0) @total = 0 end # You can observe anything but nothing: we record any observation but nil. # If nil is set, we use the first value as the default. def observe(value=nil) value = self.values.first if value.nil? unless self.values.include?(value) self.values << value self.observations << 0 end index = self.values.index(value) self.observations[index] += 1 @total += 1 end # Lookup observations def observed(value) index = self.values.index(value) return 0 unless index self.observations[index] end def inspect "Variable: #{self.name} #{self.values.inspect}" end class << self def infer(obj, *values) return obj if obj.is_a?(Variable) case obj when Symbol Variable.new(obj, *values) when String Variable.new(obj.to_sym, *values) else nil end end end end require 'rubygems' require 'spec' describe Variable do before do @v = Variable.new(:v1) end it "should require a name" do lambda{Variable.new}.should raise_error(ArgumentError) lambda{@v = Variable.new(:name)}.should_not raise_error @v.name.should eql(:name) end it "should default to true and false as parameter values" do v = Variable.new(:v) v.values.should eql([true, false]) end it "should be able to take a variables parameters" do v = Variable.new :v, :red, :blue, :green v.values.should eql([:red, :blue, :green]) end it "should be able to infer a variable from a variable" do v = Variable.new(:v) Variable.infer(v).should eql(v) end it "should be able to infer a variable from a symbol" do v = Variable.infer(:v) v.should be_a(Variable) v.name.should eql(:v) end it "should be able to infer a variable from a string" do v = Variable.infer('v') v.should be_a(Variable) v.name.should eql(:v) end it "should be able to infer values from a list" do v = Variable.infer :v, 1, 2 v.values.should eql([1,2]) end it "should start with zero observations" do @v.total.should eql(0) end it "should increment observations" do @v.observe @v.total.should eql(1) @v.observe @v.total.should eql(2) end it "should record observations" do @v.observe(true) @v.total.should eql(1) @v.observed(true).should eql(1) @v.observed(false).should eql(0) @v.observe(false) @v.total.should eql(2) @v.observed(true).should eql(1) @v.observed(false).should eql(1) end end describe Node do before do @season = Variable.new(:season, :spring, :summer, :fall, :winter) @x1 = Node.new(@season) @x2 = Node.new(:rain, @x1) @x3 = Node.new(:sprinkler, @x1) @x4 = Node.new(:wet, @x3, @x2) @x5 = Node.new(:slippery, @x4) end it "should infer a variable for the node" do v = Variable.new(:v) n = Node.new(v) n.variable.should eql(v) n = Node.new(:v) v = n.variable v.should be_a(Variable) v.name.should eql(:v) end it "should raise an error when it cannot infer a variable for the node" do lambda{Node.new(1)}.should raise_error(ArgumentError, /A valid variable cannot be implied from/) end it "should be able to create a node with parents" do @x1.parents.should be_empty @x2.parents.should eql([@x1]) @x3.parents.should eql([@x1]) @x4.parents.should eql([@x3, @x2]) @x5.parents.should eql([@x4]) end # it "should be able to infer a node" do # n = Node.infer(:v1, :v2) # n.name.should eql(:v1) # n.variable.name.should eql(:v1) # n.variable.should be_a(Variable) # n.parents.size.should eql(1) # p = n.parents.first # p.name.should eql(:v2) # p.should be_a(Variable) # end end