sfplanner 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.1
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 interface to a planner that generates a plan as the solution of a planning problem specified in [SFP language](https://github.com/herry13/nuri/wiki/SFP-language).
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
- Tested on: MacOS X 10.8, Ubuntu 12.04, and Scientific Linux 6.
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
- - include file *sfplanner* library in your codes:
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
- - to parse an SFP file: create a Sfp::Parser object, and then pass the content of the file:
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
- - to solve a planning task: create a Sfp::Planner object, and then pass the file's path:
52
+ - parse an SFP file, and then generate the plan in JSON:
74
53
 
75
- # Create Sfp::Planner object.
76
- planner = Sfp::Planner.new
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 Problem
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
- $ sfp task.sfp
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.1 (c) 2013 Herry"
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
- puts planner.solve({:file => ARGV[0], :pretty_json => true, :parallel => opts[:parallel]})
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
Binary file
@@ -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 600s/5mins)
8
- @@timeout = 600
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 solve_sas(parser)
245
- return nil if parser.nil?
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> /dev/null; " +
365
- "#{planner}/downward #{params} " +
366
- "--plan-file #{plan_file} < output 1> /dev/null;"
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
- command = "#{command} 1> /dev/null 2>/dev/null"
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
- # 1) solve with FF
390
- planner1 = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'ff')
391
- Kernel.system(planner1)
392
- # 1b) if not found, try CEA
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
- planner2 = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'cea')
395
- Kernel.system(planner2)
442
+ planner = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'mad')
443
+ Kernel.system(planner)
396
444
  end
397
- # 1c) if not found, try CG
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
- planner3 = Sfp::Planner.getcommand(@dir, @sas_file, @plan_file, 'cg')
400
- Kernel.system(planner3)
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
- # LMCUT cannot find the sub-optimized plan
414
- File.delete(@plan_file)
415
- File.rename(new_plan, @plan_file) if File.exist?(new_plan)
416
-
417
- true
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.1'
4
- s.date = '2013-07-03'
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.0'
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.1
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-07-03 00:00:00.000000000 Z
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.0
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.0
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.