precedence 0.6.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 ADDED
@@ -0,0 +1,5 @@
1
+ = 0.6: Initial Release
2
+ Basic precedence network functionality:
3
+ * Earlist/latest start/finish
4
+ * Generating float and determining critical path
5
+ * Diagram generation via dot files
data/README ADDED
@@ -0,0 +1,63 @@
1
+ = Precedence
2
+
3
+ Precedence is a library that allows for the creation, manipulation and analysis
4
+ of precedence networks.
5
+
6
+ == Download and Install
7
+
8
+ Available as a RubyGem from Rubyforge. To install
9
+ $ gem install precedence
10
+ will fetch the latest gem from Rubyforge and install it.
11
+
12
+ Source can also be downloaded from http://rubyforge.org/projects/precedence.
13
+
14
+ == Example Usage
15
+ require('precedence')
16
+
17
+ # Set up network
18
+ net = Precedence::Network.new('Begin','End')
19
+ net.new_activity('act-1-1',3,'System specification')
20
+ net.new_activity('act-1-2',2,'Review')
21
+ net.new_activity('act-1-3',2,'System re-specification')
22
+ net.new_activity('act-2-1',3,'Test tool design')
23
+ net.new_activity('act-2-2',5,'Test tool implementation')
24
+ net.new_activity('act-3-1',3,'System design')
25
+ net.new_activity('act-3-2',12,'System implementation')
26
+ net.new_activity('act-2-3',10,'System testing')
27
+ net.connect('act-1-1','act-1-2')
28
+ net.connect('act-1-2','act-1-3')
29
+ net.connect('act-1-3','act-3-1')
30
+ net.connect('act-1-2','act-2-1')
31
+ net.connect('act-2-1','act-2-2')
32
+ net.connect('act-2-2','act-3-2')
33
+ net.connect('act-3-1','act-3-2')
34
+ net.connect('act-3-2','act-2-3')
35
+ net.fix_connections!
36
+
37
+ # Generate a diagram
38
+ File.open('network.dot',File::CREAT|File::TRUNC|File::WRONLY) do|file|
39
+ file.puts(net.to_dot)
40
+ end
41
+ system("dot","-Tpng","-onetwork.png","network.dot")
42
+
43
+ # Perform some analysis of the activities
44
+ activity = net.activities['act-1-2']
45
+ activity.on_critical_path? # => true
46
+ activity.earliest_start # => 3.0
47
+ activity.latest_finish # => 5.0
48
+ activity.total_float # => 0 - activities on the critical path have no float
49
+
50
+ == Documentation
51
+
52
+ The Precedence API online documentation is available at
53
+ http://precedence.rubyforge.org.
54
+
55
+ Refer to the CHANGELOG and TODO files for past changes and upcoming plans.
56
+
57
+ == Credits
58
+
59
+ Farrel Lifson <farrel@lifson.info>
60
+
61
+ == License
62
+
63
+ This software is made available under the BSD license.
data/TODO ADDED
@@ -0,0 +1,22 @@
1
+ = To Do
2
+
3
+ I plan on adding the following leading up to a 1.0 release
4
+
5
+ == 0.7
6
+
7
+ * Adding resource usage (money, coffee, bulldozers) to each activity.
8
+ * Saving and loading precedence networks (probably using YAML).
9
+
10
+ == 0.8
11
+
12
+ * Adding an 'each_day' method to the network that will return the set of
13
+ activities used on that day.
14
+ * Calculating daily resource usage and generating some graphs from that.
15
+
16
+ == 0.9
17
+
18
+ * Generating Gantt charts
19
+
20
+ == 1.0 and further
21
+
22
+ * Some probability based reasoning using Beta/Normal distributions.
@@ -0,0 +1,251 @@
1
+ module Precedence
2
+
3
+ # A representation of an activity in a precedence network. Each activity
4
+ # has a user specified reference, description and duration. When activities
5
+ # are connected (via the add_post/pre_activities functions) then various
6
+ # other properties of the activity can be determined such as it's earliest
7
+ # finish time, float and so on.
8
+ #
9
+
10
+ class Activity
11
+
12
+ # A textual description of the activity.
13
+ attr_accessor :description
14
+ # The time the activity will take to complete.
15
+ attr_accessor :duration
16
+ # A unique reference for this activity.
17
+ attr_reader :reference
18
+ # The collection of activites that are dependent on the completion of
19
+ # this activity.
20
+ attr_reader :post_activities
21
+ # The collection of activities that this activity depends on before it
22
+ # can begin executing.
23
+ 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.
28
+ #
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"
35
+ 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
41
+ end
42
+
43
+ # Adds the activities in the parameter list to the post_activities
44
+ # collection of this activity and also adds this activity to the
45
+ # pre_activities collection of each of the activities.
46
+ def add_post_activities(*post_activities)
47
+ post_activities.each do |activity|
48
+ activity.register_pre_activity(self)
49
+ register_post_activity(activity)
50
+ end
51
+ end
52
+
53
+ # Adds the activities in the parameter list to the pre_activities
54
+ # collection of this activity and also adds this activity to the
55
+ # post_activities collection of each of the activities.
56
+ def add_pre_activities(*pre_activities)
57
+ pre_activities.each do |activity|
58
+ activity.register_post_activity(self)
59
+ register_pre_activity(activity)
60
+ end
61
+ end
62
+
63
+ # Removes the list of activities from the post_activities collection of
64
+ # the activity.
65
+ def remove_post_activities(*post_activities)
66
+ post_activities.each do |activity|
67
+ activity.deregister_pre_activity(self)
68
+ deregister_post_activity(activity)
69
+ end
70
+ end
71
+
72
+ # Removes the list of activities from the pre_activities collection of
73
+ # the activity.
74
+ def remove_pre_activities(*post_activities)
75
+ post_activities.each do |activity|
76
+ activity.deregister_post_activity(self)
77
+ deregister_pre_activity(activity)
78
+ end
79
+ end
80
+
81
+ # The earliest possible time this activity can finish.
82
+ def earliest_finish
83
+ return @duration + earliest_start
84
+ end
85
+
86
+ # The earliest possible time this activity can start.
87
+ def earliest_start
88
+ unless @pre_activities.empty?
89
+ (@pre_activities.max do |a,b|
90
+ a.earliest_finish <=> b.earliest_finish
91
+ end).earliest_finish
92
+ else
93
+ 0.0
94
+ end
95
+ end
96
+
97
+ # The latest possible time this activity can start so as not to delay
98
+ # any dependent activities.
99
+ def latest_start
100
+ return latest_finish - @duration
101
+ end
102
+
103
+ # The latest possible time this activity can finish so as not to delay
104
+ # any dependent activities.
105
+ def latest_finish
106
+ unless @post_activities.empty?
107
+ (@post_activities.min do |a,b|
108
+ a.latest_start <=> b.latest_start
109
+ end).latest_start
110
+ else
111
+ return earliest_finish
112
+ end
113
+ end
114
+
115
+ # 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|
119
+ a.earliest_finish <=> b.earliest_finish
120
+ end).earliest_finish
121
+ else
122
+ return 0
123
+ end
124
+ end
125
+
126
+ # 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|
130
+ a.earliest_start <=> b.earliest_start
131
+ end).earliest_start
132
+ else
133
+ return latest_finish
134
+ end
135
+ end
136
+
137
+ # If the activity is on the critical path returns true, returns false
138
+ # otherwise.
139
+ def on_critical_path?
140
+ if earliest_finish == latest_finish
141
+ return true
142
+ else
143
+ return false
144
+ end
145
+ end
146
+
147
+ # The amount of float this activity has such that it does not delay
148
+ # the completion of the entire precedence network.
149
+ def total_float
150
+ return latest_finish - earliest_finish
151
+ end
152
+
153
+ # The amount of float this activity has if all preceding and succeeding
154
+ # activities start as early as possible.
155
+ #
156
+ # *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
158
+ # no late_float method is defined.
159
+ def early_float
160
+ return post_activities_min_earliest_start -
161
+ pre_activities_max_earliest_finish - @duration
162
+ end
163
+
164
+ # Register this activity as a post-activity on the parameter.
165
+ def register_post_activity(activity) #:nodoc:
166
+ unless (@post_activities.detect do |post_activity|
167
+ activity.reference == post_activity.reference
168
+ end)
169
+ @post_activities << activity
170
+ end
171
+ end
172
+
173
+ # Register this activity as a pre-activity on the parameter.
174
+ def register_pre_activity(activity) #:nodoc:
175
+ unless (@pre_activities.detect do |pre_activity|
176
+ activity.reference == pre_activity.reference
177
+ end)
178
+ @pre_activities << activity
179
+ end
180
+ end
181
+
182
+ # Deregister this activity as a post-activity on the parameter.
183
+ def deregister_post_activity(activity) #:nodoc:
184
+ if (@post_activities.detect do |post_activity|
185
+ activity.reference == post_activity.reference
186
+ end)
187
+ @post_activities.delete(activity)
188
+ end
189
+ end
190
+
191
+ # Deregister this activity as a pre-activity on the parameter.
192
+ def deregister_pre_activity(activity) #:nodoc:
193
+ if (@pre_activities.detect do |pre_activity|
194
+ activity.reference == pre_activity.reference
195
+ end)
196
+ @pre_activities.delete(activity)
197
+ end
198
+ end
199
+
200
+ # Returns this activity in an Array object.
201
+ def to_a #:nodoc:
202
+ return [self]
203
+ end
204
+
205
+ # Redefines the inspect method.
206
+ def inspect #:nodoc:
207
+ "#{@reference}(#{@duration})"
208
+ end
209
+
210
+ # Redefines the to_s method
211
+ def to_s #:nodoc:
212
+ return "Reference: #{@reference}\n"+
213
+ "Description: #{@description}\n"+
214
+ "Duration: #{@duration}"+
215
+ ("\nDepends on:\n " unless @pre_activities.empty?).to_s +
216
+ (@pre_activities.collect do |activity|
217
+ activity.reference
218
+ end).join(',')
219
+ end
220
+
221
+ # Priviliege settings
222
+ protected :register_post_activity, :register_pre_activity,
223
+ :deregister_post_activity, :deregister_pre_activity
224
+
225
+ private :pre_activities_max_earliest_finish,
226
+ :post_activities_min_earliest_start
227
+
228
+
229
+ end
230
+
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}'."
237
+ end
238
+
239
+ @reference = reference.to_s
240
+ if description
241
+ @description = description.to_s
242
+ else
243
+ @description = reference.to_s
244
+ end
245
+ @duration = duration.to_f
246
+ @post_activities = post_activities.to_a
247
+ @pre_activities = pre_activities.to_a
248
+ end
249
+ end
250
+ end
251
+
@@ -0,0 +1,187 @@
1
+ require('erb')
2
+
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
15
+
16
+ # Represents an entire precedence network.
17
+ class Network
18
+
19
+ # A hashed collection of all the activities in the network. The index
20
+ # is the reference of the activity.
21
+ attr_reader :activities
22
+
23
+ # Initialises the precedence network. The descriptions for the start
24
+ # and finish activities can be set in the parameters. The start and
25
+ # finish activities of the network have as references 'start' and
26
+ # 'finish' respectively (and as such these references are reserved)
27
+ # and a duration of 0.
28
+ def initialize(startDescription='Start',
29
+ finishDescription='Finish')
30
+ @start = Precedence::StartFinishActivity.new('start',
31
+ startDescription)
32
+ @finish = Precedence::StartFinishActivity.new('finish',
33
+ finishDescription)
34
+ @activities = Precedence::ActivityHash.new
35
+ @activities[@start.reference] = @start
36
+ @activities[@finish.reference] = @finish
37
+ end
38
+
39
+ # Creates a new activity and adds it to the precedence network. The
40
+ # reference is the only mandatory parameter. The post_activities and
41
+ # pre_activities parameters should contain the references to activities
42
+ # already defined within the network and not the actual
43
+ # Precedence::Activity object.
44
+ #
45
+ # Exapmple usage:
46
+ # precNetwork.new_activity('a2',3,'Activity2',['a1'])
47
+ # 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,duration=0,description="",
50
+ post_activities=[],pre_activities=[])
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
78
+ end
79
+
80
+ # Adds a Precedence::Activity object to the network. This should be a
81
+ # single activity (no pre- or post-activities should be referenced from
82
+ # it) and it's reference should not exist in the network.
83
+ #
84
+ # Example usage:
85
+ # activity = Precedence::Activity.new('a1',1,'Activity 1')
86
+ # precNetwork.add_activity(activity)
87
+ # will add the existing activity 'a1' to the network.
88
+ def add_activity(activity)
89
+ if @activities[activity.reference]
90
+ raise "Activity #{activity.reference} already exists in the "+
91
+ "network."
92
+ end
93
+
94
+ unless activity.post_activities == []
95
+ raise "Can not add an activity with post activities."
96
+ end
97
+
98
+ unless activity.pre_activities == []
99
+ raise "Can not add an activity with pre activities."
100
+ end
101
+
102
+ @activities[activity.reference] = activity
103
+
104
+ end
105
+
106
+ # Connects two or more activities together. The pre_ref activity will
107
+ # become a pre-activity to all the activities referenced in the
108
+ # post_refs parameter.
109
+ #
110
+ # Example uysage:
111
+ # precNetwork.connect(:h1,:h2,:h3)
112
+ # will add activity 'h1' as a pre-activity to activities 'h2' and 'h3'.
113
+ def connect(pre_ref,*post_refs)
114
+ unless @activities[pre_ref]
115
+ raise "Pre-activity with reference #{pre_ref} "+
116
+ "was not found."
117
+ end
118
+
119
+ post_refs.each do |post_ref|
120
+ unless @activities[post_ref]
121
+ raise "Post-activity with reference #{post_ref} "+
122
+ "was not found."
123
+ end
124
+ end
125
+
126
+ post_refs.each do |post_ref|
127
+ @activities[pre_ref].add_post_activities(@activities[post_ref])
128
+ end
129
+ end
130
+
131
+ # Ensures that the network is properly connected by connecting any
132
+ # activity without pre-activities to the start node and to the finish
133
+ # node if it has no post-activities.
134
+ def fix_connections!
135
+ @activities.each do |ref,activity|
136
+ next if (ref == 'start' or ref == 'finish')
137
+ if activity.pre_activities.size == 0
138
+ connect(:start,ref)
139
+ end
140
+
141
+ if activity.post_activities.size == 0
142
+ connect(ref,:finish)
143
+ end
144
+ end
145
+ end
146
+
147
+ # Returns the rdot (Ruby embedded in a dot file) template that is used
148
+ # by default when generating the precedence network diagrams.
149
+ def get_rdot_template
150
+ return <<EOS
151
+ /* Generated by Precedence on <%= Time.now.to_s %> */
152
+ digraph network {
153
+ rankdir=LR;
154
+ node [shape=record];
155
+
156
+ /* Activities */
157
+ <% @activities.each do |ref,activity| %>
158
+ <% case ref == 'start'
159
+ when 'start'%>
160
+ "<%= ref %>" [label="<%= activity.description %>"];
161
+ <% when 'finish' %>
162
+ "<%= ref %>" [label="{<%= activity.description%>|<%= activity.earliest_finish %>}"];
163
+ <% else %>
164
+ "<%= 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
+ <% end %>
166
+ <% end %>
167
+
168
+ /* Dependencies */
169
+ <% @activities.each do |ref,activity|%>
170
+ <% 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];<% else %>;<% end %>
172
+ <% end %>
173
+ <% end %>
174
+ }
175
+ EOS
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)
185
+ end
186
+ end
187
+ end
data/lib/precedence.rb ADDED
@@ -0,0 +1,2 @@
1
+ require('precedence/activity')
2
+ require('precedence/network')
@@ -0,0 +1,343 @@
1
+ require('lib/precedence/activity')
2
+
3
+ class TC_Activity < Test::Unit::TestCase #:nodoc:
4
+
5
+ def setup
6
+ @activity1 = Precedence::Activity.new(:activity1,1,"Activity One")
7
+ @activity2 = Precedence::Activity.new(:activity2,2,"Activity Two")
8
+ @activity3 = Precedence::Activity.new(:activity3,3,"Activity Three")
9
+ @activity4 = Precedence::Activity.new(:activity4,4,"Activity Four")
10
+ @activity5 = Precedence::Activity.new(:activity5,5,"Activity Five")
11
+ @activity6 = Precedence::Activity.new(:activity6,6,"Activity Six")
12
+ @activity7 = Precedence::Activity.new(:activity7,7,"Activity Seven")
13
+ @activity8 = Precedence::Activity.new(:activity8,8,"Activity Eight")
14
+ end
15
+
16
+ def test_initialize
17
+
18
+ assert_raises(RuntimeError) do
19
+ Precedence::Activity.new(:start)
20
+ end
21
+ assert_raises(RuntimeError) do
22
+ Precedence::Activity.new(:finish)
23
+ end
24
+
25
+ prec = nil
26
+ assert_nothing_raised do
27
+ prec = Precedence::Activity.new(:reference)
28
+ end
29
+ assert_equal(:reference.to_s, prec.reference)
30
+ assert_equal("",prec.description)
31
+ assert_equal(0,prec.duration)
32
+ assert_equal([],prec.post_activities)
33
+ assert_equal([],prec.pre_activities)
34
+
35
+ assert_nothing_raised do
36
+ prec = Precedence::Activity.new(:reference,1)
37
+ end
38
+ assert_equal(:reference.to_s, prec.reference)
39
+ assert_equal("", prec.description)
40
+ assert_equal(1,prec.duration)
41
+ assert_equal([],prec.post_activities)
42
+ assert_equal([],prec.pre_activities)
43
+
44
+ assert_nothing_raised do
45
+ prec = Precedence::Activity.new(:reference,1,"description")
46
+ end
47
+ assert_equal(:reference.to_s, prec.reference)
48
+ assert_equal("description", prec.description)
49
+ assert_equal(1,prec.duration)
50
+ assert_equal([],prec.post_activities)
51
+ assert_equal([],prec.pre_activities)
52
+
53
+ prec2 = nil
54
+ assert_nothing_raised do
55
+ prec2 = Precedence::Activity.new(:reference,1,"description",prec)
56
+ end
57
+ assert_equal(:reference.to_s, prec2.reference)
58
+ assert_equal("description", prec2.description)
59
+ assert_equal(1,prec2.duration)
60
+ assert_equal([prec],prec2.post_activities)
61
+ assert_equal([],prec.pre_activities)
62
+
63
+ prec3 = nil
64
+ assert_nothing_raised do
65
+ prec3 = Precedence::Activity.new(:reference,1,"description")
66
+ end
67
+ assert_nothing_raised do
68
+ prec2 = Precedence::Activity.new(:reference,1,"description",prec,
69
+ prec3)
70
+ end
71
+ assert_equal(:reference.to_s, prec2.reference)
72
+ assert_equal("description", prec2.description)
73
+ assert_equal(1,prec2.duration)
74
+ assert_equal([prec],prec2.post_activities)
75
+ assert_equal([prec3],prec2.pre_activities)
76
+ end
77
+
78
+ def test_to_a
79
+ assert_equal([@activity1],@activity1.to_a)
80
+ end
81
+
82
+ def test_add_post_activities
83
+ assert_nothing_raised do
84
+ @activity1.add_post_activities(@activity2)
85
+ end
86
+ assert_equal(@activity2.to_a,@activity1.post_activities)
87
+ assert_equal(@activity1.to_a,@activity2.pre_activities)
88
+
89
+ assert_nothing_raised do
90
+ @activity1.add_post_activities(@activity2)
91
+ end
92
+ assert_equal(1,@activity1.post_activities.size)
93
+ assert_equal(1,@activity2.pre_activities.size)
94
+
95
+ assert_nothing_raised do
96
+ @activity1.add_post_activities(@activity3,@activity4)
97
+ end
98
+ assert_equal([@activity2,@activity3,@activity4],
99
+ @activity1.post_activities)
100
+ assert_equal(@activity1.to_a,@activity3.pre_activities)
101
+ assert_equal(@activity1.to_a,@activity4.pre_activities)
102
+ end
103
+
104
+ def test_add_pre_activities
105
+ assert_nothing_raised do
106
+ @activity1.add_pre_activities(@activity2)
107
+ end
108
+ assert_equal(@activity2.to_a,@activity1.pre_activities)
109
+ assert_equal(@activity1.to_a,@activity2.post_activities)
110
+
111
+ assert_nothing_raised do
112
+ @activity1.add_pre_activities(@activity2)
113
+ end
114
+ assert_equal(1,@activity1.pre_activities.size)
115
+ assert_equal(1,@activity2.post_activities.size)
116
+
117
+ assert_nothing_raised do
118
+ @activity1.add_pre_activities(@activity3,@activity4)
119
+ end
120
+ assert_equal([@activity2,@activity3,@activity4],
121
+ @activity1.pre_activities)
122
+ assert_equal(@activity1.to_a,@activity3.post_activities)
123
+ assert_equal(@activity1.to_a,@activity4.post_activities)
124
+ end
125
+
126
+ def test_remove_post_activities
127
+ @activity1.add_post_activities(@activity2)
128
+ assert_nothing_raised do
129
+ @activity1.remove_post_activities(@activity2)
130
+ end
131
+ assert_equal([],@activity1.post_activities)
132
+ assert_equal([],@activity2.pre_activities)
133
+
134
+ @activity1.add_post_activities(@activity2,@activity3)
135
+ assert_nothing_raised do
136
+ @activity1.remove_post_activities(@activity2,@activity3)
137
+ end
138
+ assert_equal([],@activity1.post_activities)
139
+ assert_equal([],@activity2.pre_activities)
140
+ assert_equal([],@activity3.pre_activities)
141
+ end
142
+
143
+ def test_remove_pre_activities
144
+ @activity1.add_pre_activities(@activity2)
145
+ assert_nothing_raised do
146
+ @activity1.remove_pre_activities(@activity2)
147
+ end
148
+ assert_equal([],@activity1.pre_activities)
149
+ assert_equal([],@activity2.post_activities)
150
+
151
+ @activity1.add_pre_activities(@activity2,@activity3)
152
+ assert_nothing_raised do
153
+ @activity1.remove_pre_activities(@activity2,@activity3)
154
+ end
155
+ assert_equal([],@activity1.pre_activities)
156
+ assert_equal([],@activity2.post_activities)
157
+ assert_equal([],@activity3.post_activities)
158
+ end
159
+
160
+ def test_earliest_finish
161
+ assert_equal(1,@activity1.earliest_finish)
162
+
163
+ # a1-a2
164
+ @activity1.add_post_activities(@activity2)
165
+ assert_equal(3,@activity2.earliest_finish)
166
+
167
+ # a1-a3-a4
168
+ # '-a2-'
169
+ @activity1.add_post_activities(@activity3)
170
+ @activity3.add_post_activities(@activity4)
171
+ @activity2.add_post_activities(@activity4)
172
+ assert_equal(8,@activity4.earliest_finish)
173
+ end
174
+
175
+ def test_latest_finish
176
+ assert_equal(1,@activity1.latest_finish)
177
+
178
+ # a1-a2
179
+ @activity1.add_post_activities(@activity2)
180
+ assert_equal(3,@activity2.latest_finish)
181
+
182
+ # a1-a3-a4
183
+ # '-a2-'
184
+ @activity1.add_post_activities(@activity3)
185
+ @activity3.add_post_activities(@activity4)
186
+ @activity2.add_post_activities(@activity4)
187
+ assert_equal(2,@activity2.latest_start)
188
+ end
189
+
190
+ def test_on_critical_path?
191
+ # a1-a2
192
+ @activity1.add_post_activities(@activity2)
193
+ assert_equal(true,@activity1.on_critical_path?)
194
+ assert_equal(true,@activity2.on_critical_path?)
195
+
196
+ # a1-a3-a4
197
+ # '-a2-'
198
+ @activity1.add_post_activities(@activity3)
199
+ @activity3.add_post_activities(@activity4)
200
+ @activity2.add_post_activities(@activity4)
201
+ assert_equal(true,@activity1.on_critical_path?)
202
+ assert_equal(false,@activity2.on_critical_path?)
203
+ assert_equal(true,@activity3.on_critical_path?)
204
+ assert_equal(true,@activity4.on_critical_path?)
205
+ end
206
+
207
+ def test_total_float
208
+ # a1-a3-a4
209
+ # '-a2-'
210
+ @activity1.add_post_activities(@activity2)
211
+ @activity1.add_post_activities(@activity3)
212
+ @activity3.add_post_activities(@activity4)
213
+ @activity2.add_post_activities(@activity4)
214
+
215
+ assert_equal(0,@activity1.total_float)
216
+ assert_equal(0,@activity3.total_float)
217
+ assert_equal(0,@activity4.total_float)
218
+ assert_equal(1,@activity2.total_float)
219
+ end
220
+
221
+ def test_early_total_floats
222
+ # a1-a3---.
223
+ # a2-a4-a6-a7
224
+ # 'a5-a7'
225
+ @activity1.duration = 1
226
+ @activity2.duration = 3
227
+ @activity3.duration = 2
228
+ @activity4.duration = 4
229
+ @activity5.duration = 2
230
+ @activity6.duration = 1
231
+ @activity7.duration = 2
232
+ @activity8.duration = 4
233
+
234
+ @activity1.add_post_activities(@activity3)
235
+ @activity2.add_post_activities(@activity4,@activity5)
236
+ @activity3.add_post_activities(@activity8)
237
+ @activity4.add_post_activities(@activity6)
238
+ @activity5.add_post_activities(@activity7)
239
+ @activity6.add_post_activities(@activity8)
240
+ @activity7.add_post_activities(@activity8)
241
+
242
+ assert_equal(true,@activity2.on_critical_path?)
243
+ assert_equal(true,@activity4.on_critical_path?)
244
+ assert_equal(true,@activity6.on_critical_path?)
245
+ assert_equal(true,@activity8.on_critical_path?)
246
+
247
+ assert_equal(false,@activity1.on_critical_path?)
248
+ assert_equal(false,@activity3.on_critical_path?)
249
+ assert_equal(false,@activity5.on_critical_path?)
250
+ assert_equal(false,@activity7.on_critical_path?)
251
+
252
+ assert_equal(0,@activity1.earliest_start)
253
+ assert_equal(5,@activity1.latest_start)
254
+ assert_equal(1,@activity1.earliest_finish)
255
+ assert_equal(6,@activity1.latest_finish)
256
+ assert_equal(5,@activity1.total_float)
257
+ assert_equal(0,@activity1.early_float)
258
+
259
+ assert_equal(0,@activity2.earliest_start)
260
+ assert_equal(0,@activity2.latest_start)
261
+ assert_equal(3,@activity2.earliest_finish)
262
+ assert_equal(3,@activity2.latest_finish)
263
+ assert_equal(0,@activity2.total_float)
264
+ assert_equal(0,@activity2.early_float)
265
+
266
+ assert_equal(1,@activity3.earliest_start)
267
+ assert_equal(6,@activity3.latest_start)
268
+ assert_equal(3,@activity3.earliest_finish)
269
+ assert_equal(8,@activity3.latest_finish)
270
+ assert_equal(5,@activity3.total_float)
271
+ assert_equal(5,@activity3.early_float)
272
+
273
+ assert_equal(3,@activity4.earliest_start)
274
+ assert_equal(3,@activity4.latest_start)
275
+ assert_equal(7,@activity4.earliest_finish)
276
+ assert_equal(7,@activity4.latest_finish)
277
+ assert_equal(0,@activity4.total_float)
278
+ assert_equal(0,@activity4.early_float)
279
+
280
+ assert_equal(3,@activity5.earliest_start)
281
+ assert_equal(4,@activity5.latest_start)
282
+ assert_equal(5,@activity5.earliest_finish)
283
+ assert_equal(6,@activity5.latest_finish)
284
+ assert_equal(1,@activity5.total_float)
285
+ assert_equal(0,@activity5.early_float)
286
+
287
+ assert_equal(7,@activity6.earliest_start)
288
+ assert_equal(7,@activity6.latest_start)
289
+ assert_equal(8,@activity6.earliest_finish)
290
+ assert_equal(8,@activity6.latest_finish)
291
+ assert_equal(0,@activity6.total_float)
292
+ assert_equal(0,@activity6.early_float)
293
+
294
+ assert_equal(5,@activity7.earliest_start)
295
+ assert_equal(6,@activity7.latest_start)
296
+ assert_equal(7,@activity7.earliest_finish)
297
+ assert_equal(8,@activity7.latest_finish)
298
+ assert_equal(1,@activity7.total_float)
299
+ assert_equal(1,@activity7.early_float)
300
+
301
+ assert_equal(8,@activity8.earliest_start)
302
+ assert_equal(8,@activity8.latest_start)
303
+ assert_equal(12,@activity8.earliest_finish)
304
+ assert_equal(12,@activity8.latest_finish)
305
+ assert_equal(0,@activity8.total_float)
306
+ assert_equal(0,@activity8.early_float)
307
+ end
308
+
309
+ def test_to_s
310
+ assert_equal("Reference: activity1\nDescription: Activity One\n"+
311
+ "Duration: 1.0",@activity1.to_s)
312
+
313
+ @activity1.add_pre_activities(@activity2,@activity3)
314
+ assert_equal("Reference: activity1\nDescription: Activity One\n"+
315
+ "Duration: 1.0\nDepends on:\n activity2,activity3",@activity1.to_s)
316
+
317
+
318
+ end
319
+ end
320
+
321
+ class TC_StartFinishActivity < Test::Unit::TestCase
322
+ def test_new
323
+ assert_raises(RuntimeError) do
324
+ Precedence::StartFinishActivity.new('bogus')
325
+ end
326
+
327
+ assert_nothing_raised do
328
+ Precedence::StartFinishActivity.new('start')
329
+ end
330
+
331
+ assert_nothing_raised do
332
+ Precedence::StartFinishActivity.new('finish')
333
+ end
334
+
335
+ assert_nothing_raised do
336
+ Precedence::StartFinishActivity.new(:finish)
337
+ end
338
+
339
+ assert_nothing_raised do
340
+ Precedence::StartFinishActivity.new(:start)
341
+ end
342
+ end
343
+ end
@@ -0,0 +1,208 @@
1
+ require('lib/precedence/network')
2
+
3
+ class TC_ActivityHash < Test::Unit::TestCase
4
+ def test_all
5
+ activityHash = nil
6
+ assert_nothing_raised do
7
+ activityHash = Precedence::ActivityHash.new
8
+ end
9
+
10
+ assert_nothing_raised do
11
+ activityHash[:test] = "test"
12
+ end
13
+ assert_equal("test",activityHash[:test])
14
+ assert_equal("test",activityHash['test'])
15
+ end
16
+ end
17
+
18
+ class TC_Network < Test::Unit::TestCase
19
+
20
+ def setup
21
+ @network = Precedence::Network.new
22
+ @activity1 = Precedence::Activity.new('a1',1,'Activity1')
23
+ @activity2 = Precedence::Activity.new('a2',1,'Activity2')
24
+ @activity3 = Precedence::Activity.new('a3',1,'Activity3')
25
+ end
26
+
27
+ def test_initialize
28
+ net = nil
29
+ assert_nothing_raised do
30
+ net = Precedence::Network.new
31
+ end
32
+ assert_equal(2,net.activities.size)
33
+ assert_equal('start',net.activities[:start].reference)
34
+ assert_equal('finish',net.activities[:finish].reference)
35
+ assert_equal('Start',net.activities[:start].description)
36
+ assert_equal('Finish',net.activities[:finish].description)
37
+
38
+ assert_nothing_raised do
39
+ net = Precedence::Network.new('Begin','End')
40
+ end
41
+ assert_equal('Begin',net.activities[:start].description)
42
+ assert_equal('End',net.activities[:finish].description)
43
+ end
44
+
45
+ def test_new_activity
46
+ assert_raises(ArgumentError) do
47
+ @network.new_activity()
48
+ end
49
+
50
+ assert_nothing_raised do
51
+ @network.new_activity('a1',1)
52
+ end
53
+ assert_equal(3,@network.activities.size)
54
+ assert_equal('a1',@network.activities['a1'].reference)
55
+ assert_equal(1,@network.activities['a1'].duration)
56
+
57
+ assert_nothing_raised do
58
+ @network.new_activity('a2',2,'Activity Two')
59
+ end
60
+ assert_equal(4,@network.activities.size)
61
+ assert_equal('a2',@network.activities['a2'].reference)
62
+ assert_equal('Activity Two',@network.activities['a2'].description)
63
+ assert_equal(2,@network.activities['a2'].duration)
64
+
65
+ assert_nothing_raised do
66
+ @network.new_activity('a3',3,'Activity Three')
67
+ end
68
+ assert_equal(5,@network.activities.size)
69
+ assert_equal('a3',@network.activities['a3'].reference)
70
+ assert_equal('Activity Three',@network.activities['a3'].description)
71
+ assert_equal(3,@network.activities['a3'].duration)
72
+
73
+ assert_nothing_raised do
74
+ @network.new_activity('a4',4,'Activity Four',['a3'])
75
+ end
76
+ assert_equal(6,@network.activities.size)
77
+ assert_equal('a4',@network.activities['a4'].reference)
78
+ assert_equal('Activity Four',@network.activities['a4'].description)
79
+ assert_equal(4,@network.activities['a4'].duration)
80
+ assert_equal(1,@network.activities['a4'].post_activities.size)
81
+ assert_equal('a3',@network.activities['a4'].post_activities[0].reference)
82
+ assert_equal('a4',@network.activities['a3'].pre_activities[0].reference)
83
+
84
+ assert_nothing_raised do
85
+ @network.new_activity('a5',5,'Activity Five',['a3'],['a4'])
86
+ end
87
+ assert_equal(7,@network.activities.size)
88
+ assert_equal('a5',@network.activities['a5'].reference)
89
+ assert_equal('Activity Five',@network.activities['a5'].description)
90
+ assert_equal(5,@network.activities['a5'].duration)
91
+ assert_equal(1,@network.activities['a5'].post_activities.size)
92
+ assert_equal(1,@network.activities['a5'].pre_activities.size)
93
+ assert_equal('a3',@network.activities['a5'].post_activities[0].reference)
94
+ assert_equal('a5',@network.activities['a3'].pre_activities[1].reference)
95
+ assert_equal('a4',@network.activities['a5'].pre_activities[0].reference)
96
+ assert_equal('a5',@network.activities['a4'].post_activities[1].reference)
97
+
98
+ assert_raises(RuntimeError) do
99
+ @network.new_activity('a6',6,'Activity Six',['a7'])
100
+ end
101
+
102
+ assert_raises(RuntimeError) do
103
+ @network.new_activity('a6',6,'Activity Six',['a5','a7'])
104
+ end
105
+ assert_equal(nil,
106
+ @network.activities['a5'].pre_activities.detect do |activity|
107
+ activity.reference == 'a6'
108
+ end)
109
+
110
+ assert_raises(RuntimeError) do
111
+ @network.new_activity('a6',6,'Activity Six',[],['a5','a7'])
112
+ end
113
+ assert_equal(nil,
114
+ @network.activities['a5'].post_activities.detect do |activity|
115
+ activity.reference == 'a6'
116
+ end)
117
+ end
118
+
119
+ def test_add_activity
120
+ assert_nothing_raised do
121
+ @network.add_activity(@activity1)
122
+ end
123
+ assert_equal(3,@network.activities.size)
124
+ assert_equal(@activity1,@network.activities[@activity1.reference])
125
+
126
+ assert_raises(RuntimeError) do
127
+ @network.add_activity(@activity1)
128
+ end
129
+
130
+ @network = Precedence::Network.new
131
+ @activity1.add_post_activities(@activity2)
132
+ assert_raises(RuntimeError) do
133
+ @network.add_activity(@activity1)
134
+ end
135
+ assert_equal(2,@network.activities.size)
136
+ end
137
+
138
+ def test_connect
139
+ @network.add_activity(@activity1)
140
+ @network.add_activity(@activity2)
141
+
142
+ assert_nothing_raised do
143
+ @network.connect(@activity1.reference,@activity2.reference)
144
+ end
145
+ assert_equal(1,@activity1.post_activities.size)
146
+ assert_equal(1,@activity2.pre_activities.size)
147
+ assert_equal('a2',@activity1.post_activities[0].reference)
148
+ assert_equal('a1',@activity2.pre_activities[0].reference)
149
+
150
+ assert_raises(RuntimeError) do
151
+ @network.connect(@activity1.reference,'bogus reference')
152
+ end
153
+ assert_equal(1,@activity1.post_activities.size)
154
+ assert_equal('a2',@activity1.post_activities[0].reference)
155
+
156
+ assert_raises(RuntimeError) do
157
+ @network.connect('bogus reference','a2')
158
+ end
159
+ assert_equal(1,@activity2.pre_activities.size)
160
+ assert_equal('a1',@activity2.pre_activities[0].reference)
161
+ end
162
+
163
+ def test_fix_connections!
164
+ @network.add_activity(@activity1)
165
+ @network.add_activity(@activity2)
166
+ @network.add_activity(@activity3)
167
+ @network.connect('start',@activity1.reference)
168
+ @network.connect(@activity1.reference,@activity2.reference)
169
+ @network.connect(@activity3.reference,'finish')
170
+ @network.fix_connections!
171
+ assert_equal(1,@activity2.post_activities.size)
172
+ assert_equal('finish',@activity2.post_activities[0].reference)
173
+ assert_equal(1,@activity3.pre_activities.size)
174
+ assert_equal('start',@activity3.pre_activities[0].reference)
175
+ end
176
+
177
+ def test_to_dot
178
+ net = Precedence::Network.new('Begin','End')
179
+ net.new_activity('act-1-1',3,'System specification')
180
+ net.new_activity('act-1-2',2,'Review')
181
+ net.new_activity('act-1-3',2,'System re-specification')
182
+ net.new_activity('act-2-1',3,'Test tool design')
183
+ net.new_activity('act-2-2',5,'Test tool implementation')
184
+ net.new_activity('act-3-1',3,'System design')
185
+ net.new_activity('act-3-2',12,'System implementation')
186
+ net.new_activity('act-2-3',10,'System testing')
187
+ net.connect('act-1-1','act-1-2')
188
+ net.connect('act-1-2','act-1-3')
189
+ net.connect('act-1-3','act-3-1')
190
+ net.connect('act-1-2','act-2-1')
191
+ net.connect('act-2-1','act-2-2')
192
+ net.connect('act-2-2','act-3-2')
193
+ net.connect('act-3-1','act-3-2')
194
+ net.connect('act-3-2','act-2-3')
195
+ net.fix_connections!
196
+
197
+ assert_nothing_raised do
198
+ File.open('test_to_dot.dot',File::CREAT|File::TRUNC|File::WRONLY) do|f|
199
+ f.puts(net.to_dot)
200
+ end
201
+ end
202
+ $stdout.puts("\nTest dot file test_to_dot.dot file generated.")
203
+
204
+ if system("dot","-Tpng","-otest_to_dot.png","test_to_dot.dot")
205
+ $stdout.puts("Test png file test_to_dot.png files generated.")
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,14 @@
1
+ require('test/unit')
2
+
3
+ require('tests/tc_activity')
4
+ require('tests/tc_network')
5
+
6
+ class TS_Precedence < Test::Unit::TestSuite
7
+ def initialize(name="Precedence Test Suite")
8
+ super(name)
9
+ @tests << TC_Activity.suite
10
+ @tests << TC_StartFinishActivity.suite
11
+ @tests << TC_ActivityHash.suite
12
+ @tests << TC_Network.suite
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: precedence
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.6.0
7
+ date: 2005-05-15
8
+ summary: A library for the creation manipulation and analysis of precedence networks.
9
+ require_paths:
10
+ - lib
11
+ email: farrel@lifson.info
12
+ homepage: http://precedence.rubyforge.org
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: precedence
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Farrel Lifson
29
+ files:
30
+ - lib/precedence
31
+ - lib/precedence.rb
32
+ - lib/precedence/activity.rb
33
+ - lib/precedence/network.rb
34
+ - tests/tc_activity.rb
35
+ - tests/tc_network.rb
36
+ - tests/ts_precedence.rb
37
+ - README
38
+ - CHANGELOG
39
+ - TODO
40
+ test_files:
41
+ - tests/ts_precedence.rb
42
+ rdoc_options: []
43
+ extra_rdoc_files:
44
+ - README
45
+ - CHANGELOG
46
+ - TODO
47
+ executables: []
48
+ extensions: []
49
+ requirements: []
50
+ dependencies: []