longjing 0.1.0
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 +7 -0
- data/.gitignore +10 -0
- data/.ruby-version +1 -0
- data/.travis.yml +27 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +64 -0
- data/TODO +27 -0
- data/benchmark.log +10 -0
- data/bin/console +14 -0
- data/bin/longjing +37 -0
- data/bin/setup +7 -0
- data/lib/longjing.rb +54 -0
- data/lib/longjing/ff/action.rb +39 -0
- data/lib/longjing/ff/connectivity_graph.rb +35 -0
- data/lib/longjing/ff/ordering.rb +125 -0
- data/lib/longjing/ff/preprocess.rb +43 -0
- data/lib/longjing/ff/relaxed_graph_plan.rb +140 -0
- data/lib/longjing/logging.rb +65 -0
- data/lib/longjing/parameters.rb +32 -0
- data/lib/longjing/pddl.rb +24 -0
- data/lib/longjing/pddl/action.rb +32 -0
- data/lib/longjing/pddl/literal.rb +220 -0
- data/lib/longjing/pddl/obj.rb +26 -0
- data/lib/longjing/pddl/parser.tab.rb +842 -0
- data/lib/longjing/pddl/parser.y +326 -0
- data/lib/longjing/pddl/predicate.rb +20 -0
- data/lib/longjing/pddl/type.rb +24 -0
- data/lib/longjing/pddl/var.rb +20 -0
- data/lib/longjing/problem.rb +29 -0
- data/lib/longjing/search.rb +4 -0
- data/lib/longjing/search/base.rb +59 -0
- data/lib/longjing/search/ff.rb +138 -0
- data/lib/longjing/search/ff_greedy.rb +28 -0
- data/lib/longjing/search/greedy.rb +43 -0
- data/lib/longjing/search/statistics.rb +10 -0
- data/lib/longjing/state.rb +27 -0
- data/lib/longjing/version.rb +3 -0
- data/longjing.gemspec +24 -0
- metadata +123 -0
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            require 'longjing/parameters'
         | 
| 2 | 
            +
            require 'longjing/problem'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Longjing
         | 
| 5 | 
            +
              module FF
         | 
| 6 | 
            +
                class Preprocess
         | 
| 7 | 
            +
                  module NegGoal
         | 
| 8 | 
            +
                    def applicable?(set)
         | 
| 9 | 
            +
                      set.include?(self)
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                    def apply(set)
         | 
| 12 | 
            +
                      set << self
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def execute(problem)
         | 
| 17 | 
            +
                    ret = propositionalize(problem)
         | 
| 18 | 
            +
                    reverse_negative_goals(ret.goal)
         | 
| 19 | 
            +
                    ret
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def propositionalize(problem)
         | 
| 23 | 
            +
                    actions = problem[:actions].map do |action|
         | 
| 24 | 
            +
                      params = Parameters.new(action)
         | 
| 25 | 
            +
                      params.propositionalize(problem[:objects])
         | 
| 26 | 
            +
                    end.flatten
         | 
| 27 | 
            +
                    Problem.new(actions, problem[:init], problem[:goal])
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def reverse_negative_goals(goal)
         | 
| 31 | 
            +
                    case goal
         | 
| 32 | 
            +
                    when PDDL::And
         | 
| 33 | 
            +
                      goal.literals.each do |lit|
         | 
| 34 | 
            +
                        reverse_negative_goals(lit)
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
                    when PDDL::Not
         | 
| 37 | 
            +
                      goal.extend(NegGoal)
         | 
| 38 | 
            +
                      goal.ff_neg_goal = true
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| @@ -0,0 +1,140 @@ | |
| 1 | 
            +
            require 'longjing/pddl/literal'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Longjing
         | 
| 4 | 
            +
              module PDDL
         | 
| 5 | 
            +
                class Literal
         | 
| 6 | 
            +
                  attr_accessor :ff_layer, :ff_goal
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              module FF
         | 
| 11 | 
            +
                class RelaxedGraphPlan
         | 
