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 removing placeholder
120 savedIndex = self.index;
121 savedName = self.name;
122 savedTargetJobClass = self.targetJobClass;
124 % Save outputStrategy entries
for this signal at all
nodes
125 % These need to be restored at the
new index after resolution
126 savedOutputStrategies = cell(1, length(self.model.nodes));
127 for n = 1:length(self.model.nodes)
128 node = self.model.
nodes{n};
129 if ~isempty(node.output) && isprop(node.output,
'outputStrategy')
130 outStrat = node.output.outputStrategy;
131 % Check both 1D and 2D indexing patterns
132 if iscell(outStrat) && length(outStrat) >= savedIndex
133 savedOutputStrategies{n} = outStrat{savedIndex};
134 elseif iscell(outStrat) && size(outStrat, 2) >= savedIndex
135 savedOutputStrategies{n} = outStrat{1, savedIndex};
140 % Remove placeholder from model
's classes list to avoid duplicate
141 % when concrete signal is created (its constructor calls addJobClass)
142 placeholderIdx = find(cellfun(@(c) c == self, self.model.classes));
143 if ~isempty(placeholderIdx)
144 self.model.classes(placeholderIdx) = [];
145 % Update indices of classes that come after
146 for c = placeholderIdx:length(self.model.classes)
147 self.model.classes{c}.index = c;
151 % Create concrete signal (constructor will add it to model.classes)
153 concrete = OpenSignal(self.model, savedName, self.signalType, self.priority);
155 concrete = ClosedSignal(self.model, savedName, self.signalType, refstat, ...
156 self.priority, self.removalDistribution, self.removalPolicy);
159 % Restore saved outputStrategy entries at the new index
160 newIndex = concrete.index;
161 for n = 1:length(self.model.nodes)
162 if ~isempty(savedOutputStrategies{n})
163 node = self.model.nodes{n};
164 if ~isempty(node.output) && isprop(node.output, 'outputStrategy
')
165 % Ensure outputStrategy has enough elements
166 outStrat = node.output.outputStrategy;
167 while length(outStrat) < newIndex
168 outStrat{end+1} = {}; %#ok<AGROW>
170 outStrat{newIndex} = savedOutputStrategies{n};
171 node.output.outputStrategy = outStrat;
176 % Copy over targetJobClass association
177 if ~isempty(savedTargetJobClass)
178 concrete.forJobClass(savedTargetJobClass);
181 % Copy removal configuration (both OpenSignal and ClosedSignal have these now)
182 concrete.removalDistribution = self.removalDistribution;
183 concrete.removalPolicy = self.removalPolicy;
186 function type = getSignalType(self)
187 % GETSIGNALTYPE Get the signal type
189 % @return type The SignalType of this signal class
190 type = self.signalType;
193 function self = forJobClass(self, jobClass)
194 % FORJOBCLASS Associate this signal with a job class
196 % self = FORJOBCLASS(self, jobClass) associates this signal with
197 % the specified job class. For REPLY signals, this specifies which
198 % job class's servers will be unblocked when
this signal arrives.
200 % @param jobClass The JobClass to associate with
this signal
201 % @
return self The modified Signal instance (
for chaining)
204 % replySignal = Signal(model,
'Reply', SignalType.REPLY).forJobClass(reqClass);
206 self.targetJobClass = jobClass;
208 % Also update the JobClass to know about
this signal (bidirectional link)
209 if ~isempty(jobClass)
210 jobClass.replySignalClass = self;
214 function jobClass = getTargetJobClass(self)
215 % GETTARGETJOBCLASS Get the associated job
class
217 % @
return jobClass The JobClass associated with
this signal
218 jobClass = self.targetJobClass;
221 function idx = getTargetJobClassIndex(self)
222 % GETTARGETJOBCLASSINDEX Get the index of the associated job
class
224 % @
return idx Index of the associated JobClass, or -1 if none
225 if isempty(self.targetJobClass)
228 idx = self.targetJobClass.index;
232 function dist = getRemovalDistribution(self)
233 % GETREMOVALDISTRIBUTION Get the removal distribution
235 % @
return dist The discrete distribution
for batch removals
236 dist = self.removalDistribution;
239 function self = setRemovalDistribution(self, dist)
240 % SETREMOVALDISTRIBUTION Set the removal distribution
242 % @param dist DiscreteDistribution
for number of removals
243 self.removalDistribution = dist;
246 function policy = getRemovalPolicy(self)
247 % GETREMOVALPOLICY Get the removal policy
249 % @
return policy The RemovalPolicy constant
250 policy = self.removalPolicy;
253 function self = setRemovalPolicy(self, policy)
254 % SETREMOVALPOLICY Set the removal policy
256 % @param policy RemovalPolicy constant (RANDOM, FCFS, LCFS)
257 self.removalPolicy = policy;
260 function b = isCatastrophe(self)
261 % ISCATASTROPHE Check
if this is a catastrophe signal
263 % @
return b
true if signalType
is SignalType.CATASTROPHE
264 b = (self.signalType == SignalType.CATASTROPHE);