class MiniPGM::Model

Represents a Probabilistic Graphical Model (PGM)

Attributes

edges[R]

Edges between individual nodes, sorted by label of the outgoing edge, e.g:

Pollution -> Cancer
Smoker -> Cancer
error[R]

most recent error after calling `valid?`

nodes[R]

Lookup table of labelled nodes, each associated with a set of labels for all incoming edges, e.g:

{ Pollution, Smoker } -> Cancer -> { }
{ } -> Pollution -> { Cancer }
{ } -> Smoker -> { Cancer }

Public Class Methods

new(*edges) click to toggle source
# File lib/mini_pgm/model.rb, line 32
def initialize(*edges)
  @edges = sort_edges(edges)
  @nodes = reduce_edges(edges)
end

Public Instance Methods

add_cpd(cpd) click to toggle source
# File lib/mini_pgm/model.rb, line 37
def add_cpd(cpd)
  node = @nodes[cpd.variable.label]
  raise ArgumentError, "node does not exist for label #{node.label}" unless node

  check_cpd_evidence!(cpd.evidence.map(&:label), node.incoming_edges)
  node.cpd = cpd
end
to_s() click to toggle source
# File lib/mini_pgm/model.rb, line 45
def to_s
  ['Edges:', edges_to_s, '', 'Nodes:', nodes_to_s, '', 'Valid:', valid?, ''].join("\n")
end
valid?() click to toggle source
# File lib/mini_pgm/model.rb, line 60
def valid?
  @error = nil
  validate!
  true
rescue ModelError => e
  @error = e
  false
end
validate!() click to toggle source
# File lib/mini_pgm/model.rb, line 49
def validate!
  @nodes.each_value do |node|
    raise ModelError, "node '#{node.label}' does not have a CPD" unless node.cpd
  end

  # validate cardinality between nodes for each edge
  @edges.each do |edge|
    validate_cardinality!(@nodes[edge.to], @nodes[edge.from])
  end
end

Private Instance Methods

check_cpd_evidence!(cpd_evidence, node_dependencies) click to toggle source
# File lib/mini_pgm/model.rb, line 71
def check_cpd_evidence!(cpd_evidence, node_dependencies)
  cpd_evidence.each do |evidence|
    raise ArgumentError, "node is missing dependency for CPD evidence '#{evidence}'" \
      unless node_dependencies.include?(evidence)
  end

  node_dependencies.each do |dependency|
    raise ArgumentError, "CPD is missing evidence for node dependency '#{dependency}'" \
      unless cpd_evidence.include?(dependency)
  end
end
edges_to_s() click to toggle source
# File lib/mini_pgm/model.rb, line 83
def edges_to_s
  @edges.map(&:to_s).join("\n")
end
nodes_to_s() click to toggle source
# File lib/mini_pgm/model.rb, line 87
def nodes_to_s
  @nodes.keys.sort.map { |key| @nodes[key].to_s }.join("\n")
end
reduce_edges(edges) click to toggle source
# File lib/mini_pgm/model.rb, line 91
def reduce_edges(edges)
  edges.each_with_object({}) do |edge, reduced|
    # create node for incoming edge
    (reduced[edge.to] ||= MiniPGM::Node.new(edge.to)).incoming_edges.add(edge.from)

    # create node for outgoing edge
    (reduced[edge.from] ||= MiniPGM::Node.new(edge.from)).outgoing_edges.add(edge.to)
  end
end
sort_edges(edges) click to toggle source
# File lib/mini_pgm/model.rb, line 101
def sort_edges(edges)
  edges.sort_by(&:from)
end
validate_cardinality!(to, from) click to toggle source
# File lib/mini_pgm/model.rb, line 105
def validate_cardinality!(to, from)
  expected = to.cpd.evidence.find { |ev| ev.label == from.label }.cardinality
  actual = from.cpd.variable.cardinality

  raise ModelError, "cardinality mismatch in CPDs of '#{from.label}' (#{actual}) and '#{to.label}' (#{expected})" \
    unless expected == actual
end