-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathRandomEventDispatcher.m
423 lines (392 loc) · 18.3 KB
/
RandomEventDispatcher.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
%% RandomEventDispatcher
% generate random events.
% This class has enhanced the functionality of <RequestEvnet>, specifically including
% following improvements:
%
% # Introduce the concept of <Entity> and <EntityBuilder> for better organizing events.
% <Entity> represents the entity that produces events (Arrival/Departure), and
% <EntityBuilder> is used to generate these entities.
% # Introduce <ListArray> to store entity builders and event entities, and <PriorityQueue>
% to store events. Therefore, the maintenance of entities and events can be much
% simplified. Besides, the storage capacity for <ListArray> and <PriorityQueue> can be
% flexibly adjusted.
% # The entities are added to <ListArray> by arrival time, while the events are added to
% <PriorityQueue> by departure time. On the other hand, in <RequestEvent> two lists are
% maintained for arrival and departure events, and we need to sort the departure events.
% #
classdef RandomEventDispatcher < matlab.mixin.Copyable & HeteroObject
properties (SetAccess = protected)
rand_state;
seed;
avg_arrive_interval; % update when entity source changes
entity_probability; % update when entity source changes
entity_builder;
entities;
event_queue;
b_pending_arrive = false;
end
properties (Access = private)
current_time = 0;
end
properties(Dependent)
CurrentTime;
end
methods
% * |event_set|: specifies the parameters of comming events, including
% _ArrivalRate_, _ServiceInterval_;
% this = RandomEventDispatcher(entity_builder, seed, cur_time)
function this = RandomEventDispatcher(entity_builder, varargin)
if nargin == 0
error('error: RandomEvent must be initialized with event entity.');
end
if nargin >= 1
%%%
% Calculate the accumulate probability
if isempty(entity_builder)
entity_builder = ListArray('EntityBuilder');
end
if isa(entity_builder, 'ListArray') && ...
istype(entity_builder.TypeName, 'EntityBuilder')
this.entity_builder = entity_builder.copy;
elseif isa(entity_builder, 'EntityBuilder')
this.entity_builder = ListArray([], entity_builder);
else
error('error: Invalid data for entity source.');
end
this.update_entity_builder;
end
if length(varargin) >= 1
rng(varargin{1});
this.seed = varargin{1};
rng(this.seed);
else
warning('random number seed is not specified (set as shuffle).');
rng('shuffle');
scurr = rng;
this.seed = scurr.Seed;
end
if length(varargin) >= 2
this.current_time = varargin{2};
% this.current_arrive_time = this.current_time;
end
this.entities = ListArray('Entity');
priority = struct('field', 'Time', 'sorttype', 'ascend');
this.event_queue = PriorityQueue('Event', priority);
this.rand_state = rng;
end
function delete(this)
delete(this.entities);
delete(this.entity_builder);
delete(this.event_queue);
end
end
methods (Access=protected)
function newobj = copyElement(this)
newobj = copyElement@matlab.mixin.Copyable(this);
%% Deep Copy Issues
% *entity_builder*: we should relocate the flow entity builder's parent for the new copy.
% *entities*: we should relocate the entity's builder for the new copy. Furthermore, if
% the entity is of class <SliceEntity>, we need to relocate the |Child| of the new
% copy. <RandomEventDispatcher> has access to <Entity.Builder>, and
% <SliceEntity.Child>.
%
newobj.entity_builder = this.entity_builder.copy();
newobj.entities = this.entities.copy();
newobj.event_queue = this.event_queue.copy();
for i = 1:newobj.entity_builder.Length
if isa(this.entity_builder{i}, 'FlowEntityBuilder')
if ~isempty(this.entity_builder{i}.Parent)
idx = this.entities.Find(this.entity_builder{i}.Parent);
eb = newobj.entity_builder{i};
eb.Parent = newobj.entities{idx};
end
end
end
for i = 1:newobj.entities.Length
if ~isempty(this.entities{i}.Builder)
idx = this.entity_builder.Find(this.entities{i}.Builder);
et = newobj.entities{i};
% Since (slice) entities may be mannually added to the dispatcher, thus without
% associated builder in the dispatcher. Since the external builder will not be
% used, we set the |Builder| field of the new copy as empty.
if isempty(idx)
et.Builder = creatempty('EntityBuilder');
else
et.Builder = newobj.entity_builder{idx};
end
end
if isa(this.entities{i}, 'SliceEntity')
if ~isempty(this.entities{i}.Child)
warning('<Child> is non-empty.');
end
end
end
for i = 1:newobj.event_queue.Length
if ~isempty(this.event_queue{i}.Entity)
idx = this.entities.Find(this.event_queue{i}.Entity);
ev = newobj.event_queue{i};
ev.Entity = newobj.entities{idx};
end
end
end
end
methods
function reset(this, seed)
if nargin >= 2
this.seed = seed;
end
this.entities.Clear();
this.event_queue.Clear();
rng(this.seed);
this.rand_state = rng;
% this.sojourn_type = zeros(this.NumberEventType,1);
% this.stat.total_arrival = 0;
% this.stat.arrive_type = zeros(this.NumberEventType,1);
% this.depart_id(1) = 1;
% this.current_arrive_pos = 1;
% this.current_depart_pos = 1;
% this.accumulate_arrival = 1;
end
end
methods
function t = get.CurrentTime(this)
t = this.current_time;
end
end
methods
function ev = nextEvent(this)
rng(this.rand_state);
if this.event_queue.Length == 0 % && this.entities.Length == 0
if this.entity_builder.Length > 0
this.addnewentity;
else
warning('No more events will be generated.');
ev = Event.empty;
return;
end
end
if this.event_queue{1}.Type == EventType.Depart
% When there is another 'Arrive' event in the event queue, we know there
% is no other 'Arrive' event before this event. So we will process the
% 'Depart' event.
% On the other hand, if there is no 'Arrive' event in the queue, we do not
% know if there is events before the 'Depart' event, since it might not
% have been generated. So we first generate a new entity, and
% see if the new 'Arrive' event is before the 'Depart' event.
% (1) if true, then the new 'Arrive' event will be first processed;
% (2) otherwise, the 'Depart' event will be processed, since it is now
% determined that no more event before it.
if ~this.b_pending_arrive
this.addnewentity;
assert(issorted(this.event_queue{'Time'}), 'error: EventQueue.');
this.b_pending_arrive = true;
end
end
ev = this.event_queue.PopFront();
this.current_time = ev.Time;
%%%
% when removing events, we need to check if we also need to remove the
% associated Entity.
if ev.Type == EventType.Depart
ev.userdata = this.entities.Remove(ev.Entity);
% for i = 1:this.event_queue.Length
% if this.event_queue(i).Type == EventType.Depart
% this.nextdeparttime = this.event_queue(i).Time;
% break;
% end
% end
else
ev.userdata = ev.Entity;
if this.b_pending_arrive
this.b_pending_arrive = false;
end
end
this.rand_state = rng;
end
function AddEntityBuilder(this, src)
this.entity_builder.Add(src);
this.update_entity_builder;
end
end
methods (Access=protected)
function t = nextEntityId(this)
r = rand;
for t = 1:this.entity_builder.Length
if r<=this.entity_probability(t)
break;
end
end
end
function update_entity_builder(this)
arrive_rate = this.entity_builder{'ArrivalRate'};
if ~isempty(arrive_rate)
this.avg_arrive_interval = 1/sum(arrive_rate);
this.entity_probability = cumsum(arrive_rate/sum(arrive_rate));
end
end
%%%
% When Entity Source is removed, it will no longer be used.
% Before remove it, we may need to remove the associated events/entities.
%
% NOTE: this class might not need this function, while it can be used by child
% class. See also <SliceFlowEventDispatcher.removeEntityBuilder>
function removeEntityBuilder(this, argin, index)
if isinteger(argin)
eb_id = argin;
for i = 1:this.entity_builder.Length
if this.entity_builder{i, 'Identifer'} == eb_id
%%%
% with no return value, the object of entity source will be
% deleted.
this.entity_builder.Remove(i);
break;
end
end
elseif isa(argin, 'EntityBuilder')
if nargin >= 3
this.entity_builder.Remove(index);
else
this.entity_builder.Remove(argin);
end
else
error('error: invalid input arguments.');
end
this.update_entity_builder;
end
function e = addnewentity(this)
arrive_time = this.current_time + exprnd(this.avg_arrive_interval);
ei = this.nextEntityId;
service_time = exprnd(this.entity_builder{ei}.ServiceInterval);
e = Entity(arrive_time, service_time, this.entity_builder{ei});
this.entities.Add(e);
%%%
% PushBack can return the storage index in the queue.
% this.last_arrive_pos = ...
this.event_queue.PushBack(...
Event(arrive_time, EventType.Arrive, this.entities{this.entities.Length}), 'front');
% depart_pos = ...
this.event_queue.PushBack(...
Event(e.DepartTime, EventType.Depart, this.entities{this.entities.Length}));
% if depart_pos < this.next_depart_pos
% this.next_depart_pos = depart_pos;
% end
% if e.DepartTime < this.nextdeparttime
% this.nextdeparttime = e.DepartTime;
% end
% this.current_arrive_time = arrive_time;
end
function t = randtime(this, mean_time, n)
rng(this.rand_state);
if nargin <= 2
n = 1;
end
t = exprnd(mean_time, n, 1);
this.rand_state = rng;
end
end
properties
%% Deprecated
% |arrive_times|, |depart_times|, and |arrive_type| can be accessed from entity
% list or event queue.
% arrive_times;
% depart_times;
% arrive_type;
% <EventType> should be used with <EntityType> to identify a unique event type.
% The information is stored in |entity_builder|.
% NumberEventType;
% |depart_id| in <RequestEvent> record the index of departure events by the order
% of depart time. This is not need in <RandomEventDispatcher>, since we have
% sorted all events by depart time.
% depart_id;
% |sojourn_type| can be obtained, by access entity list or event queue. This is
% equivalent to |arrive_type|, since this class will remove the departed entities
% and events.
% sojourn_type;
% NumberSojourns;
%% TO BE UPDATED
% These fields can be maintained to accelarate the access of entity list and event
% queue.
% nextdeparttime = inf;
% current_arrive_time = 0;
% next_depart_pos = inf; % Next depart event's location in the event queue
% last_arrive_pos; % The most recent arrival event's location in the event queue.
% lastarrivetime;
%% TODO
% Log the statistics of entities and events from the beginning.
% stat;
end
methods (Access = private)
%% TO BE UPDATED
% generate events in the given interval.
% NOTE: in this interval, the event source (entity builder) will not change. Since
% this may not applied to the subclass (<SliceFlowEventDispatcher>, which can
% dynamic add/remove entity builder, this function is accessed privately.
% function generateEvents(this, start_time, interval)
% current_time = start_time;
% while current_time < interval
% arrive_time = current_time + exprnd(this.avg_arrive_interval);
% type = this.nextType;
% service_time = exprnd(this.event_set.ServiceInterval(type));
% e = Entity(type, arrive_time, service_time);
% this.entities.Add(e);
% current_time = arrive_time;
% end
% depart_time = zeros(this.entities.Length,1);
% [~,idx] = sort(depart_time, 'ascend');
% i = 1;
% j = 1;
% while i<this.entities.Length && j < this.entities.Length
% ta = this.entities{i}.ArriveTime ;
% ts = this.entities{idx(j)}.DepartTime;
% if ta <= ts
% this.event_sequence.Add(Event(ta, 'SliceArrive', i));
% i = i + 1;
% else
% this.event_sequence.Add(Event(ts, 'SliceDepart', idx(j)));
% j = j + 1;
% end
% end
% while i<this.entities.Length
% ta = this.entities{i}.ArriveTime ;
% this.event_sequence.Add(Event(ta, 'SliceArrive', i));
% i = i + 1;
% end
% while i<this.entities.Length && j < this.entities.Length
% ts = this.entities{idx(j)}.DepartTime;
% this.event_sequence.Add(Event(ts, 'SliceDepart', idx(j)));
% j = j + 1;
% end
% end
%
% function t = get.nextdeparttime(this)
% t = this.event_queue(this.next_depart_pos).Time;
% end
%
% Last arrival time can be calculated from the Entity Queue, or from the Event
% Queue, which you need to record the location of the last arrival event.
% function t = get.lastarrivetime(this)
% t = this.entities(end).ArriveTime;
% %%%
% % t = this.event_queue(this.last_arrive_pos).Time;
% end
%
% function n = get.NumberEventType(this)
% n = height(this.event_set);
% end
% function n = get.NumberSojourns(this)
% n = sum(this.sojourn_type);
% end
% function ev = nextEvent(this)
% % this.next_depart_pos = this.next_depart_pos - 1;
% % this.last_arrive_pos = this.last_arrive_pos - 1;
% % if this.next_depart_pos == 0
% % for i = 1:this.event_queue.Length
% % if this.event_queue(i).Type == EventType.Depart
% % this.next_depart_pos = i;
% % break;
% % end
% % end
% % end
% end
end
end