| 12 | 
            +
                  attr_reader :actions, :literals
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize(cg)
         | 
| 15 | 
            +
                    @actions = cg.actions
         | 
| 16 | 
            +
                    @add2actions = cg.add2actions
         | 
| 17 | 
            +
                    @pre2actions = cg.pre2actions
         | 
| 18 | 
            +
                    @literals = cg.literals
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def layers(goal, state)
         | 
| 22 | 
            +
                    step = 0
         | 
| 23 | 
            +
                    scheduled_facts = state.raw.to_a
         | 
| 24 | 
            +
                    scheduled_actions = []
         | 
| 25 | 
            +
                    @literals.each do |lit|
         | 
| 26 | 
            +
                      lit.ff_layer = nil
         | 
| 27 | 
            +
                      lit.ff_goal = false
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                    goal.each do |lit|
         | 
| 30 | 
            +
                      lit.ff_goal = true
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                    @actions.each do |action|
         | 
| 33 | 
            +
                      action.counter = 0
         | 
| 34 | 
            +
                      action.layer = Float::INFINITY
         | 
| 35 | 
            +
                      if action.pre.empty?
         | 
| 36 | 
            +
                        action.difficulty = 0
         | 
| 37 | 
            +
                        scheduled_actions << action
         | 
| 38 | 
            +
                      else
         | 
| 39 | 
            +
                        action.difficulty = Float::INFINITY
         | 
| 40 | 
            +
                      end
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                    goal_count = goal.size
         | 
| 43 | 
            +
                    loop do
         | 
| 44 | 
            +
                      scheduled_facts.each do |lit|
         | 
| 45 | 
            +
                        next unless lit.ff_layer.nil?
         | 
| 46 | 
            +
                        lit.ff_layer = step
         | 
| 47 | 
            +
                        if lit.ff_goal
         | 
| 48 | 
            +
                          goal_count -= 1
         | 
| 49 | 
            +
                        end
         | 
| 50 | 
            +
                        if actions = @pre2actions[lit]
         | 
| 51 | 
            +
                          actions.each do |action|
         | 
| 52 | 
            +
                            next if action.counter == action.count_target
         | 
| 53 | 
            +
                            action.counter += 1
         | 
| 54 | 
            +
                            if action.counter == action.count_target
         | 
| 55 | 
            +
                              action.difficulty = step
         | 
| 56 | 
            +
                              scheduled_actions << action
         | 
| 57 | 
            +
                            end
         | 
| 58 | 
            +
                          end
         | 
| 59 | 
            +
                        end
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                      break if goal_count == 0
         | 
| 62 | 
            +
                      scheduled_facts = []
         | 
| 63 | 
            +
                      scheduled_actions.each do |action|
         | 
| 64 | 
            +
                        action.layer = step
         | 
| 65 | 
            +
                        action.add.each do |lit|
         | 
| 66 | 
            +
                          if lit.ff_layer.nil?
         | 
| 67 | 
            +
                            scheduled_facts << lit
         | 
| 68 | 
            +
                          end
         | 
| 69 | 
            +
                        end
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                      scheduled_actions = []
         | 
| 72 | 
            +
                      break if scheduled_facts.empty?
         | 
| 73 | 
            +
                      step += 1
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  def extract(goal, state, added_goals=[])
         | 
| 78 | 
            +
                    layers(goal, state)
         | 
| 79 | 
            +
                    goal_layers = goal.map(&:ff_layer)
         | 
| 80 | 
            +
                    return nil if goal_layers.any?(&:nil?)
         | 
| 81 | 
            +
                    # m = first layer contains all goals
         | 
| 82 | 
            +
                    m = goal_layers.max
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    marks = Hash.new{|h,k| h[k]={}}
         | 
| 85 | 
            +
                    layer2facts = Array.new(m + 1) { [] }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    goal.each do |lit|
         | 
| 88 | 
            +
                      layer2facts[lit.ff_layer] << lit
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    plan = []
         | 
