LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
Node.m
1classdef Node < NetworkElement
2 % An abstract for a node in a Network model
3 %
4 % Copyright (c) 2012-2026, Imperial College London
5 % All rights reserved.
6
7 properties
8 model;
9 input;
10 server;
11 output;
12 index;
13 end
14
15 methods(Hidden)
16 %Constructor
17 function self = Node(name)
18 % SELF = NODE(NAME)
19
20 self@NetworkElement(char(name));
21 self.index = NaN;
22 end
23
24 function self = setModel(self, model)
25 % SELF = SETMODEL(MODEL)
26 %
27 % Add a pointer to the model object
28
29 self.model = model;
30 end
31
32 function self = link(self, nodeTo)
33 % SELF = LINK(NODETO)
34 %
35 %
36
37 self.model.addLink(self,nodeTo);
38 end
39
40 function self = reset(self)
41 % SELF = RESET()
42 %
43 % Reset internal data structures when the network model is
44 % reset
45
46 end
47 end
48
49 methods
50
51 function sections = getSections(self)
52 % SECTIONS = GETSECTIONS()
53
54 sections = {self.input, self.server, self.output};
55 end
56
57 function remaining = remainingClassIndexes(self, jobclass)
58 % REMAINING = REMAININGCLASSINDEXES(JOBCLASS)
59 %
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.
64
65 K = length(self.model.classes);
66 r = self.model.getClassByName(jobclass.name).index;
67 remaining = setdiff(1:K, r);
68 end
69
70 function self = removeJobClass(self, jobclass)
71 % SELF = REMOVEJOBCLASS(JOBCLASS)
72 %
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.
79
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);
84 end
85 end
86
87 function setProbRouting(self, class, destination, probability)
88 % SETPROBROUTING(CLASS, DESTINATION, PROBABILITY)
89
90 setRouting(self, class, RoutingStrategy.PROB, destination, probability);
91 end
92
93 function setRouting(self, class, strategy, par1, par2)
94 % SETROUTING(CLASS, STRATEGY, PARAM)
95 % SETROUTING(CLASS, STRATEGY, DESTINATION, PROBABILITY)
96
97 %global GlobalConstants.CoarseTol
98
99 if self.model.isJavaNative()
100 jline_classes = self.model.obj.getClasses();
101 switch strategy
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());
108 weight = par2;
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.');
114 end
115 return
116 end
117
118 if isa(self,'Cache')
119 switch strategy
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.');
122 end
123 end
124 switch strategy
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.
129 if nargin < 4
130 par1 = 2; % d: sampled destinations
131 end
132 param_d = par1;
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.');
135 end
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.');
138 end
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
142 destination = par1;
143 weight = par2;
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};
148 else
149 self.output.outputStrategy{1, class.index}{3}{end+1} = {destination, weight};
150 end
151 else
152 line_error(mfilename,'Weighted round robin weights must be integers.')
153 end
154 case RoutingStrategy.RL
155 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
156 if nargin < 4
157 par1 = -1;
158 par2 = {-1, -1};
159 end
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)
163 otherwise
164 switch nargin
165 case 3 % no destination specified
166 self.output.outputStrategy{1, class.index}{2} = RoutingStrategy.toText(strategy);
167 case 5
168 destination = par1;
169 probability = par2;
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};
173 else
174 self.output.outputStrategy{1, class.index}{3}{end+1} = {destination, probability};
175 end
176 end
177 end
178 end
179
180 function bool = hasClassSwitching(self)
181 % BOOL = HASCLASSSWITCHING()
182
183 bool = isa(self.server,'ClassSwitcher');
184 end
185
186 function bool = isStateful(self)
187 % BOOL = ISSTATEFUL()
188
189 bool = isa(self,'StatefulNode');
190 end
191
192 function bool = isStation(self)
193 % BOOL = ISSTATION()
194
195 bool = isa(self,'Station');
196 end
197 end
198
199 methods(Access = protected)
200 % Override copyElement method:
201 function clone = copyElement(self)
202 % CLONE = COPYELEMENT()
203
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;
210 end
211 end
212
213 methods (Access = public)
214 function ind = subsindex(self)
215 % IND = SUBSINDEX()
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);
221 end
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);
227 end
228 ind = double(node_idx)-1; % 0 based
229 elseif isa(self.model,'JNetwork')
230 ind = self.model.obj.getNodeIndex(self.obj);
231 else
232 error('Node:subsindex', 'Unsupported model type: %s', class(self.model));
233 end
234 end
235
236 function V = horzcat(self, varargin)
237 % V = HORZCAT(VARARGIN)
238
239 V = zeros(1, length(varargin) + 1);
240 try
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));
244 end
245 V(1) = 1+ self_idx;
246
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));
252 end
253 V(1+v) = 1+ node_idx;
254 else
255 error('Node:horzcat', 'Element %d is not a Node object', v);
256 end
257 end
258 catch e
259 error('Node:horzcat', 'Error in horizontal concatenation: %s', e.message);
260 end
261 end
262
263 function V = vertcat(self, varargin)
264 % V = VERTCAT(VARARGIN)
265
266 V = zeros(length(varargin) + 1, 1);
267 try
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;
272 else
273 error('Node:vertcat', 'Element %d is not a Node object', v);
274 end
275 end
276 catch e
277 error('Node:vertcat', 'Error in vertical concatenation: %s', e.message);
278 end
279 end
280
281 function summary(self)
282 % SUMMARY()
283
284 line_printf('\nNode: <strong>%s</strong>',self.getName);
285 %self.input.summary;
286 % self.server.summary;
287 % self.output.summary;
288 end
289 end
290end