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