| 92 | 
            +
                    (1..m).to_a.reverse.each do |i|
         | 
| 93 | 
            +
                      subplan = []
         | 
| 94 | 
            +
                      layer2facts[i].each do |g|
         | 
| 95 | 
            +
                        next if marks[g].include?(i)
         | 
| 96 | 
            +
                        next unless actions = @add2actions[g]
         | 
| 97 | 
            +
                        action = actions.select do |a|
         | 
| 98 | 
            +
                          a.layer == i - 1
         | 
| 99 | 
            +
                        end.min_by(&:difficulty)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                        action.pre.each do |lit|
         | 
| 102 | 
            +
                          if lit.ff_layer != 0 && !marks[lit].include?(i - 1)
         | 
| 103 | 
            +
                            layer2facts[lit.ff_layer] << lit
         | 
| 104 | 
            +
                          end
         | 
| 105 | 
            +
                        end
         | 
| 106 | 
            +
                        action.add.each do |lit|
         | 
| 107 | 
            +
                          marks[lit][i] = true
         | 
| 108 | 
            +
                          marks[lit][i-1] = true
         | 
| 109 | 
            +
                        end
         | 
| 110 | 
            +
                        unless added_goals.empty?
         | 
| 111 | 
            +
                          action.del.each do |lit|
         | 
| 112 | 
            +
                            if added_goals.include?(lit)
         | 
| 113 | 
            +
                              return nil
         | 
| 114 | 
            +
                            end
         | 
| 115 | 
            +
                          end
         | 
| 116 | 
            +
                        end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                        subplan << action
         | 
| 119 | 
            +
                      end
         | 
| 120 | 
            +
                      unless subplan.empty?
         | 
| 121 | 
            +
                        plan << subplan
         | 
| 122 | 
            +
                      end
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
                    if plan.empty?
         | 
| 125 | 
            +
                      [plan]
         | 
| 126 | 
            +
                    else
         | 
| 127 | 
            +
                      helpful_actions = {}
         | 
| 128 | 
            +
                      layer2facts[1].each do |lit|
         | 
| 129 | 
            +
                        @add2actions[lit].each do |action|
         | 
| 130 | 
            +
                          if action.layer == 0
         | 
| 131 | 
            +
                            helpful_actions[action.action] = true
         | 
| 132 | 
            +
                          end
         | 
| 133 | 
            +
                        end
         | 
| 134 | 
            +
                      end
         | 
| 135 | 
            +
                      [plan, helpful_actions.empty? ? nil : helpful_actions.keys]
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            require 'logger'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Longjing
         | 
| 4 | 
            +
              module Logging
         | 
| 5 | 
            +
                def logger
         | 
| 6 | 
            +
                  @@logger ||= Logger.new(STDOUT).tap do |l|
         | 
| 7 | 
            +
                    l.level = Logger::WARN
         | 
| 8 | 
            +
                    l.formatter = proc do |severity, datetime, progname, msg|
         | 
| 9 | 
            +
                      "#{severity[0]} #{datetime}: #{msg}\n"
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def logger=(l)
         | 
| 15 | 
            +
                  @@logger = l
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def log(event=nil, *args, &block)
         | 
| 19 | 
            +
                  case event
         | 
| 20 | 
            +
                  when :exploring
         | 
| 21 | 
            +
                    state = args[0]
         | 
| 22 | 
            +
                    logger.debug { "\n\nExploring: #{state}\n=======================" }
         | 
| 23 | 
            +
                  when :action
         | 
| 24 | 
            +
                    action, result = args
         | 
| 25 | 
            +
                    logger.debug { "\nAction: #{action.signature}\n-----------------------" }
         | 
| 26 | 
            +
                    logger.debug { "=>  #{result}" }
         | 
| 27 | 
            +
                  when :heuristic
         | 
| 28 | 
            +
                    new_state, solution, dist, best = args
         | 
