precedence 0.6.0 → 0.8.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.
@@ -1,3 +1,5 @@
1
+ require('yaml')
2
+
1
3
  module Precedence
2
4
 
3
5
  # A representation of an activity in a precedence network. Each activity
@@ -6,13 +8,30 @@ module Precedence
6
8
  # other properties of the activity can be determined such as it's earliest
7
9
  # finish time, float and so on.
8
10
  #
9
-
10
- class Activity
11
+ # The activity also has a probabilistic component.
12
+ class Activity
13
+
14
+ MEAN_DURATION = :mean_duration
15
+ EXPECTED_DURATION = :expected_duration
16
+ MINIMUM_DURATION = :minimum_duration
17
+ MAXIMUM_DURATION = :maximum_duration
11
18
 
19
+ @duration_type = EXPECTED_DURATION
20
+
21
+ EARLIEST_START = :earliest_start
22
+ LATEST_START = :latest_start
23
+ DROP_START = :drop_start
24
+
25
+ @start_type = EARLIEST_START
26
+
12
27
  # A textual description of the activity.
13
28
  attr_accessor :description
14
- # The time the activity will take to complete.
15
- attr_accessor :duration
29
+ # The expected duration of the activity
30
+ attr_accessor :expected_duration
31
+ # The minimum duration of the activity
32
+ attr_accessor :minimum_duration
33
+ # The maximum duration of the activity
34
+ attr_accessor :maximum_duration
16
35
  # A unique reference for this activity.
17
36
  attr_reader :reference
18
37
  # The collection of activites that are dependent on the completion of
@@ -21,90 +40,147 @@ module Precedence
21
40
  # The collection of activities that this activity depends on before it
22
41
  # can begin executing.
23
42
  attr_reader :pre_activities
24
-
25
- # Creates a new activity. The post_activities and pre_activities
26
- # collections should contain the activities themselves and not their
27
- # references.
43
+ # A collection of resources that are used by this activitity. Stating
44
+ # that
45
+ # activity.resources['concrete'] = 5
46
+ # indicates that the activity will use 5 units of concrete per time
47
+ # period.
48
+ attr_reader :resources
49
+ # Sets what the duration method should return. Can be set to one of:
50
+ # * EXPECTED_DURATION
51
+ # * MAXIMUM_DURATION
52
+ # * MINIMUM_DURATION
53
+ # * MEAN_DURATION
54
+ # Initially set to the expected duration
55
+ attr_accessor :duration_type
56
+ # Sets what the start attribute should return can be set to one of:
57
+ # * EARLIEST_START
58
+ # * LATEST_START
59
+ # * DROP_START
60
+ # Initially set to the earliest start
61
+ attr_accessor :start_type
62
+
63
+ # Creates a new activity. The only required field is the reference.
64
+ # The description, duration can be set in the block, which will have
65
+ # as a parameter the newly created activity. If they are not set in the
66
+ # block they will have as values "" and 0 respectively. Post- and
67
+ # pre-activities can also be set in the block by using the
68
+ # Activity.add_post_activities and Activity.add_pre_activities methods.
28
69
  #
29
- # *Note*: When assigning a reference for an activity 'start' and
30
- # 'finish' are reserved for internal usage.
31
- def initialize(reference,duration=0,description="",post_activities=[],
32
- pre_activities=[])
33
- if (reference.to_s == 'start') || (reference.to_s == 'finish')
34
- raise "The reference '#{reference.to_s}' is reserved"
70
+ # *Note*: When assigning a reference for an activity
71
+ # StartActivity::REFERENCE and FinishActivity::REFERENCE are reserved
72
+ # for internal usage.
73
+ def initialize(reference, &block)
74
+ reference.respond_to?(:to_s) ? nil : raise("Parameter reference"+
75
+ "must respond to 'to_s'.")
76
+ reference = reference.to_s
77
+ if ((reference != Precedence::StartActivity::REFERENCE) &&
78
+ (reference != Precedence::FinishActivity::REFERENCE))
79
+ @reference = reference.to_s
80
+ else
81
+ raise "Activity reference '#{reference}' is reserved."
35
82
  end
