shrewd 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1fb0db5cb8f287f162dd3832976cc02b28ff950a
4
+ data.tar.gz: 68db1125777aa925467df4a711ea63f3207690db
5
+ SHA512:
6
+ metadata.gz: 6b54c374656059836ec26c9d92f7a279f64107bbb2fe605159973b4904b6eb74a5dab34a34fb79dab68518448ee88e7c45c228846d9e06bda3a3fd1ccab669c3
7
+ data.tar.gz: 876aa24eb1be625b5b9a131a6c1de3fb7c533ef8669e3c08a79738144cb56ce37ec0c4e6da6b0abc914eaee23c7217f02e79d99315ee40531672431b21d4a30c
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ .DS_Store
3
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # Source
2
+ source "https://rubygems.org"
3
+
4
+ # Gems defined in gemspec
5
+ gemspec
6
+
7
+ # Gems
8
+ group :test do
9
+ gem 'rake'
10
+ gem 'rspec'
11
+ end
data/README.md ADDED
@@ -0,0 +1,416 @@
1
+ # Shrewd
2
+ Shrewd is a Ruby gem that helps you build discrete event simulations
3
+ using a event-based workflow.
4
+
5
+ ## Installing Shrewd
6
+ Shrewd can be found on RubyGems at [this link](http://rubygems.org/gems/shrewd).
7
+ Install Shrewd with RubyGems:
8
+
9
+ ```shell
10
+ gem install shrewd
11
+ ```
12
+
13
+ Include in your Gemfile:
14
+
15
+ ```ruby
16
+ gem 'shrewd'
17
+ ```
18
+
19
+ ## What is a Discrete Event Simulation?
20
+ A [discrete event simulation](https://en.wikipedia.org/wiki/Discrete_event_simulation)
21
+ is a method of simulating a system whose state fluctuates over time.
22
+ Discrete event simulations are known as such because, unlike continuous
23
+ simulations, the state of the simulation is modified at discrete points in time
24
+ rather than continuously. These simulations are used to model thousands of
25
+ different systems, including traffic congestion, manufacturing throughput,
26
+ aircraft wind resistance, economic policies, etc. Simulations can provide
27
+ insights into the effects of resource scarcity and constraints in ways that
28
+ other forms of statistical analysis cannot provide. They're also extremely fun
29
+ to model and visualize!
30
+
31
+ ## How does a Discrete Event Simulation Work?
32
+ Discrete event simulations can be modeled in a number of different ways. This
33
+ gem uses an approach known as event-based modeling.
34
+
35
+ Event-based modeling involves several core concepts: Events, Processes, and
36
+ State Variables.
37
+
38
+ **State Variables** hold the state of the simulated system. They can represent
39
+ everything from the length of a queue for a bathroom in Disneyland, to the
40
+ number of available taxis in New York City, to a binary value representing a
41
+ factory in a state of downtime. The rationale for these variables and their
42
+ underlying values ultimately represent and define the system that you are
43
+ simulating.
44
+
45
+ The state of the system is modified in a simulation by **events**. Events occur at
46
+ discrete points in time and do two things: modify the state of the system by
47
+ modifying system state variables and initiate processes.
48
+
49
+ An example of an event might be an "arrival" event which represents a
50
+ customer "arriving" in line a retail store. At this point in time, the state of
51
+ the system is modified by incrementing the queue by 1.
52
+
53
+ In order to trigger future events, events often initiate **processes** that schedule
54
+ future events. A process can have boolean conditions that verify the process
55
+ should be initiated. These might verify, for example, that there is a cashier
56
+ able to handle a newly arrived customer. If the conditions pass, the process
57
+ schedules a given event at a given point in the future.
58
+
59
+ In the example of our "arrival" event above, an arrival event might have a
60
+ process that it initiates after incrementing the queue to schedule a "service"
61
+ event. If there is an available cashier, the customer will be helped
62
+ immediately by immediately scheduling a service event, which would decrement
63
+ the queue by 1. If there is not a cashier available, then the "service" event is
64
+ not scheduled by the process.
65
+
66
+ ## Your First Simulation
67
+ Let's put these concepts into practice. In this example, we will model a
68
+ simple grocery store queue. For the sake of simplicity, there will be one queue
69
+ and the person at the front of the queue will be sent to the first available
70
+ employee (5 total), where the customer will be serviced over a period of 1 to 4
71
+ minutes (uniformly randomly distributed). Customers will arrive to the queue
72
+ every 0 to 5 minutes (uniformly randomly distributed).
73
+
74
+ First, we'll initialize the state variables for our simulation. The state of
75
+ this system is determined by the number of the customers in the queue and the
76
+ number of available employees.
77
+
78
+
79
+ ```ruby
80
+ # Create system variables
81
+ queue = Shrewd::Variable.new('Queue', '# of customers in the queue.', 0)
82
+ employees = Shrewd::Variable.new('Employees', '# of available employees.', 5)
83
+ variables = [queue, employees]
84
+ ```
85
+
86
+ We will then initialize the events in the simulation. The main events of the
87
+ simulation are the arrival of customers to the queue every 0 to 5 minutes, the
88
+ start of a customer service, and finally the end of the customer service 1 to 4
89
+ minutes after the start.
90
+
91
+ ```ruby
92
+ # Create events
93
+ customer_arrival = Shrewd::Event.new('Customer Arrival',
94
+ 'Customer arrives to grocery store.')
95
+ customer_arrival.action = Proc.new { |vars| vars[:queue].increment }
96
+
97
+ start = Shrewd::Event.new('Start Service', 'Start customer service.')
98
+ start.action = Proc.new do |vars|
99
+ vars[:queue].decrement
100
+ vars[:employees].decrement
101
+ end
102
+
103
+ finish = Shrewd::Event.new('Finish Service', 'Finish customer service.')
104
+ finish.action = Proc.new { |vars| vars[:employees].increment }
105
+ ```
106
+
107
+ We now define the processes for the simulation. The processes include the
108
+ arrival process which schedules a new arrival event, a start process that
109
+ schedules the service start event, and a service process that schedules the
110
+ finish service event.
111
+
112
+ ```ruby
113
+ # Create processes
114
+ arrival = Shrewd::Process.new('Arrival', 'Arrival of a customer.')
115
+ arrival.delay = Proc.new { |vars| Random.new.rand * 5 }
116
+ arrival.event = customer_arrival
117
+
118
+ start_service = Shrewd::Process.new('Start Service',
119
+ 'Start customer service.')
120
+ start_service.delay = Proc.new { |vars| 0 }
121
+ start_service.conditions = Proc.new do |vars|
122
+ vars[:employees] > 0 && vars[:queue] > 0
123
+ end
124
+ start_service.event = start
125
+
126
+ service_widget = Shrewd::Process.new('Service', 'Servicing a customer.')
127
+ service_widget.delay = Proc.new { |vars| Random.new.rand * 3 + 1 }
128
+ service_widget.event = finish
129
+ ```
130
+
131
+ We then add these processes to the events that trigger them.
132
+
133
+ ```ruby
134
+ # Add processes to events
135
+ customer_arrival.processes << arrival << start_service
136
+ start.processes << service_widget
137
+ finish.processes << start_service
138
+ initial_processes = [ arrival ]
139
+ ```
140
+
141
+ Finally, we can initialize and run the simulation for 10,000 minutes.
142
+
143
+ ```ruby
144
+ # Create simulation
145
+ simulation = Shrewd::Simulation.new(variables, initial_processes)
146
+ simulation.start(10000)
147
+ log = simulation.print_log
148
+ log.each do |entry|
149
+ puts "#{entry[:time]} - #{entry[:event].name}"
150
+ end
151
+ ```
152
+
153
+ ## Simulations
154
+ `Shrewd::Simulation` is the core of the Shrewd gem. Its instances
155
+ orchestrate simulations by providing an interface to build a discrete event
156
+ simulation, hold the state of the simulation during runtime, manage the
157
+ simulation during runtime, and track historical data of the simulation.
158
+ At any time, a simulation instance can only hold the state for a single
159
+ simulation. If you wish to run multiple concurrent simulations, you must
160
+ instantiate multiple simulation instances.
161
+
162
+ ### Creating a Simulation
163
+ You can create a simulation by initializing a simulation object with an array
164
+ of the state variables that will be used during the simulation and an array of
165
+ the process that should be run at the start of the simulation.
166
+
167
+ ```ruby
168
+ sim_variables = [Shrewd::Variable instances...]
169
+ initial_processes = [Shrewd::Process instances...]
170
+ my_sim = Shrewd::Simulation.new(sim_variables, initial_processes)
171
+ ```
172
+
173
+ ### Modifying a Simulation
174
+ While you cannot add or remove state variables from a simulation once created,
175
+ you can add or remove processes by modifying the `initial_processes` attribute.
176
+
177
+ ```ruby
178
+ # add an initial process
179
+ my_sim.initial_processes << Shrewd::Process.new('New Process')
180
+
181
+ # remove an initial process
182
+ last_process = my_sim.initial_processes.pop
183
+ ```
184
+
185
+ ### Running a Simulation
186
+ You can start a simulation by calling the `start` method and providing the
187
+ length of simulation time that the simulation should run. For example, if you
188
+ were to run your simulation for 10,000 units of time you would start the
189
+ simulation with:
190
+
191
+ ```ruby
192
+ my_sim.start(10000)
193
+ ```
194
+
195
+ The default simulation length, if a time length is not provided to the `start`
196
+ method, is 1000 time units.
197
+
198
+ ### Printing the Simulation Results
199
+ You can see the results of the simulation by retrieving the simulation log.
200
+
201
+ ```ruby
202
+ my_sim.print_log
203
+ ```
204
+
205
+ The log will have will have an array of event hashes of the format:
206
+ `{ event: event_object, time: event_time }`.
207
+
208
+ ### Resetting a Simulation
209
+ If you wish to reset your simulation to run another instance, you can use the
210
+ reset method.
211
+ ```ruby
212
+ my_sim.reset
213
+ ```
214
+ This will clear the simulation logs, reset the clock to 0, and will return the
215
+ state variables to their initial state.
216
+
217
+ ## Variables
218
+ `Shrewd::Variable` is a finite simulation state variable, which must be
219
+ initialized before the simulation is started. State variables are modified in
220
+ quantity by `Shrewd::Event` instances through the course of the simulation. They
221
+ are commonly used in the conditions for `Shrewd::Process` instances, determining
222
+ whether or not there are available resources to schedule the corresponding
223
+ simulation event (see how Processes work in the documentation below).
224
+
225
+ An example of a state variable might be the number of cashiers at a grocery
226
+ store. Once a customer arrives, a cashier become occupied for a time to help the
227
+ customer, thereby decreasing the number of the available cashier variables by
228
+ one. If customers arrive and there are no available cashiers to help them, the
229
+ customers are added to a queue. This queue would be represented by another
230
+ state variable that is incremented as customers arrive and decremented as
231
+ customers are served.
232
+
233
+ State variables can also be parameterized so that multiple instances of a
234
+ Variable can exist. This proves useful in, for example, a simulation of a
235
+ manufacturing plant which processes multiple different types of products. Each
236
+ product must under go the same events in the manufacturing system, e.g.
237
+ assembly, washing, testing, etc. It is much cleaner and easier in the case to
238
+ parameterize a single variable rather than create instance variables to
239
+ represent each different product.
240
+
241
+ ### Creating a State Variable
242
+ You can create a state variable by passing in the name, description, and
243
+ initial values of the variables.
244
+
245
+ ```ruby
246
+ my_var = Shrewd::Variable.new("Queue", "A queue at a grocery store.", 0)
247
+ ```
248
+
249
+ Only the name is required. If a value is not specified, the state variable will
250
+ default to 0.
251
+
252
+ ### Modifying a State Variable
253
+ You can modify the name, description, and value of a state variable at any
254
+ point before or during the simulation.
255
+
256
+ ```ruby
257
+ # modify name
258
+ my_var.name = "Employees"
259
+
260
+ # modify description
261
+ my_var.description = "Number of available employees in a grocery store."
262
+
263
+ # modify value
264
+ my_var.value = 20
265
+ ```
266
+
267
+ ### Incrementing a State Variable
268
+ You can increment a state variable using the increment helper.
269
+
270
+ ```ruby
271
+ # increment variable by 1
272
+ my_var.increment
273
+
274
+ # increment variable by 10
275
+ my_var.increment(10)
276
+ ```
277
+
278
+ ### Decrementing a State Variable
279
+ You can decrement a state variable using the decrement helper.
280
+
281
+ ```ruby
282
+ # decrement variable by 1
283
+ my_var.decrement
284
+
285
+ # decrement variable by 10
286
+ my_var.decrement(10)
287
+ ```
288
+
289
+ ### Comparing State Variables
290
+ You can compare state variables with number values.
291
+
292
+ ```ruby
293
+ if my_var > 10
294
+ puts "Greater than 10."
295
+ elsif my_var == 10
296
+ puts "Equal to 10."
297
+ end
298
+ ```
299
+
300
+ ## Events
301
+ `Shrewd::Event` denotes an event in time during a discrete event simulation.
302
+ Events can change the state of the system by modifying state variables.
303
+ during simulation runtime. Events are scheduled by `Shrewd::Process` instances.
304
+ A historical record of events for a simulation run is recorded by the
305
+ `Shrewd::Simulation` instance. After an event changes the system state by
306
+ modifying simulation state variables, it can execute one or many processes,
307
+ which in turn schedules future events.
308
+
309
+ ### Creating an Event
310
+ You can create an event by providing a name, description, an array of
311
+ processes that this event will enqueue when it is triggered (after the action
312
+ is complete), and a ruby block that defines the action the event will take
313
+ when triggered.
314
+
315
+ ```ruby
316
+ # define processes that this event will trigger
317
+ processes = [Shrewd::Process.new("Service")]
318
+
319
+ # define action that will occurr when event is triggered
320
+ action = proc { |vars| vars[:queue].increment }
321
+
322
+ # create event
323
+ my_event = Shrewd::Event.new("Arrival", "Description", processes, action)
324
+ ```
325
+
326
+
327
+ The name attribute is the only required attribute to create an event.
328
+ The simulation state variables will be injected into the event action block.
329
+ The event action block is the primary way by which you modify the state of the
330
+ system throughout the simulation.
331
+
332
+ ### Modifying an Event
333
+ The name, description, processes, and action attributes of events are all
334
+ modifiable before and event during the simulation. It is not recommended to
335
+ modify events during the simulation.
336
+
337
+ ```ruby
338
+ # modify name
339
+ my_event.name = "Start Service"
340
+
341
+ # modify description
342
+ my_event.description = "Start the service for a customer in the queue."
343
+
344
+ # modify processes
345
+ my_event.processes = [Shrewd::Process.new("Finish")]
346
+
347
+ # modify action
348
+ my_event.action = proc do |vars|
349
+ vars[:queue].decrement
350
+ vars[:employees].decrement
351
+ end
352
+ ```
353
+
354
+ ## Processes
355
+ `Shrewd::Process` is a process that is ultimately triggered by an event. Each
356
+ process can have conditions that determine whether the process should carry out.
357
+ If it passes the conditions, the process schedules one `Shrewd::Event` in a
358
+ provided time in the future.
359
+
360
+ The conditions for a process can use state variables as well as randomized
361
+ numbers, and are defined by a Ruby block. The time in the future at which the
362
+ corresponding event should be schedule is also defined by a ruby block. This
363
+ allows you greater control over this time variable, allowing you to use random
364
+ distributions or define the time conditioned on the state of the system.
365
+
366
+ ### Creating a Process
367
+ You can create a process by providing the name, description, the
368
+ `Shrewd::Event` to schedule, a ruby conditions block , and a ruby time block.
369
+
370
+ The conditions block should provide a boolean return. A value of `true` means
371
+ that the process passed the validations and can schedule the event. A value of
372
+ `false` will prevent the process from scheduling the event. If a conditions
373
+ block is not provided, then the process will always schedule the event.
374
+
375
+ The time block should return the value of time in the future at which the
376
+ future event should be scheduled. If not provided, the time block will default
377
+ to 0 (event will be scheduled immediately).
378
+
379
+ ```ruby
380
+ # define event that this process will enqueue
381
+ event = Shrewd::Event.new("Start Service")
382
+
383
+ # define conditions - conditions must return boolean
384
+ conditions = proc { |vars| vars[:queue] > 0 }
385
+
386
+ # define time until event is enqueued - time must return a
387
+ time = proc { |vars| Random.new.rand > 0.5 }
388
+
389
+ # create the process
390
+ my_process = Shrewd::Process.new("Service", "Servicing a customer.", event, conditions, time)
391
+ ```
392
+
393
+ The name attribute is the only required attribute to create a process. If an
394
+ event is not provided, then no events will be queued by the process.
395
+
396
+ ### Modifying a Process
397
+ You can modify the name, description, event, conditions block, and time block
398
+ of a process at any point before or during a simulation. It is not recommended
399
+ to modify a process during a simulation.
400
+
401
+ ```ruby
402
+ # modify name
403
+ my_process.name = "Finish"
404
+
405
+ # modify description
406
+ my_process.description = "Finishing a customer's service."
407
+
408
+ # modify event
409
+ my_process.event = Shrewd::Event.new("Finish Service")
410
+
411
+ # modify conditions block
412
+ my_process.conditions = proc { |vars| vars[:queue] > 0 }
413
+
414
+ # modify time block
415
+ my_process.time = proc { |vars| Random.new.rand < 0.1 }
416
+ ```
data/Rakefile ADDED
File without changes
data/lib/shrewd.rb ADDED
@@ -0,0 +1 @@
1
+ require 'shrewd/simulation'