sfplanner 0.0.1 → 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.
- data/README.md +23 -66
- data/bin/sfplanner +8 -2
- data/bin/solver/linux-x86/downward +0 -0
- data/bin/solver/macos/downward +0 -0
- data/lib/sfplanner/planner.rb +96 -31
- data/lib/sfplanner.rb +2 -1
- data/sfplanner.gemspec +3 -3
- metadata +4 -4
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
SFP Planner for Ruby
|
2
2
|
====================
|
3
3
|
- Author: Herry (herry13@gmail.com)
|
4
|
-
- Version: 0.0
|
4
|
+
- Version: 0.1.0
|
5
5
|
- License: [BSD License](https://github.com/herry13/sfp-ruby/blob/master/LICENSE)
|
6
6
|
|
7
|
-
A Ruby gem that provides a Ruby
|
7
|
+
A Ruby gem that provides a Ruby API to SFP planner that solves a planning task written in [SFP language](https://github.com/herry13/nuri/wiki/SFP-language).
|
8
8
|
|
9
9
|
Click [here](https://github.com/herry13/nuri/wiki/SFP-language), for more details about SFP language.
|
10
10
|
|
@@ -25,64 +25,41 @@ Requirements
|
|
25
25
|
- antlr3
|
26
26
|
- json
|
27
27
|
|
28
|
+
Tested on:
|
29
|
+
- Ubuntu 12.04
|
30
|
+
- Debian Squeeze
|
31
|
+
- Scientific Linux 6.
|
32
|
+
- MacOS X 10.8
|
28
33
|
|
29
|
-
Supporting Platforms
|
30
|
-
--------------------
|
31
|
-
- Linux (x86)
|
32
|
-
- MacOS X
|
33
34
|
|
34
|
-
|
35
|
+
To use as a command line
|
36
|
+
------------------------
|
37
|
+
- solve a planning task, and then print the output in JSON
|
35
38
|
|
36
|
-
|
37
|
-
To use as a command line to solve a planning task
|
38
|
-
-------------------------------------------------
|
39
|
-
- solve a planning task, and then print a sequential plan (if found) in JSON
|
40
|
-
|
41
|
-
$ sfplanner <sfp-file>
|
42
|
-
|
43
|
-
The planning task must be written in [SFP language](https://github.com/herry13/nuri/wiki/SFP-language).
|
44
|
-
|
45
|
-
|
46
|
-
To generate a parallel (partial-order) plan
|
47
|
-
-------------------------------------------
|
48
|
-
- use option **--parallel** to generate a partial order plan
|
49
|
-
|
50
|
-
$ sfplanner --parallel <sfp-file>
|
39
|
+
$ sfplanner <sfp-task-file>
|
51
40
|
|
52
41
|
|
53
42
|
To use as Ruby library
|
54
43
|
----------------------
|
55
|
-
-
|
44
|
+
- parse an SFP file, and then generate the plan (if found) in Hash:
|
56
45
|
|
46
|
+
# include sfplanner library
|
57
47
|
require 'sfplanner'
|
58
48
|
|
59
|
-
|
60
|
-
|
61
|
-
# Determine the home directory of your SFP file.
|
62
|
-
home_dir = File.expand_path(File.dirname("my_file.sfp"))
|
63
|
-
|
64
|
-
# Create Sfp::Parser object
|
65
|
-
parser = Sfp::Parser.new({:home_dir => "./"})
|
66
|
-
|
67
|
-
# Parse the file.
|
68
|
-
parser.parse(File.read("my_file.sfp"))
|
69
|
-
|
70
|
-
# Get the result in Hash data structure
|
71
|
-
result = parser.root
|
49
|
+
# solve and return the plan in Hash
|
50
|
+
planner.solve({:file => file_path})
|
72
51
|
|
73
|
-
-
|
52
|
+
- parse an SFP file, and then generate the plan in JSON:
|
74
53
|
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
# Solve a planning task written in "my_file.sfp", then print
|
79
|
-
# the result in JSON.
|
80
|
-
puts planner.solve({:file => "my_file.sfp", :json => true})
|
54
|
+
# include sfplanner library
|
55
|
+
require 'sfplanner'
|
81
56
|
|
57
|
+
# solve and return the plan in JSON
|
58
|
+
planner.solve({:file => file_path, :json => true})
|
82
59
|
|
83
60
|
|
84
|
-
Example of Planning
|
85
|
-
|
61
|
+
Example of Planning Task
|
62
|
+
------------------------
|
86
63
|
- Create file **types.sfp** to hold required schemas:
|
87
64
|
|
88
65
|
schema Service {
|
@@ -159,7 +136,7 @@ Example of Planning Problem
|
|
159
136
|
- To generate the workflow, we invoke **sfp** command with argument
|
160
137
|
the path of the task file:
|
161
138
|
|
162
|
-
$
|
139
|
+
$ sfplanner task.sfp
|
163
140
|
|
164
141
|
Which will generate a workflow in JSON
|
165
142
|
|
@@ -207,23 +184,3 @@ Example of Planning Problem
|
|
207
184
|
This workflow is sequential that has 3 procedures. If you executes
|
208
185
|
the workflow in given order, it will achieves the goal state as well
|
209
186
|
as perserves the global constraints during the execution.
|
210
|
-
|
211
|
-
- To generate and execute the plan using Bash framework, we invoke **sfp**
|
212
|
-
command with an option *--solve-execute* and with an argument the path of
|
213
|
-
the task file:
|
214
|
-
|
215
|
-
$ sfp --solve-execute task.sfp
|
216
|
-
|
217
|
-
It will generate and execute the plan by invoking the Bash scripts in
|
218
|
-
the current directory (or as specified in environment variable SFP_HOME)
|
219
|
-
in the following sequence:
|
220
|
-
|
221
|
-
./modules/b/start
|
222
|
-
./modules/pc/redirect "$.b"
|
223
|
-
./modules/a/stop
|
224
|
-
|
225
|
-
- If you save the plan in a file e.g. **plan.json**, you could execute it
|
226
|
-
later using option *--execute*
|
227
|
-
|
228
|
-
$ sfp --execute plan.json
|
229
|
-
|
data/bin/sfplanner
CHANGED
@@ -4,7 +4,7 @@ libdir = File.expand_path(File.dirname(__FILE__))
|
|
4
4
|
require "#{libdir}/../lib/sfplanner"
|
5
5
|
|
6
6
|
opts = Trollop::options do
|
7
|
-
version "sfplanner 0.0.
|
7
|
+
version "sfplanner 0.0.2 (c) 2013 Herry"
|
8
8
|
banner <<-EOS
|
9
9
|
Solve a planning task specified in SFP language, and print the plan (if found) in JSON format.
|
10
10
|
|
@@ -15,6 +15,8 @@ where [options] are:
|
|
15
15
|
EOS
|
16
16
|
|
17
17
|
opt :parallel, "Generate a parallel (partial-order) plan, instead of sequential."
|
18
|
+
opt :json_input, "Input is in JSON format"
|
19
|
+
opt :pretty, "Print the plan in pretty JSON format"
|
18
20
|
end
|
19
21
|
|
20
22
|
def parse(filepath)
|
@@ -28,7 +30,11 @@ filepath = ARGV[0].to_s
|
|
28
30
|
|
29
31
|
if filepath != ''
|
30
32
|
planner = Sfp::Planner.new
|
31
|
-
|
33
|
+
if opts[:json_input]
|
34
|
+
puts 'Option "--json-input" is not supported yet.'
|
35
|
+
else
|
36
|
+
puts planner.solve({:file => ARGV[0], :pretty_json => opts[:pretty], :parallel => opts[:parallel]})
|
37
|
+
end
|
32
38
|
else
|
33
39
|
Trollop::help
|
34
40
|
end
|
Binary file
|
data/bin/solver/macos/downward
CHANGED
Binary file
|
data/lib/sfplanner/planner.rb
CHANGED
@@ -4,8 +4,8 @@ module Sfp
|
|
4
4
|
Debug = false
|
5
5
|
|
6
6
|
class Config
|
7
|
-
# The timeout for the solver in seconds (default
|
8
|
-
@@timeout =
|
7
|
+
# The timeout for the solver in seconds (default 60s/1mins)
|
8
|
+
@@timeout = 60
|
9
9
|
|
10
10
|
def self.timeout; @@timeout; end
|
11
11
|
|
@@ -24,8 +24,8 @@ module Sfp
|
|
24
24
|
attr_reader :parser
|
25
25
|
|
26
26
|
def initialize(params={})
|
27
|
-
@debug = Debug
|
28
27
|
@parser = Sfp::Parser.new(params)
|
28
|
+
@debug = Debug
|
29
29
|
end
|
30
30
|
|
31
31
|
# @param :string : SFP task in string
|
@@ -48,6 +48,10 @@ module Sfp
|
|
48
48
|
@parser.parse(File.read(params[:file]))
|
49
49
|
end
|
50
50
|
|
51
|
+
@debug = true if params[:debug]
|
52
|
+
|
53
|
+
save_sfp_task if @debug
|
54
|
+
|
51
55
|
if not @parser.conformant
|
52
56
|
return self.solve_classical_task(params)
|
53
57
|
else
|
@@ -79,6 +83,12 @@ module Sfp
|
|
79
83
|
end
|
80
84
|
|
81
85
|
protected
|
86
|
+
def save_sfp_task
|
87
|
+
sfp_task = Sfp::Helper.deep_clone(@parser.root)
|
88
|
+
sfp_task.accept(Sfp::Visitor::ParentEliminator.new)
|
89
|
+
File.open('/tmp/planning.json', 'w') { |f| f.write(JSON.pretty_generate(sfp_task)) }
|
90
|
+
end
|
91
|
+
|
82
92
|
def solve_conformant_task(params={})
|
83
93
|
# TODO
|
84
94
|
# 1) generate all possible initial states
|
@@ -140,7 +150,7 @@ module Sfp
|
|
140
150
|
end
|
141
151
|
|
142
152
|
def solve_classical_task(params={})
|
143
|
-
@plan, @sas_task = self.solve_sas(@parser)
|
153
|
+
@plan, @sas_task = self.solve_sas(@parser, params)
|
144
154
|
|
145
155
|
return @plan if params[:sas_plan]
|
146
156
|
|
@@ -241,11 +251,25 @@ module Sfp
|
|
241
251
|
end
|
242
252
|
end
|
243
253
|
|
244
|
-
def
|
245
|
-
return
|
254
|
+
def plan_preprocessing(plan)
|
255
|
+
return plan if plan.nil? or plan[0,2] != '1:'
|
256
|
+
plan1 = ''
|
257
|
+
plan.each_line { |line|
|
258
|
+
_, line = line.split(':', 2)
|
259
|
+
plan1 += "#{line.strip}\n"
|
260
|
+
}
|
261
|
+
plan1.strip
|
262
|
+
end
|
246
263
|
|
264
|
+
def solve_sas(parser, p={})
|
265
|
+
return nil if parser.nil?
|
266
|
+
|
247
267
|
tmp_dir = '/tmp/nuri_' + (rand * 100000).to_i.abs.to_s
|
248
268
|
begin
|
269
|
+
parser.compile_step_1
|
270
|
+
p[:sas_post_processor].sas_post_processor(parser) if p[:sas_post_processor]
|
271
|
+
parser.compile_step_2
|
272
|
+
|
249
273
|
while File.exist?(tmp_dir)
|
250
274
|
tmp_dir = '/tmp/nuri_' + (rand * 100000).to_i.abs.to_s
|
251
275
|
end
|
@@ -253,7 +277,8 @@ module Sfp
|
|
253
277
|
sas_file = tmp_dir + '/problem.sas'
|
254
278
|
plan_file = tmp_dir + '/out.plan'
|
255
279
|
File.open(sas_file, 'w') do |f|
|
256
|
-
f.write(parser.to_sas)
|
280
|
+
#f.write(parser.to_sas)
|
281
|
+
f.write(parser.sas)
|
257
282
|
f.flush
|
258
283
|
end
|
259
284
|
|
@@ -265,6 +290,7 @@ module Sfp
|
|
265
290
|
Kernel.system(command)
|
266
291
|
end
|
267
292
|
plan = (File.exist?(plan_file) ? File.read(plan_file) : nil)
|
293
|
+
plan = plan_preprocessing(plan)
|
268
294
|
|
269
295
|
if plan != nil
|
270
296
|
plan = extract_sas_plan(plan, parser)
|
@@ -336,6 +362,24 @@ module Sfp
|
|
336
362
|
lazy_wastar([hff2,hlm2],preferred=[hff2,hlm2],w=2),
|
337
363
|
lazy_wastar([hff2,hlm2],preferred=[hff2,hlm2],w=1)],
|
338
364
|
repeat_last=true,continue_on_fail=true)"'
|
365
|
+
when 'autotune' then ' \
|
366
|
+
--heuristic "hCea=cea(cost_type=2)" \
|
367
|
+
--heuristic "hCg=cg(cost_type=1)" \
|
368
|
+
--heuristic "hGoalCount=goalcount(cost_type=2)" \
|
369
|
+
--heuristic "hFF=ff(cost_type=0)" \
|
370
|
+
--heuristic "hMad=mad()" \
|
371
|
+
--search "lazy(alt([single(sum([weight(g(), 2),weight(hFF, 3)])),
|
372
|
+
single(sum([weight(g(), 2),weight(hFF, 3)]),pref_only=true),
|
373
|
+
single(sum([weight(g(), 2),weight(hCg, 3)])),
|
374
|
+
single(sum([weight(g(), 2),weight(hCg, 3)]),pref_only=true),
|
375
|
+
single(sum([weight(g(), 2),weight(hCea, 3)])),
|
376
|
+
single(sum([weight(g(), 2),weight(hCea, 3)]),pref_only=true),
|
377
|
+
single(sum([weight(g(), 2),weight(hGoalCount, 3)])),
|
378
|
+
single(sum([weight(g(), 2),weight(hGoalCount, 3)]),pref_only=true),
|
379
|
+
single(sum([weight(g(), 2),weight(hMad, 3)])),
|
380
|
+
single(sum([weight(g(), 2),weight(hMad, 3)]),pref_only=true)],
|
381
|
+
boost=200),
|
382
|
+
preferred=[hCea,hGoalCount],reopen_closed=false,cost_type=1)"'
|
339
383
|
else '--search "lazy_greedy(ff(cost_type=0))"'
|
340
384
|
end
|
341
385
|
end
|
@@ -344,10 +388,10 @@ module Sfp
|
|
344
388
|
# - within given working directory "dir"
|
345
389
|
# - problem in SAS+ format, available in"sas_file"
|
346
390
|
# - solution will be saved in "plan_file"
|
347
|
-
def self.getcommand(dir, sas_file, plan_file, heuristic='ff', debug=false)
|
391
|
+
def self.getcommand(dir, sas_file, plan_file, heuristic='ff', debug=false, timeout=nil)
|
348
392
|
planner = Sfp::Planner.path
|
349
393
|
params = Sfp::Planner.parameters(heuristic)
|
350
|
-
timeout = Sfp::Planner::Config.timeout
|
394
|
+
timeout = Sfp::Planner::Config.timeout if timeout.nil?
|
351
395
|
|
352
396
|
os = `uname -s`.downcase.strip
|
353
397
|
command = case os
|
@@ -357,19 +401,20 @@ module Sfp
|
|
357
401
|
"#{planner}/preprocess < #{sas_file} 2>/dev/null 1>/dev/null; " +
|
358
402
|
"if [ -f 'output' ]; then " +
|
359
403
|
"timeout #{timeout} nice #{planner}/downward #{params} " +
|
360
|
-
"--plan-file #{plan_file} < output; fi"
|
404
|
+
"--plan-file #{plan_file} < output 1>>search.log 2>>search.log; fi"
|
361
405
|
when 'macos', 'darwin'
|
362
406
|
then "cd #{dir}; " +
|
363
407
|
"ulimit -Sv #{Sfp::Planner::Config.max_memory}; " +
|
364
|
-
"#{planner}/preprocess < #{sas_file} 1
|
365
|
-
"
|
366
|
-
|
408
|
+
"#{planner}/preprocess < #{sas_file} 1>/dev/null 2>/dev/null ; " +
|
409
|
+
"if [ -f 'output' ]; then " +
|
410
|
+
"nice #{planner}/downward #{params} " +
|
411
|
+
"--plan-file #{plan_file} < output 1>>search.log 2>>search.log; fi"
|
367
412
|
else nil
|
368
413
|
end
|
369
414
|
|
370
|
-
if not command.nil? and (os == 'linux' or os == 'macos' or os == 'darwin')
|
371
|
-
|
372
|
-
end
|
415
|
+
#if not command.nil? and (os == 'linux' or os == 'macos' or os == 'darwin')
|
416
|
+
# command = "#{command}" #1> /dev/null 2>/dev/null"
|
417
|
+
#end
|
373
418
|
|
374
419
|
command
|
375
420
|
end
|
@@ -386,21 +431,41 @@ module Sfp
|
|
386
431
|
end
|
387
432
|
|
388
433
|
def solve
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
434
|
+
use_admissible = false
|
435
|
+
|
436
|
+
# 1a) solve with autotune (see fd-autotune-2)
|
437
|
+
planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'autotune')
|
438
|
+
Kernel.system(planner)
|
439
|
+
|
440
|
+
# 1b) if not found, try mad
|
393
441
|
if not File.exist?(@plan_file)
|
394
|
-
|
395
|
-
Kernel.system(
|
442
|
+
planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'mad')
|
443
|
+
Kernel.system(planner)
|
396
444
|
end
|
397
|
-
|
445
|
+
# if not File.exist?(@plan_file)
|
446
|
+
# planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'ff')
|
447
|
+
# Kernel.system(planner)
|
448
|
+
# end
|
449
|
+
# 1c) if not found, try CEA
|
398
450
|
if not File.exists?(@plan_file)
|
399
|
-
|
400
|
-
Kernel.system(
|
401
|
-
return false if not File.exist?(@plan_file)
|
451
|
+
planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'cea')
|
452
|
+
Kernel.system(planner)
|
402
453
|
end
|
403
454
|
|
455
|
+
# final try: using an admissible heuristic
|
456
|
+
#if not File.exist?(@plan_file)
|
457
|
+
# use_admissible = true
|
458
|
+
# planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'lmcut', false, '20m')
|
459
|
+
# Kernel.system(planner)
|
460
|
+
#end
|
461
|
+
|
462
|
+
return false if not File.exist?(@plan_file)
|
463
|
+
optimise_plan if not use_admissible
|
464
|
+
|
465
|
+
true
|
466
|
+
end
|
467
|
+
|
468
|
+
def optimise_plan
|
404
469
|
# 2) remove unselected operators
|
405
470
|
new_sas = @sas_file + '.2'
|
406
471
|
new_plan = @plan_file + '.2'
|
@@ -410,11 +475,11 @@ module Sfp
|
|
410
475
|
lmcut = Sfp::Planner.getcommand(@dir, new_sas, new_plan, 'lmcut')
|
411
476
|
Kernel.system(lmcut)
|
412
477
|
|
413
|
-
#
|
414
|
-
File.
|
415
|
-
|
416
|
-
|
417
|
-
|
478
|
+
# 4) LMCUT cannot find the sub-optimized plan
|
479
|
+
if File.exist?(new_plan)
|
480
|
+
File.delete(@plan_file)
|
481
|
+
File.rename(new_plan, @plan_file)
|
482
|
+
end
|
418
483
|
end
|
419
484
|
|
420
485
|
def filter_operators(sas, plan, new_sas)
|
data/lib/sfplanner.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# external dependencies
|
2
2
|
require 'rubygems'
|
3
3
|
require 'json'
|
4
|
-
require 'sfp'
|
4
|
+
#require 'sfp'
|
5
5
|
|
6
6
|
# internal dependencies
|
7
7
|
libdir = File.expand_path(File.dirname(__FILE__))
|
8
8
|
|
9
|
+
require libdir + '/../../sfp-ruby/lib/sfp.rb'
|
9
10
|
require libdir + '/sfplanner/sas'
|
10
11
|
require libdir + '/sfplanner/planner'
|
data/sfplanner.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'sfplanner'
|
3
|
-
s.version = '0.0
|
4
|
-
s.date = '2013-
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2013-08-05'
|
5
5
|
s.summary = 'SFPlanner'
|
6
6
|
s.description = 'A Ruby gem that provides a Ruby API and a script to the SFP planner. This planner can automatically generate a plan that solves a planning problem written in SFP language.'
|
7
7
|
s.authors = ['Herry']
|
@@ -16,5 +16,5 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.homepage = 'https://github.com/herry13/sfplanner'
|
17
17
|
s.rubyforge_project = 'sfplanner'
|
18
18
|
|
19
|
-
s.add_dependency 'sfp', '~> 0.3.
|
19
|
+
s.add_dependency 'sfp', '~> 0.3.6'
|
20
20
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sfplanner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-08-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sfp
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.3.
|
21
|
+
version: 0.3.6
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 0.3.
|
29
|
+
version: 0.3.6
|
30
30
|
description: A Ruby gem that provides a Ruby API and a script to the SFP planner.
|
31
31
|
This planner can automatically generate a plan that solves a planning problem written
|
32
32
|
in SFP language.
|