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,326 @@
|
|
1
|
+
class Longjing::PDDL::Parser
|
2
|
+
options no_result_var
|
3
|
+
|
4
|
+
token DEFINE DOMAIN REQUIREMENTS TYPES PREDICATES
|
5
|
+
ACTION PARAMETERS PRECONDITION EFFECT
|
6
|
+
PROBLEM OBJECTS GOAL INIT
|
7
|
+
NOT AND EQUAL
|
8
|
+
OPEN_BRACE CLOSE_BRACE
|
9
|
+
SYMBOL DASH ID VAR
|
10
|
+
|
11
|
+
rule
|
12
|
+
|
13
|
+
target
|
14
|
+
: OPEN_BRACE DEFINE domain_name requirements types predicates actions CLOSE_BRACE
|
15
|
+
{ val[2].merge!({
|
16
|
+
requirements: val[3],
|
17
|
+
types: val[4],
|
18
|
+
predicates: val[5],
|
19
|
+
actions: val[6]
|
20
|
+
})}
|
21
|
+
| OPEN_BRACE DEFINE domain_name requirements predicates actions CLOSE_BRACE
|
22
|
+
{ val[2].merge!({
|
23
|
+
requirements: val[3],
|
24
|
+
predicates: val[4],
|
25
|
+
actions: val[5]
|
26
|
+
})}
|
27
|
+
| OPEN_BRACE DEFINE domain_name types predicates actions CLOSE_BRACE
|
28
|
+
{ val[2].merge!({
|
29
|
+
requirements: [:strips],
|
30
|
+
types: val[3],
|
31
|
+
predicates: val[4],
|
32
|
+
actions: val[5]
|
33
|
+
})}
|
34
|
+
| OPEN_BRACE DEFINE domain_name predicates actions CLOSE_BRACE
|
35
|
+
{ val[2].merge!({
|
36
|
+
requirements: [:strips],
|
37
|
+
predicates: val[3],
|
38
|
+
actions: val[4]
|
39
|
+
})}
|
40
|
+
| OPEN_BRACE DEFINE domain_problem objects init goal CLOSE_BRACE
|
41
|
+
{ val[2].merge({ objects: val[3], init: val[4], goal: val[5] })}
|
42
|
+
| OPEN_BRACE DEFINE domain_problem init goal CLOSE_BRACE
|
43
|
+
{ val[2].merge({ objects: [], init: val[3], goal: val[4] })}
|
44
|
+
;
|
45
|
+
|
46
|
+
domain_name
|
47
|
+
: OPEN_BRACE DOMAIN name CLOSE_BRACE { domain(val[2]) }
|
48
|
+
;
|
49
|
+
|
50
|
+
domain_problem
|
51
|
+
: OPEN_BRACE PROBLEM name CLOSE_BRACE OPEN_BRACE DOMAIN name CLOSE_BRACE
|
52
|
+
{ problem(val[2], val[6]) }
|
53
|
+
;
|
54
|
+
|
55
|
+
requirements
|
56
|
+
: OPEN_BRACE REQUIREMENTS symbols CLOSE_BRACE { requirements(val[2]) }
|
57
|
+
;
|
58
|
+
|
59
|
+
types
|
60
|
+
: OPEN_BRACE TYPES type_list CLOSE_BRACE { val[2] }
|
61
|
+
;
|
62
|
+
|
63
|
+
predicates
|
64
|
+
: OPEN_BRACE PREDICATES predicate_list CLOSE_BRACE { val[2] }
|
65
|
+
;
|
66
|
+
|
67
|
+
objects
|
68
|
+
: OPEN_BRACE OBJECTS object_list CLOSE_BRACE { val[2] }
|
69
|
+
| OPEN_BRACE OBJECTS CLOSE_BRACE { [] }
|
70
|
+
;
|
71
|
+
|
72
|
+
init
|
73
|
+
: OPEN_BRACE INIT literals CLOSE_BRACE { val[2] }
|
74
|
+
| OPEN_BRACE INIT CLOSE_BRACE { [] }
|
75
|
+
;
|
76
|
+
|
77
|
+
goal
|
78
|
+
: OPEN_BRACE GOAL literal CLOSE_BRACE { val[2] }
|
79
|
+
;
|
80
|
+
|
81
|
+
actions
|
82
|
+
: action actions { [val[0]] + val[1] }
|
83
|
+
| action { [val[0]] }
|
84
|
+
;
|
85
|
+
|
86
|
+
predicate_list
|
87
|
+
: predicate predicate_list { [val[0]] + val[1] }
|
88
|
+
| predicate { [val[0]] }
|
89
|
+
;
|
90
|
+
|
91
|
+
action
|
92
|
+
: OPEN_BRACE ACTION name parameters precondition effect CLOSE_BRACE
|
93
|
+
{ @params = nil; Action.new(val[2], val[3], val[4], val[5]) }
|
94
|
+
;
|
95
|
+
|
96
|
+
parameters
|
97
|
+
: PARAMETERS OPEN_BRACE vars_list CLOSE_BRACE { parameters(val[2]) }
|
98
|
+
| PARAMETERS empty { [] }
|
99
|
+
;
|
100
|
+
|
101
|
+
precondition
|
102
|
+
: PRECONDITION literal { val[1] }
|
103
|
+
| PRECONDITION empty { val[1] }
|
104
|
+
;
|
105
|
+
|
106
|
+
effect
|
107
|
+
: EFFECT literal { val[1] }
|
108
|
+
| EFFECT empty { val[1] }
|
109
|
+
;
|
110
|
+
|
111
|
+
literals
|
112
|
+
: literal literals { [val[0]] + val[1] }
|
113
|
+
| literal { [val[0]] }
|
114
|
+
;
|
115
|
+
|
116
|
+
literal
|
117
|
+
: atom_literal
|
118
|
+
| OPEN_BRACE AND atom_literals CLOSE_BRACE { And.new(val[2]) }
|
119
|
+
;
|
120
|
+
|
121
|
+
atom_literals
|
122
|
+
: atom_literal atom_literals { [val[0]] + val[1] }
|
123
|
+
| atom_literal { [val[0]] }
|
124
|
+
;
|
125
|
+
|
126
|
+
atom_literal
|
127
|
+
: OPEN_BRACE name object_list CLOSE_BRACE { Fact[@predicates.fetch(val[1]), val[2]] }
|
128
|
+
| OPEN_BRACE name vars_list CLOSE_BRACE { Formula.new(@predicates.fetch(val[1]), val[2]) }
|
129
|
+
| OPEN_BRACE EQUAL object_list CLOSE_BRACE { Equal.new(*(val[2])) }
|
130
|
+
| OPEN_BRACE EQUAL vars_list CLOSE_BRACE { EqualFormula.new(*(val[2])) }
|
131
|
+
| OPEN_BRACE name CLOSE_BRACE { Fact[@predicates.fetch(val[1]), []] }
|
132
|
+
| OPEN_BRACE NOT atom_literal CLOSE_BRACE { Not[val[2]] }
|
133
|
+
;
|
134
|
+
|
135
|
+
object_list
|
136
|
+
: objects_t object_list { val[0] + val[1] }
|
137
|
+
| objects_t { val[0] }
|
138
|
+
| names { val[0].map {|n| object(n)} }
|
139
|
+
;
|
140
|
+
|
141
|
+
objects_t
|
142
|
+
: names type { val[0].map {|n| object(n, val[1])} }
|
143
|
+
;
|
144
|
+
|
145
|
+
type_list
|
146
|
+
: types_t type_list { val[0] + val[1] }
|
147
|
+
| types_t { val[0] }
|
148
|
+
| names { val[0].map {|t| type(t)} }
|
149
|
+
;
|
150
|
+
|
151
|
+
types_t
|
152
|
+
: names type { val[0].map {|t| type(t, val[1])} }
|
153
|
+
;
|
154
|
+
|
155
|
+
predicate
|
156
|
+
: OPEN_BRACE name vars_list CLOSE_BRACE { predicate(val[1], val[2]) }
|
157
|
+
| OPEN_BRACE name CLOSE_BRACE { predicate(val[1]) }
|
158
|
+
;
|
159
|
+
|
160
|
+
vars_list
|
161
|
+
: vars_t vars_list { val[0] + val[1] }
|
162
|
+
| vars_t { val[0] }
|
163
|
+
| var_names { val[0].map{|v| @params ? @params.fetch(v) : Var.new(v)} }
|
164
|
+
;
|
165
|
+
|
166
|
+
vars_t
|
167
|
+
: var_names type { val[0].map{|v| Var.new(v, val[1])} }
|
168
|
+
;
|
169
|
+
|
170
|
+
names
|
171
|
+
: name names { [val[0]] + val[1] }
|
172
|
+
| name { [val[0]] }
|
173
|
+
;
|
174
|
+
|
175
|
+
type
|
176
|
+
: DASH name { type(val[1]) }
|
177
|
+
;
|
178
|
+
|
179
|
+
var_names
|
180
|
+
: VAR var_names { [val[0]] + val[1] }
|
181
|
+
| VAR { [val[0]] }
|
182
|
+
;
|
183
|
+
|
184
|
+
symbols
|
185
|
+
: SYMBOL symbols { [val[0]] + val[1] }
|
186
|
+
| SYMBOL { [val[0]] }
|
187
|
+
;
|
188
|
+
|
189
|
+
name
|
190
|
+
: ID
|
191
|
+
| DEFINE
|
192
|
+
| DOMAIN
|
193
|
+
| PROBLEM
|
194
|
+
| NOT
|
195
|
+
| AND
|
196
|
+
;
|
197
|
+
|
198
|
+
empty
|
199
|
+
: OPEN_BRACE CLOSE_BRACE { EMPTY }
|
200
|
+
;
|
201
|
+
---- header ----
|
202
|
+
require 'strscan'
|
203
|
+
require 'longjing/pddl/type'
|
204
|
+
require 'longjing/pddl/var'
|
205
|
+
require 'longjing/pddl/obj'
|
206
|
+
require 'longjing/pddl/predicate'
|
207
|
+
require 'longjing/pddl/literal'
|
208
|
+
require 'longjing/pddl/action'
|
209
|
+
---- inner ----
|
210
|
+
SUPPORTED_REQUIREMENTS = [:strips, :typing,
|
211
|
+
:'negative-preconditions',
|
212
|
+
:equality]
|
213
|
+
|
214
|
+
def domain(name)
|
215
|
+
@predicates, @types = {}, {}
|
216
|
+
@domains[name] = { domain: name }
|
217
|
+
end
|
218
|
+
|
219
|
+
def problem(name, domain_name)
|
220
|
+
domain = @domains[domain_name]
|
221
|
+
raise UnknownDomain unless domain
|
222
|
+
@predicates = Hash[domain[:predicates].map{|pred| [pred.name, pred]}]
|
223
|
+
@types = if domain[:types]
|
224
|
+
Hash[domain[:types].map{|t| [t.name, t]}]
|
225
|
+
end
|
226
|
+
@objects = {}
|
227
|
+
{ problem: name }.merge(domain)
|
228
|
+
end
|
229
|
+
|
230
|
+
def requirements(reqs)
|
231
|
+
unsupported = reqs - SUPPORTED_REQUIREMENTS
|
232
|
+
raise UnsupportedRequirements, unsupported unless unsupported.empty?
|
233
|
+
reqs
|
234
|
+
end
|
235
|
+
|
236
|
+
def predicate(name, vars=nil)
|
237
|
+
raise "Duplicated predicate name #{name}" if @predicates.has_key?(name)
|
238
|
+
@predicates[name] = Predicate.new(name, vars)
|
239
|
+
end
|
240
|
+
|
241
|
+
def type(name, parent=nil)
|
242
|
+
@types[name] ||= Type.new(name, parent)
|
243
|
+
end
|
244
|
+
|
245
|
+
def object(name, type=nil)
|
246
|
+
@objects[name] ||= Obj.new(name, type)
|
247
|
+
end
|
248
|
+
|
249
|
+
def parameters(params)
|
250
|
+
@params = Hash[params.map{|param| [param.name, param]}]
|
251
|
+
params
|
252
|
+
end
|
253
|
+
|
254
|
+
def parse(str, domains)
|
255
|
+
@domains = domains
|
256
|
+
@tokens = []
|
257
|
+
str = "" if str.nil?
|
258
|
+
scanner = StringScanner.new(str + ' ')
|
259
|
+
|
260
|
+
until scanner.eos?
|
261
|
+
case
|
262
|
+
when scanner.scan(/\s+/)
|
263
|
+
# ignore space
|
264
|
+
when scanner.scan(/;.*$/)
|
265
|
+
# ignore comments
|
266
|
+
when m = scanner.scan(/[\(]/)
|
267
|
+
@tokens.push [:OPEN_BRACE, m]
|
268
|
+
when m = scanner.scan(/[\)]/)
|
269
|
+
@tokens.push [:CLOSE_BRACE, m]
|
270
|
+
when m = scanner.scan(/-\s/)
|
271
|
+
@tokens.push [:DASH, m.strip.to_sym]
|
272
|
+
when m = scanner.scan(/=\s/)
|
273
|
+
@tokens.push [:EQUAL, m.strip.to_sym]
|
274
|
+
when m = scanner.scan(/define\b/i)
|
275
|
+
@tokens.push [:DEFINE, m.to_sym]
|
276
|
+
when m = scanner.scan(/\:?domain\b/i)
|
277
|
+
@tokens.push [:DOMAIN, m.to_sym]
|
278
|
+
when m = scanner.scan(/problem\b/i)
|
279
|
+
@tokens.push [:PROBLEM, m.to_sym]
|
280
|
+
when m = scanner.scan(/\:requirements\b/i)
|
281
|
+
@tokens.push [:REQUIREMENTS, m]
|
282
|
+
when m = scanner.scan(/\:types\b/i)
|
283
|
+
@tokens.push [:TYPES, m]
|
284
|
+
when m = scanner.scan(/\:predicates\b/i)
|
285
|
+
@tokens.push [:PREDICATES, m]
|
286
|
+
when m = scanner.scan(/\:action\b/i)
|
287
|
+
@tokens.push [:ACTION, m]
|
288
|
+
when m = scanner.scan(/\:parameters\b/i)
|
289
|
+
@tokens.push [:PARAMETERS, m]
|
290
|
+
when m = scanner.scan(/\:precondition\b/i)
|
291
|
+
@tokens.push [:PRECONDITION, m]
|
292
|
+
when m = scanner.scan(/\:effect\b/i)
|
293
|
+
@tokens.push [:EFFECT, m]
|
294
|
+
when m = scanner.scan(/\:objects\b/i)
|
295
|
+
@tokens.push [:OBJECTS, m]
|
296
|
+
when m = scanner.scan(/\:goal\b/i)
|
297
|
+
@tokens.push [:GOAL, m]
|
298
|
+
when m = scanner.scan(/\:init\b/i)
|
299
|
+
@tokens.push [:INIT, m]
|
300
|
+
when m = scanner.scan(/not\b/i)
|
301
|
+
@tokens.push [:NOT, m.to_sym]
|
302
|
+
when m = scanner.scan(/and\b/i)
|
303
|
+
@tokens.push [:AND, m.to_sym]
|
304
|
+
when m = scanner.scan(/\:[\w\-]+\b/i)
|
305
|
+
@tokens.push [:SYMBOL, m[1..-1].to_sym]
|
306
|
+
when m = scanner.scan(/\?[a-z][\w\-]*\b/i)
|
307
|
+
@tokens.push [:VAR, m.to_sym]
|
308
|
+
when m = scanner.scan(/[a-z][\w\-]*\b/i)
|
309
|
+
@tokens.push [:ID, m.to_sym]
|
310
|
+
else
|
311
|
+
raise "unexpected characters: #{scanner.peek(5).inspect}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
@tokens.push [false, false]
|
315
|
+
do_parse
|
316
|
+
end
|
317
|
+
|
318
|
+
def next_token
|
319
|
+
@tokens.shift
|
320
|
+
end
|
321
|
+
|
322
|
+
def on_error(t, val, vstack)
|
323
|
+
trace = vstack.each_with_index.map{|l, i| "#{' ' * i}#{l}"}
|
324
|
+
raise ParseError,
|
325
|
+
"\nparse error on value #{val.inspect}\n#{trace.join("\n")}"
|
326
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Longjing
|
2
|
+
module PDDL
|
3
|
+
class Predicate
|
4
|
+
attr_reader :name, :hash
|
5
|
+
def initialize(name, vars=nil)
|
6
|
+
@name = name
|
7
|
+
@vars = vars || []
|
8
|
+
@hash = name.hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"(#{[name, *@vars].join(' ')})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"(pred #{to_s})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Longjing
|
2
|
+
module PDDL
|
3
|
+
class Type
|
4
|
+
attr_reader :name, :parent, :hash
|
5
|
+
def initialize(name, parent=nil)
|
6
|
+
@name = name
|
7
|
+
@parent = if name != :object
|
8
|
+
parent || Type::OBJECT
|
9
|
+
end
|
10
|
+
@hash = name.hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@parent ? "#{@name} - #{@parent}" : @name.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"(type #{to_s})"
|
19
|
+
end
|
20
|
+
|
21
|
+
OBJECT = Type.new(:object)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Longjing
|
2
|
+
module PDDL
|
3
|
+
class Var
|
4
|
+
attr_reader :name, :type, :hash
|
5
|
+
def initialize(name, type=nil)
|
6
|
+
@name = name
|
7
|
+
@type = type || Type::OBJECT
|
8
|
+
@hash = name.hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{@name} - #{@type}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"(var #{to_s})"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'longjing/state'
|
3
|
+
|
4
|
+
module Longjing
|
5
|
+
class Problem
|
6
|
+
attr_reader :initial, :goal, :all_actions
|
7
|
+
|
8
|
+
def initialize(actions, init, goal)
|
9
|
+
@all_actions = actions
|
10
|
+
@initial = State.new(init.to_set)
|
11
|
+
@goal = goal
|
12
|
+
end
|
13
|
+
|
14
|
+
def goal?(state)
|
15
|
+
@goal.applicable?(state.raw)
|
16
|
+
end
|
17
|
+
|
18
|
+
def actions(state)
|
19
|
+
@all_actions.select do |action|
|
20
|
+
action.precond.applicable?(state.raw)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def result(action, state)
|
25
|
+
raw = action.effect.apply(state.raw)
|
26
|
+
State.new(raw, state.path + [action])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'longjing/logging'
|
2
|
+
require 'longjing/search/statistics'
|
3
|
+
|
4
|
+
module Longjing
|
5
|
+
module Search
|
6
|
+
class Base
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
attr_reader :statistics
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@t = Time.now
|
13
|
+
@statistics = Statistics.new
|
14
|
+
reset_best_heuristic
|
15
|
+
end
|
16
|
+
|
17
|
+
def reset_best_heuristic
|
18
|
+
@best = Float::INFINITY
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_progress(state)
|
22
|
+
return if @best <= state.cost
|
23
|
+
@best = state.cost
|
24
|
+
log {
|
25
|
+
msg = [
|
26
|
+
"#{statistics.generated} generated",
|
27
|
+
"#{statistics.evaluated} evaluated",
|
28
|
+
"#{statistics.expanded} expanded",
|
29
|
+
"h=#{@best}",
|
30
|
+
"#{state.path.size} steps",
|
31
|
+
"t=#{Time.now - @t}"
|
32
|
+
].join(', ')
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_solution(steps)
|
37
|
+
log { "Solution found!" }
|
38
|
+
log { "Actual search time: #{Time.now - @t}" }
|
39
|
+
log { "Plan length: #{steps.size} step(s)." }
|
40
|
+
log { "Expanded #{statistics.expanded} state(s)." }
|
41
|
+
log { "Evaluated #{statistics.evaluated} state(s)." }
|
42
|
+
log { "Generated #{statistics.generated} state(s)." }
|
43
|
+
log { "Solution:" }
|
44
|
+
steps.each_with_index do |step, i|
|
45
|
+
log { "#{i}. #{step}" }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def solution(state)
|
50
|
+
state.path.map(&:signature).tap(&method(:log_solution))
|
51
|
+
end
|
52
|
+
|
53
|
+
def no_solution
|
54
|
+
log { "No solution found!" }
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|