sfp 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/.gitignore +1 -0
- data/LICENSE +30 -0
- data/README.md +200 -0
- data/bin/sfp +24 -0
- data/bin/solver/linux/downward +0 -0
- data/bin/solver/linux/preprocess +0 -0
- data/bin/solver/macos/downward +0 -0
- data/bin/solver/macos/preprocess +0 -0
- data/lib/sfp/SfpLangLexer.rb +3127 -0
- data/lib/sfp/SfpLangParser.rb +9770 -0
- data/lib/sfp/Sfplib.rb +357 -0
- data/lib/sfp/parser.rb +128 -0
- data/lib/sfp/planner.rb +460 -0
- data/lib/sfp/sas.rb +966 -0
- data/lib/sfp/sas_translator.rb +1836 -0
- data/lib/sfp/sfw2graph.rb +168 -0
- data/lib/sfp/visitors.rb +132 -0
- data/lib/sfp.rb +17 -0
- data/sfp.gemspec +26 -0
- data/src/SfpLang.g +1005 -0
- data/src/build.sh +7 -0
- data/test/cloud-classes.sfp +77 -0
- data/test/cloud1.sfp +33 -0
- data/test/cloud2.sfp +41 -0
- data/test/cloud3.sfp +42 -0
- data/test/service-classes.sfp +151 -0
- data/test/service1.sfp +24 -0
- data/test/service3.sfp +27 -0
- data/test/task.sfp +22 -0
- data/test/test.inc +9 -0
- data/test/test.sfp +13 -0
- data/test/test1.sfp +19 -0
- data/test/test2.inc +40 -0
- data/test/test2.sfp +17 -0
- data/test/types.sfp +28 -0
- data/test/v1.1.sfp +22 -0
- metadata +120 -0
data/lib/sfp/sas.rb
ADDED
@@ -0,0 +1,966 @@
|
|
1
|
+
module Nuri
|
2
|
+
module Sas
|
3
|
+
class Task
|
4
|
+
attr_reader :variables, :mutexes, :operators, :axioms, :init, :goal
|
5
|
+
attr_reader :global_variable, :global_operators
|
6
|
+
attr_accessor :sas_plan, :goal_operator_name
|
7
|
+
|
8
|
+
GlobalVariable = 'global'
|
9
|
+
GlobalOperator = 'globalop'
|
10
|
+
SometimeVariable = 'sometime'
|
11
|
+
SometimeOperator = 'sometime'
|
12
|
+
GoalOperator = 'goal'
|
13
|
+
GoalVariable = 'goal'
|
14
|
+
|
15
|
+
def initialize(sas_file)
|
16
|
+
@goal_operator_name = nil
|
17
|
+
|
18
|
+
f = File.new(sas_file, "r")
|
19
|
+
### read version
|
20
|
+
f.gets
|
21
|
+
@version = f.gets.to_i
|
22
|
+
f.gets
|
23
|
+
### read metrix
|
24
|
+
f.gets
|
25
|
+
@metric = f.gets.to_i
|
26
|
+
f.gets
|
27
|
+
### read variables
|
28
|
+
@variables = []
|
29
|
+
total_variables = f.gets.to_i
|
30
|
+
for i in 1..total_variables
|
31
|
+
variable = Variable.new(:io => f, :index => i-1)
|
32
|
+
@global_variable = variable if @global_variable.nil? and variable.is_global
|
33
|
+
@variables << variable
|
34
|
+
end
|
35
|
+
### skip mutex
|
36
|
+
total_mutex = f.gets.to_i
|
37
|
+
@mutexes = []
|
38
|
+
# TODO -- parse mutex
|
39
|
+
total_mutex = 0
|
40
|
+
begin line = f.gets; line.chop!; end until line == "begin_state"
|
41
|
+
### read state
|
42
|
+
@init = State.new
|
43
|
+
for i in 0..(total_variables-1)
|
44
|
+
@variables[i].init = @init[i] = f.gets.to_i
|
45
|
+
end
|
46
|
+
f.gets # end_state
|
47
|
+
### read goal
|
48
|
+
@goal = Goal.new(:io => f, :variables => @variables)
|
49
|
+
### read operators
|
50
|
+
@operators = []
|
51
|
+
@global_operators = []
|
52
|
+
total_operators = f.gets.to_i
|
53
|
+
for i in 1..total_operators
|
54
|
+
operator = Operator.new(:io => f, :variables => @variables)
|
55
|
+
@operators << operator if not operator.is_global
|
56
|
+
@global_operators << operator if operator.is_global
|
57
|
+
end
|
58
|
+
### read axiom
|
59
|
+
@axioms = []
|
60
|
+
total_axioms = f.gets.to_i
|
61
|
+
for i in 1..total_axioms
|
62
|
+
axiom = Axiom.new(:io => f, :variables => @variables)
|
63
|
+
@axioms << axiom
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Write the planning task to given IO object.
|
68
|
+
# @param - io: an IO object
|
69
|
+
def write_io(io)
|
70
|
+
io.puts('begin_version', @version, 'end_version')
|
71
|
+
io.puts('begin_metric', @metric, 'end_metric')
|
72
|
+
io.puts(@variables.length)
|
73
|
+
@variables.each { |variable| variable.write_io(io) }
|
74
|
+
io.puts(@mutexes.length)
|
75
|
+
@mutexes.each { |mutex| mutex.write_io(io) }
|
76
|
+
@init.write_io(io)
|
77
|
+
@goal.write_io(io)
|
78
|
+
io.puts(@operators.length + @global_operators.length)
|
79
|
+
@operators.each { |operator| operator.write_io(io) }
|
80
|
+
@global_operators.each { |operator| operator.write_io(io) }
|
81
|
+
io.puts(@axioms.length)
|
82
|
+
@axioms.each { |axiom| axiom.write_io(io) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return the partial-order workflow in JSON
|
86
|
+
# @param - parser: Nuri::Sfp::Parser object
|
87
|
+
# @return: workflow, initial operators' indexes, length
|
88
|
+
def get_partial_order_workflow(parser)
|
89
|
+
plan = self.to_partial_order
|
90
|
+
return nil, nil, nil if plan.nil? # no-valid plan
|
91
|
+
|
92
|
+
sfw = []
|
93
|
+
init = []
|
94
|
+
name_index = {}
|
95
|
+
distance = plan.length
|
96
|
+
plan.each_index do |i|
|
97
|
+
op_name = plan[i].name.split(' ')[0]
|
98
|
+
operator = parser.operators[op_name]
|
99
|
+
raise Exception, 'Cannot find operator: ' + op_name if operator.nil?
|
100
|
+
op_sfw = operator.to_sfw
|
101
|
+
op_sfw['id'] = i
|
102
|
+
op_sfw['distance'] = distance
|
103
|
+
op_sfw['successors'] = []
|
104
|
+
op_sfw['predecessors'] = []
|
105
|
+
sfw << op_sfw
|
106
|
+
name_index[op_name] = i
|
107
|
+
init << i if plan[i].predecessors.length <= 0
|
108
|
+
distance -= 1
|
109
|
+
end
|
110
|
+
plan.each_index do |i|
|
111
|
+
plan[i].predecessors.each do |op|
|
112
|
+
op_name = op.name.split(' ')[0]
|
113
|
+
j = name_index[op_name]
|
114
|
+
next if j.nil? # HACK!
|
115
|
+
sfw[j]['successors'] << i
|
116
|
+
sfw[i]['predecessors'] << j
|
117
|
+
end
|
118
|
+
end
|
119
|
+
return sfw, init, sfw.length
|
120
|
+
end
|
121
|
+
|
122
|
+
def goal_state
|
123
|
+
goal = []
|
124
|
+
if @goal_operator_name.nil?
|
125
|
+
@goal.each { |var| goal << {:id => var.index, :value => var.goal} }
|
126
|
+
else
|
127
|
+
@operators.each do |op|
|
128
|
+
if op.name == @goal_operator_name
|
129
|
+
op.prevails.each { |param|
|
130
|
+
goal << {:id => param.var.index, :value => param.prevail}
|
131
|
+
}
|
132
|
+
break
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
goal
|
137
|
+
end
|
138
|
+
|
139
|
+
def final_state
|
140
|
+
state = @init.clone
|
141
|
+
self.collect_operators.each { |op| state.apply!(op) }
|
142
|
+
final = []
|
143
|
+
#for i in 0..(state.length-1)
|
144
|
+
# final << {:id => i, :value => state[i]} #if state[i] != @init[i]
|
145
|
+
#end
|
146
|
+
for i in 0..(state.length-1)
|
147
|
+
next if @variables[i].is_global or @variables[i].is_goal
|
148
|
+
final << {:id => i, :value => state[i]}
|
149
|
+
end
|
150
|
+
return final
|
151
|
+
end
|
152
|
+
|
153
|
+
protected
|
154
|
+
def self.is_global_variable?(variable_name)
|
155
|
+
return (variable_name.split('_')[2] == GlobalVariable)
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.is_global_operator?(operator_name)
|
159
|
+
return (operator_name.split('-')[1] == GlobalOperator)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.is_sometime_operator?(operator_name)
|
163
|
+
return (operator_name.split('-')[1] == SometimeOperator)
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.is_sometime_variable?(variable_name)
|
167
|
+
return (variable_name.split('-')[1] == SometimeVariable)
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.is_goal_variable?(variable_name)
|
171
|
+
return (variable_name.split('-')[1] == GoalVariable)
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.is_goal_operator?(operator_name)
|
175
|
+
return (operator_name.split('-')[1] == GoalOperator)
|
176
|
+
end
|
177
|
+
|
178
|
+
def find_operator(name)
|
179
|
+
@operators.each { |op| return op if op.name == name }
|
180
|
+
return nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def find_global_operator(name)
|
184
|
+
@global_operators.each { |op| return op if op.name == name }
|
185
|
+
return nil
|
186
|
+
end
|
187
|
+
|
188
|
+
def to_partial_order
|
189
|
+
if @global_operators.length <= 0
|
190
|
+
return self.plan_without_trajectory_to_partial_order
|
191
|
+
else
|
192
|
+
return self.plan_with_trajectory_to_partial_order
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def collect_operators
|
197
|
+
plan = []
|
198
|
+
selected_operator = nil
|
199
|
+
# get the operators
|
200
|
+
@sas_plan.each do |name|
|
201
|
+
if Task::is_global_operator?(name)
|
202
|
+
if selected_operator != nil
|
203
|
+
selected_operator.global_op = find_global_operator(name[1, name.length-2])
|
204
|
+
raise Exception, 'not found: ' + name if selected_operator.global_op.nil?
|
205
|
+
end
|
206
|
+
selected_operator = nil
|
207
|
+
elsif Task::is_goal_operator?(name)
|
208
|
+
# TODO -- handle "goal-verifier" action for handling "sometime" constraint
|
209
|
+
else
|
210
|
+
selected_operator = find_operator(name[1, name.length-2])
|
211
|
+
raise Exception, 'not found: ' + name if selected_operator.nil?
|
212
|
+
plan << selected_operator
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# remove global variable from plan's operators
|
216
|
+
plan.each { |op| op.remove_global_variable(@global_variable) }
|
217
|
+
|
218
|
+
return plan
|
219
|
+
end
|
220
|
+
|
221
|
+
def set_predecessors(plan)
|
222
|
+
(plan.length-1).downto(0) do |i|
|
223
|
+
op1 = plan[i]
|
224
|
+
op1.prevails.each do |p|
|
225
|
+
(i-1).downto(0) do |j|
|
226
|
+
op2 = plan[j]
|
227
|
+
if op2.support_assignment?(p[:var], p[:prevail])
|
228
|
+
op1.predecessors << op2
|
229
|
+
elsif op2.threat_assignment?(p[:var], p[:prevail])
|
230
|
+
break
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
op1.pre_posts.each do |p|
|
235
|
+
(i-1).downto(0) do |j|
|
236
|
+
op2 = plan[j]
|
237
|
+
if op2.threatened_by_assignment?(p[:var], p[:post])
|
238
|
+
op1.predecessors << op2
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def remove_transitive_predecessors(plan)
|
246
|
+
# foreach operator, remove duplicates in predecessors list
|
247
|
+
@operators.each { |op| op.predecessors.uniq! }
|
248
|
+
|
249
|
+
# remove "transitive" predecessor link
|
250
|
+
plan.each do |op|
|
251
|
+
valid_predecessors = []
|
252
|
+
op.predecessors.each do |pred_op|
|
253
|
+
valid_predecessors << pred_op if not op.has_transitive_predecessors?(pred_op)
|
254
|
+
end
|
255
|
+
op.predecessors = valid_predecessors
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def add_adversary_predecessors(plan)
|
260
|
+
# search primary operators (the last operators that provide the goal)
|
261
|
+
primary_ops = []
|
262
|
+
@goal.each do |var|
|
263
|
+
(plan.length-1).downto(0) do |i|
|
264
|
+
if plan[i].support_assignment?(var, var.goal)
|
265
|
+
primary_ops << [var, var.goal, i, plan[i]]
|
266
|
+
break;
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
# search adversary operators (all operators that threat the goal)
|
271
|
+
primary_ops.each do |variable, value, last_index, operator|
|
272
|
+
(last_index-1).downto(0) do |j|
|
273
|
+
next if operator == plan[j]
|
274
|
+
operator.predecessors << plan[j] if plan[j].threat_assignment?(variable, value)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
#self.dump_predecessors(plan)
|
278
|
+
end
|
279
|
+
|
280
|
+
# given a state, find the first applicable "global" operator
|
281
|
+
def is_state_satisfy_global?(state)
|
282
|
+
@global_operators.each { |op| return op if state.applicable?(op) }
|
283
|
+
return nil
|
284
|
+
end
|
285
|
+
|
286
|
+
def plan_with_trajectory_to_partial_order
|
287
|
+
begin
|
288
|
+
# get the operators
|
289
|
+
plan = collect_operators
|
290
|
+
|
291
|
+
# supporting operators
|
292
|
+
set_predecessors(plan)
|
293
|
+
|
294
|
+
# adversary predecessors
|
295
|
+
add_adversary_predecessors(plan)
|
296
|
+
|
297
|
+
# fill-in blank-precondition (pre := -1) by simulating the plan
|
298
|
+
states = simulate_plan(plan)
|
299
|
+
|
300
|
+
# generate the stage-workflow
|
301
|
+
stages, states = to_stage_workflow(plan, states)
|
302
|
+
#puts '==> failed' if stages.nil?
|
303
|
+
return nil if stages.nil?
|
304
|
+
|
305
|
+
# generate the partial-order workflow
|
306
|
+
if not stages.nil?
|
307
|
+
to_partial_order_workflow(stages, states)
|
308
|
+
end
|
309
|
+
|
310
|
+
#stages.each { |stage|
|
311
|
+
# puts "-- stage -- "
|
312
|
+
# stage.each { |op| puts op.name }
|
313
|
+
#}
|
314
|
+
|
315
|
+
remove_transitive_predecessors(plan)
|
316
|
+
|
317
|
+
plan = remove_sometime_operators(plan)
|
318
|
+
|
319
|
+
return plan
|
320
|
+
|
321
|
+
rescue Exception => e
|
322
|
+
$stderr.puts e.to_s
|
323
|
+
$stderr.puts e.backtrace
|
324
|
+
end
|
325
|
+
return nil
|
326
|
+
end
|
327
|
+
|
328
|
+
def remove_sometime_operators(plan, parallel=true)
|
329
|
+
sometimes = []
|
330
|
+
plan.each { |op| sometimes << op if Task.is_sometime_operator?(op.name) }
|
331
|
+
plan.delete_if { |op| sometimes.include?(op) }
|
332
|
+
plan.each do |op|
|
333
|
+
sometimes.each do |op1|
|
334
|
+
index = op.predecessors.index(op1)
|
335
|
+
if not index.nil?
|
336
|
+
op1.predecessors.each { |op2|
|
337
|
+
op.predecessors << op2 if plan.include?(op2)
|
338
|
+
}
|
339
|
+
op1.predecessors.delete_at(index)
|
340
|
+
op1.predecessors.uniq!
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
plan
|
345
|
+
end
|
346
|
+
|
347
|
+
def set_global(state, valid=true)
|
348
|
+
if valid
|
349
|
+
state[@global_variable.index] = (@init[@global_variable.index] == 0 ? 1 : 0)
|
350
|
+
else
|
351
|
+
state[@global_variable.index] = @init[@global_variable.index]
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def to_partial_order_workflow(stages, states)
|
356
|
+
# splitting -- add required predecessor-links between operators of two stages
|
357
|
+
(stages.length-1).downto(1) do |i|
|
358
|
+
current_stage = stages[i]
|
359
|
+
prev_stage = stages[i-1]
|
360
|
+
prev_prev_state = states[i-1]
|
361
|
+
current_stage.each do |op1|
|
362
|
+
prev_stage.each do |op2|
|
363
|
+
if op1.predecessors.index(op2).nil?
|
364
|
+
new_state = prev_prev_state.clone
|
365
|
+
# generate the next state if "op2" is not included
|
366
|
+
prev_stage.each { |op3| new_state.apply!(op3) if op3 != op2 }
|
367
|
+
self.set_global(new_state, false)
|
368
|
+
if self.is_state_satisfy_global?(new_state).nil?
|
369
|
+
op1.predecessors << op2
|
370
|
+
else
|
371
|
+
new_state.apply!(op1)
|
372
|
+
self.set_global(new_state, false)
|
373
|
+
if self.is_state_satisfy_global?(new_state).nil?
|
374
|
+
op1.predecessors << op2
|
375
|
+
else
|
376
|
+
#puts op2.name + " || " + op1.name
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
has_predecessor = false
|
383
|
+
prev_stage.each { |op2|
|
384
|
+
has_predecessor = (not op1.predecessors.index(op2).nil?)
|
385
|
+
break if has_predecessor
|
386
|
+
}
|
387
|
+
if not has_predecessor
|
388
|
+
promotion = false
|
389
|
+
if prev_prev_state.applicable?(op1) and not prev_stage.threaten?(op1)
|
390
|
+
new_state = prev_prev_state.apply(op1)
|
391
|
+
self.set_global(new_state, false)
|
392
|
+
promotion = (not self.is_state_satisfy_global?(new_state).nil?)
|
393
|
+
end
|
394
|
+
|
395
|
+
if promotion
|
396
|
+
prev_stage << op1
|
397
|
+
else
|
398
|
+
prev_stage.each { |op2| op1.predecessors << op2 }
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# promotion
|
405
|
+
end
|
406
|
+
|
407
|
+
# TODO -- Fix bugs
|
408
|
+
def to_stage_workflow(plan, states)
|
409
|
+
begin
|
410
|
+
current_state = states[states.length-1].clone
|
411
|
+
operators = plan.reverse
|
412
|
+
counter = 0
|
413
|
+
stages = []
|
414
|
+
states = []
|
415
|
+
|
416
|
+
while not current_state.equals?(@init) and operators.length > 0 and counter < plan.length
|
417
|
+
selected_ops, current_state = search_supporting_operators(current_state, operators)
|
418
|
+
if selected_ops.length > 0
|
419
|
+
stages << selected_ops
|
420
|
+
selected_ops.each { |op| operators.delete(op) }
|
421
|
+
states << current_state
|
422
|
+
end
|
423
|
+
counter += 1
|
424
|
+
end
|
425
|
+
|
426
|
+
begin
|
427
|
+
#current_state.each_index { |i|
|
428
|
+
# next if current_state[i] == @init[i]
|
429
|
+
# puts i.to_s + ' ' + current_state[i].to_s + ' != ' + @init[i].to_s
|
430
|
+
#}
|
431
|
+
#puts current_state.inspect
|
432
|
+
#puts @init.inspect
|
433
|
+
return nil, nil
|
434
|
+
end if not current_state.equals?(@init)
|
435
|
+
return stages.reverse, states.reverse
|
436
|
+
rescue Exception => e
|
437
|
+
$stderr.puts e.to_s
|
438
|
+
$stderr.puts e.backtrace
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def search_supporting_operators(state, operators) #, selected_operators)
|
443
|
+
selected_operators = OperatorLayer.new
|
444
|
+
not_candidates = OperatorLayer.new
|
445
|
+
candidates = OperatorLayer.new
|
446
|
+
operators.each do |op|
|
447
|
+
if state.applicable_reverse?(op) and
|
448
|
+
not candidates.threat?(op) and
|
449
|
+
not candidates.threat_reverse?(op) and
|
450
|
+
not candidates.require?(op)
|
451
|
+
candidates << op
|
452
|
+
else
|
453
|
+
not_candidates << op
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
result_state = state.clone
|
458
|
+
candidates.each do |op|
|
459
|
+
if not_candidates.require?(op)
|
460
|
+
not_candidates << op
|
461
|
+
else
|
462
|
+
new_state = state.apply_reverse(op)
|
463
|
+
self.set_global(new_state, false)
|
464
|
+
if not self.is_state_satisfy_global?(new_state).nil?
|
465
|
+
new_result_state = result_state.apply_reverse(op)
|
466
|
+
self.set_global(new_result_state, false)
|
467
|
+
if not self.is_state_satisfy_global?(new_result_state).nil?
|
468
|
+
result_state = new_result_state
|
469
|
+
selected_operators << op
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
return selected_operators, result_state
|
475
|
+
end
|
476
|
+
|
477
|
+
|
478
|
+
def simulate_plan(plan)
|
479
|
+
state = @init.clone
|
480
|
+
self.set_global(state, true)
|
481
|
+
states = [state.clone]
|
482
|
+
plan.each do |op|
|
483
|
+
op.pre_posts.each { |p| p[:pre] = state[p[:var].index] if p[:pre] < 0 }
|
484
|
+
state.apply!(op)
|
485
|
+
state.apply!(op.global_op)
|
486
|
+
states.push(state.clone)
|
487
|
+
end
|
488
|
+
return states
|
489
|
+
end
|
490
|
+
|
491
|
+
# Return the partial-order plan from a total-order plan kept in @sas_plan
|
492
|
+
# with an assumption that the planning task does not have trajectory constraints
|
493
|
+
#
|
494
|
+
# @return - a partial-order plan
|
495
|
+
def plan_without_trajectory_to_partial_order
|
496
|
+
# get the operators
|
497
|
+
plan = collect_operators
|
498
|
+
|
499
|
+
# supporting operators
|
500
|
+
set_predecessors(plan)
|
501
|
+
#self.dump_predecessors(plan)
|
502
|
+
|
503
|
+
# add adversary predecessors
|
504
|
+
add_adversary_predecessors(plan)
|
505
|
+
|
506
|
+
# remove transitive predecessors
|
507
|
+
remove_transitive_predecessors(plan)
|
508
|
+
|
509
|
+
plan = remove_sometime_operators(plan)
|
510
|
+
|
511
|
+
return plan
|
512
|
+
end
|
513
|
+
|
514
|
+
def dump_predecessors(plan)
|
515
|
+
puts "=== predecessors ==="
|
516
|
+
plan.each { |op|
|
517
|
+
puts op.name
|
518
|
+
op.predecessors.each { |op2| puts "\t" + op2.name }
|
519
|
+
}
|
520
|
+
end
|
521
|
+
|
522
|
+
# Test if the given state contains goal or not
|
523
|
+
# @param - state: the state to be tested
|
524
|
+
# @return True if the given state contains goal, otherwise False
|
525
|
+
def at_goal?(state, details=false)
|
526
|
+
# TODO -- test it
|
527
|
+
if not details
|
528
|
+
@goal.each { |var| return false if state[var.index] != var.goal }
|
529
|
+
return true
|
530
|
+
else
|
531
|
+
valid = true
|
532
|
+
@goal.each do |var|
|
533
|
+
if state[var.index] != var.goal
|
534
|
+
valid = false
|
535
|
+
end
|
536
|
+
end
|
537
|
+
return valid
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
class OperatorLayer < Array
|
543
|
+
def applied_to(state)
|
544
|
+
self.each { |op| return false if not state.apply!(op) }
|
545
|
+
return true
|
546
|
+
end
|
547
|
+
|
548
|
+
def applied_reverse_to(state)
|
549
|
+
self.each { |op| state.apply_reverse!(op) }
|
550
|
+
end
|
551
|
+
|
552
|
+
def threaten?(operator)
|
553
|
+
self.each { |op| return true if operator.threat?(op) }
|
554
|
+
return false
|
555
|
+
end
|
556
|
+
|
557
|
+
def threat?(operator)
|
558
|
+
self.each { |op| return true if op.threat?(operator) }
|
559
|
+
return false
|
560
|
+
end
|
561
|
+
|
562
|
+
def threat_reverse?(operator)
|
563
|
+
self.each { |op| return true if op.threat_reverse?(operator) }
|
564
|
+
return false
|
565
|
+
end
|
566
|
+
|
567
|
+
def support?(operator)
|
568
|
+
self.each { |op| return true if op.support?(operator) }
|
569
|
+
return false
|
570
|
+
end
|
571
|
+
|
572
|
+
# an operator is required iff there's a casual to any operator in this layer
|
573
|
+
def require?(operator)
|
574
|
+
self.each { |op| return true if not op.predecessors.index(operator).nil? }
|
575
|
+
return false
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
class Operator
|
580
|
+
attr_reader :name, :prevails, :pre_posts, :cost, :is_global
|
581
|
+
attr_accessor :predecessors # all-operators that preceed 'self'
|
582
|
+
attr_accessor :global_op
|
583
|
+
attr_accessor :sfw_operator
|
584
|
+
|
585
|
+
def initialize(params={})
|
586
|
+
if params.has_key?(:io)
|
587
|
+
self.read_io(params[:io], params[:variables])
|
588
|
+
else
|
589
|
+
self.read_parameters(params)
|
590
|
+
end
|
591
|
+
@predecessors = []
|
592
|
+
@is_global = Task.is_global_operator?(@name)
|
593
|
+
end
|
594
|
+
|
595
|
+
def has_transitive_predecessors?(operator)
|
596
|
+
@predecessors.each do |pre_op|
|
597
|
+
return true if pre_op != operator and
|
598
|
+
pre_op.has_predecessor?(operator)
|
599
|
+
end
|
600
|
+
return false
|
601
|
+
end
|
602
|
+
|
603
|
+
def has_predecessor?(operator)
|
604
|
+
if not @predecessors.index(operator).nil?
|
605
|
+
return true
|
606
|
+
else
|
607
|
+
@predecessors.each { |op| return true if op.has_predecessor?(operator) }
|
608
|
+
end
|
609
|
+
return false
|
610
|
+
end
|
611
|
+
|
612
|
+
def support?(operator)
|
613
|
+
# TODO -- test it
|
614
|
+
@pre_posts.each do |p1|
|
615
|
+
operator.prevails.each { |p2|
|
616
|
+
return true if p1[:var] == p2[:var] and
|
617
|
+
p1[:post] == p2[:prevail]
|
618
|
+
}
|
619
|
+
operator.pre_posts.each { |p2|
|
620
|
+
return true if p2[:pre] >= 0 and
|
621
|
+
p1[:var] == p2[:var] and
|
622
|
+
p1[:post] == p2[:pre]
|
623
|
+
}
|
624
|
+
end
|
625
|
+
return false
|
626
|
+
end
|
627
|
+
|
628
|
+
def threatened_by_assignment?(variable, value)
|
629
|
+
return false if value < 0
|
630
|
+
@prevails.each { |p| return true if p[:var] == variable and p[:prevail] != value }
|
631
|
+
@pre_posts.each { |p| return true if p[:var] == variable and p[:pre] != value }
|
632
|
+
return false
|
633
|
+
end
|
634
|
+
|
635
|
+
def support_assignment?(variable, value)
|
636
|
+
return true if value < 0
|
637
|
+
@pre_posts.each do |p|
|
638
|
+
return true if p[:var] == variable and p[:post] == value
|
639
|
+
end
|
640
|
+
return false
|
641
|
+
end
|
642
|
+
|
643
|
+
def threat_assignment?(variable, value)
|
644
|
+
return false if value < 0
|
645
|
+
@pre_posts.each do |p|
|
646
|
+
return true if p[:var] == variable and p[:post] != value
|
647
|
+
end
|
648
|
+
return false
|
649
|
+
end
|
650
|
+
|
651
|
+
def threat_prevails?(prevails)
|
652
|
+
return false if prevails.length <= 0 or pre_posts.length <= 0
|
653
|
+
prevails.each do |p1|
|
654
|
+
@pre_posts.each do |p2|
|
655
|
+
if p1[:var] == p2[:var]
|
656
|
+
#begin puts p1[:var].name + '>>' + p1[:prevail].to_s + ' ' + p2[:post].to_s; return true; end if p1[:prevail] != p2[:post]
|
657
|
+
return true if p1[:prevail] != p2[:post]
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
return false
|
662
|
+
end
|
663
|
+
|
664
|
+
def mutex?(operator)
|
665
|
+
# TODO -- test it
|
666
|
+
@prevails.each do |p1|
|
667
|
+
operator.pre_posts.each { |p2| return false if p1[:var] == p2[:var] }
|
668
|
+
end
|
669
|
+
@pre_posts.each do |p1|
|
670
|
+
operator.prevails.each { |p2| return false if p1[:var] == p2[:var] }
|
671
|
+
operator.pre_posts.each { |p2| return false if p1[:var] == p2[:var] and p1[:post] != p2[:post] }
|
672
|
+
end
|
673
|
+
return true
|
674
|
+
end
|
675
|
+
|
676
|
+
def threat?(operator)
|
677
|
+
@pre_posts.each do |p1|
|
678
|
+
operator.prevails.each { |p2|
|
679
|
+
if p1[:var] == p2[:var] and p1[:post] != p2[:prevail]
|
680
|
+
#puts "\t" + self.name + " -- " + operator.name
|
681
|
+
#puts "\t" + p1[:var].name + " p1:" + p1[:post].to_s + " p2:" + p2[:prevail].to_s
|
682
|
+
return true
|
683
|
+
end
|
684
|
+
}
|
685
|
+
operator.pre_posts.each { |p2| return true if p1[:var] == p2[:var] and p1[:post] != p2[:post] }
|
686
|
+
end
|
687
|
+
return false
|
688
|
+
end
|
689
|
+
|
690
|
+
def threat_reverse?(operator)
|
691
|
+
@pre_posts.each do |p1|
|
692
|
+
operator.prevails.each { |p2|
|
693
|
+
return true if p1[:var] == p2[:var] and p1[:pre] != p2[:prevail]
|
694
|
+
}
|
695
|
+
operator.pre_posts.each { |p2|
|
696
|
+
return true if p1[:var] == p2[:var] and p1[:pre] != p2[:pre]
|
697
|
+
}
|
698
|
+
end
|
699
|
+
return false
|
700
|
+
end
|
701
|
+
|
702
|
+
def remove_global_variable(var)
|
703
|
+
@pre_posts.delete_if { |p| p[:var] == var }
|
704
|
+
end
|
705
|
+
|
706
|
+
def write_io(io)
|
707
|
+
io.puts('begin_operator', @name, @prevails.length.to_s)
|
708
|
+
@prevails.each { |p| io.puts(p[:var].index.to_s + ' ' + p[:prevail].to_s) }
|
709
|
+
io.puts(@pre_posts.length.to_s)
|
710
|
+
@pre_posts.each { |p| io.puts('0 ' + p[:var].index.to_s + ' ' + p[:pre].to_s + ' ' + p[:post].to_s) }
|
711
|
+
io.puts(@cost, 'end_operator')
|
712
|
+
end
|
713
|
+
|
714
|
+
def dump
|
715
|
+
puts @name
|
716
|
+
@prevails.each { |p| print p[:var].index.to_s + '=' + p[:prevail].to_s + ";" }
|
717
|
+
puts '' if @prevails.length > 0
|
718
|
+
@pre_posts.each { |p| print p[:var].index.to_s + '=' + p[:pre].to_s + ',' + p[:post].to_s + ';' }
|
719
|
+
puts ''
|
720
|
+
#puts @cost
|
721
|
+
end
|
722
|
+
|
723
|
+
protected
|
724
|
+
def read_io(io, variables)
|
725
|
+
io.gets # begin_operator
|
726
|
+
@name = io.gets.chop
|
727
|
+
@prevails = []
|
728
|
+
for j in 1..io.gets.to_i
|
729
|
+
tuple = io.gets.split(' ')
|
730
|
+
@prevails << { :var => variables[tuple[0].to_i],
|
731
|
+
:prevail => tuple[1].to_i }
|
732
|
+
end
|
733
|
+
@pre_posts = []
|
734
|
+
for j in 1..io.gets.to_i
|
735
|
+
tuple = io.gets.split(' ')
|
736
|
+
@pre_posts << { :var => variables[tuple[1].to_i],
|
737
|
+
:pre => tuple[2].to_i,
|
738
|
+
:post => tuple[3].to_i }
|
739
|
+
end
|
740
|
+
@cost = io.gets.to_i
|
741
|
+
io.gets # end_operator
|
742
|
+
end
|
743
|
+
|
744
|
+
def read_parameters(params) #name, prevails, pre_posts, cost, is_global=false)
|
745
|
+
@name = params[:name]
|
746
|
+
@prevails = params[:prevails]
|
747
|
+
@pre_posts = params[:pre_posts]
|
748
|
+
@cost = params[:cost]
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
class Axiom < Operator
|
753
|
+
def initialize(params={})
|
754
|
+
if params.has_key?(:io)
|
755
|
+
self.read_io(params[:io], params[:variables])
|
756
|
+
else
|
757
|
+
self.read_parameters(params)
|
758
|
+
end
|
759
|
+
@predecessors = []
|
760
|
+
@is_global = false
|
761
|
+
end
|
762
|
+
|
763
|
+
def write_io(io)
|
764
|
+
io.puts('begin_rule', @prevails.length)
|
765
|
+
@prevails.each { |p| io.puts(p[:var].index.to_s + ' ' + p[:prevail].to_s) }
|
766
|
+
@pre_posts.each { |p| io.puts(p[:var].index.to_s + ' ' + p[:pre].to_s + ' ' + p[:post].to_s) }
|
767
|
+
io.puts('end_rule')
|
768
|
+
end
|
769
|
+
|
770
|
+
protected
|
771
|
+
def read_io(io, variables)
|
772
|
+
io.gets # begin_rule
|
773
|
+
@name = :axiom
|
774
|
+
prevails = []
|
775
|
+
for j in 1..io.gets.to_i
|
776
|
+
tuple = io.gets.split(' ')
|
777
|
+
prevails << { :var => variables[tuple[0].to_i],
|
778
|
+
:prevail => tuple[1].to_i }
|
779
|
+
end
|
780
|
+
pre_posts = []
|
781
|
+
tuple = io.gets.split(' ')
|
782
|
+
pre_posts << { :var => variables[tuple[0].to_i],
|
783
|
+
:pre => tuple[1].to_i,
|
784
|
+
:post => tuple[2].to_i }
|
785
|
+
io.gets # end_rule
|
786
|
+
end
|
787
|
+
|
788
|
+
def read_parameters(params) #name, prevails, pre_posts)
|
789
|
+
@name = params[:name]
|
790
|
+
@prevails = params[:prevails]
|
791
|
+
@pre_posts = params[:pre_posts]
|
792
|
+
@cost = 0
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
class State < Array
|
797
|
+
def write_io(io)
|
798
|
+
io.puts('begin_state')
|
799
|
+
for i in 0..(self.length-1)
|
800
|
+
io.puts(self[i])
|
801
|
+
end
|
802
|
+
io.puts('end_state')
|
803
|
+
end
|
804
|
+
|
805
|
+
def apply_reverse!(operator, do_check=false)
|
806
|
+
return false if do_check and not self.applicable_reverse?(operator)
|
807
|
+
operator.pre_posts.each { |p| return false if p[:pre] < 0 }
|
808
|
+
operator.pre_posts.each { |p| self[p[:var].index] = p[:pre] }
|
809
|
+
return true
|
810
|
+
end
|
811
|
+
|
812
|
+
def apply_reverse(operator, do_check=false)
|
813
|
+
return nil if do_check and not self.applicable_reverse?(operator)
|
814
|
+
state = self.clone
|
815
|
+
state.apply_reverse!(operator)
|
816
|
+
return state
|
817
|
+
end
|
818
|
+
|
819
|
+
def apply!(operator, do_check=false)
|
820
|
+
return false if do_check and not self.applicable?(operator)
|
821
|
+
operator.pre_posts.each { |p| self[p[:var].index] = p[:post] }
|
822
|
+
return true
|
823
|
+
end
|
824
|
+
|
825
|
+
def apply(operator, do_check=false)
|
826
|
+
return nil if do_check and not self.applicable?(operator)
|
827
|
+
state = self.clone
|
828
|
+
state.apply!(operator)
|
829
|
+
return state
|
830
|
+
end
|
831
|
+
|
832
|
+
def applicable_reverse?(operator)
|
833
|
+
operator.prevails.each { |p| return false if self[p[:var].index] != p[:prevail] }
|
834
|
+
operator.pre_posts.each { |p| return false if self[p[:var].index] != p[:post] }
|
835
|
+
return true
|
836
|
+
end
|
837
|
+
|
838
|
+
def applicable?(operator)
|
839
|
+
operator.prevails.each { |p| return false if self[p[:var].index] != p[:prevail] }
|
840
|
+
operator.pre_posts.each { |p| return false if p[:pre] >= 0 and self[p[:var].index] != p[:pre] }
|
841
|
+
return true
|
842
|
+
end
|
843
|
+
|
844
|
+
def clone
|
845
|
+
state = State.new
|
846
|
+
for i in 0..(self.length-1)
|
847
|
+
state[i] = self[i]
|
848
|
+
end
|
849
|
+
return state
|
850
|
+
end
|
851
|
+
|
852
|
+
def satisfy?(goal)
|
853
|
+
goal.each { |var| return false if self[var.index].to_i != var.goal.to_i }
|
854
|
+
return true
|
855
|
+
end
|
856
|
+
|
857
|
+
def equals?(state)
|
858
|
+
return false if state.length != self.length
|
859
|
+
for i in 0..(self.length-1)
|
860
|
+
return false if self[i] != state[i]
|
861
|
+
end
|
862
|
+
return true
|
863
|
+
end
|
864
|
+
|
865
|
+
def dump
|
866
|
+
for i in 0..(self.length-1)
|
867
|
+
print i.to_s + '=' + self[i].to_s + ','
|
868
|
+
end
|
869
|
+
puts ''
|
870
|
+
end
|
871
|
+
end
|
872
|
+
|
873
|
+
class Goal < Array
|
874
|
+
def initialize(params={})
|
875
|
+
if params.has_key?(:io)
|
876
|
+
self.read_io(params[:io], params[:variables])
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
def write_io(io)
|
881
|
+
io.puts('begin_goal')
|
882
|
+
io.puts(self.length)
|
883
|
+
self.each { |var| io.puts(var.index.to_s + ' ' + var.goal.to_s) }
|
884
|
+
io.puts('end_goal')
|
885
|
+
end
|
886
|
+
|
887
|
+
def dump
|
888
|
+
self.each { |var| print var.index.to_s + '=' + var.goal.to_s + ',' }
|
889
|
+
puts ''
|
890
|
+
end
|
891
|
+
|
892
|
+
protected
|
893
|
+
def read_io(io, variables)
|
894
|
+
io.gets # begin_goal
|
895
|
+
total = io.gets.to_i
|
896
|
+
for i in 1..total
|
897
|
+
tuple = io.gets.split(' ')
|
898
|
+
variable = variables[tuple[0].to_i]
|
899
|
+
variable.goal = tuple[1].to_i
|
900
|
+
self << variable
|
901
|
+
end
|
902
|
+
io.gets # end_goal
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
class Prevail
|
907
|
+
attr_accessor :var, :prevail
|
908
|
+
end
|
909
|
+
|
910
|
+
class PrePost
|
911
|
+
attr_accessor :var, :pre, :post
|
912
|
+
end
|
913
|
+
|
914
|
+
class Variable
|
915
|
+
attr_accessor :init, :goal
|
916
|
+
attr_reader :name, :axiom_layer, :length, :index, :is_global, :is_goal
|
917
|
+
|
918
|
+
def initialize(params={})
|
919
|
+
if params.has_key?(:io)
|
920
|
+
self.read_io(params[:io])
|
921
|
+
else
|
922
|
+
self.read_parameters(params)
|
923
|
+
end
|
924
|
+
@is_global = Task::is_global_variable?(@name)
|
925
|
+
@is_goal = Task::is_goal_variable?(@name)
|
926
|
+
@index = params[:index]
|
927
|
+
end
|
928
|
+
|
929
|
+
def write_io(io)
|
930
|
+
io.puts('begin_variable')
|
931
|
+
io.puts(@name)
|
932
|
+
io.puts(@axiom_layer.to_s)
|
933
|
+
io.puts(@length.to_s)
|
934
|
+
for i in 0..(@length-1)
|
935
|
+
io.puts(i)
|
936
|
+
end
|
937
|
+
io.puts('end_variable')
|
938
|
+
end
|
939
|
+
|
940
|
+
protected
|
941
|
+
def read_io(io)
|
942
|
+
io.gets # begin_variable
|
943
|
+
@name = io.gets.chop
|
944
|
+
@axiom_layer = io.gets.to_i
|
945
|
+
@length = io.gets.to_i
|
946
|
+
for j in 1..@length
|
947
|
+
io.gets
|
948
|
+
end
|
949
|
+
io.gets # end_variable
|
950
|
+
end
|
951
|
+
|
952
|
+
def read_parameters(params)
|
953
|
+
@name = params[:name]
|
954
|
+
@axiom_layer = params[:axiom_layer]
|
955
|
+
@length = params[:length]
|
956
|
+
@init = @goal = -1
|
957
|
+
@is_global = false
|
958
|
+
end
|
959
|
+
end
|
960
|
+
|
961
|
+
end
|
962
|
+
end
|
963
|
+
|
964
|
+
if __FILE__ == $0
|
965
|
+
sas = Nuri::Sas::Task.new($1)
|
966
|
+
end
|