| 29 | 
            +
                    logger.debug {
         | 
| 30 | 
            +
                      buf = ""
         | 
| 31 | 
            +
                      solution[0].reverse.each_with_index do |a, i|
         | 
| 32 | 
            +
                        buf << "  #{i}. [#{a.map(&:signature).join(", ")}]\n"
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                      "Relaxed plan (cost: #{dist}):\n#{buf}\n  helpful actions: #{solution[1] ? solution[1].map(&:signature).join(", ") : '[]'}"
         | 
| 35 | 
            +
                    }
         | 
| 36 | 
            +
                    if dist < best
         | 
| 37 | 
            +
                      logger.debug { "Add to plan #{new_state.path.last.signature}, cost: #{dist}" }
         | 
| 38 | 
            +
                    else
         | 
| 39 | 
            +
                      logger.debug { "Add to frontier" }
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  when :facts
         | 
| 42 | 
            +
                    args[0].each_slice(3) do |group|
         | 
| 43 | 
            +
                      logger.info { "  #{group.map(&:to_s).join(' ')}"}
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
                  when :problem
         | 
| 46 | 
            +
                    prob = args[0]
         | 
| 47 | 
            +
                    logger.info {
         | 
| 48 | 
            +
                      "Problem: #{prob[:problem]}, domain: #{prob[:domain]}"
         | 
| 49 | 
            +
                    }
         | 
| 50 | 
            +
                    logger.info {
         | 
| 51 | 
            +
                      "Requirements: #{prob[:requirements].join(', ')}"
         | 
| 52 | 
            +
                    }
         | 
| 53 | 
            +
                    log(:problem_stats, prob)
         | 
| 54 | 
            +
                  when :problem_stats
         | 
| 55 | 
            +
                    prob = args[0]
         | 
| 56 | 
            +
                    logger.info { "# types: #{Array(prob[:types]).size}" }
         | 
| 57 | 
            +
                    logger.info { "# predicates: #{prob[:predicates].size}" }
         | 
| 58 | 
            +
                    logger.info { "# object: #{prob[:objects].size}" }
         | 
| 59 | 
            +
                    logger.info { "# actions: #{prob[:actions].size}" }
         | 
| 60 | 
            +
                  when NilClass
         | 
| 61 | 
            +
                    logger.info(&block)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            module Longjing
         | 
| 2 | 
            +
              class Parameters
         | 
| 3 | 
            +
                def initialize(action)
         | 
| 4 | 
            +
                  @action = action
         | 
| 5 | 
            +
                  @params = action.params
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def propositionalize(objects)
         | 
| 9 | 
            +
                  return [@action] if objects.empty?
         | 
| 10 | 
            +
                  permutate(objects).map do |arguments|
         | 
| 11 | 
            +
                    @action.substitute(arguments)
         | 
| 12 | 
            +
                  end.compact
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # return: [objs, objs...]
         | 
| 16 | 
            +
                def permutate(objects)
         | 
| 17 | 
            +
                  return [] if @params.empty?
         | 
| 18 | 
            +
                  type_args = @params.map do |param|
         | 
| 19 | 
            +
                    args = objects.select { |obj| obj.is_a?(param.type) }
         | 
| 20 | 
            +
                    return [] if args.empty?
         | 
| 21 | 
            +
                    args
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  Longjing.logger.debug {
         | 
| 24 | 
            +
                    "type arguments: #{type_args.map {|v| v.size}.join(', ')}"
         | 
| 25 | 
            +
                  }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  type_args[0].product(*type_args[1..-1]).reject do |array|
         | 
| 28 | 
            +
                    array.uniq.size < @params.size
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require 'longjing/pddl/parser.tab.rb'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Longjing
         | 
| 4 | 
            +
              class Error < StandardError
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class UnknownDomain < Error
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              class UnsupportedRequirements < Error
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              module PDDL
         | 
| 14 | 
            +
                module_function
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def domains
         | 
| 17 | 
            +
                  @domains ||= {}
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def parse(pddl)
         | 
