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
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
|