1classdef NetworkGenerator < handle
2 % A generator
object that generates queueing network models
3 % based on user specification. Characteristics of generated
4 % models can be configured via the generator
's properties. See
5 % user guide in report for detailed usage instructions.
12 hasVaryingServiceRates (1, 1) logical
13 hasMultiServerQueues (1, 1) logical
14 hasRandomCSNodes (1, 1) logical
15 hasMultiChainCS (1, 1) logical
16 topologyFcn function_handle
19 properties (SetAccess = private)
21 highJobLoadRange = [31 40]
22 medJobLoadRange = [11 20]
23 lowJobLoadRange = [1 5]
27 function obj = NetworkGenerator(varargin)
29 addParameter(p, 'schedStrat
', 'randomize
'); % alternatives: 'fcfs
', 'ps
'
30 addParameter(p, 'routingStrat
', 'randomize
'); % alternatives: 'Random
', 'Probabilities
'
31 addParameter(p, 'distribution
', 'randomize
'); % alternatives: 'exp
', 'erlang
', 'hyperexp
'
32 addParameter(p, 'cclassJobLoad
', 'randomize
'); % alternatives: 'low
', 'medium
', 'high
'
33 addParameter(p, 'hasVaryingServiceRates
', true);
34 addParameter(p, 'hasMultiServerQueues
', true);
35 addParameter(p, 'hasRandomCSNodes
', true);
36 addParameter(p, 'hasMultiChainCS
', true);
37 addParameter(p, 'topologyFcn
', @NetworkGenerator.randGraph);
38 parse(p, varargin{:});
40 obj.schedStrat = p.Results.schedStrat;
41 obj.routingStrat = p.Results.routingStrat;
42 obj.distribution = p.Results.distribution;
43 obj.cclassJobLoad = p.Results.cclassJobLoad;
44 obj.hasVaryingServiceRates = p.Results.hasVaryingServiceRates;
45 obj.hasMultiServerQueues = p.Results.hasMultiServerQueues;
46 obj.hasRandomCSNodes = p.Results.hasRandomCSNodes;
47 obj.hasMultiChainCS = p.Results.hasMultiChainCS;
48 obj.topologyFcn = p.Results.topologyFcn;
51 %% Main function to call.
52 % Returns a generated QN model according to
53 % specified properties of the NetworkGenerator object
54 function model = generate(obj, numQueues, numDelays, numOClass, numCClass)
55 if nargin < 2 || isempty(numQueues)
56 numQueues = randi([1, 8]);
58 if nargin < 3 || isempty(numDelays)
60 numDelays = randi([0,1]);
65 if nargin < 4 || isempty(numOClass)
68 if nargin < 5 || isempty(numCClass)
69 numCClass = randi([1, 4]);
71 obj.validateArgs(numQueues, numDelays, numOClass, numCClass);
72 model = Network('nw
');
73 stations = obj.createStations(model, numQueues, numDelays, numOClass);
74 classes = obj.createClasses(model, numOClass, numCClass);
75 obj.setServiceProcesses(stations, classes);
76 obj.defineTopology(model, obj.topologyFcn(length(stations)));
77 %model = NetworkGenerator.initDefaultCustom(model);
80 function set.schedStrat(obj, strat)
81 if (strcmp(strat, 'fcfs
') ... % All stations have FCFS
82 || strcmp(strat, 'ps
') ... % All stations have PS
83 || strcmp(strat, 'inf
') ...
84 || strcmp(strat, 'lcfs
') ...
85 || strcmp(strat, 'lcfspr
') ...
86 || strcmp(strat, 'siro
') ...
87 || strcmp(strat, 'sjf
') ...
88 || strcmp(strat, 'ljf
') ...
89 || strcmp(strat, 'sept
') ...
90 || strcmp(strat, 'lept
') ...
91 || strcmp(strat, 'randomize
')) % Randomize across stations
92 obj.schedStrat = strat;
94 error('NG:schedStrat
', ...
95 'Scheduling strategy does not exist or
is not supported
');
99 function set.routingStrat(obj, strat)
100 if (strcmp(strat, 'Probabilities
') ... % All probabilistic routing
101 || strcmp(strat, 'Random
') ... % All random routing
102 || strcmp(strat, 'randomize
')) % Randomize across all classes and stations
103 obj.routingStrat = strat;
105 error('NG:routingStrat
', ...
106 'Routing strategy does not exist or
is not supported
');
110 function set.distribution(obj, distrib)
111 if (strcmpi(distrib, 'Exp
') ... % All exponentially distributed service distributions
112 || strcmpi(distrib, 'HyperExp
') ... % All hyperexponentially distributed
113 || strcmpi(distrib, 'Erlang
') ... % All Erlang distributed
114 || strcmpi(distrib, 'randomize
')) % Randomize across all classes and stations
115 obj.distribution = distrib;
117 error('NG:distribution
', ...
118 'Distribution does not exist or
is not supported
');
122 function set.cclassJobLoad(obj, load)
123 if (strcmpi(load, 'high
') ... % All stations have high job load
124 || strcmpi(load, 'medium
') ... % All stations have medium job load
125 || strcmpi(load, 'low
') ... % All stations have low job load
126 || strcmpi(load, 'randomize
')) % Randomize loads across stations
127 obj.cclassJobLoad = load;
129 error('NG:cclassJobLoad
', ...
130 'Model load can only be high, medium, or low
');
133 % Can be any function handle that takes an integer and returns a
135 function set.topologyFcn(obj, fcn)
139 line_error(mfilename,'topologyFcn should take a positive integer and
return a digraph
');
142 if ~isa(graph, 'digraph
') || numnodes(graph) ~= 2
143 line_error(mfilename,'topologyFcn should take a positive integer and
return a digraph
');
146 obj.topologyFcn = fcn;
151 methods (Access = private)
152 % Validates that parameter values for the network are sound
153 function validateArgs(~, numQueues, numDelays, numOClass, numCClass)
154 if numQueues < 0 || numDelays < 0 || numOClass < 0 || numCClass < 0
155 error('NG:negativeArgs
', 'Arguments must be non-negative
');
156 elseif numQueues + numDelays <= 0 || numOClass + numCClass <= 0
157 error('NG:noStationsOrJobs
', 'At least one station and one job
class required');
161 % Creates the stations in the network, including source and sink
162 function stations = createStations(obj, model, numQueues, numDelays, hasOClass)
163 queues = cell(numQueues, 1);
164 for i = 1 : numQueues
165 queues{i} = Queue(model, obj.name(
'queue', i), obj.chooseSchedStrat);
166 queues{i}.setNumberOfServers(chooseNumServers);
169 delays = cell(numDelays, 1);
170 for i = 1 : numDelays
171 delays{i} = Delay(model, obj.name(
'delay', i));
175 Source(model,
'source');
178 % Source and sink intentionally excluded from station list
179 stations = [queues; delays];
181 function n = chooseNumServers
182 if obj.hasMultiServerQueues
183 n = randi(obj.maxServers);
190 % Creates the job
classes that are serviced within the network
191 % Note: Arrival processes
for open
classes are set here
for simplicity
192 function
classes = createClasses(obj, model, numOClass, numCClass)
193 openClasses = cell(numOClass, 1);
194 for i = 1 : numOClass
195 openClasses{i} = OpenClass(model, obj.name(
'OClass', i));
196 model.getSource.setArrival(openClasses{i}, obj.chooseDistribution);
199 closedClasses = cell(numCClass, 1);
200 refStat = model.stations{randi(model.getNumberOfStations - (numOClass > 0))};
201 for i = 1 : numCClass
202 closedClasses{i} = ClosedClass(model, ...
203 obj.name(
'CClass', i), chooseNumJobs, refStat);
206 classes = [openClasses; closedClasses];
208 function numJobs = chooseNumJobs
209 switch lower(obj.cclassJobLoad)
211 numJobs = randi(obj.highJobLoadRange);
213 numJobs = randi(obj.medJobLoadRange);
215 numJobs = randi(obj.lowJobLoadRange);
217 numJobs = randi(obj.highJobLoadRange(2));
222 % Sets the service processes for all job
classes at all stations
223 % Note: Stations here excludes the Source node for simplicity
224 function setServiceProcesses(obj, stations,
classes)
226 for j = 1 : length(stations)
227 stations{j}.setService(
classes{i}, obj.chooseDistribution);
232 % Defines the topology of the network, adding random
class switching
nodes
233 function defineTopology(obj, model, topology)
234 numStations = model.getNumberOfStations;
235 csMask = obj.genCSMask(model);
236 % Add source and sink to topology graph
237 if model.hasOpenClasses
238 topology = addnode(topology, 2);
239 topology = addedge(topology, model.getIndexSourceNode, randi(numStations - 1));
240 topology = addedge(topology, randi(numStations - 1), model.getIndexSinkNode);
242 for i = 1 : numStations
243 [~, destIDs] = outedges(topology, i);
244 outgoingNodes = obj.addOutgoingLinks(model, i, sort(destIDs), csMask);
245 obj.setRoutingStrategies(model, model.stations{i}, outgoingNodes);
249 % Creates a mask that indicates which
classes can
switch with each other
250 function mask = genCSMask(obj, model)
251 numOClasses = length(model.getIndexOpenClasses);
252 numCClasses = length(model.getIndexClosedClasses);
253 mask = zeros(numOClasses + numCClasses);
255 if ~obj.hasMultiChainCS
256 mask(1 : numOClasses, 1 : numOClasses) = 1;
257 mask(numOClasses + 1 : end, numOClasses + 1 : end) = 1;
258 mask = logical(mask);
264 if model.hasOpenClasses
265 openChains = assignChains(numOClasses, randi(numOClasses));
267 if model.hasClosedClasses
268 closedChains = assignChains(numCClasses, randi(numCClasses));
270 allChains = [openChains closedChains];
273 for i = 1 : length(allChains)
274 endIdx = startIdx + allChains(i) - 1;
275 mask(startIdx : endIdx, startIdx : endIdx) = 1;
276 startIdx = endIdx + 1;
279 mask = logical(mask);
281 function chains = assignChains(numClasses, numChains)
282 chains = randintfixedsum(numClasses, numChains);
283 chains = chains(randperm(numChains));
285 function res = randintfixedsum(s, n)
293 first = randi(s - n);
294 res = [first randintfixedsum(s - first, n - 1)];
299 % For a single node, add all outgoing links and random
class switch
nodes
300 function outgoingNodes = addOutgoingLinks(obj, model, sourceID, allDestIDs, mask)
301 sourceNode = model.nodes{sourceID};
302 outgoingNodes = cell(length(allDestIDs), 1);
304 for i = 1 : length(allDestIDs)
305 destNode = model.nodes{allDestIDs(i)};
306 if obj.hasRandomCSNodes ...
307 && randi([0 1]) == 1 ...
308 && ~(isa(sourceNode,
'Source') || isa(destNode,
'Sink'))
309 cs = obj.randClassSwitchNode(model, mask, sourceNode, destNode);
310 outgoingNodes{i} = cs;
311 model.addLink(sourceNode, cs);
312 model.addLink(cs, destNode);
313 for j = 1 : model.getNumberOfClasses
314 cs.setProbRouting(model.classes{j}, destNode, 1);
317 outgoingNodes{i} = destNode;
318 model.addLink(sourceNode, destNode);
323 % Instantiates a
class switch node with a random switching matrix
324 function cs = randClassSwitchNode(obj, model, mask, sourceNode, destNode)
325 name = obj.name(
'cs', sourceNode.getName, destNode.getName);
326 cs = ClassSwitch(model, name, randClassSwitchMatrix);
328 function matrix = randClassSwitchMatrix
329 matrix = zeros(length(mask));
330 for i = 1 : length(mask)
331 matrix(i, mask(i, :)) = obj.randfixedsumone(nnz(mask(i, :)));
336 % Set routing strategies
for each job
class at a specified station
337 function setRoutingStrategies(obj, model, station, outgoingNodes)
338 if isa(station, 'Source')
339 openClasses = model.classes(model.getIndexOpenClasses);
340 for i = 1 : length(openClasses)
341 for j = 1 : length(outgoingNodes)
342 station.setProbRouting(openClasses{i}, outgoingNodes{j}, 1);
350 strat = obj.chooseRoutingStrat;
351 station.setRouting(
classes{i}, RoutingStrategy.fromText(strat));
352 if strcmp(strat,
'Probabilities')
353 if isa(
classes{i},
'ClosedClass') && isa(outgoingNodes{end},
'Sink')
354 station.setProbRouting(
classes{i}, outgoingNodes{end}, 0);
355 probs = obj.randfixedsumone(length(outgoingNodes) - 1);
357 probs = obj.randfixedsumone(length(outgoingNodes));
359 for j = 1 : length(probs)
360 station.setProbRouting(
classes{i}, outgoingNodes{j}, probs(j));
366 % Generate random probabilities that sum up to one
367 function probs = randfixedsumone(obj, numElems)
368 probs = randfixedsum(numElems, 1, 1, 0, 1);
369 probs = ceil(probs * 1000) / 1000;
370 [~, maxIdx] = max(probs);
371 probs(maxIdx) = probs(maxIdx) - (sum(probs) - 1);
374 % Creates a name
string for network elements via concatenation
375 function name = name(~, str, varargin)
376 if strcmpi(str,
'cs')
377 name = strcat(str, '_', varargin{1},
'_', varargin{2});
379 name = strcat(str, int2str(varargin{1}));
383 % Returns a random scheduling strategy
for a queueing station
384 function strat = chooseSchedStrat(obj)
385 if strcmpi(obj.schedStrat,
'randomize')
389 strat = SchedStrategy.FCFS;
391 strat = SchedStrategy.PS;
394 strat = categorical({obj.schedStrat});
398 % Returns a random routing strategy
for a network node
399 function strat = chooseRoutingStrat(obj)
400 if strcmpi(obj.routingStrat,
'randomize')
406 strat = 'Probabilities';
409 strat = obj.routingStrat;
413 % Returns a random distribution
object for a service/arrival process
414 function dist = chooseDistribution(obj)
415 switch lower(obj.distribution)
426 if obj.hasVaryingServiceRates
427 mean = 2 ^ randi([-6 6]);
434 dist = Exp.fitMeanAndSCV(mean, 1);
436 dist = Erlang.fitMeanAndSCV(mean, 1/(2^randi([0 6])));
438 dist = HyperExp.fitMeanAndSCV(mean, 2^randi([0 6]));
444 graph = randGraph(numVertices);
445 model = initDefaultCustom(model,
nodes);