sfplanner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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