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
|