2 % Source External job arrival node
for open queueing networks
4 % Source represents an external arrival node that generates jobs
for open
5 % classes according to specified arrival processes. It serves as the entry
6 % point
for jobs entering the network from the external environment, with
7 % configurable arrival rates and distributions
for each job
class.
9 % @brief External arrival node generating jobs
for open queueing networks
11 % Key characteristics:
12 % - External job generation
for open classes
13 % - Class-dependent arrival processes
14 % - Infinite capacity job source
15 % - Configurable inter-arrival time distributions
16 % - Integration with network routing
18 % Source node features:
19 % - Multiple job
class support
20 % - Flexible arrival process specification
21 % - Poisson, MAP, and general arrival processes
22 % - Arrival rate configuration per
class
23 % - Disabled arrival capability
for specific classes
26 % - Web server client arrivals
27 % - Manufacturing job arrivals
28 % - Call center customer generation
29 % - Network packet injection
30 % - Open system workload modeling
34 % model = Network(
'WebServer');
35 % source = Source(model,
'ClientArrivals');
36 % webClass = OpenClass(model,
'WebRequests', 1);
37 % source.setArrival(webClass, Exp(2.0)); % Poisson arrivals, rate 2
40 % Copyright (c) 2012-2026, Imperial College London
41 % All rights reserved.
46 markedProcess; % shared MarkedMAP driving the marked classes (empty
if none)
47 markedClasses; % (1,K)
class indexes bound to marks 1..K of markedProcess
48 arrivalBatch; % (1,K) cell of batch-size DiscreteDistribution per
class (empty = single arrivals)
53 function self = Source(model, name)
54 % SOURCE Create an external arrival source node
56 % @brief Creates a Source node
for external job generation
57 % @param model Network model to add the source node to
58 % @param name String identifier
for the source node
59 % @
return self Source instance ready
for arrival process configuration
61 if model.isMatlabNative()
62 self.numberOfServers = 1;
64 classes = model.getClasses();
65 self.classCap = Inf*ones(1,length(classes));
66 self.output = Dispatcher(classes);
67 self.server = ServiceTunnel();
68 self.input = RandomSource(classes);
69 self.schedStrategy = SchedStrategy.EXT;
73 elseif model.isJavaNative()
75 self.obj=jline.lang.nodes.Source(model.obj, name);
79 function setArrivalBatch(self,
class, batchSize)
80 % SETARRIVALBATCH(CLASS, BATCHSIZE)
82 % Turns each arrival epoch of CLASS into the simultaneous release of
83 % a batch of jobs. The interarrival distribution set by SETARRIVAL
84 % keeps spacing the epochs;
this decides how many jobs each epoch
85 % releases. Geometric interarrivals with a Geometric batch size
is
86 % the Geo^X arrival stream, whose analytical counterpart
is
89 % The batch size must be supported on {1,2,...}: an epoch that
90 % releases no job
is not an arrival epoch, so a law that can
return
91 % zero
is rejected rather than clamped. Pass [] to restore single
94 if numel(self.arrivalBatch) >=
class.index
95 self.arrivalBatch{1,
class.index} = [];
99 if ~isa(batchSize,
'DiscreteDistribution')
100 line_error(mfilename, 'arrival batch size must be a DiscreteDistribution');
102 if batchSize.getMean() < 1
103 line_error(mfilename, sprintf(['arrival batch size for class ''%s'' has mean %g; ' ...
104 'a batch must carry at least one job, so its support must be {1,2,...}
'], ...
105 class.name, batchSize.getMean()));
108 self.arrivalBatch{1, class.index} = batchSize;
110 self.obj.setArrivalBatch(class.obj, batchSize.obj);
114 function self = removeJobClass(self, jobclass)
115 % SELF = REMOVEJOBCLASS(JOBCLASS)
117 % Drop the arrival process of JOBCLASS, at the source and inside
118 % its input section, on top of what Station and Node remove.
120 remaining = self.remainingClassIndexes(jobclass);
121 removeJobClass@Station(self, jobclass);
122 K = numel(remaining) + 1;
123 if numel(self.arrivalProcess) == K
124 self.arrivalProcess = self.arrivalProcess(remaining);
126 if numel(self.arrivalBatch) == K
127 self.arrivalBatch = self.arrivalBatch(remaining);
129 if numel(self.markedClasses) == K
130 self.markedClasses = self.markedClasses(remaining);
132 if ~isempty(self.input) && isprop(self.input, 'sourceClasses
') ...
133 && numel(self.input.sourceClasses) == K
134 self.input.sourceClasses = self.input.sourceClasses(remaining);
138 function batchSize = getArrivalBatch(self, class)
139 % BATCHSIZE = GETARRIVALBATCH(CLASS)
140 % Returns the batch-size law bound to CLASS, or [] for single arrivals.
143 if isobject(class) && isprop(class, 'index
')
146 if numel(self.arrivalBatch) >= idx
147 batchSize = self.arrivalBatch{1, idx};
151 function setArrival(self, class, distribution)
152 % SETARRIVAL(CLASS, DISTRIBUTION)
153 % distribution can be a Distribution object or a Workflow object
155 % If Workflow, convert to PH distribution
156 if isa(distribution, 'Workflow
')
157 distribution = distribution.toPH();
161 % Check if arrival was already configured
162 if length(self.input.sourceClasses) >= class.index && ~isempty(self.input.sourceClasses{1, class.index})
163 % Note: We no longer invalidate hasStruct here as it causes severe performance
164 % issues in iterative solvers like LN. The refreshRates/refreshProcesses methods
165 % called during solver post-iteration phase handle updating procid appropriately.
166 self.model.setInitialized(false);
168 self.input.sourceClasses{1, class.index}{2} = ServiceStrategy.LI;
169 self.input.sourceClasses{1, class.index}{3} = distribution;
170 self.arrivalProcess{1,class.index} = distribution;
171 if distribution.isDisabled()
172 self.classCap(class.index) = 0;
174 self.classCap(class.index) = Inf;
176 % Update cached procid if struct exists to avoid stale values
177 % This is needed because we don't invalidate hasStruct
for performance
178 if self.model.hasStruct && ~isempty(self.model.sn)
179 ist = self.model.getStationIndex(self);
181 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
182 self.model.sn.procid(ist, c) = procTypeId;
185 self.obj.setArrival(class.obj, distribution.obj);
186 % Also update MATLAB-side storage to keep in sync with Java
object
187 % This ensures getArrivalProcess returns the correct distribution
188 % Check if arrival was already configured
189 if length(self.input.sourceClasses) >= class.index && ~isempty(self.input.sourceClasses{1,
class.index})
190 % Note: We no longer invalidate hasStruct here as it causes severe performance
191 % issues in iterative solvers like LN. The refreshRates/refreshProcesses methods
192 % called during solver post-iteration phase handle updating procid appropriately.
193 self.model.setInitialized(false);
195 self.input.sourceClasses{1,
class.index}{2} = ServiceStrategy.LI;
196 self.input.sourceClasses{1,
class.index}{3} = distribution;
197 self.arrivalProcess{1,
class.index} = distribution;
198 if distribution.isDisabled()
199 self.classCap(
class.index) = 0;
201 self.classCap(
class.index) = Inf;
203 % Update cached procid
if struct exists to avoid stale values
204 if self.model.hasStruct && ~isempty(self.model.sn)
205 ist = self.model.getStationIndex(self);
207 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
208 self.model.sn.procid(ist, c) = procTypeId;
213 function distrib = getArrivalProcess(self, oclass)
214 distrib = self.arrivalProcess{oclass};
217 function setMarkedArrival(self, mmap, classes)
218 % SETMARKEDARRIVAL(
MMAP, CLASSES)
220 % Bind a MarkedMAP with K marks to K open classes: mark k emits
221 % jobs of
class CLASSES{k}, with all marks driven by one shared
222 % modulating chain. CLASSES
is a cell array or vector of K
223 % distinct OpenClass handles, ordered by mark index.
224 if ~isa(mmap,
'MarkedMAP')
225 line_error(mfilename, 'setMarkedArrival requires a MarkedMAP/
MMAP arrival process.');
227 if ~isempty(self.obj)
228 line_error(mfilename, 'setMarkedArrival
is not yet supported with the Java backend (lang=''java'').');
230 K = mmap.getNumberOfTypes;
234 classList = num2cell(classes);
236 if numel(classList) ~= K
237 line_error(mfilename, sprintf('The MarkedMAP has %d types but %d classes were supplied.', K, numel(classList)));
239 markClasses = zeros(1, K);
242 if ~isa(cls,
'OpenClass')
243 line_error(mfilename, 'setMarkedArrival requires open classes.');
245 markClasses(k) = cls.index;
247 if numel(unique(markClasses)) ~= K
248 line_error(mfilename, 'setMarkedArrival requires distinct classes for the marks.');
250 % Per-class
binding: every marked class stores the shared MarkedMAP
251 %
object, so procid resolves to
MMAP and rates/SCV are derived from
252 % the per-mark marginal in the refresh layer.
254 self.setArrival(classList{k}, mmap);
256 self.markedProcess = mmap;
257 self.markedClasses = markClasses;