longjing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|