sfplanner 0.1.3 → 0.1.4

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