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 batchSize = getArrivalBatch(self, class)
115 % BATCHSIZE = GETARRIVALBATCH(CLASS)
116 % Returns the batch-size law bound to CLASS, or [] for single arrivals.
119 if isobject(class) && isprop(class, 'index
')
122 if numel(self.arrivalBatch) >= idx
123 batchSize = self.arrivalBatch{1, idx};
127 function setArrival(self, class, distribution)
128 % SETARRIVAL(CLASS, DISTRIBUTION)
129 % distribution can be a Distribution object or a Workflow object
131 % If Workflow, convert to PH distribution
132 if isa(distribution, 'Workflow
')
133 distribution = distribution.toPH();
137 % Check if arrival was already configured
138 if length(self.input.sourceClasses) >= class.index && ~isempty(self.input.sourceClasses{1, class.index})
139 % Note: We no longer invalidate hasStruct here as it causes severe performance
140 % issues in iterative solvers like LN. The refreshRates/refreshProcesses methods
141 % called during solver post-iteration phase handle updating procid appropriately.
142 self.model.setInitialized(false);
144 self.input.sourceClasses{1, class.index}{2} = ServiceStrategy.LI;
145 self.input.sourceClasses{1, class.index}{3} = distribution;
146 self.arrivalProcess{1,class.index} = distribution;
147 if distribution.isDisabled()
148 self.classCap(class.index) = 0;
150 self.classCap(class.index) = Inf;
152 % Update cached procid if struct exists to avoid stale values
153 % This is needed because we don't invalidate hasStruct
for performance
154 if self.model.hasStruct && ~isempty(self.model.sn)
155 ist = self.model.getStationIndex(self);
157 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
158 self.model.sn.procid(ist, c) = procTypeId;
161 self.obj.setArrival(class.obj, distribution.obj);
162 % Also update MATLAB-side storage to keep in sync with Java
object
163 % This ensures getArrivalProcess returns
the correct distribution
164 % Check if arrival was already configured
165 if length(self.input.sourceClasses) >= class.index && ~isempty(self.input.sourceClasses{1,
class.index})
166 % Note: We no longer invalidate hasStruct here as it causes severe performance
167 % issues in iterative
solvers like LN. The refreshRates/refreshProcesses methods
168 % called during solver post-iteration phase handle updating procid appropriately.
169 self.model.setInitialized(false);
171 self.input.sourceClasses{1,
class.index}{2} = ServiceStrategy.LI;
172 self.input.sourceClasses{1,
class.index}{3} = distribution;
173 self.arrivalProcess{1,
class.index} = distribution;
174 if distribution.isDisabled()
175 self.classCap(
class.index) = 0;
177 self.classCap(
class.index) = Inf;
179 % Update cached procid
if struct exists to avoid stale values
180 if self.model.hasStruct && ~isempty(self.model.sn)
181 ist = self.model.getStationIndex(self);
183 procTypeId = ProcessType.toId(ProcessType.fromText(builtin('class', distribution)));
184 self.model.sn.procid(ist, c) = procTypeId;
189 function distrib = getArrivalProcess(self, oclass)
190 distrib = self.arrivalProcess{oclass};
193 function setMarkedArrival(self, mmap, classes)
194 % SETMARKEDARRIVAL(
MMAP, CLASSES)
196 % Bind a MarkedMAP with K marks to K open classes: mark k emits
197 % jobs of
class CLASSES{k}, with all marks driven by one shared
198 % modulating chain. CLASSES
is a cell array or vector of K
199 % distinct OpenClass handles, ordered by mark index.
200 if ~isa(mmap,
'MarkedMAP')
201 line_error(mfilename, 'setMarkedArrival requires a MarkedMAP/
MMAP arrival process.');
203 if ~isempty(self.obj)
204 line_error(mfilename, 'setMarkedArrival
is not yet supported with
the Java backend (lang=''java'').');
206 K = mmap.getNumberOfTypes;
210 classList = num2cell(classes);
212 if numel(classList) ~= K
213 line_error(mfilename, sprintf('The MarkedMAP has %d types but %d classes were supplied.', K, numel(classList)));
215 markClasses = zeros(1, K);
218 if ~isa(cls,
'OpenClass')
219 line_error(mfilename, 'setMarkedArrival requires open classes.');
221 markClasses(k) = cls.index;
223 if numel(unique(markClasses)) ~= K
224 line_error(mfilename, 'setMarkedArrival requires distinct classes for
the marks.');
226 % Per-class
binding: every marked class stores
the shared MarkedMAP
227 %
object, so procid resolves to
MMAP and rates/SCV are derived from
228 %
the per-mark marginal in
the refresh layer.
230 self.setArrival(classList{k}, mmap);
232 self.markedProcess = mmap;
233 self.markedClasses = markClasses;