1classdef Signal < JobClass
2 % Signal Job
class representing a signal (e.g., negative customer in G-networks)
4 % Signal
is a placeholder
class that automatically resolves to OpenSignal
5 % or ClosedSignal based on
the network structure. Users can simply use
6 % Signal in both open and closed networks -
the resolution happens when
7 %
the model
is finalized (during getStruct/refreshStruct).
9 % @brief Job
class for modeling signals in G-networks and related models
11 % Key characteristics:
12 % - Automatically resolves to OpenSignal or ClosedSignal
13 % - Supports different signal types (NEGATIVE, REPLY, CATASTROPHE)
14 % - NEGATIVE signals remove jobs from destination queues
15 % - CATASTROPHE signals reset
the state of queues
16 % - Used in G-networks (Gelenbe networks)
19 % - SignalType.NEGATIVE: Removes a job from
the destination queue
20 % - SignalType.REPLY: Triggers a reply action
21 % - SignalType.CATASTROPHE: Resets destination queue to empty state
23 % Example (Open Network):
25 % model = Network(
'GNetwork');
26 % source = Source(model,
'Source');
27 % sink = Sink(model,
'Sink');
28 % queue = Queue(model,
'Queue', SchedStrategy.FCFS);
29 % posClass = OpenClass(model,
'Positive'); % Normal customers
30 % negClass = Signal(model,
'Negative', SignalType.NEGATIVE); % Resolves to OpenSignal
31 % source.setArrival(posClass, Exp(1.0));
32 % source.setArrival(negClass, Exp(0.3));
35 % Example (Closed Network):
37 % model = Network(
'ClosedGNetwork');
38 % delay = Delay(model,
'Think');
39 % queue = Queue(model,
'Queue', SchedStrategy.FCFS);
40 % jobClass = ClosedClass(model,
'Job', 5, delay);
41 % replySignal = Signal(model,
'Reply', SignalType.REPLY).forJobClass(jobClass); % Resolves to ClosedSignal
44 % Reference: Gelenbe, E. (1991).
"Product-form queueing networks with
45 % negative and positive customers", Journal of Applied Probability
47 % Copyright (c) 2012-2026, Imperial College London
48 % All rights reserved.
51 signalType % SignalType constant (NEGATIVE, REPLY, CATASTROPHE)
52 targetJobClass % JobClass that this signal
is associated with (for REPLY: the class to unblock)
53 removalDistribution % DiscreteDistribution for number of removals (empty = remove exactly 1)
54 removalPolicy % RemovalPolicy constant (RANDOM, FCFS, LCFS)
55 model % Reference to the Network model
61 function self = Signal(model, name, signalType, prio, removalDistribution, removalPolicy)
62 % SIGNAL Create a signal class instance
64 % @brief Creates a Signal class for G-network modeling
65 % @param model Network model to add the signal class to
66 % @param name String identifier for the signal class
67 % @param signalType SignalType constant (REQUIRED: NEGATIVE, REPLY, or CATASTROPHE)
68 % @param prio Optional priority level (default: 0)
69 % @param removalDistribution Optional discrete distribution for batch removals (default: [])
70 % @param removalPolicy Optional RemovalPolicy constant (default: RemovalPolicy.RANDOM)
71 % @return self Signal instance ready for arrival specification
73 if nargin < 3 || isempty(signalType)
74 line_error(mfilename, 'signalType
is required. Use SignalType.NEGATIVE, SignalType.REPLY, or SignalType.CATASTROPHE.');
76 if nargin < 6 || isempty(removalPolicy)
77 removalPolicy = RemovalPolicy.RANDOM;
80 removalDistribution = [];
82 if nargin < 4 || isempty(prio)
86 % Initialize as JobClass (type will be determined during resolution)
87 self@JobClass(JobClassType.OPEN, name); % Default to OPEN, resolved later
89 self.signalType = signalType;
90 self.targetJobClass = [];
91 self.removalDistribution = removalDistribution;
92 self.removalPolicy = removalPolicy;
95 % Register with
the model
96 model.addJobClass(self);
98 % Set
default routing
for this class at all
nodes
99 for i = 1:length(model.nodes)
100 if isa(model.
nodes{i},
'Join')
101 model.nodes{i}.setStrategy(self, JoinStrategy.STD);
102 model.nodes{i}.setRequired(self, -1);
104 if ~isempty(model.nodes{i})
105 model.
nodes{i}.setRouting(self, RoutingStrategy.RAND);
109 % Java interop
is handled during resolution
112 function concrete = resolve(self, isOpen, refstat)
113 % RESOLVE Resolve
this Signal placeholder to OpenSignal or ClosedSignal
115 % @param isOpen
true if the network
is open (has Source node)
116 % @param refstat Reference station
for closed networks (ignored
for open)
117 % @
return concrete OpenSignal or ClosedSignal instance
119 % Save properties before replacing
the placeholder
120 savedIndex = self.index;
121 savedName = self.name;
122 savedTargetJobClass = self.targetJobClass;
125 % stored by
class index and
is not renumbered when a class moves.
126 % Resolving a signal at a
new index therefore reassigns another
127 %
class's routing: with two signals the second one's routes were
128 % overwritten by
the first
's, leaving its station unreachable and
129 % its reply lost. So snapshot the routing, put the concrete signal
130 % back in the placeholder's slot, and restore
the snapshot:
the
131 % index it occupies never changes.
132 savedOutputStrategy = cell(1, length(self.model.nodes));
133 for n = 1:length(self.model.nodes)
134 node = self.model.
nodes{n};
135 if ~isempty(node.output) && isprop(node.output,
'outputStrategy')
136 savedOutputStrategy{n} = node.output.outputStrategy;
140 % The placeholder must go before
the concrete signal
is created,
141 % since addJobClass rejects a duplicate name.
142 placeholderIdx = find(cellfun(@(c) c == self, self.model.classes), 1);
143 if isempty(placeholderIdx)
144 placeholderIdx = savedIndex;
146 self.model.classes(placeholderIdx) = [];
149 % Create concrete signal (constructor will add it to model.classes)
151 concrete = OpenSignal(self.model, savedName, self.signalType, self.priority);
153 concrete = ClosedSignal(self.model, savedName, self.signalType, refstat, ...
154 self.priority, self.removalDistribution, self.removalPolicy);
157 appendedIdx = find(cellfun(@(c) c == concrete, self.model.classes), 1);
158 if ~isempty(appendedIdx)
159 self.model.classes(appendedIdx) = [];
161 isColumn = size(self.model.classes, 2) == 1;
162 cls = self.model.classes(:).
';
163 cls = [cls(1:placeholderIdx-1), {concrete}, cls(placeholderIdx:end)];
167 self.model.classes = cls;
168 for c = 1:length(self.model.classes)
169 self.model.classes{c}.index = c;
172 for n = 1:length(self.model.nodes)
173 if ~isempty(savedOutputStrategy{n})
174 self.model.nodes{n}.output.outputStrategy = savedOutputStrategy{n};
178 % Copy over targetJobClass association
179 if ~isempty(savedTargetJobClass)
180 concrete.forJobClass(savedTargetJobClass);
183 % Copy removal configuration (both OpenSignal and ClosedSignal have these now)
184 concrete.removalDistribution = self.removalDistribution;
185 concrete.removalPolicy = self.removalPolicy;
188 function type = getSignalType(self)
189 % GETSIGNALTYPE Get
the signal type
191 % @
return type The SignalType of
this signal
class
192 type = self.signalType;
195 function self = forJobClass(self, jobClass)
196 % FORJOBCLASS Associate
this signal with a job
class
198 % self = FORJOBCLASS(self, jobClass) associates
this signal with
199 %
the specified job
class. For REPLY signals,
this specifies which
200 % job
class's servers will be unblocked when this signal arrives.
202 % @param jobClass The JobClass to associate with this signal
203 % @return self The modified Signal instance (for chaining)
206 % replySignal = Signal(model, 'Reply
', SignalType.REPLY).forJobClass(reqClass);
208 self.targetJobClass = jobClass;
210 % Only a REPLY signal establishes the synchronous-call link:
211 % for a NEGATIVE or CATASTROPHE signal forJobClass merely names the
212 % victim class, and setting replySignalClass there made
213 % sn.syncreply >= 0, so the state layer reserved a held-server
214 % counter and rejected every non-FCFS station.
215 if ~isempty(jobClass) && self.signalType == SignalType.REPLY
216 jobClass.replySignalClass = self;
220 function jobClass = getTargetJobClass(self)
221 % GETTARGETJOBCLASS Get the associated job class
223 % @return jobClass The JobClass associated with this signal
224 jobClass = self.targetJobClass;
227 function idx = getTargetJobClassIndex(self)
228 % GETTARGETJOBCLASSINDEX Get the index of the associated job class
230 % @return idx Index of the associated JobClass, or -1 if none
231 if isempty(self.targetJobClass)
234 idx = self.targetJobClass.index;
238 function dist = getRemovalDistribution(self)
239 % GETREMOVALDISTRIBUTION Get the removal distribution
241 % @return dist The discrete distribution for batch removals
242 dist = self.removalDistribution;
245 function self = setRemovalDistribution(self, dist)
246 % SETREMOVALDISTRIBUTION Set the removal distribution
248 % @param dist DiscreteDistribution for number of removals
249 self.removalDistribution = dist;
252 function policy = getRemovalPolicy(self)
253 % GETREMOVALPOLICY Get the removal policy
255 % @return policy The RemovalPolicy constant
256 policy = self.removalPolicy;
259 function self = setRemovalPolicy(self, policy)
260 % SETREMOVALPOLICY Set the removal policy
262 % @param policy RemovalPolicy constant (RANDOM, FCFS, LCFS)
263 self.removalPolicy = policy;
266 function b = isCatastrophe(self)
267 % ISCATASTROPHE Check if this is a catastrophe signal
269 % @return b true if signalType is SignalType.CATASTROPHE
270 b = (self.signalType == SignalType.CATASTROPHE);