sfplanner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ *.swp
data/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ BSD LICENSE
2
+
3
+ Copyright (c) 2013, Herry
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+ The views and conclusions contained in the software and documentation are those
27
+ of the authors and should not be interpreted as representing official policies,
28
+ either expressed or implied, of the FreeBSD Project.
29
+
30
+ The solver binaries are under GPL License from http://fast-downward.org.
data/README.md ADDED
@@ -0,0 +1,229 @@
1
+ SFP Planner for Ruby
2
+ ====================
3
+ - Author: Herry (herry13@gmail.com)
4
+ - Version: 0.0.1
5
+ - License: [BSD License](https://github.com/herry13/sfp-ruby/blob/master/LICENSE)
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).
8
+
9
+ Click [here](https://github.com/herry13/nuri/wiki/SFP-language), for more details about SFP language.
10
+
11
+ This is a spin-out project from [Nuri](https://github.com/herry13/nuri).
12
+
13
+
14
+ To install
15
+ ----------
16
+
17
+ $ gem install sfplanner
18
+
19
+
20
+ Requirements
21
+ ------------
22
+ - Ruby (>= 1.8.7)
23
+ - Rubygems
24
+ - sfp (>= 0.3.0)
25
+ - antlr3
26
+ - json
27
+
28
+
29
+ Supporting Platforms
30
+ --------------------
31
+ - Linux (x86)
32
+ - MacOS X
33
+
34
+ Tested on: MacOS X 10.8, Ubuntu 12.04, and Scientific Linux 6.
35
+
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>
51
+
52
+
53
+ To use as Ruby library
54
+ ----------------------
55
+ - include file *sfplanner* library in your codes:
56
+
57
+ require 'sfplanner'
58
+
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
72
+
73
+ - to solve a planning task: create a Sfp::Planner object, and then pass the file's path:
74
+
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})
81
+
82
+
83
+
84
+ Example of Planning Problem
85
+ ---------------------------
86
+ - Create file **types.sfp** to hold required schemas:
87
+
88
+ schema Service {
89
+ running is false
90
+ procedure start {
91
+ conditions {
92
+ this.running is false
93
+ }
94
+ effects {
95
+ this.running is true
96
+ }
97
+ }
98
+ procedure stop {
99
+ conditions {
100
+ this.running is true
101
+ }
102
+ effects {
103
+ this.running is false
104
+ }
105
+ }
106
+ }
107
+ schema Client {
108
+ refer isref Service
109
+ procedure redirect(s isref Service) {
110
+ conditions { }
111
+ effects {
112
+ this.refer is s
113
+ }
114
+ }
115
+ }
116
+
117
+ In this file, we have two schemas that model our domain. First, schema
118
+ **Service** with an attribute **running**, procedure **start** that
119
+ changes **running**'s value from **false** to **true**, and procedure
120
+ **stop** that changes **running**'s value from **true** to **false**.
121
+
122
+ We also have schema **Client** with an attribute **refer**, which is
123
+ a reference to an instance of **Service**. There is a procedure
124
+ **redirect** that changes the value of **refer** with any instance if
125
+ **Service**.
126
+
127
+ - Create file **task.sfp** to hold the task:
128
+
129
+ include "types.sfp"
130
+
131
+ initial state {
132
+ a isa Service {
133
+ running is true
134
+ }
135
+
136
+ b isa Service // with "running" is false
137
+
138
+ pc isa Client {
139
+ refer is a
140
+ }
141
+ }
142
+
143
+ goal constraint {
144
+ pc.refer is b
145
+ a.running is false
146
+ }
147
+
148
+ global constraint {
149
+ pc.refer.running is true
150
+ }
151
+
152
+ In this file, we specify a task where in the initial state of our domain,
153
+ we have two services **a** and **b**, and a client **pc**. **a** is
154
+ running, **b** is stopped, and **pc** is referring to **a**. We want to
155
+ generate a workflow that achieves goal: **pc** is referring to **b**
156
+ and **a** is stopped, and preserves global constraint: **pc** is always
157
+ referring to a running service.
158
+
159
+ - To generate the workflow, we invoke **sfp** command with argument
160
+ the path of the task file:
161
+
162
+ $ sfp task.sfp
163
+
164
+ Which will generate a workflow in JSON
165
+
166
+ {
167
+ "type": "sequential",
168
+ "workflow": [
169
+ {
170
+ "name": "$.b.start",
171
+ "parameters": {
172
+ },
173
+ "condition": {
174
+ "$.b.running": false
175
+ },
176
+ "effect": {
177
+ "$.b.running": true
178
+ }
179
+ },
180
+ {
181
+ "name": "$.pc.redirect",
182
+ "parameters": {
183
+ "$.s": "$.b"
184
+ },
185
+ "condition": {
186
+ },
187
+ "effect": {
188
+ "$.pc.refer": "$.b"
189
+ }
190
+ },
191
+ {
192
+ "name": "$.a.stop",
193
+ "parameters": {
194
+ },
195
+ "condition": {
196
+ "$.a.running": true
197
+ },
198
+ "effect": {
199
+ "$.a.running": false
200
+ }
201
+ }
202
+ ],
203
+ "version": "1",
204
+ "total": 3
205
+ }
206
+
207
+ This workflow is sequential that has 3 procedures. If you executes
208
+ the workflow in given order, it will achieves the goal state as well
209
+ 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 ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ libdir = File.expand_path(File.dirname(__FILE__))
4
+ require "#{libdir}/../lib/sfplanner"
5
+
6
+ opts = Trollop::options do
7
+ version "sfplanner 0.0.1 (c) 2013 Herry"
8
+ banner <<-EOS
9
+ Solve a planning task specified in SFP language, and print the plan (if found) in JSON format.
10
+
11
+ Usage:
12
+ sfplanner [options] <file>
13
+
14
+ where [options] are:
15
+ EOS
16
+
17
+ opt :parallel, "Generate a parallel (partial-order) plan, instead of sequential."
18
+ end
19
+
20
+ def parse(filepath)
21
+ home_dir = File.expand_path(File.dirname(filepath))
22
+ parser = Sfp::Parser.new({:home_dir => home_dir})
23
+ parser.parse(File.read(filepath))
24
+ parser
25
+ end
26
+
27
+ filepath = ARGV[0].to_s
28
+
29
+ if filepath != ''
30
+ planner = Sfp::Planner.new
31
+ puts planner.solve({:file => ARGV[0], :pretty_json => true, :parallel => opts[:parallel]})
32
+ else
33
+ Trollop::help
34
+ end
data/bin/sfw2graph ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'json'
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
129
+ end
130
+
131
+ def main
132
+ if ARGV.length < 1
133
+ puts "Usage: sfw2dot.rb <input-file> [output-file]\n\n"
134
+ exit
135
+ end
136
+
137
+ fp = open(ARGV[0], "rb")
138
+ json = JSON.parse(fp.read)
139
+ fp.close
140
+
141
+ dot = ""
142
+ case json["type"]
143
+ when 'partial-order', 'parallel'
144
+ dot = Nuri::Planner::Graph::partial2dot(json)
145
+ when 'sequential'
146
+ dot = Nuri::Planner::Graph::sequential2dot(json)
147
+ when 'stage'
148
+ dot = Nuri::Planner::Graph::stage2dot(json)
149
+ else
150
+ throw Exception, "Unrecognised type of workflow: #{json['type']}"
151
+ end
152
+
153
+ outfile = "/tmp/#{ARGV[0]}.dot"
154
+ fp = open(outfile, "w");
155
+ fp.write(dot)
156
+ fp.close
157
+
158
+ cmd = 'dot -Tpng -o';
159
+ if ARGV.length > 1
160
+ cmd += "#{ARGV[1]} #{outfile}"
161
+ else
162
+ cmd += ARGV[0].sub(/\.[a-zA-Z]*/,'') + ".png < #{outfile}"
163
+ end
164
+ system(cmd)
165
+ File.delete(outfile)
166
+ end
167
+
168
+ main if __FILE__ == $0
Binary file
Binary file
Binary file
Binary file