36
- @reference = reference.to_s
37
- @description = description.to_s
38
- @duration = duration.to_f
39
- @post_activities = post_activities.to_a
40
- @pre_activities = pre_activities.to_a
83
+
84
+ @description = ""
85
+ @post_activities = []
86
+ @pre_activities = []
87
+ @resources = Precedence::Utilities::ResourceHash.new
88
+ @expected_duration = 0
89
+ @minimum_duration = 0
90
+ @maximum_duration = 0
91
+ @duration_type = EXPECTED_DURATION
92
+ @start_type = EARLIEST_START
93
+
94
+ # Call the block if it is present
95
+ block ? block.call(self) : nil
96
+
97
+ # Type conversion
98
+ @expected_duration = @expected_duration.to_f
99
+ @minimum_duration = @minimum_duration.to_f
100
+ @maximum_duration = @maximum_duration.to_f
41
101
  end
42
-
102
+
43
103
  # Adds the activities in the parameter list to the post_activities
44
104
  # collection of this activity and also adds this activity to the
45
105
  # pre_activities collection of each of the activities.
46
- def add_post_activities(*post_activities)
47
- post_activities.each do |activity|
106
+ #
107
+ # *Note*: When using the Network class it is better to use
108
+ # Network.connect than Activity.add_pre_activities or
109
+ # Activity.add_post_activities directly.
110
+ def add_post_activities(*post_acts) #:nodoc:
111
+ post_acts.flatten!
112
+ post_acts.each do |activity|
48
113
  activity.register_pre_activity(self)
49
114
  register_post_activity(activity)
50
115
  end
116
+ return post_acts
51
117
  end
52
118
 
53
119
  # Adds the activities in the parameter list to the pre_activities
54
120
  # collection of this activity and also adds this activity to the
55
121
  # post_activities collection of each of the activities.
56
- def add_pre_activities(*pre_activities)
57
- pre_activities.each do |activity|
122
+ #
123
+ # *Note*: When using the Network class it is better to use
124
+ # Network.connect than Activity.add_pre_activities or
125
+ # Activity.add_post_activities directly.
126
+ def add_pre_activities(*pre_acts) #:nodoc:
127
+ pre_acts.flatten!
128
+ pre_acts.each do |activity|
58
129
  activity.register_post_activity(self)
59
130
  register_pre_activity(activity)
60
131
  end
132
+ return pre_acts
61
133
  end
62
134
 
63
135
  # Removes the list of activities from the post_activities collection of
64
136
  # the activity.
65
- def remove_post_activities(*post_activities)
66
- post_activities.each do |activity|
137
+ def remove_post_activities(*post_acts) #:nodoc:
138
+ post_acts.flatten!
139
+ post_acts.each do |activity|
67
140
  activity.deregister_pre_activity(self)
68
141
  deregister_post_activity(activity)
69
- end
142
+ end
143
+ return post_acts
70
144
  end
71
145
 
72
146
  # Removes the list of activities from the pre_activities collection of
73
147
  # the activity.
74
- def remove_pre_activities(*post_activities)
75
- post_activities.each do |activity|
148
+ def remove_pre_activities(*pre_acts) #:nodoc:
149
+ pre_acts.flatten!
150
+ pre_acts.each do |activity|
76
151
  activity.deregister_post_activity(self)
77
152
  deregister_pre_activity(activity)
78
153
  end
154
+ return pre_acts
79
155
  end
80
-
156
+
81
157
  # The earliest possible time this activity can finish.
82
158
  def earliest_finish
83
- return @duration + earliest_start
159
+ return earliest_start + duration
84
160
  end
85
161
 
86
162
  # The earliest possible time this activity can start.
87
163
  def earliest_start
