precedence 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +118 -0
- data/README +41 -9
- data/TODO +10 -5
- data/lib/precedence/activity.rb +429 -80
- data/lib/precedence/network.rb +223 -88
- data/lib/precedence/utilities.rb +44 -0
- data/lib/precedence.rb +1 -0
- data/tests/tc_activity.rb +287 -45
- data/tests/tc_network.rb +277 -39
- data/tests/ts_precedence.rb +2 -0
- metadata +3 -2
data/lib/precedence/network.rb
CHANGED
@@ -1,80 +1,60 @@
|
|
1
|
+
# Requirements from Ruby standard library
|
1
2
|
require('erb')
|
3
|
+
require('yaml')
|
2
4
|
|
3
|
-
module Precedence
|
4
|
-
|
5
|
-
# An overwritten Hash that has all keys as strings
|
6
|
-
class ActivityHash < Hash #:nodoc:
|
7
|
-
def[]=(ref,value)
|
8
|
-
super(ref.to_s, value)
|
9
|
-
end
|
10
|
-
|
11
|
-
def[](ref)
|
12
|
-
super(ref.to_s)
|
13
|
-
end
|
14
|
-
end
|
5
|
+
module Precedence
|
15
6
|
|
16
|
-
# Represents an entire precedence network.
|
7
|
+
# Represents an entire precedence network. It wraps around an underlying
|
8
|
+
# directed graph of Activity objects and adds a number of utility methods
|
9
|
+
# and extra functionality that would not be available if one just used
|
10
|
+
# Activity objects.
|
17
11
|
class Network
|
18
12
|
|
19
13
|
# A hashed collection of all the activities in the network. The index
|
20
14
|
# is the reference of the activity.
|
21
15
|
attr_reader :activities
|
22
16
|
|
17
|
+
# The reference for the StartActivity in the network.
|
18
|
+
START = Precedence::StartActivity::REFERENCE
|
19
|
+
|
20
|
+
# The reference for the FinishActivity in the network.
|
21
|
+
FINISH = Precedence::FinishActivity::REFERENCE
|
22
|
+
|
23
23
|
# Initialises the precedence network. The descriptions for the start
|
24
24
|
# and finish activities can be set in the parameters. The start and
|
25
25
|
# finish activities of the network have as references 'start' and
|
26
|
-
# 'finish'
|
26
|
+
# 'finish' (held in constants Network::START and Network::FINISH)
|
27
|
+
# respectively (and as such these references are reserved)
|
27
28
|
# and a duration of 0.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@activities
|
29
|
+
#
|
30
|
+
# Example usage:
|
31
|
+
# precNetwork = Precedence::Network.new('Begin','End')
|
32
|
+
# will create a new precedence network with the start and finish
|
33
|
+
# activities having descriptions of 'Begin' and 'End' respectively.
|
34
|
+
def initialize()
|
35
|
+
@start = Precedence::StartActivity.new
|
36
|
+
@finish = Precedence::FinishActivity.new
|
37
|
+
@activities = Precedence::Utilities::ActivityHash.new(@start,@finish)
|
38
|
+
#@activities[START] = @start
|
39
|
+
#@activities[FINISH] = @finish
|
37
40
|
end
|
38
41
|
|
42
|
+
|
39
43
|
# Creates a new activity and adds it to the precedence network. The
|
40
|
-
# reference is the only mandatory parameter.
|
41
|
-
#
|
42
|
-
# already defined within the network and not the actual
|
43
|
-
# Precedence::Activity object.
|
44
|
+
# reference is the only mandatory parameter. This method will take the
|
45
|
+
# block given and pass it to the Activity.new method.
|
44
46
|
#
|
45
47
|
# Exapmple usage:
|
46
|
-
# precNetwork.new_activity('a2'
|
48
|
+
# precNetwork.new_activity('a2') do |activity|
|
49
|
+
# activity.duration = 3
|
50
|
+
# activity.description = 'Activity2'
|
51
|
+
# end
|
52
|
+
# precNetwork.connect('a2','a1')
|
47
53
|
# will create a new activity in the network with referecne 'a2' and with
|
48
|
-
# activity 'a1' as a post-activity.
|
49
|
-
def new_activity(reference
|
50
|
-
|
51
|
-
|
52
|
-
post_activities.each do |post_activity_ref|
|
53
|
-
unless @activities[post_activity_ref]
|
54
|
-
raise "Post-activity with reference #{post_activity_ref} "+
|
55
|
-
"was not found."
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
pre_activities.each do |pre_activity_ref|
|
60
|
-
unless @activities[pre_activity_ref]
|
61
|
-
raise "Pre-activity with reference #{pre_activity_ref} "+
|
62
|
-
"was not found."
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
activity =
|
67
|
-
Precedence::Activity.new(reference,duration,description)
|
68
|
-
|
69
|
-
post_activities.each do |post_activity_ref|
|
70
|
-
activity.add_post_activities(@activities[post_activity_ref])
|
71
|
-
end
|
72
|
-
|
73
|
-
pre_activities.each do |pre_activity_ref|
|
74
|
-
activity.add_pre_activities(@activities[pre_activity_ref])
|
75
|
-
end
|
76
|
-
|
77
|
-
@activities[reference]=activity
|
54
|
+
# activity 'a1' as a post-activity.
|
55
|
+
def new_activity(reference,&block)
|
56
|
+
activity = Precedence::Activity.new(reference,&block)
|
57
|
+
activities[reference]=activity
|
78
58
|
end
|
79
59
|
|
80
60
|
# Adds a Precedence::Activity object to the network. This should be a
|
@@ -86,21 +66,20 @@ module Precedence
|
|
86
66
|
# precNetwork.add_activity(activity)
|
87
67
|
# will add the existing activity 'a1' to the network.
|
88
68
|
def add_activity(activity)
|
89
|
-
if
|
69
|
+
if (reference_exists?(activity.reference))
|
90
70
|
raise "Activity #{activity.reference} already exists in the "+
|
91
71
|
"network."
|
92
72
|
end
|
93
73
|
|
94
|
-
unless activity.post_activities == []
|
74
|
+
unless (activity.post_activities == [])
|
95
75
|
raise "Can not add an activity with post activities."
|
96
76
|
end
|
97
77
|
|
98
|
-
unless activity.pre_activities == []
|
78
|
+
unless (activity.pre_activities == [])
|
99
79
|
raise "Can not add an activity with pre activities."
|
100
80
|
end
|
101
81
|
|
102
|
-
@activities[activity.reference] = activity
|
103
|
-
|
82
|
+
@activities[activity.reference] = activity
|
104
83
|
end
|
105
84
|
|
106
85
|
# Connects two or more activities together. The pre_ref activity will
|
@@ -111,43 +90,208 @@ module Precedence
|
|
111
90
|
# precNetwork.connect(:h1,:h2,:h3)
|
112
91
|
# will add activity 'h1' as a pre-activity to activities 'h2' and 'h3'.
|
113
92
|
def connect(pre_ref,*post_refs)
|
114
|
-
unless
|
93
|
+
unless reference_exists?(pre_ref)
|
115
94
|
raise "Pre-activity with reference #{pre_ref} "+
|
116
95
|
"was not found."
|
117
96
|
end
|
118
97
|
|
119
98
|
post_refs.each do |post_ref|
|
120
|
-
unless
|
99
|
+
unless reference_exists?(post_ref)
|
121
100
|
raise "Post-activity with reference #{post_ref} "+
|
122
101
|
"was not found."
|
123
102
|
end
|
124
103
|
end
|
125
104
|
|
126
105
|
post_refs.each do |post_ref|
|
127
|
-
|
106
|
+
activities[pre_ref].add_post_activities(activities[post_ref])
|
128
107
|
end
|
129
108
|
end
|
130
109
|
|
110
|
+
# Disconnects an activity from one or more post activities.
|
111
|
+
#
|
112
|
+
# Example usage:
|
113
|
+
# precNetwork.disconnect(:h1,:h2,:h3)
|
114
|
+
# will remove activity 'h1' as a pre-activity to activities 'h2' and
|
115
|
+
# 'h3'.
|
116
|
+
def disconnect(pre_ref,*post_refs)
|
117
|
+
unless reference_exists?(pre_ref)
|
118
|
+
raise "Pre-activity with reference #{pre_ref} "+
|
119
|
+
"was not found."
|
120
|
+
end
|
121
|
+
|
122
|
+
post_refs.each do |post_ref|
|
123
|
+
unless reference_exists?(post_ref)
|
124
|
+
raise "Post-activity with reference #{post_ref} "+
|
125
|
+
"was not found."
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
post_refs.each do |post_ref|
|
130
|
+
activities[pre_ref].remove_post_activities(activities[post_ref])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns true if the reference is currently in the network. This
|
135
|
+
# includes START and FINISH.
|
136
|
+
def reference_exists?(reference)
|
137
|
+
if ((!activities[reference].nil?) ||
|
138
|
+
(reference == START) ||
|
139
|
+
(reference == FINISH))
|
140
|
+
return true
|
141
|
+
end
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
131
145
|
# Ensures that the network is properly connected by connecting any
|
132
146
|
# activity without pre-activities to the start node and to the finish
|
133
|
-
# node if it has no post-activities.
|
147
|
+
# node if it has no post-activities. Must be called before any analysis
|
148
|
+
# on the network is done.
|
149
|
+
#
|
150
|
+
# Example usage:
|
151
|
+
# precNetwork.connect('h1','h2')
|
152
|
+
# precNetwork.connect('h2','h3','h4)
|
153
|
+
# precNetwork.fix_connections!
|
154
|
+
# precNetwork.finish
|
155
|
+
# precNetwork.activities['h2'].earliest_finish
|
134
156
|
def fix_connections!
|
135
|
-
|
136
|
-
next if (ref == 'start' or ref == 'finish')
|
157
|
+
activities.each do |ref,activity|
|
137
158
|
if activity.pre_activities.size == 0
|
138
|
-
connect(
|
159
|
+
connect(START,ref)
|
139
160
|
end
|
140
161
|
|
141
162
|
if activity.post_activities.size == 0
|
142
|
-
connect(ref
|
163
|
+
connect(ref,FINISH)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns a dot file capable of being rendered by the Dot graph renderer
|
169
|
+
# available from GraphViz (http://www.graphviz.org).
|
170
|
+
#
|
171
|
+
# Example usage:
|
172
|
+
# File.open('test_to_dot.dot',File::CREAT|File::TRUNC|File::WRONLY) do|f|
|
173
|
+
# f.puts(net.to_dot)
|
174
|
+
# end
|
175
|
+
def to_dot(template=nil)
|
176
|
+
unless template
|
177
|
+
template = Precedence::Network.get_rdot_template
|
178
|
+
end
|
179
|
+
return ERB.new(template).result(binding)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns a YAML representation of the network.For more information on
|
183
|
+
# YAML go to http://yaml.org
|
184
|
+
#
|
185
|
+
# Example usage:
|
186
|
+
# File.open('test_to_yaml.yaml',File::CREAT|File::TRUNC|File::WRONLY) do|f|
|
187
|
+
# f.puts(net.to_yaml)
|
188
|
+
# end
|
189
|
+
def to_yaml
|
190
|
+
(activities.values.collect do |activity|
|
191
|
+
activity.to_yaml
|
192
|
+
end).join("\n")
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
# Returns a Precedence::Network object that is represented by the
|
197
|
+
# YAML document.
|
198
|
+
#
|
199
|
+
# Example usage:
|
200
|
+
# precNetwork = nil
|
201
|
+
# File.open('precnetwork.yaml',File::RDONLY) do |f|
|
202
|
+
# precNetwork = Precedence::Network.from_yaml(f)
|
203
|
+
# end
|
204
|
+
def self.from_yaml(yaml)
|
205
|
+
activities = {}
|
206
|
+
connections = {}
|
207
|
+
|
208
|
+
YAML::load_documents(yaml) do |doc|
|
209
|
+
activity = Precedence::Activity.from_yaml_object(doc)
|
210
|
+
activities[activity.reference] = activity
|
211
|
+
connections[activity.reference] =
|
212
|
+
doc[activity.reference]['post activities']
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
network = Precedence::Network.new()
|
217
|
+
|
218
|
+
activities.values.each do |activity|
|
219
|
+
unless ((activity.reference == START) or
|
220
|
+
(activity.reference == FINISH))
|
221
|
+
network.add_activity(activity)
|
143
222
|
end
|
223
|
+
end
|
224
|
+
|
225
|
+
connections.each do |ref,connections|
|
226
|
+
connections.to_a.each do |post_ref|
|
227
|
+
network.connect(ref,post_ref)
|
228
|
+
end
|
144
229
|
end
|
230
|
+
return network
|
145
231
|
end
|
146
232
|
|
233
|
+
# The time it will take to finish all activities in the network.
|
234
|
+
# (Shortcut to precNetwork.activities['finish'].finish)
|
235
|
+
#
|
236
|
+
# Example usage:
|
237
|
+
# precNetwork.finish
|
238
|
+
def finish
|
239
|
+
return @finish.finish
|
240
|
+
end
|
241
|
+
|
242
|
+
# Iterates over the networks duration yielding an array of activities
|
243
|
+
# that are active at that time to a block. The size of the time jumps
|
244
|
+
# can be set using the tick parameter. The duration starts at time 0
|
245
|
+
# and ends at time network.finish - tick.
|
246
|
+
#
|
247
|
+
# Example usage:
|
248
|
+
# precNetwork.each_time_period do |activities|
|
249
|
+
# coffeeUsed == 0
|
250
|
+
# activities.each do |activity|
|
251
|
+
# coffeeUsed += activity.resources['coffee']
|
252
|
+
# end
|
253
|
+
# puts "Used #{coffeeUsed} units of coffee in #{activities.size} activities."
|
254
|
+
# end
|
255
|
+
def each_time_period(tick=1)
|
256
|
+
0.step(finish-tick,tick) do |current_time|
|
257
|
+
yield(activities_at_time(current_time))
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# The same functionality as each_time_period except that the time is
|
262
|
+
# yielded along with the array of active activities.
|
263
|
+
#
|
264
|
+
# Example usage:
|
265
|
+
# precNetwork.each_time_period_with_index do |activities,time|
|
266
|
+
# puts "At time #{time}, #{activities.size} activities were active."
|
267
|
+
# end
|
268
|
+
def each_time_period_with_index(tick=1)
|
269
|
+
0.step(finish-tick,tick) do |current_time|
|
270
|
+
yield(activities_at_time(current_time),current_time)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Returns an array of activities with each activity being active at
|
275
|
+
# the time of the parameter.
|
276
|
+
def activities_at_time(time)
|
277
|
+
return (@activities.values.find_all do |activity|
|
278
|
+
activity.active_on?(time)
|
279
|
+
end)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Returns an array of activities with each acitivity being active
|
283
|
+
# deuring the time range of the parameter
|
284
|
+
def activities_during_time(range)
|
285
|
+
return (@activities.values.find_all do |activity|
|
286
|
+
activity.active_during?(range)
|
287
|
+
end)
|
288
|
+
end
|
289
|
+
|
290
|
+
|
147
291
|
# Returns the rdot (Ruby embedded in a dot file) template that is used
|
148
292
|
# by default when generating the precedence network diagrams.
|
149
|
-
def get_rdot_template
|
150
|
-
return <<
|
293
|
+
def self.get_rdot_template #:nodoc:
|
294
|
+
return <<END_OF_STRING
|
151
295
|
/* Generated by Precedence on <%= Time.now.to_s %> */
|
152
296
|
digraph network {
|
153
297
|
rankdir=LR;
|
@@ -155,12 +299,12 @@ digraph network {
|
|
155
299
|
|
156
300
|
/* Activities */
|
157
301
|
<% @activities.each do |ref,activity| %>
|
158
|
-
<% case ref
|
159
|
-
when
|
302
|
+
<% case ref
|
303
|
+
when START%>
|
160
304
|
"<%= ref %>" [label="<%= activity.description %>"];
|
161
|
-
|
305
|
+
<% when FINISH %>
|
162
306
|
"<%= ref %>" [label="{<%= activity.description%>|<%= activity.earliest_finish %>}"];
|
163
|
-
|
307
|
+
<% else %>
|
164
308
|
"<%= ref %>" [label="<%=ref%>|{{<%=activity.earliest_start%>|<%=activity.latest_start%>}|{<%=activity.description%>|{<%=activity.total_float%>|<%=activity.early_float%>}}|{<%=activity.earliest_finish%>|<%=activity.latest_finish%>}}|<%=activity.duration%>"];
|
165
309
|
<% end %>
|
166
310
|
<% end %>
|
@@ -168,20 +312,11 @@ digraph network {
|
|
168
312
|
/* Dependencies */
|
169
313
|
<% @activities.each do |ref,activity|%>
|
170
314
|
<% activity.post_activities.each do |post_activity| %>
|
171
|
-
"<%= activity.reference %>" -> "<%= post_activity.reference %>" <% if (activity.on_critical_path? and post_activity.on_critical_path?)%>[style=bold]
|
315
|
+
"<%= activity.reference %>" -> "<%= post_activity.reference %>" <% if (activity.on_critical_path? and post_activity.on_critical_path?)%>[style=bold]<% end %>;
|
172
316
|
<% end %>
|
173
317
|
<% end %>
|
174
318
|
}
|
175
|
-
|
176
|
-
end
|
177
|
-
|
178
|
-
# Returns a dot file capable of being rendered by the Dot graph renderer
|
179
|
-
# (available from GraphViz http://www.graphviz.org).
|
180
|
-
def to_dot(template=nil)
|
181
|
-
unless template
|
182
|
-
template = get_rdot_template
|
183
|
-
end
|
184
|
-
return ERB.new(template).result(binding)
|
319
|
+
END_OF_STRING
|
185
320
|
end
|
186
321
|
end
|
187
322
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Precedence
|
2
|
+
module Utilities #:nodoc:
|
3
|
+
# A modified Hash that has all keys as strings
|
4
|
+
class ActivityHash < Hash #:nodoc:
|
5
|
+
|
6
|
+
def initialize(startActivity,finishActivity)
|
7
|
+
super(nil)
|
8
|
+
@start = startActivity
|
9
|
+
@finish = finishActivity
|
10
|
+
end
|
11
|
+
|
12
|
+
def[]=(ref,value)
|
13
|
+
if ((ref != @start.reference) && (ref != @finish.reference))
|
14
|
+
super(ref.to_s, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def[](ref)
|
19
|
+
if (ref.to_s == @start.reference)
|
20
|
+
return @start
|
21
|
+
elsif (ref.to_s == @finish.reference)
|
22
|
+
return @finish
|
23
|
+
else
|
24
|
+
return super(ref.to_s)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# A modified Hash that has all keys as strings and all values as floats
|
30
|
+
class ResourceHash < Hash #:nodoc:
|
31
|
+
def initialze
|
32
|
+
super(0.0)
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](resource)
|
36
|
+
return super(resource.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(resource,value)
|
40
|
+
return super(resource.to_s,value.to_f)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/precedence.rb
CHANGED