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