88
- unless @pre_activities.empty?
89
- (@pre_activities.max do |a,b|
164
+ unless self.pre_activities.empty?
165
+ return (pre_activities.max do |a,b|
90
166
  a.earliest_finish <=> b.earliest_finish
91
167
  end).earliest_finish
92
168
  else
93
- 0.0
169
+ return 0.0
94
170
  end
95
171
  end
96
-
172
+
97
173
  # The latest possible time this activity can start so as not to delay
98
174
  # any dependent activities.
99
175
  def latest_start
100
- return latest_finish - @duration
176
+ return latest_finish - duration
101
177
  end
102
178
 
103
179
  # The latest possible time this activity can finish so as not to delay
104
180
  # any dependent activities.
105
181
  def latest_finish
106
- unless @post_activities.empty?
107
- (@post_activities.min do |a,b|
182
+ unless post_activities.empty?
183
+ return (post_activities.min do |a,b|
108
184
  a.latest_start <=> b.latest_start
109
185
  end).latest_start
110
186
  else
@@ -113,9 +189,9 @@ module Precedence
113
189
  end
114
190
 
115
191
  # The maximum earliest finish of this activities pre-activities.
116
- def pre_activities_max_earliest_finish
117
- unless @pre_activities.empty?
118
- (@pre_activities.max do |a,b|
192
+ def pre_activities_max_earliest_finish #:nodoc:
193
+ unless pre_activities.empty?
194
+ return (pre_activities.max do |a,b|
119
195
  a.earliest_finish <=> b.earliest_finish
120
196
  end).earliest_finish
121
197
  else
@@ -124,9 +200,9 @@ module Precedence
124
200
  end
125
201
 
126
202
  # The minimum earliest start of this activities post-activities.
127
- def post_activities_min_earliest_start
128
- unless @post_activities.empty?
129
- (@post_activities.min do |a,b|
203
+ def post_activities_min_earliest_start #:nodoc:
204
+ unless post_activities.empty?
205
+ return (post_activities.min do |a,b|
130
206
  a.earliest_start <=> b.earliest_start
131
207
  end).earliest_start
132
208
  else
@@ -154,46 +230,46 @@ module Precedence
154
230
  # activities start as early as possible.
155
231
  #
156
232
  # *Note*: In almost all practical cases this is the same as if all
157
- # preceding and successing activities start as lates as possible and so
233
+ # preceding and successing activities start as lates as possible and so
158
234
  # no late_float method is defined.
159
235
  def early_float
160
236
  return post_activities_min_earliest_start -
161
- pre_activities_max_earliest_finish - @duration
237
+ pre_activities_max_earliest_finish - duration
162
238
  end
163
239
 
164
240
  # Register this activity as a post-activity on the parameter.
165
241
  def register_post_activity(activity) #:nodoc:
166
- unless (@post_activities.detect do |post_activity|
242
+ unless (post_activities.detect do |post_activity|
167
243
  activity.reference == post_activity.reference
168
244
  end)
169
- @post_activities << activity
245
+ post_activities << activity
170
246
  end
171
247
  end
172
248
 
173
249
  # Register this activity as a pre-activity on the parameter.
174
250
  def register_pre_activity(activity) #:nodoc:
175
- unless (@pre_activities.detect do |pre_activity|
251
+ unless (pre_activities.detect do |pre_activity|
176
252
  activity.reference == pre_activity.reference
177
253
  end)
178
- @pre_activities << activity
254
+ pre_activities << activity
179
255
  end
180
256
  end
181
257
 
182
258
  # Deregister this activity as a post-activity on the parameter.
183
259
  def deregister_post_activity(activity) #:nodoc:
184
- if (@post_activities.detect do |post_activity|
260
+ if (post_activities.detect do |post_activity|
185
261
  activity.reference == post_activity.reference
186
262
  end)
187
- @post_activities.delete(activity)
263
+ post_activities.delete(activity)
188
264
  end
189
265
  end
190
266
 
191
267
  # Deregister this activity as a pre-activity on the parameter.
192
268
  def deregister_pre_activity(activity) #:nodoc:
193
- if (@pre_activities.detect do |pre_activity|
269
+ if (pre_activities.detect do |pre_activity|
194
270
  activity.reference == pre_activity.reference
195
271
  end)
196
- @pre_activities.delete(activity)
272
+ pre_activities.delete(activity)
197
273
  end
198
274
  end
199
275
 
@@ -204,48 +280,321 @@ module Precedence
204
280
 
205
281
  # Redefines the inspect method.
206
282
  def inspect #:nodoc:
207
- "#{@reference}(#{@duration})"
283
+ "#{reference}(#{duration})"
208
284
  end
209
285
 
210
286
  # Redefines the to_s method
211
287
  def to_s #:nodoc:
212
- return "Reference: #{@reference}\n"+
213
- "Description: #{@description}\n"+
214
- "Duration: #{@duration}"+
288
+ return "Reference: #{reference}\n"+
289
+ "Description: #{description}\n"+
290
+ "Duration: #{duration}"+
215
291
  ("\nDepends on:\n " unless @pre_activities.empty?).to_s +
216
292
  (@pre_activities.collect do |activity|
217
293
  activity.reference
218
294
  end).join(',')
219
295
  end
220
296
 
221
- # Priviliege settings
222
- protected :register_post_activity, :register_pre_activity,
223
- :deregister_post_activity, :deregister_pre_activity
297
+ # Returns a YAML document representing the activity object.
298
+ def to_yaml()
299
+ "---\n#{reference}:\n"+
300
+ (description == "" ? "" : " description: #{description}\n")+
301
+ (expected_duration == 0 ? "" : " expected duration: #{expected_duration}\n")+
302
+ (minimum_duration == 0 ? "" : " minimum duration: #{minimum_duration}\n")+
303
+ (maximum_duration == 0 ? "" : " maximum duration: #{maximum_duration}\n")+
304
+ (post_activities.empty? ? "" : " post activities:\n") +
305
+ (post_activities.collect do |activity|
306
+ " - #{activity.reference}"
307
+ end).join("\n")+"\n"+
308
+ (resources.empty? ? "" : " resources:\n")+
309
+ (resources.to_a.collect do |resource,value|
310
+ " #{resource}: #{value}"
311
+ end).join("\n")+"\n"
312
+ end
313
+
314
+ # Returns true if two activity objects have the same duration and
315
+ # reference, false otherwise.
316
+ def eql?(other)
317
+ if (self.reference == other.reference) and
318
+ (self.duration == other.duration)
319
+ return true
320
+ end
321
+ return false
322
+ end
323
+
324
+ alias == eql?
325
+
326
+ # Returns true if the activity is active during the time given.
327
+ #
328
+ # *Note*: If an activity has a start of x and a finish of y, then
329
+ # activity.active_on?(x)
330
+ # will return true, while
331
+ # activity.active_on?(y)
332
+ # will return false.
333
+ def active_on?(time)
334
+ return duration_range === time
335
+ end
336
+
337
+ # Returns true if the activity is active during the range given.
338
+ #
339
+ # *Note*: If a range given includes the last element
340
+ # (range.include_end? == true) it is treated as a range that does
341
+ # not include the last element
342
+ def active_during?(range)
343
+ unless range.exclude_end?
344
+ range = Range.new(range.begin,range.end,true)
345
+ end
224
346
 
225
- private :pre_activities_max_earliest_finish,
226
- :post_activities_min_earliest_start
347
+ return (range === start) ||
348
+ ((finish > range.begin) && (finish < range.end)) ||
349
+ ((start < range.begin) && (finish > range.begin))
350
+
351
+ end
352
+
353
+ # Returns a range object representing the duration of the activity. The
354
+ # range object return will have range.exclude_end? set to true.
355
+ #
356
+ # Example usage:
357
+ # activity.duration_range
358
+ def duration_range
359
+ return Range.new(start,finish,true)
360
+ end
361
+
362
+ # Sets what duration type the activity should use.
363
+ #
364
+ # Example usage:
365
+ # Activity.duration_type = Activity::MEAN_DURATION
366
+ # will change the duration type to the mean duration for all activities.
367
+ def duration_type=(type) #:nodoc:
368
+ case (type)
369
+ when MEAN_DURATION,EXPECTED_DURATION,
370
+ MINIMUM_DURATION, MAXIMUM_DURATION
371
+ @duration_type = type
372
+ else
373
+ raise "Duration type '#{type}' is unknown."
374
+ end
375
+ end
227
376
 
228
-
229
- end
377
+ # Returns the duration of the activity dependent on what the
378
+ def duration
379
+ case(duration_type)
380
+ when MEAN_DURATION
381
+ return mean_duration
382
+ when EXPECTED_DURATION
383
+ return expected_duration
384
+ when MINIMUM_DURATION
385
+ return minimum_duration
386
+ when MAXIMUM_DURATION
387
+ return maximum_duration
388
+ else
389
+ raise "Duration type '#{type}' is unknown."
390
+ end
391
+ end
392
+
393
+ # Returns the mean duration which is defined as
394
+ # (4*expected_duration + minimum_duration + maximum_duration)/6
395
+ def mean_duration
396
+ return ((4*expected_duration)+minimum_duration+maximum_duration)/6.0
397
+ end
398
+
399
+ # The variance of the duration of the activity. Defined as
400
+ # (maximum_duration - minimum_duration)^2/36
401
+ def variance
402
+ return standard_deviation**2
403
+ end
404
+
405
+ # The standard deviation of the duration of the activity dfined as
406
+ # (maximum_duraion - minimum_duration) / 6
407
+ def standard_deviation
408
+ (maximum_duration - minimum_duration)/6.0
409
+ end
410
+
411
+ # Sets start type the activity should use.
412
+ #
413
+ # Example usage:
414
+ # Activity.start_type = Activity::LATEST_START
415
+ # will change the start type to the latest start for all activities.
416
+ def start_type=(type) #:nodoc:
417
+ case(type)
418
+ when EARLIEST_START,LATEST_START,DROP_START
419
+ @start_type = type
420
+ else
421
+ raise "Start type '#{type}' is unknown."
422
+ end
423
+ end
424
+
425
+ # Returns the start time of the activity dependent on what the
426
+ # start_type has been set to.
427
+ def start
428
+ case(start_type)
429
+ when EARLIEST_START
430
+ return self.earliest_start
431
+ when LATEST_START
432
+ return self.latest_start
433
+ when DROP_START
434
+ return self.drop_start
435
+ else
436
+ raise "Start type '#{type}' is unknown."
437
+ end
438
+ end
439
+
440
+ # Returns the finish time of the activity dependent on what the
441
+ # start_type and duration_type have been set to. This is equivalent
442
+ # to calling
443
+ # activity.start + activity.duration
444
+ def finish
445
+ start + duration
446
+ end
447
+
448
+ # Returns an Activity object that is represented by the YAML object.
449
+ # A YAML object is the object returned from a YAML::load or
450
+ # YAML::load_documents method. This method will automatically load
451
+ # a Start/FinishActivity if such a yamlObject is given.
452
+ #
453
+ # *Note*: This method will only restore the reference, varios durations,
454
+ # description and resource parameters. Post-/pre-activities are not
455
+ # restored (they are restored when using Precedence::Network.from_yaml).
456
+ def self.from_yaml_object(yamlObject) #:nodoc:
457
+ reference,activity = yamlObject.to_a[0]
458
+ if (reference.to_s == StartActivity::REFERENCE)
459
+ return StartActivity.from_yaml_object(yamlObject)
460
+ elsif (reference.to_s == FinishActivity::REFERENCE)
461
+ return FinishActivity.from_yaml_object(yamlObject)
462
+ else
463
+ return Precedence::Activity.new(reference.to_s) do |act|
464
+ act.expected_duration = activity['expected duration'].to_f
465
+ act.minimum_duration = activity['minimum duration'].to_f
466
+ act.maximum_duration = activity['maximum duration'].to_f
467
+ act.description = activity['description'].to_s
468
+ if activity['resources']
469
+ activity['resources'].each do |resource,value|
470
+ act.resources[resource] = value
471
+ end
472
+ end
473
+ end
474
+ end
475
+ end
476
+
477
+ # Returns an Activity object that is represented by a YAML document.
478
+ #
479
+ # *Note*: This method will only restore the reference, various durations,
480
+ # description and resource parameters. Post-/pre-activities are not
481
+ # restored (they are restored when using Precedence::Network.from_yaml).
482
+ def self.from_yaml(yaml)
483
+ return self.from_yaml_object(YAML::load(yaml))
484
+ end
485
+
486
+ # Priviliege settings
487
+ protected :register_post_activity, :register_pre_activity
488
+ protected :deregister_post_activity, :deregister_pre_activity
489
+
490
+ private :pre_activities_max_earliest_finish
491
+ private :post_activities_min_earliest_start
492
+ end
493
+
494
+
495
+ # A special activity which signifies the start of a precedence network.
496
+ # It has a duration of 0 and is not allowed to have any pre_activities.
497
+ # It's reference will always be 'start'.
498
+ class StartActivity < Precedence::Activity #:nodoc:
230
499
 
231
- # Represents the special case of a start or finish activity.
232
- class StartFinishActivity < Precedence::Activity #:nodoc:
233
- def initialize(reference,description=nil)
234
- if (reference.to_s != 'start') and (reference.to_s != 'finish')
235
- raise "StartFinishActivity reference may only be set to"+
236
- "'start' or 'finish'. Given reference was '#{reference}'."
500
+ attr_reader :expected_duration, :maximum_duration, :minimum_duration
501
+
502
+ # Reference for the StartActivity
503
+ REFERENCE = 'start'
504
+
505
+ # Creates a new start activity.
506
+ def initialize(description=nil)
507
+ @reference = REFERENCE
508
+ if (description.nil?)
509
+ @description = @reference
510
+ else
511
+ @description = description.to_s
237
512
  end
513
+ @expected_duration = 0
514
+ @minimum_duration = 0
515
+ @maximum_duration = 0
516
+ @post_activities = []
517
+ @pre_activities = []
518
+ @resources = {}
519
+ @duration_type = EXPECTED_DURATION
520
+ @start_type = EARLIEST_START
521
+ end
522
+
238
523
 
239
- @reference = reference.to_s
240
- if description
241
- @description = description.to_s
524
+ def self.from_yaml_object(yamlObj) #:nodoc:
525
+ reference,activity = yamlObj.to_a[0]
526
+ if (reference == REFERENCE)
527
+ if (!activity.nil?)
528
+ return Precedence::StartActivity.new(activity['description'])
529
+ else
530
+ return Precedence::StartActivity.new
531
+ end
242
532
  else
243
- @description = reference.to_s
533
+ raise("A StartActivity can only have a reference of '#{REFERENCE}'."+
534
+ " Given reference was '#{reference}'.")
535
+ end
536
+ end
537
+
538
+ def add_pre_activities(*pre_activities) #:nodoc:
539
+ self
540
+ end
541
+
542
+ def register_pre_activity(activity) #:nodoc:
543
+ raise "This activity can not be a post-activity of any other "+
544
+ "activity."
545
+ end
546
+ end
547
+
548
+ # A special activity which signifies the finish of a precedence network.
549
+ # It has a duration of 0 and is not allowed to have any post_activities.
550
+ # It's reference will always be 'finish'.
551
+ class FinishActivity < Precedence::Activity #:nodoc:
552
+
553
+ attr_reader :expected_duration, :maximum_duration, :minimum_duration
554
+
555
+ # Reference for the FinishActivity
556
+ REFERENCE = 'finish'
557
+
558
+ # Creates a new finish activity.
559
+ def initialize(description=nil)
560
+
561
+ @reference = REFERENCE
562
+ if (description.nil?)
563
+ @description = @reference
564
+ else
565
+ @description = description.to_s
244
566
  end
245
- @duration = duration.to_f
246
- @post_activities = post_activities.to_a
247
- @pre_activities = pre_activities.to_a
567
+ @expected_duration = 0
568
+ @minimum_duration = 0
569
+ @maximum_duration = 0
570
+ @post_activities = []
571
+ @pre_activities = []
572
+ @resources = {}
573
+ @duration_type = EXPECTED_DURATION
574
+ @start_type = EARLIEST_START
575
+ end
576
+
577
+ def self.from_yaml_object(yamlObj) #:nodoc:
578
+ reference,activity = yamlObj.to_a[0]
579
+ if (reference == REFERENCE)
580
+ if (!activity.nil?)
581
+ return Precedence::FinishActivity.new(activity['description'])
582
+ else
583
+ return Precedence::FinishActivity.new
584
+ end
585
+ else
586
+ raise "A FinishActivity can only have a reference of '#{REFERENCE}'."+
587
+ " Given reference was '#{reference}'."
588
+ end
589
+ end
590
+
591
+ def add_post_activities(*post_activities) #:nodoc:
592
+ self
593
+ end
594
+
595
+ def register_post_activity(activity) #:nodoc:
596
+ raise "This activity can not be a pre-activity of any other "+
597
+ "activity."
248
598
  end
249
599
  end
250
- end
251
-
600
+ end