1classdef Node < NetworkElement
2 % An abstract
for a node in a Network model
4 % Copyright (c) 2012-2026, Imperial College London
17 function self = Node(name)
20 self@NetworkElement(
char(name));
24 function self = setModel(self, model)
25 % SELF = SETMODEL(MODEL)
27 % Add a pointer to the model
object
32 function self = link(self, nodeTo)
37 self.model.addLink(self,nodeTo);
40 function self = reset(self)
43 % Reset internal data structures when the network model
is
51 function sections = getSections(self)
52 % SECTIONS = GETSECTIONS()
54 sections = {self.input, self.server, self.output};
57 function remaining = remainingClassIndexes(self,
jobclass)
58 % REMAINING = REMAININGCLASSINDEXES(JOBCLASS)
60 % Indexes of the classes that survive the removal of JOBCLASS.
61 % The lookup
is by name so that a
class object belonging to
62 % another copy of the model still resolves, as in
63 % @MNetwork/removeClass.m.
65 K = length(self.model.classes);
66 r = self.model.getClassByName(
jobclass.name).index;
67 remaining = setdiff(1:K, r);
70 function self = removeJobClass(self,
jobclass)
71 % SELF = REMOVEJOBCLASS(JOBCLASS)
73 % Remove all per-
class configuration referencing JOBCLASS from
74 %
this node. The base implementation drops the
class's routing
75 % (output) strategy; subclasses extend it to drop service,
76 % capacity, arrival and class-switching configuration. Called by
77 % @MNetwork/removeClass.m, mirroring Node.removeJobClass in the
78 % JAR and Node.remove_job_class in python.
80 remaining = self.remainingClassIndexes(jobclass);
81 if ~isempty(self.output) && isprop(self.output, 'outputStrategy') ...
82 && numel(self.output.outputStrategy) == numel(remaining) + 1
83 self.output.outputStrategy = self.output.outputStrategy(remaining);
87 function setProbRouting(self, class, destination, probability)
88 % SETPROBROUTING(CLASS, DESTINATION, PROBABILITY)
90 setRouting(self, class, RoutingStrategy.PROB, destination, probability);
93 function setRouting(self, class, strategy, par1, par2)
94 % SETROUTING(CLASS, STRATEGY, PARAM)
95 % SETROUTING(CLASS, STRATEGY, DESTINATION, PROBABILITY)
97 %global GlobalConstants.CoarseTol
99 if self.model.isJavaNative()
100 jline_classes = self.model.obj.getClasses();
102 case RoutingStrategy.RAND
103 self.obj.setRouting(jline_classes.get(class.index-1),jline.lang.constant.RoutingStrategy.RAND);
104 case RoutingStrategy.RROBIN
105 self.obj.setRouting(jline_classes.get(class.index-1),jline.lang.constant.RoutingStrategy.RROBIN);
106 case RoutingStrategy.WRROBIN
107 node_target = self.model.obj.getNodeByName(par1.getName());
109 self.obj.setRouting(jline_classes.get(class.index-1),jline.lang.constant.RoutingStrategy.WRROBIN, node_target, weight);
110 case RoutingStrategy.DISABLED
111 self.obj.setRouting(jline_classes.get(class.index-1),jline.lang.constant.RoutingStrategy.DISABLED);
112 case RoutingStrategy.PROB
113 line_error(mfilename, 'Use setProbRouting to assign routing probabilities
for a JNetwork node.
');
120 case {RoutingStrategy.SQ, RoutingStrategy.WRROBIN, RoutingStrategy.RROBIN}
121 line_error(mfilename,'State-dependent routing not supported with caches. Add instead a Router node after the cache.
');
125 case RoutingStrategy.SQ
126 % SETROUTING(CLASS, RoutingStrategy.SQ, D) - SQ(d): sample D
127 % destinations uniformly with replacement and route to the
128 % shortest of them. Dispatcher memory is not supported.
130 par1 = 2; % d: sampled destinations
133 if ~isscalar(param_d) || param_d < 1 || abs(param_d-round(param_d)) > GlobalConstants.CoarseTol
134 line_error(mfilename,'SQ parameter d must be a positive integer.
');
136 if nargin >= 5 && ~isempty(par2) && any(double(par2) ~= 0)
137 line_error(mfilename,'SQ with dispatcher memory
is not supported. Only SQ(d)
is available.
');
139 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
140 self.output.outputStrategy{1, class.index}{3}{1} = round(param_d);
141 case RoutingStrategy.WRROBIN
144 if abs(weight-round(weight)) < GlobalConstants.CoarseTol
145 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
146 if length(self.output.outputStrategy{1, class.index})<3
147 self.output.outputStrategy{1, class.index}{3}{1} = {destination, weight};
149 self.output.outputStrategy{1, class.index}{3}{end+1} = {destination, weight};
152 line_error(mfilename,'Weighted round robin weights must be integers.
')
154 case RoutingStrategy.RL
155 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
160 self.output.outputStrategy{1, class.index}{3} = par1; % part1 is value function (tabular or FA)
161 self.output.outputStrategy{1, class.index}{4} = par2{1}; % part2{2} is nodes that need action
162 self.output.outputStrategy{1, class.index}{5} = par2{2}; % part2{2} is state size (truncation_value + 1)
165 case 3 % no destination specified
166 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
170 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
171 if length(self.output.outputStrategy{1, class.index})<3
172 self.output.outputStrategy{1, class.index}{3}{1} = {destination, probability};
174 self.output.outputStrategy{1, class.index}{3}{end+1} = {destination, probability};
180 function bool = hasClassSwitching(self)
181 % BOOL = HASCLASSSWITCHING()
183 bool = isa(self.server,'ClassSwitcher
');
186 function bool = isStateful(self)
187 % BOOL = ISSTATEFUL()
189 bool = isa(self,'StatefulNode
');
192 function bool = isStation(self)
199 methods(Access = protected)
200 % Override copyElement method:
201 function clone = copyElement(self)
202 % CLONE = COPYELEMENT()
204 % Make a shallow copy of all properties
205 clone = copyElement@Copyable(self);
206 % Make a deep copy of each object
207 clone.input = self.input.copy;
208 clone.server = self.server.copy;
209 clone.output = self.output.copy;
213 methods (Access = public)
214 function ind = subsindex(self)
216 if isa(self.model,'Network
')
217 % Handle the new delegation pattern
218 node_idx = self.model.getNodeIndex(self.name);
219 if isempty(node_idx) || isnan(node_idx) || node_idx <= 0
220 error('Node:subsindex
', 'Invalid node index
for %s: %g
', self.name, node_idx);
222 ind = double(node_idx)-1; % 0 based for MATLAB indexing
223 elseif isa(self.model,'MNetwork
')
224 node_idx = self.model.getNodeIndex(self.name);
225 if isempty(node_idx) || isnan(node_idx) || node_idx <= 0
226 error('Node:subsindex
', 'Invalid node index
for %s: %g
', self.name, node_idx);
228 ind = double(node_idx)-1; % 0 based
229 elseif isa(self.model,'JNetwork
')
230 ind = self.model.obj.getNodeIndex(self.obj);
232 error('Node:subsindex
', 'Unsupported model type: %s
', class(self.model));
236 function V = horzcat(self, varargin)
237 % V = HORZCAT(VARARGIN)
239 V = zeros(1, length(varargin) + 1);
241 self_idx = self.subsindex();
242 if numel(self_idx) ~= 1
243 error('Node:horzcat
', 'subsindex returned non-scalar value
for %s: %s
', self.name, mat2str(self_idx));
247 for v=1:length(varargin)
248 if isa(varargin{v}, 'Node
')
249 node_idx = varargin{v}.subsindex();
250 if numel(node_idx) ~= 1
251 error('Node:horzcat
', 'subsindex returned non-scalar value
for %s: %s
', varargin{v}.name, mat2str(node_idx));
253 V(1+v) = 1+ node_idx;
255 error('Node:horzcat
', 'Element %d
is not a Node
object', v);
259 error('Node:horzcat
', 'Error in horizontal concatenation: %s
', e.message);
263 function V = vertcat(self, varargin)
264 % V = VERTCAT(VARARGIN)
266 V = zeros(length(varargin) + 1, 1);
268 V(1) = 1+ self.subsindex;
269 for v=1:length(varargin)
270 if isa(varargin{v}, 'Node
')
271 V(1+v) = 1+varargin{v}.subsindex;
273 error('Node:vertcat
', 'Element %d
is not a Node
object', v);
277 error('Node:vertcat
', 'Error in vertical concatenation: %s
', e.message);
281 function summary(self)
284 line_printf('\nNode: <strong>%s</strong>
',self.getName);
286 % self.server.summary;
287 % self.output.summary;