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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a17ae65cb063c4303bd29aabd3398ea2f2a08265
|
4
|
+
data.tar.gz: 50a3fdd3658157a6c5794b94585352b4323505fe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29296cb079d586c4ba748179c6877a5773a50bb135730bf1f15ba71ad28861db3e7e84ec63597f88b48a481c900fc8df27fc63671b78c47c235916822f0c5f4c
|
7
|
+
data.tar.gz: 53d69b19419c40586f270dbeeee8639cde8e9d1f963e9956650bbbb58766d1d637ca35c6a85212c8b218171a97a1b13a3c08925f7fd52b97817f0ab740b0e3a1
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.1
|
data/.travis.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2.1
|
4
|
+
before_install: gem install bundler -v 1.10.2
|
5
|
+
env:
|
6
|
+
global:
|
7
|
+
- JRUBY_OPTS="$JRUBY_OPTS --debug"
|
8
|
+
gemfile:
|
9
|
+
- Gemfile
|
10
|
+
language: ruby
|
11
|
+
rvm:
|
12
|
+
- 1.9.3
|
13
|
+
- 2.0.0
|
14
|
+
- 2.1
|
15
|
+
- 2.2.1
|
16
|
+
- jruby-19mode
|
17
|
+
- jruby-head
|
18
|
+
- rbx-2
|
19
|
+
- ruby-head
|
20
|
+
matrix:
|
21
|
+
allow_failures:
|
22
|
+
- rvm: jruby-head
|
23
|
+
- rvm: rbx-2
|
24
|
+
- rvm: ruby-head
|
25
|
+
fast_finish: true
|
26
|
+
sudo: false
|
27
|
+
before_install: gem install bundler -v 1.10.2
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Longjing 龙井
|
2
|
+
|
3
|
+
Longjing is a variety of pan-roasted green tea from the area of Longjing Village near Hangzhou in Zhejiang Province, China.
|
4
|
+
|
5
|
+
Longjing is classical planner which resolves problems defined by a specific version of PDDL (Planning Domain Definition Language) described in book "Artificial Intelligence: a modern approach".
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'longjing'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install longjing
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake false` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/xli/longjing.
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'longjing'
|
4
|
+
|
5
|
+
def pddl_problems
|
6
|
+
Dir['test/domains/*.pddl'].each do |f|
|
7
|
+
Longjing.pddl(f)
|
8
|
+
end
|
9
|
+
|
10
|
+
Dir['test/problems/*.pddl'].map do |f|
|
11
|
+
prob = Longjing.pddl(f)
|
12
|
+
{name: prob[:problem], prob: prob}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def problems
|
17
|
+
pddl_problems
|
18
|
+
end
|
19
|
+
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs << "test"
|
22
|
+
t.test_files = FileList['test/*test.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
task :default => [:test, :benchmark]
|
27
|
+
|
28
|
+
task :profile do
|
29
|
+
require 'ruby-prof'
|
30
|
+
puts "Profile started"
|
31
|
+
result = RubyProf.profile do
|
32
|
+
problems.each do |prob|
|
33
|
+
Longjing.plan(prob[:prob])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
puts "output: profile.html"
|
37
|
+
printer = RubyProf::CallStackPrinter.new(result)
|
38
|
+
File.open("profile.html", 'w') do |f|
|
39
|
+
printer.print(f, {})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :b => :benchmark
|
44
|
+
|
45
|
+
task :benchmark do
|
46
|
+
require 'benchmark'
|
47
|
+
puts "Benchmark started, output: benchmark.log"
|
48
|
+
stdout = STDOUT
|
49
|
+
$stdout = File.new('benchmark.log', 'w')
|
50
|
+
$stdout.sync = true
|
51
|
+
label_len = problems.map{|p|p[:name].length}.max + 1
|
52
|
+
Benchmark.benchmark(Benchmark::CAPTION, label_len, Benchmark::FORMAT, ">total:", ">avg:") do |x|
|
53
|
+
bms = problems.map do |prob|
|
54
|
+
x.report(prob[:name].to_s.ljust(20)) do
|
55
|
+
10.times do
|
56
|
+
Longjing.plan(prob[:prob])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
[bms.reduce(:+), bms.reduce(:+)/bms.size]
|
61
|
+
end
|
62
|
+
$stdout = stdout
|
63
|
+
puts File.read('benchmark.log')
|
64
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
* planning graph
|
2
|
+
** !support typing
|
3
|
+
*** Inheritance
|
4
|
+
** !propositionalize action schema
|
5
|
+
** !planning graph for heuristic estimation
|
6
|
+
*** ? A* search
|
7
|
+
*** !Enforced hill climbing search
|
8
|
+
* FF
|
9
|
+
** !relaxed graph plan as heuristic
|
10
|
+
** !hill climbing search
|
11
|
+
** !handle negative goal
|
12
|
+
** !helper actions
|
13
|
+
** !added goal deletion
|
14
|
+
** !fallback to greedy best-first search when hill climbing search failed
|
15
|
+
** !goals agenda
|
16
|
+
* support pddl 3.1 strip pddl input and output
|
17
|
+
** !pddl file parser
|
18
|
+
** analysis support requirements, only do plan with supported problem
|
19
|
+
** test parser with more complex domains and problems
|
20
|
+
** typing support
|
21
|
+
* full support of pddl 3.1
|
22
|
+
** support definition of constant objects
|
23
|
+
|
24
|
+
--------
|
25
|
+
* remove actions having conflict effects:
|
26
|
+
** !contain positive & negative of a fact
|
27
|
+
** use predicates to validate?
|
data/benchmark.log
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
user system total real
|
2
|
+
BW-rand-8 0.130000 0.000000 0.130000 ( 0.126941)
|
3
|
+
BW-rand-12 0.540000 0.000000 0.540000 ( 0.543002)
|
4
|
+
BW-rand-20 14.630000 0.030000 14.660000 ( 14.657970)
|
5
|
+
BW-rand-4 0.030000 0.000000 0.030000 ( 0.033519)
|
6
|
+
cake-a 0.000000 0.000000 0.000000 ( 0.001633)
|
7
|
+
cargo-a 0.010000 0.000000 0.010000 ( 0.008611)
|
8
|
+
freecell-f2-c2-s2-i1-02-12 4.220000 0.020000 4.240000 ( 4.236310)
|
9
|
+
>total: 19.560000 0.050000 19.610000 ( 19.607987)
|
10
|
+
>avg: 2.794286 0.007143 2.801429 ( 2.801141)
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "longjing"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/longjing
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib_path = File.expand_path('../../lib', __FILE__)
|
4
|
+
$:.unshift(lib_path)
|
5
|
+
|
6
|
+
require 'longjing'
|
7
|
+
|
8
|
+
input = ARGV.dup
|
9
|
+
|
10
|
+
Longjing.logger.level = if input.include?('-v')
|
11
|
+
input.delete('-v')
|
12
|
+
Logger::DEBUG
|
13
|
+
elsif input.include?('-s')
|
14
|
+
input.delete('-s')
|
15
|
+
Logger::WARN
|
16
|
+
else
|
17
|
+
Logger::INFO
|
18
|
+
end
|
19
|
+
|
20
|
+
prof = if input.include?('-p')
|
21
|
+
input.delete('-p')
|
22
|
+
require 'ruby-prof'
|
23
|
+
puts "Profile start"
|
24
|
+
RubyProf.start
|
25
|
+
end
|
26
|
+
|
27
|
+
Longjing.pddl_plan(*input)
|
28
|
+
|
29
|
+
if prof
|
30
|
+
result = RubyProf.stop
|
31
|
+
puts "output: profile.html"
|
32
|
+
# printer = RubyProf::CallStackPrinter.new(result)
|
33
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
34
|
+
File.open("profile.html", 'w') do |f|
|
35
|
+
printer.print(f, {})
|
36
|
+
end
|
37
|
+
end
|
data/bin/setup
ADDED
data/lib/longjing.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "longjing/version"
|
2
|
+
require 'longjing/logging'
|
3
|
+
require 'longjing/pddl'
|
4
|
+
require 'longjing/search'
|
5
|
+
|
6
|
+
module Longjing
|
7
|
+
extend Logging
|
8
|
+
|
9
|
+
module_function
|
10
|
+
def pddl(file)
|
11
|
+
raise "File #{file.inspect} does not exist" unless File.exist?(file)
|
12
|
+
PDDL.parse(File.read(file))
|
13
|
+
end
|
14
|
+
|
15
|
+
def pddl_problem(domain_file, problem_file)
|
16
|
+
pddl(domain_file)
|
17
|
+
pddl(problem_file)
|
18
|
+
end
|
19
|
+
|
20
|
+
def pddl_plan(domain_file, problem_file, search=:ff)
|
21
|
+
prob = pddl_problem(domain_file, problem_file)
|
22
|
+
plan(prob, search).tap do |solution|
|
23
|
+
validate!(prob, solution)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def plan(problem, search=:ff)
|
28
|
+
log(:problem, problem)
|
29
|
+
Search.send(search).new.search(problem)
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate!(prob, solution)
|
33
|
+
raise "No solution" if solution.nil?
|
34
|
+
goal = prob[:goal]
|
35
|
+
actions = Hash[prob[:actions].map{|o| [o.name, o]}]
|
36
|
+
objects = Hash[prob[:objects].map{|o| [o.name, o]}]
|
37
|
+
state = prob[:init].to_set
|
38
|
+
|
39
|
+
solution.each do |step|
|
40
|
+
name, *args = step.gsub(/[()]/, ' ').strip.split(' ').map(&:to_sym)
|
41
|
+
action = actions[name]
|
42
|
+
args = Array(args).map{|arg| objects[arg]}
|
43
|
+
action = action.substitute(args)
|
44
|
+
if action.precond.applicable?(state)
|
45
|
+
state = action.effect.apply(state)
|
46
|
+
else
|
47
|
+
raise "Invalid solution, failed at step: #{step.inspect}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
unless goal.applicable?(state)
|
51
|
+
raise "Invalid solution, end with #{state}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Longjing
|
2
|
+
module FF
|
3
|
+
class Action
|
4
|
+
attr_accessor :counter, :difficulty, :layer
|
5
|
+
attr_reader :count_target, :action, :hash
|
6
|
+
attr_reader :pre, :add, :del
|
7
|
+
|
8
|
+
def initialize(action)
|
9
|
+
@action = action
|
10
|
+
@pre = []
|
11
|
+
@add = []
|
12
|
+
@del = []
|
13
|
+
@action.precond.to_a.each do |lit|
|
14
|
+
if lit.ff_neg_goal || !lit.is_a?(PDDL::Not)
|
15
|
+
@pre << lit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
@action.effect.to_a.each do |lit|
|
20
|
+
if lit.ff_neg_goal || !lit.is_a?(PDDL::Not)
|
21
|
+
@add << lit
|
22
|
+
else
|
23
|
+
@del << lit.literal
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@count_target = @pre.size
|
27
|
+
@hash = self.object_id
|
28
|
+
end
|
29
|
+
|
30
|
+
def signature
|
31
|
+
@action.signature
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"Action[#{signature}]"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'longjing/ff/action'
|
2
|
+
|
3
|
+
module Longjing
|
4
|
+
module FF
|
5
|
+
class ConnectivityGraph
|
6
|
+
attr_reader :actions, :add2actions, :pre2actions, :del2actions, :literals
|
7
|
+
|
8
|
+
def initialize(problem)
|
9
|
+
@actions = problem.all_actions.map do |action|
|
10
|
+
Action.new(action)
|
11
|
+
end
|
12
|
+
@add2actions = {}
|
13
|
+
@pre2actions = {}
|
14
|
+
@del2actions = {}
|
15
|
+
@actions.each do |action|
|
16
|
+
action.pre.each do |lit|
|
17
|
+
@pre2actions[lit] ||= []
|
18
|
+
@pre2actions[lit] << action
|
19
|
+
end
|
20
|
+
action.add.each do |lit|
|
21
|
+
@add2actions[lit] ||= []
|
22
|
+
@add2actions[lit] << action
|
23
|
+
end
|
24
|
+
action.del.each do |lit|
|
25
|
+
@del2actions[lit] ||= {}
|
26
|
+
@del2actions[lit][action] = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@literals = (@pre2actions.keys +
|
30
|
+
@add2actions.keys +
|
31
|
+
@del2actions.keys).uniq
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Longjing
|
2
|
+
module FF
|
3
|
+
class Ordering
|
4
|
+
def initialize(connectivity_graph)
|
5
|
+
@actions = connectivity_graph.actions
|
6
|
+
@add2actions = connectivity_graph.add2actions
|
7
|
+
@del2actions = connectivity_graph.del2actions
|
8
|
+
end
|
9
|
+
|
10
|
+
def da(f)
|
11
|
+
if actions = @add2actions[f]
|
12
|
+
set = Hash[actions[0].del.map{|l|[l, true]}]
|
13
|
+
actions[1..-1].inject(set) do |memo, action|
|
14
|
+
n = {}
|
15
|
+
action.del.each do |f|
|
16
|
+
if memo.include?(f)
|
17
|
+
n[f] = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
n
|
21
|
+
end
|
22
|
+
else
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def possibly_achievable_atoms(p, action_set)
|
28
|
+
if actions = @add2actions[p]
|
29
|
+
actions.any? do |action|
|
30
|
+
next unless action_set.include?(action)
|
31
|
+
action.pre.all? do |lit|
|
32
|
+
if acs = @add2actions[lit]
|
33
|
+
acs.any? do |a|
|
34
|
+
action_set.include?(a) && a != action
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def heuristic_fixpoint_reduction(a)
|
43
|
+
facts = da(a)
|
44
|
+
del_a_actions = @del2actions[a] || {}
|
45
|
+
actions = {}
|
46
|
+
@actions.each do |action|
|
47
|
+
next if del_a_actions.include?(action)
|
48
|
+
next if action.pre.any?{|l| facts.include?(l)}
|
49
|
+
actions[action] = true
|
50
|
+
end
|
51
|
+
|
52
|
+
fixpoint = false
|
53
|
+
while(!fixpoint) do
|
54
|
+
fixpoint = true
|
55
|
+
facts.keys.each do |f|
|
56
|
+
if possibly_achievable_atoms(f, actions)
|
57
|
+
facts.delete(f)
|
58
|
+
actions = {}
|
59
|
+
@actions.each do |action|
|
60
|
+
next if del_a_actions.include?(action)
|
61
|
+
next if action.pre.any?{|l| facts.include?(l)}
|
62
|
+
actions[action] = true
|
63
|
+
end
|
64
|
+
fixpoint = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
[facts, actions]
|
69
|
+
end
|
70
|
+
|
71
|
+
def heuristic_ordering(a, b)
|
72
|
+
facts, actions = heuristic_fixpoint_reduction(a)
|
73
|
+
possibly_achievable_atoms(b, actions)
|
74
|
+
end
|
75
|
+
|
76
|
+
def goal_agenda(prob)
|
77
|
+
g = Hash.new{|h,k|h[k]={}}
|
78
|
+
list = prob.goal.to_a
|
79
|
+
list.each do |a|
|
80
|
+
list.each do |b|
|
81
|
+
next if a == b
|
82
|
+
g[a][b] = !heuristic_ordering(b, a)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
compute_transitive_closure(list, g)
|
87
|
+
|
88
|
+
# in&out edges
|
89
|
+
degree = Hash.new{|h,k| h[k]=0}
|
90
|
+
hits = Hash.new{|h,k| h[k]=0}
|
91
|
+
list.each do |a|
|
92
|
+
list.each do |b|
|
93
|
+
next if a == b
|
94
|
+
if g[a][b]
|
95
|
+
degree[a] -= 1
|
96
|
+
degree[b] += 1
|
97
|
+
hits[a] += 1
|
98
|
+
hits[b] += 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# order by increasing degree
|
104
|
+
# disconnected at the end
|
105
|
+
list.sort_by do |a|
|
106
|
+
hits[a] == 0 ? Float::INFINITY : degree[a]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def compute_transitive_closure(nodes, graph)
|
111
|
+
nodes.each do |j|
|
112
|
+
nodes.each do |i|
|
113
|
+
if graph[i][j]
|
114
|
+
nodes.each do |k|
|
115
|
+
if graph[j][k]
|
116
|
+
graph[i][k] = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|