| 21 | 
            +
                  Parser.new.parse(pddl, self.domains)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            require 'longjing/pddl/literal'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Longjing
         | 
| 4 | 
            +
              module PDDL
         | 
| 5 | 
            +
                class Action
         | 
| 6 | 
            +
                  attr_reader :name, :params, :precond, :effect
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(name, params, precond, effect)
         | 
| 9 | 
            +
                    @name = name
         | 
| 10 | 
            +
                    @params = params
         | 
| 11 | 
            +
                    @precond = precond
         | 
| 12 | 
            +
                    @effect = effect
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def substitute(arguments)
         | 
| 16 | 
            +
                    variables = Hash[@params.zip(arguments)]
         | 
| 17 | 
            +
                    return nil unless precond = @precond.substitute(variables)
         | 
| 18 | 
            +
                    return nil unless effect = @effect.substitute(variables)
         | 
| 19 | 
            +
                    Action.new(@name, arguments, precond, effect)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def signature
         | 
| 23 | 
            +
                    "#{@name}(#{@params.map(&:name).map(&:to_s).join(' ')})"
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def to_s
         | 
| 27 | 
            +
                    "(action #{@name} :parameters #{@params.join(' ')} :precondition #{@precond} :effect #{@effect})"
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                  alias :inspect :to_s
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,220 @@ | |
| 1 | 
            +
            module Longjing
         | 
| 2 | 
            +
              module PDDL
         | 
| 3 | 
            +
                class Literal
         | 
| 4 | 
            +
                  def applicable?(set)
         | 
| 5 | 
            +
                    raise "Unsupported operation"
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def apply(set)
         | 
| 9 | 
            +
                    raise "Unsupported operation"
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def substitute(variables)
         | 
| 13 | 
            +
                    raise "Unsupported operation"
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def to_a
         | 
| 17 | 
            +
                    [self]
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                class Empty < Literal
         | 
| 22 | 
            +
                  def applicable?(set)
         | 
| 23 | 
            +
                    true
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def apply(set)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def substitute(variables)
         | 
| 30 | 
            +
                    self
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def to_s
         | 
| 34 | 
            +
                    "()"
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def inspect
         | 
| 38 | 
            +
                    "<empty>"
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                EMPTY = Empty.new
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                class Fact < Literal
         | 
| 44 | 
            +
                  @@insts = {}
         | 
| 45 | 
            +
                  class << self
         | 
| 46 | 
            +
                    def [](*args)
         | 
| 47 | 
            +
                      @@insts[args] ||= new(*args)
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  attr_reader :hash
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def initialize(pred, objs)
         | 
| 54 | 
            +
                    @pred = pred
         | 
| 55 | 
            +
                    @objs = objs
         | 
| 56 | 
            +
                    @hash = [pred, objs].hash
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def applicable?(set)
         | 
| 60 | 
            +
                    set.include?(self)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def apply(set)
         | 
| 64 | 
            +
                    set << self
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def substitute(variables)
         | 
| 68 | 
            +
                    self
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def to_s
         | 
| 72 | 
            +
                    "(#{[@pred.name, *@objs.map(&:name)].join(' ')})"
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def inspect
         | 
| 76 | 
            +
                    "(fact #{[@pred.name, *@objs].join(' ')})"
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                class Formula < Literal
         | 
| 81 | 
            +
                  attr_reader :pred, :vars, :hash
         | 
| 82 | 
            +
                  def initialize(pred, vars)
         | 
| 83 | 
            +
                    @pred = pred
         | 
| 84 | 
            +
                    @vars = vars
         | 
| 85 | 
            +
                    @hash = [pred, vars].hash
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def substitute(variables)
         | 
| 89 | 
            +
                    Fact[@pred, @vars.map{|v| variables[v]}]
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def to_s
         | 
| 93 | 
            +
                    "(#{[@pred.name, *@vars].join(' ')})"
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  def inspect
         | 
