LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Station.m
1classdef Station < StatefulNode
2 % An abstract class for nodes where jobs station
3 %
4 % Copyright (c) 2012-2026, Imperial College London
5 % All rights reserved.
6
7 properties
8 numberOfServers;
9 cap;
10 dropRule;
11 classCap;
12 lldScaling; % limited load-dependence scaling factors
13 lcdScaling; % limited class-dependence scaling factors
14 lcdScalingPeak; % peak (max) class-dependent rate scaling per class, used to normalize Util (T*S/peak)
15 stationIndex;
16 patienceDistributions; % per-class patience distributions (cell array indexed by class)
17 end
18
19 methods(Hidden)
20 %Constructor
21 function self = Station(name)
22 % SELF = STATION(NAME)
23
24 self@StatefulNode(name);
25 self.cap = Inf;
26 self.classCap = [];
27 self.lldScaling = [];
28 self.patienceDistributions = [];
29 end
30
31 % don't expose to avoid accidental call without checking the queue
32 % scheduling discipline
33 function setLimitedLoadDependence(self, alpha)
34 % SETLIMITEDLOADDEPENDENCE(self, alpha)
35 % alpha(ni) is the service rate scaling when there are ni>=1
36 % jobs in the system
37 self.lldScaling = alpha;
38 end
39
40 % don't expose to avoid accidental call without checking the queue
41 % scheduling discipline
42 function setLimitedClassDependence(self, gamma, peakRatePerClass)
43 % SETLIMITEDCLASSDEPENDENCE(self, gamma, peakRatePerClass)
44 %
45 % gamma(ni) is a function handle, where ni=[ni1,...,niR]
46 % is the service rate scaling when there are nir jobs at
47 % station i in class r.
48 % peakRatePerClass is the peak (maximum) value of the rate
49 % scaling per class (e.g. the effective number of servers). It is
50 % REQUIRED and is used to normalize utilization as Util = T*S/peak,
51 % matching the T*S/c convention of ordinary multiserver stations.
52 % A scalar is broadcast to every class.
53 if ~isa(gamma,'function_handle')
54 line_error(mfilename, 'Class dependence must be specified through a function handle.');
55 end
56 if nargin < 3 || isempty(peakRatePerClass)
57 line_error(mfilename, 'Class dependence requires an explicit peak rate: setClassDependence(beta, peakRatePerClass). Pass a scalar (identical peak for every class) or a per-class vector.');
58 end
59 if ~isnumeric(peakRatePerClass) || any(peakRatePerClass(:) <= 0)
60 line_error(mfilename, 'peakRatePerClass must be a positive scalar or per-class vector.');
61 end
62 self.lcdScaling = gamma;
63 self.lcdScalingPeak = peakRatePerClass(:)';
64 end
65
66 % don't expose to avoid accidental call without checking the queue
67
68
69 % don't expose to avoid accidental call without checking the queue
70
71
72 end
73
74 methods
75
76 function self = setDropRule(self, class, drop)
77 % SELF = SETDROPRULE(CLASS, DROPRULE)
78
79 self.dropRule(class) = drop;
80 end
81
82
83 function setNumServers(self, value)
84 % SETNUMSERVERS(VALUE)
85
86 self.numberOfServers = value;
87 end
88
89 function setNumberOfServers(self, value)
90 % SETNUMBEROFSERVERS(VALUE)
91
92 self.numberOfServers = value;
93 end
94
95 function value = getNumServers(self)
96 % VALUE = GETNUMSERVERS()
97
98 value = self.numberOfServers;
99 end
100
101 function value = getNumberOfServers(self)
102 % VALUE = GETNUMBEROFSERVERS()
103
104 value = self.numberOfServers;
105 end
106
107 function setCapacity(self, value)
108 % SETCAPACITY(VALUE)
109
110 self.cap = value;
111 end
112
113 function setCap(self, value)
114 % SETCAP(VALUE)
115 % Alias for setCapacity() for backwards compatibility
116
117 self.setCapacity(value);
118 end
119
120 function setChainCapacity(self, values)
121 % SETCHAINCAPACITY(VALUES)
122
123 sn = self.model.getStruct;
124 if numel(values) ~= sn.nchains
125 line_error(mfilename,'The method requires in input a capacity value for each chain.');
126 end
127 for c = 1:sn.nchains
128 inchain = sn.inchain{c};
129 for r = inchain
130 if ~self.isServiceDisabled(r)
131 self.classCap(r) = values(c);
132 else
133 self.classCap(r) = Inf;
134 end
135 end
136 end
137 self.cap = min(sum(self.classCap(self.classCap>0)), self.cap);
138 end
139
140
141 function isD = isServiceDefined(self, class)
142 K = size(self.model.getClasses(),2);
143 isD = true(1, K);
144 switch self.server.className
145 case 'ServiceTunnel'
146 %noop
147 otherwise
148 for r=1:K
149 if isempty(self.server.serviceProcess{1,r})
150 isD(r) = false;
151 end
152 end
153 end
154 end
155
156 function isD = isServiceDisabled(self, class)
157 % ISD = ISSERVICEDISABLED(CLASS)
158 if nargin>=2
159 switch self.server.className
160 case 'ServiceTunnel'
161 isD = false;
162 otherwise
163 isD = self.server.serviceProcess{1,class}{end}.isDisabled();
164 end
165 else
166 K = size(self.model.getClasses(),2);
167 isD = false(1, K);
168 switch self.server.className
169 case 'ServiceTunnel'
170 %noop
171 otherwise
172 %isD = cellfun(@(sp) sp{end}.isDisabled, self.server.serviceProcess);
173 for r=1:K
174 isD(r) = self.server.serviceProcess{1,r}{end}.isDisabled();
175 end
176 end
177 end
178 end
179
180 function isI = isServiceImmediate(self, class)
181 % ISI = ISSERVICEIMMEDIATE(CLASS)
182
183 isI = self.server.serviceProcess{1,class}{end}.isImmediate();
184 end
185
186 function R = getNumberOfServiceClasses(self)
187 % R = GETNUMBEROFSERVICECLASSES()
188
189 R = size(self.server.serviceProcess,2);
190 end
191
192 function [p] = getSelfLoopProbabilities(self)
193 % [P] = GETSELFLOOPPROBABILITIES()
194
195 R = getNumberOfServiceClasses(self);
196 p = zeros(1,R);
197 for k=1:R
198 nOutLinks = length(self.output.outputStrategy{k}{end});
199 switch RoutingStrategy.toText(self.output.outputStrategy{k}{2})
200 case 'Random'
201 p(k) = 1 / nOutLinks;
202 case RoutingStrategy.PROB
203 for t=1:nOutLinks % for all outgoing links
204 if strcmp(self.output.outputStrategy{k}{end}{t}{1}.name, self.name)
205 p(k) = self.output.outputStrategy{k}{end}{t}{2};
206 break
207 end
208 end
209 end
210 end
211 end
212
213 function [map, mu, phi] = getSourceRates(self)
214 % [PH,MU,PHI] = GETSOURCERATES()
215
216 nclasses = size(self.input.sourceClasses,2);
217 map = cell(1,nclasses);
218 mu = cell(1,nclasses);
219 phi = cell(1,nclasses);
220 for r=1:nclasses
221 if isempty(self.input.sourceClasses{r})
222 self.input.sourceClasses{r} = {[],ServiceStrategy.LI,Disabled.getInstance()};
223 map{r} = {[NaN],[NaN]};
224 mu{r} = NaN;
225 phi{r} = NaN;
226 elseif ~self.input.sourceClasses{r}{end}.isDisabled()
227 switch class(self.input.sourceClasses{r}{end})
228 case {'Replayer', 'Trace'}
229 aph = self.input.sourceClasses{r}{end}.fitAPH;
230 map{r} = aph.getProcess();
231 mu{r} = aph.getMu;
232 phi{r} = aph.getPhi;
233 case {'Exp','Coxian','Erlang','HyperExp','Markovian','APH','PH','ME','CME','RAP'}
234 map{r} = self.input.sourceClasses{r}{end}.getProcess;
235 mu{r} = self.input.sourceClasses{r}{end}.getMu;
236 phi{r} = self.input.sourceClasses{r}{end}.getPhi;
237 case {'Det','Uniform','Pareto','Gamma','Lognormal','Weibull'}
238 map{r} = self.input.sourceClasses{r}{end}.getProcess();
239 mu{r} = [1/self.input.sourceClasses{r}{end}.getMean];
240 phi{r} = [1];
241 case {'Bernoulli','Binomial','Poisson'}
242 % Counting distributions used as an interarrival
243 % time. Like the other non-Markovian cases they
244 % carry no MAP representation, so the struct records
245 % the {mean, SCV} pair getProcess() returns. Their
246 % support includes 0; the LDES engine resolves that
247 % zero atom to an immediate interval.
248 map{r} = self.input.sourceClasses{r}{end}.getProcess();
249 mu{r} = [self.input.sourceClasses{r}{end}.getRate];
250 phi{r} = [1];
251 case 'Geometric'
252 % Lattice-valued interarrival time supported on
253 % {1,2,...}. Like the continuous non-Markovian
254 % cases above it has no MAP representation, so the
255 % struct records the {mean, SCV} pair its
256 % getProcess() returns. Without this arm map{r} is
257 % left unassigned and sn.proc comes out empty.
258 map{r} = self.input.sourceClasses{r}{end}.getProcess();
259 mu{r} = [self.input.sourceClasses{r}{end}.getRate];
260 phi{r} = [1];
261 case 'MMPP2'
262 map{r} = self.input.sourceClasses{r}{end}.getProcess();
263 mu{r} = self.input.sourceClasses{r}{end}.getMu;
264 phi{r} = self.input.sourceClasses{r}{end}.getPhi;
265 case 'MAP'
266 map{r} = self.input.sourceClasses{r}{end}.getProcess();
267 mu{r} = self.input.sourceClasses{r}{end}.getMu;
268 phi{r} = self.input.sourceClasses{r}{end}.getPhi;
269 case 'NHPP'
270 map{r} = self.input.sourceClasses{r}{end}.getProcess();
271 mu{r} = self.input.sourceClasses{r}{end}.getTimeAverageRate();
272 phi{r} = 1;
273 case 'BMAP'
274 % {D0, D1, D_batch1, ..., D_batchK}: same layout
275 % as the JAR MatrixCell for batch arrivals
276 map{r} = self.input.sourceClasses{r}{end}.getProcess();
277 mapAggr = self.input.sourceClasses{r}{end}.toMAP;
278 mu{r} = mapAggr.getMu;
279 phi{r} = mapAggr.getPhi;
280 case 'MarkedMAP'
281 % {D0, D1, D11, ..., D1K} (M3A): one shared
282 % modulating chain; the per-mark D1k matrices
283 % select the emitted class (Source.setMarkedArrival
284 % records the mark->class binding). mu/phi describe
285 % the shared chain, so they coincide across the
286 % marked classes.
287 map{r} = self.input.sourceClasses{r}{end}.getProcess();
288 mu{r} = self.input.sourceClasses{r}{end}.getMu;
289 phi{r} = self.input.sourceClasses{r}{end}.getPhi;
290 otherwise
291 % leave everything empty
292 end
293 else
294 map{r} = {[NaN],[NaN]};
295 mu{r} = NaN;
296 phi{r} = NaN;
297 end
298 end
299 end
300
301 function [map,mu,phi] = getServiceRates(self)
302 % [PH,MU,PHI] = GETSERVICERATES()
303
304 nclasses = size(self.server.serviceProcess,2);
305 map = cell(1,nclasses);
306 mu = cell(1,nclasses);
307 phi = cell(1,nclasses);
308 for r=1:nclasses
309 serviceProcess_r = self.server.serviceProcess{r};
310 if isempty(serviceProcess_r)
311 serviceProcess_r = {[],ServiceStrategy.LI,Disabled.getInstance()};
312 map{r} = {[NaN],[NaN]};
313 mu{r} = NaN;
314 phi{r} = NaN;
315 elseif serviceProcess_r{end}.isImmediate()
316 map{r} = {[-GlobalConstants.Immediate],[GlobalConstants.Immediate]};
317 mu{r} = [GlobalConstants.Immediate];
318 phi{r} = [1];
319 elseif ~serviceProcess_r{end}.isDisabled()
320 switch class(serviceProcess_r{end})
321 case {'Det','Uniform','Pareto','Gamma','Weibull','Lognormal'}
322 map{r} = serviceProcess_r{end}.getProcess();
323 mu{r} = [serviceProcess_r{end}.getRate];
324 phi{r} = [1];
325 case {'Bernoulli','Binomial','Poisson'}
326 % Counting distributions used as a service time; see
327 % the matching arm in getSourceRates above.
328 map{r} = serviceProcess_r{end}.getProcess();
329 mu{r} = [serviceProcess_r{end}.getRate];
330 phi{r} = [1];
331 case 'Geometric'
332 % Lattice-valued service time supported on
333 % {1,2,...}; see the matching arm in
334 % getSourceRates above.
335 map{r} = serviceProcess_r{end}.getProcess();
336 mu{r} = [serviceProcess_r{end}.getRate];
337 phi{r} = [1];
338 case {'Replayer', 'Trace'}
339 aph = serviceProcess_r{end}.fitAPH;
340 map{r} = aph.getProcess();
341 mu{r} = aph.getMu;
342 phi{r} = aph.getPhi;
343 case {'Exp','Coxian','Erlang','HyperExp','Markovian','APH','MAP','PH','ME','CME','RAP'}
344 map{r} = serviceProcess_r{end}.getProcess();
345 mu{r} = serviceProcess_r{end}.getMu;
346 phi{r} = serviceProcess_r{end}.getPhi;
347 case 'NHPP'
348 % Mirrors the Source arm above. Without this case the
349 % switch falls through with map/mu/phi unassigned, so a
350 % time-varying service rate reached sn.proc empty.
351 map{r} = serviceProcess_r{end}.getProcess();
352 mu{r} = serviceProcess_r{end}.getTimeAverageRate();
353 phi{r} = 1;
354 case 'MMPP2'
355 map{r} = serviceProcess_r{end}.getProcess();
356 mu{r} = serviceProcess_r{end}.getMu;
357 phi{r} = serviceProcess_r{end}.getPhi;
358 end
359 else
360 map{r} = {[NaN],[NaN]};
361 mu{r} = NaN;
362 phi{r} = NaN;
363 end
364 end
365 end
366
367 function summary(self)
368 % SUMMARY()
369
370 line_printf('\nNode: <strong>%s</strong>',self.getName);
371 line_printf('\nScheduling: %s',self.schedStrategy);
372 line_printf('\nNumber of Servers: %d',self.numberOfServers);
373 for r=1:length(self.output.outputStrategy)
374 classes = self.model.getClasses();
375 line_printf('\nRouting %s: %s',classes{r}.name,self.output.outputStrategy{r}{2});
376 end
377 % self.input.summary;
378 % self.server.summary;
379 % self.output.summary;
380 end
381
382 end
383end
Definition fjtag.m:157
Definition Station.m:245