shrewd 0.0.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.
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'