| 97 | 
            +
                    "(formula #{to_s})"
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                class Not < Literal
         | 
| 102 | 
            +
                  @@insts = {}
         | 
| 103 | 
            +
                  class << self
         | 
| 104 | 
            +
                    def [](lit)
         | 
| 105 | 
            +
                      @@insts[lit] ||= new(lit)
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  attr_reader :literal, :hash
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def initialize(literal)
         | 
| 112 | 
            +
                    @literal = literal
         | 
| 113 | 
            +
                    @hash = literal.hash
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  def applicable?(set)
         | 
| 117 | 
            +
                    !set.include?(@literal)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  def apply(set)
         | 
| 121 | 
            +
                    set.delete(@literal)
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  def substitute(variables)
         | 
| 125 | 
            +
                    ret = @literal.substitute(variables)
         | 
| 126 | 
            +
                    if ret.nil?
         | 
| 127 | 
            +
                      EMPTY
         | 
| 128 | 
            +
                    elsif ret == EMPTY
         | 
| 129 | 
            +
                      nil
         | 
| 130 | 
            +
                    else
         | 
| 131 | 
            +
                      Not[ret]
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  def to_s
         | 
| 136 | 
            +
                    "(not #{@literal})"
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  def inspect
         | 
| 140 | 
            +
                    "(not #{@literal.inspect})"
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                class Equal < Literal
         | 
| 145 | 
            +
                  attr_reader :left, :right
         | 
| 146 | 
            +
                  def initialize(left, right)
         | 
| 147 | 
            +
                    @left, @right = left, right
         | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  def substitute(variables)
         | 
| 151 | 
            +
                    variables[@left] == variables[@right] ? EMPTY : nil
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  def to_s
         | 
| 155 | 
            +
                    "(= #{@left} #{@right})"
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  def inspect
         | 
| 159 | 
            +
                    "(= #{@left.inspect} #{@right.inspect})"
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                class EqualFormula < Equal
         | 
| 164 | 
            +
                  def substitute(variables)
         | 
| 165 | 
            +
                    @left == @right ? EMPTY : nil
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                class And < Literal
         | 
| 170 | 
            +
                  attr_reader :literals
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  def initialize(literals)
         | 
| 173 | 
            +
                    @literals = literals
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  def applicable?(set)
         | 
| 177 | 
            +
                    @literals.all? do |lit|
         | 
| 178 | 
            +
                      lit.applicable?(set)
         | 
| 179 | 
            +
                    end
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                  def apply(set)
         | 
| 183 | 
            +
                    ret = set.dup
         | 
| 184 | 
            +
                    @literals.each do |lit|
         | 
| 185 | 
            +
                      lit.apply(ret)
         | 
| 186 | 
            +
                    end
         | 
| 187 | 
            +
                    ret
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  def substitute(variables)
         | 
| 191 | 
            +
                    ret = []
         | 
| 192 | 
            +
                    @literals.each do |lit|
         | 
| 193 | 
            +
                      n = lit.substitute(variables)
         | 
| 194 | 
            +
                      return nil if n.nil?
         | 
| 195 | 
            +
                      next if n == EMPTY
         | 
| 196 | 
            +
                      ret << n
         | 
| 197 | 
            +
                    end
         | 
| 198 | 
            +
                    if ret.empty?
         | 
| 199 | 
            +
                      nil
         | 
| 200 | 
            +
                    elsif ret.size == 1
         | 
| 201 | 
            +
                      ret[0]
         | 
| 202 | 
            +
                    else
         | 
| 203 | 
            +
                      And.new(ret)
         | 
| 204 | 
            +
                    end
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  def to_a
         | 
| 208 | 
            +
                    @literals
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  def to_s
         | 
| 212 | 
            +
                    "(and #{@literals.map(&:to_s).join(" ")})"
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  def inspect
         | 
| 216 | 
            +
                    "(and #{@literals.map(&:inspect).join(" ")})"
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
              end
         | 
| 220 | 
            +
            end
         |