sfplanner 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -13,6 +13,13 @@ Click [here](https://github.com/herry13/nuri/wiki/SFP-language), for more detail
13
13
  This is a spin-out project from [Nuri](https://github.com/herry13/nuri).
14
14
 
15
15
 
16
+ Features
17
+ --------
18
+ - Automatically generating a sequential or partial-order (parallel) plan.
19
+ - Use JSON as the standard format of the plan.
20
+ - Generate an image file that illustrates the graph of the plan.
21
+
22
+
16
23
  To install
17
24
  ----------
18
25
 
@@ -22,6 +29,7 @@ To install
22
29
  Requirements
23
30
  ------------
24
31
  - Ruby (>= 1.8.7)
32
+ - Graphviz
25
33
  - Rubygems
26
34
  - sfp (>= 0.3.0)
27
35
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
data/bin/sfplanner CHANGED
@@ -15,9 +15,11 @@ Usage:
15
15
  where [options] are:
16
16
  EOS
17
17
 
18
- opt :parallel, "Generate a parallel (partial-order) plan, instead of sequential."
19
- opt :json_input, "Input is in JSON format"
20
- opt :pretty, "Print the plan in pretty JSON format"
18
+ opt :parallel, "Generate a parallel (partial-order) plan, instead of sequential.", :short => '-l'
19
+ opt :json_input, "Input is in JSON format", :short => '-j'
20
+ opt :pretty_json, "Print the plan in pretty JSON format", :short => '-r'
21
+ opt :image, "Generate a graph image (PNG) of the plan"
22
+ opt :output, "Output file path", :default => ''
21
23
  end
22
24
 
23
25
  def parse(filepath)
@@ -27,20 +29,35 @@ def parse(filepath)
27
29
  parser
28
30
  end
29
31
 
30
- filepath = ARGV[0].to_s
31
-
32
- if filepath != ''
32
+ if ARGV[0]
33
33
  planner = Sfp::Planner.new
34
- options = {:file => ARGV[0],
35
- :json => !opts[:pretty],
36
- :pretty_json => opts[:pretty],
37
- :parallel => opts[:parallel]}
38
- if opts[:json_input]
39
- options[:input_json] = true
40
- puts planner.solve(options)
34
+ opts[:file] = ARGV[0]
35
+ opts[:json] = (!opts[:pretty_json] and !opts[:dot])
36
+ opts[:dot] = true if opts[:image]
37
+
38
+ result = planner.solve(opts)
39
+ if !opts[:image]
40
+ if opts[:output].length > 0
41
+ File.open(opts[:output], 'w') { |f|
42
+ f.write(result)
43
+ f.flush
44
+ }
45
+ else
46
+ puts result
47
+ end
41
48
  else
42
- puts planner.solve(options)
49
+ if opts[:output].length <= 0
50
+ parts = ARGV[0].split('/')
51
+ src_file = parts[parts.length-1]
52
+ parts = src_file.split('.')
53
+ opts[:output] = src_file[0, src_file.length-1-parts[parts.length-1].length] if parts.length >= 2
54
+ opts[:output] += '.png'
55
+ end
56
+ if !Sfp::Graph.dot2image(result, opts[:output])
57
+ $stderr.puts "Cannot generate the image graph!"
58
+ end
43
59
  end
60
+
44
61
  else
45
62
  Trollop::help
46
63
  end
data/bin/sfw2graph CHANGED
@@ -3,134 +3,14 @@
3
3
  require 'rubygems'
4
4
  require 'json'
5
5
 
6
- module Nuri
7
- module Planner
8
- module Graph
9
- ActionColor = 'white'
10
- ActionLabelWithParameters = false
11
-
12
- def self.clean(value)
13
- return value[2, value.length-2] if value[0,2] == '$.'
14
- return value
15
- end
16
-
17
- def self.get_label(action, withparameters=true)
18
- label = clean(action["name"])
19
- if withparameters and ActionLabelWithParameters
20
- label += "("
21
- if action["parameters"].length > 0
22
- action["parameters"].each { |key,value|
23
- label += "#{clean(key)}=#{clean(value.to_s)},"
24
- }
25
- label.chop!
26
- end
27
- label += ')'
28
- end
29
- return label
30
- end
31
-
32
- def self.partial2dot(json)
33
- dot = "digraph {\n"
34
-
35
- dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
36
- dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
37
- last_actions = Hash.new
38
- json["workflow"].each { |action|
39
- dot += "#{action["id"]}[label=\"#{get_label(action)}\", shape=rect, style=filled, fillcolor=#{ActionColor}];\n"
40
- last_actions[action["id"].to_s] = action
41
- }
42
-
43
- json["workflow"].each { |action|
44
- has_predecessor = false
45
- action["predecessors"].each { |prevId|
46
- dot += "#{prevId} -> #{action["id"]};\n"
47
- has_predecessor = true
48
- last_actions.delete(prevId.to_s)
49
- }
50
- if not has_predecessor
51
- dot += "init_state -> #{action["id"]};\n"
52
- end
53
- }
54
-
55
- last_actions.each { |id,action|
56
- dot += "#{id} -> final_state;\n"
57
- }
58
-
59
- dot += "}"
60
-
61
- return dot
62
- end
63
-
64
- def self.stage2dot(json)
65
- dot = "digraph {\n"
66
-
67
- dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
68
- dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
69
- index = 0
70
- prevState = "init_state"
71
- json["workflow"].each { |stage|
72
- id = 0
73
- stage.each { |action|
74
- dot += "a" + index.to_s + "a" + id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
75
- dot += prevState + " -> a" + index.to_s + "a" + id.to_s + ";\n"
76
- id += 1
77
- }
78
- if index < json["workflow"].length-1
79
- dot += "state" + index.to_s + ' [label="", shape=circle, fixedsize=true, width=0.35]' + ";\n"
80
- prevState = "state" + index.to_s
81
- id = 0
82
- stage.each { |action|
83
- dot += "a" + index.to_s + "a" + id.to_s + " -> " + prevState + ";\n"
84
- id += 1
85
- }
86
- else
87
- id = 0
88
- stage.each { |action|
89
- dot += "a" + index.to_s + "a" + id.to_s + " -> final_state;\n"
90
- id += 1
91
- }
92
- end
93
- index += 1
94
- }
95
- dot += "}"
96
- return dot
97
- end
98
-
99
- def self.sequential2dot(json)
100
- dot = "digraph {\n"
101
-
102
- dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
103
- dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
104
- id = 0
105
- json["workflow"].each { |action|
106
- dot += id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
107
- id += 1
108
- }
109
-
110
- id = 0
111
- prevActionId = nil
112
- json["workflow"].each { |action|
113
- if id == 0
114
- dot += "init_state -> " + id.to_s + ";\n"
115
- elsif id == json["workflow"].length-1
116
- dot += id.to_s + " -> final_state;\n"
117
- end
118
- if prevActionId != nil
119
- dot += prevActionId.to_s + " -> " + id.to_s + ";\n"
120
- end
121
- prevActionId = id
122
- id += 1
123
- }
124
- dot += "}"
125
- return dot
126
- end
127
- end
128
- end
6
+ module Sfp
129
7
  end
130
8
 
9
+ require File.expand_path('../../lib/sfplanner/graph.rb', __FILE__)
10
+
131
11
  def main
132
12
  if ARGV.length < 1
133
- puts "Usage: sfw2dot.rb <input-file> [output-file]\n\n"
13
+ puts "Convert SFP plan to an image graph with Graphviz (dot).\n\nUsage: sfw2dot.rb <input-file> [output-file]\n\n"
134
14
  exit
135
15
  end
136
16
 
@@ -141,11 +21,11 @@ def main
141
21
  dot = ""
142
22
  case json["type"]
143
23
  when 'partial-order', 'parallel'
144
- dot = Nuri::Planner::Graph::partial2dot(json)
24
+ dot = Sfp::Graph::partial2dot(json)
145
25
  when 'sequential'
146
- dot = Nuri::Planner::Graph::sequential2dot(json)
26
+ dot = Sfp::Graph::sequential2dot(json)
147
27
  when 'stage'
148
- dot = Nuri::Planner::Graph::stage2dot(json)
28
+ dot = Sfp::Graph::stage2dot(json)
149
29
  else
150
30
  throw Exception, "Unrecognised type of workflow: #{json['type']}"
151
31
  end
@@ -0,0 +1,132 @@
1
+ module Sfp::Graph
2
+ ActionColor = 'white'
3
+ ActionLabelWithParameters = false
4
+
5
+ def self.dot2image(dot, image_file)
6
+ dot_file = "/tmp/#{Time.now.getutc.to_i}.dot"
7
+ File.open(dot_file, 'w') { |f|
8
+ f.write(dot)
9
+ f.flush
10
+ }
11
+ !!system("dot -Tpng -o #{image_file} #{dot_file}")
12
+ ensure
13
+ File.delete(dot_file) if File.exist?(dot_file)
14
+ end
15
+
16
+ def self.clean(value)
17
+ return value[2, value.length-2] if value[0,2] == '$.'
18
+ return value
19
+ end
20
+
21
+ def self.get_label(action, withparameters=true)
22
+ label = clean(action["name"])
23
+ if withparameters and ActionLabelWithParameters
24
+ label += "("
25
+ if action["parameters"].length > 0
26
+ action["parameters"].each { |key,value|
27
+ label += "#{clean(key)}=#{clean(value.to_s)},"
28
+ }
29
+ label.chop!
30
+ end
31
+ label += ')'
32
+ end
33
+ return label
34
+ end
35
+
36
+ def self.partial2dot(json)
37
+ dot = "digraph {\n"
38
+
39
+ dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
40
+ dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
41
+ last_actions = Hash.new
42
+ json["workflow"].each { |action|
43
+ dot += "#{action["id"]}[label=\"#{get_label(action)}\", shape=rect, style=filled, fillcolor=#{ActionColor}];\n"
44
+ last_actions[action["id"].to_s] = action
45
+ }
46
+
47
+ json["workflow"].each { |action|
48
+ has_predecessor = false
49
+ action["predecessors"].each { |prevId|
50
+ dot += "#{prevId} -> #{action["id"]};\n"
51
+ has_predecessor = true
52
+ last_actions.delete(prevId.to_s)
53
+ }
54
+ if not has_predecessor
55
+ dot += "init_state -> #{action["id"]};\n"
56
+ end
57
+ }
58
+
59
+ last_actions.each { |id,action|
60
+ dot += "#{id} -> final_state;\n"
61
+ }
62
+
63
+ dot += "}"
64
+
65
+ return dot
66
+ end
67
+
68
+ def self.stage2dot(json)
69
+ dot = "digraph {\n"
70
+
71
+ dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
72
+ dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
73
+ index = 0
74
+ prevState = "init_state"
75
+ json["workflow"].each { |stage|
76
+ id = 0
77
+ stage.each { |action|
78
+ dot += "a" + index.to_s + "a" + id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
79
+ dot += prevState + " -> a" + index.to_s + "a" + id.to_s + ";\n"
80
+ id += 1
81
+ }
82
+ if index < json["workflow"].length-1
83
+ dot += "state" + index.to_s + ' [label="", shape=circle, fixedsize=true, width=0.35]' + ";\n"
84
+ prevState = "state" + index.to_s
85
+ id = 0
86
+ stage.each { |action|
87
+ dot += "a" + index.to_s + "a" + id.to_s + " -> " + prevState + ";\n"
88
+ id += 1
89
+ }
90
+ else
91
+ id = 0
92
+ stage.each { |action|
93
+ dot += "a" + index.to_s + "a" + id.to_s + " -> final_state;\n"
94
+ id += 1
95
+ }
96
+ end
97
+ index += 1
98
+ }
99
+ dot += "}"
100
+ return dot
101
+ end
102
+
103
+ def self.sequential2dot(json)
104
+ dot = "digraph {\n"
105
+
106
+ dot += "init_state [label=\"\", shape=doublecircle, fixedsize=true, width=0.35];\n"
107
+ dot += "final_state [label=\"\", shape=doublecircle, style=filled, fillcolor=black, fixedsize=true, width=0.35];\n"
108
+ id = 0
109
+ json["workflow"].each { |action|
110
+ dot += id.to_s + '[label="' + get_label(action) + '", shape=rect]' + ";\n"
111
+ id += 1
112
+ }
113
+
114
+ id = 0
115
+ prevActionId = nil
116
+ json["workflow"].each { |action|
117
+ if id == 0
118
+ dot += "init_state -> " + id.to_s + ";\n"
119
+ elsif id == json["workflow"].length-1
120
+ dot += id.to_s + " -> final_state;\n"
121
+ end
122
+ if prevActionId != nil
123
+ dot += prevActionId.to_s + " -> " + id.to_s + ";\n"
124
+ end
125
+ prevActionId = id
126
+ id += 1
127
+ }
128
+ dot += "}"
129
+ return dot
130
+ end
131
+ end
132
+
@@ -26,20 +26,26 @@ module Sfp
26
26
  attr_accessor :debug
27
27
  attr_reader :parser
28
28
 
29
+ # @param all parameters are passed to Sfp::Parser#initialize method
30
+ #
29
31
  def initialize(params={})
30
32
  @parser = Sfp::Parser.new(params)
31
33
  @debug = Debug
32
34
  end
33
35
 
34
- # @param :string : SFP task in string
35
- # @param :sfp : SFP task in Hash data structure
36
- # @param :file : SFP task in file with specified path
37
- # @param :sas_plan : if true then return a raw SAS plan
38
- # @param :parallel : if true then return a parallel (partial-order) plan,
36
+ # @param :string => SFP task in string
37
+ # :sfp => SFP task in Hash data structure
38
+ # :file => SFP task in file with specified path
39
+ # :json_input => SFP task in JSON format
40
+ # :sas_plan => if true then return a raw SAS plan
41
+ # :parallel => if true then return a parallel (partial-order) plan,
39
42
  # if false or nil then return a sequential plan
40
- # @param :json : if true then return the plan in JSON
41
- # @param :pretty_json : if true then return in pretty JSON
42
- # @param :bsig : if true then return the solution plan as a BSig model
43
+ # :json => if true then return the plan in JSON
44
+ # :pretty_json => if true then return in pretty JSON
45
+ # :bsig => if true then return the solution plan as a BSig model
46
+ #
47
+ # @return if solution plan is found then returns a JSON or Hash
48
+ # otherwise return nil
43
49
  #
44
50
  def solve(params={})
45
51
  if params[:string].is_a?(String)
@@ -48,7 +54,7 @@ module Sfp
48
54
  @parser.root = params[:sfp]
49
55
  elsif params[:file].is_a?(String)
50
56
  raise Exception, "File not found: #{params[:file]}" if not File.exist?(params[:file])
51
- if params[:input_json]
57
+ if params[:json_input]
52
58
  @parser.root = json_to_sfp(JSON[File.read(params[:file])])
53
59
  else
54
60
  @parser.home_dir = File.expand_path(File.dirname(params[:file]))
@@ -67,10 +73,15 @@ module Sfp
67
73
  end
68
74
  end
69
75
 
70
- # @param :parallel : if true then return a parallel (partial-order) plan,
71
- # if false or nil then return a sequential plan
72
- # @param :json : if true then return the plan in JSON
73
- # @param :pretty_json : if true then return in pretty JSON
76
+ # Return Behavioural Signature (BSig) model of previously generated plan.
77
+ #
78
+ # @param :parallel => if true then return a parallel (partial-order) plan,
79
+ # if false or nil then return a sequential plan
80
+ # :json => if true then return the plan in JSON
81
+ # :pretty_json => if true then return in pretty JSON
82
+ #
83
+ # @return if solution BSig model is found then returns a JSON or Hash,
84
+ # otherwise return nil
74
85
  #
75
86
  def to_bsig(params={})
76
87
  raise Exception, "Conformant task is not supported yet" if @parser.conformant
@@ -81,8 +92,12 @@ module Sfp
81
92
  (params[:pretty_json] ? JSON.pretty_generate(bsig) : bsig))
82
93
  end
83
94
 
84
- # @param :json : if true then return in JSON
85
- # @param :pretty_json : if true then return in pretty JSON
95
+ # Return the final state if the plan is executed.
96
+ #
97
+ # @param :json => if true then return in JSON
98
+ # :pretty_json => if true then return in pretty JSON
99
+ #
100
+ # @return [Hash]
86
101
  #
87
102
  def final_state(params={})
88
103
  return nil if @plan.nil?
@@ -92,6 +107,14 @@ module Sfp
92
107
  end
93
108
 
94
109
  protected
110
+ def to_dot(plan)
111
+ if plan['type'] == 'parallel'
112
+ Sfp::Graph.partial2dot(self.get_parallel_plan)
113
+ else
114
+ Sfp::Graph.sequential2dot(self.get_sequential_plan)
115
+ end
116
+ end
117
+
95
118
  def json_to_sfp(json)
96
119
  json.accept(Sfp::Visitor::SfpGenerator.new(json))
97
120
  json.each do |key,val|
@@ -177,8 +200,16 @@ module Sfp
177
200
  return to_bsig(params) if params[:bsig]
178
201
 
179
202
  plan = (params[:parallel] ? self.get_parallel_plan : self.get_sequential_plan)
180
- return (params[:json] ? JSON.generate(plan) :
181
- (params[:pretty_json] ? JSON.pretty_generate(plan) : plan))
203
+
204
+ if params[:dot]
205
+ to_dot(plan)
206
+ elsif params[:json]
207
+ JSON.generate(plan)
208
+ elsif params[:pretty_json]
209
+ JSON.pretty_generate(plan)
210
+ else
211
+ plan
212
+ end
182
213
  end
183
214
 
184
215
  def bsig_template
@@ -494,7 +525,7 @@ module Sfp
494
525
  @dir = dir
495
526
  @sas_file = sas_file
496
527
  @plan_file = plan_file
497
- @heuristics_order = ['autotune12', 'autotune22', 'ff2', 'cea2']
528
+ @heuristics_order = ['ff2', 'cea2', 'autotune12', 'autotune22']
498
529
  @heuristics_order = ENV['SFPLANNER_MIXED_HEURISTICS'].split(',') if ENV['SFPLANNER_MIXED_HEURISTICS']
499
530
  @continue = continue
500
531
  @continue = true if ENV['SFPLANNER_MIXED_CONTINUE']
data/lib/sfplanner.rb CHANGED
@@ -7,4 +7,5 @@ require 'sfp'
7
7
  libdir = File.expand_path(File.dirname(__FILE__))
8
8
 
9
9
  require libdir + '/sfplanner/sas'
10
+ require libdir + '/sfplanner/graph.rb'
10
11
  require libdir + '/sfplanner/planner'
data/sfplanner.gemspec CHANGED
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.email = 'herry13@gmail.com'
9
9
 
10
10
  s.executables << 'sfplanner'
11
+ s.executables << 'sfw2graph'
11
12
  s.files = `git ls-files`.split("\n")
12
13
 
13
14
  s.require_paths = ['lib']
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.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sfp
16
- requirement: &20029340 !ruby/object:Gem::Requirement
16
+ requirement: &16262980 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,13 +21,14 @@ dependencies:
21
21
  version: 0.3.12
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *20029340
24
+ version_requirements: *16262980
25
25
  description: A Ruby gem that provides a Ruby API and a script to the SFP planner.
26
26
  This planner can automatically generate a plan that solves a planning problem written
27
27
  in SFP language.
28
28
  email: herry13@gmail.com
29
29
  executables:
30
30
  - sfplanner
31
+ - sfw2graph
31
32
  extensions: []
32
33
  extra_rdoc_files: []
33
34
  files:
@@ -42,6 +43,7 @@ files:
42
43
  - bin/solver/macos/downward
43
44
  - bin/solver/macos/preprocess
44
45
  - lib/sfplanner.rb
46
+ - lib/sfplanner/graph.rb
45
47
  - lib/sfplanner/planner.rb
46
48
  - lib/sfplanner/sas.rb
47
49
  - sfplanner.gemspec