LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
sanitize.m
1function sanitize(self)
2% SANITIZE()
3
4% Preprocess model to ensure consistent parameterization.
5%
6% Copyright (c) 2012-2026, Imperial College London
7% All rights reserved.
8
9% Convert Signal placeholders to OpenSignal or ClosedSignal based on routing
10self.convertSignalPlaceholders();
11
12% Initialize Cache node properties always (even if checks disabled)
13% This is required for proper cache functionality
14K = getNumberOfClasses(self);
15for ist=1:self.getNumberOfNodes
16 if isa(self.nodes{ist}, 'Cache')
17 for k=1:K
18 if k > length(self.nodes{ist}.popularity) || isempty(self.nodes{ist}.popularity{k})
19 self.nodes{ist}.popularity{k} = Disabled.getInstance();
20 end
21 end
22 if isempty(self.nodes{ist}.accessProb)
23 self.nodes{ist}.accessProb = cell(K,self.nodes{ist}.items.nitems);
24 for v=1:K
25 for k=1:self.nodes{ist}.items.nitems
26 % accessProb{v,k}(l,p) is the cost (probability) for a user-v request to item k in list l to access list p
27 if isempty(self.nodes{ist}.graph)
28 self.nodes{ist}.accessProb{v,k} = diag(ones(1,self.nodes{ist}.nLevels),1);
29 self.nodes{ist}.accessProb{v,k}(1+self.nodes{ist}.nLevels,1+self.nodes{ist}.nLevels) = 1;
30 else
31 self.nodes{ist}.accessProb{v,k} = self.nodes{ist}.graph{k};
32 end
33 end
34 end
35 end
36 self.nodes{ist}.server.hitClass = round(self.nodes{ist}.server.hitClass);
37 self.nodes{ist}.server.missClass = round(self.nodes{ist}.server.missClass);
38 end
39end
40
41% Skip remaining validation if checks are disabled
42if ~self.enableChecks
43 return;
44end
45
46if isempty(self.sn)
47 M = getNumberOfStations(self);
48 K = getNumberOfClasses(self);
49 for ist=1:self.getNumberOfNodes
50 switch class(self.nodes{ist})
51 case 'Logger'
52 %no-op
53 case 'ClassSwitch'
54 %no-op
55 case 'Join'
56 for k=1:K
57 self.nodes{ist}.classCap(k) = Inf;
58 self.nodes{ist}.dropRule(k) = DropStrategy.WAITQ;
59 end
60 case 'Queue'
61 hasEnabledService = false;
62 for k=1:K
63 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
64 self.nodes{ist}.serviceProcess{k} = Disabled.getInstance();
65 self.nodes{ist}.classCap(k) = 0;
66 self.nodes{ist}.dropRule(k) = DropStrategy.WAITQ;
67 self.nodes{ist}.schedStrategyPar(k) = 0;
68 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
69 else
70 % Check if this class has an enabled service
71 if ~isa(self.nodes{ist}.server.serviceProcess{k}{3}, 'Disabled')
72 hasEnabledService = true;
73 end
74 end
75 end
76 % Validate that at least one class has service configured
77 % Skip validation for models with Cache, Petri net, or Fork/Join elements
78 hasSpecialElements = false;
79 for jnode=1:self.getNumberOfNodes
80 if isa(self.nodes{jnode}, 'Cache') || isa(self.nodes{jnode}, 'Place') || isa(self.nodes{jnode}, 'Transition') || isa(self.nodes{jnode}, 'Fork') || isa(self.nodes{jnode}, 'Join')
81 hasSpecialElements = true;
82 break;
83 end
84 end
85 if ~hasEnabledService && ~hasSpecialElements
86 line_error(mfilename, sprintf('Queue ''%s'' has no service configured for any job class. Use setService() to configure service times.', self.nodes{ist}.name));
87 end
88 for k=1:K
89 if isa(self.nodes{ist}.serviceProcess{k},'Disabled')
90 self.nodes{ist}.setRouting(self.classes{k},RoutingStrategy.DISABLED);
91 end
92 end
93 switch SchedStrategy.toId(self.nodes{ist}.schedStrategy)
94 case SchedStrategy.SEPT
95 servTime = zeros(1,K);
96 for k=1:K
97 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
98 end
99 if length(unique(servTime)) ~= K
100 line_error(mfilename,'SEPT does not support identical service time means.');
101 end
102 [servTimeSorted] = sort(unique(servTime));
103 self.nodes{ist}.schedStrategyPar = zeros(1,K);
104 for k=1:K
105 if ~isnan(servTime(k))
106 self.nodes{ist}.schedStrategyPar(k) = find(servTimeSorted == servTime(k));
107 else
108 self.nodes{ist}.schedStrategyPar(k) = find(isnan(servTimeSorted));
109 end
110 end
111 case SchedStrategy.LEPT
112 servTime = zeros(1,K);
113 for k=1:K
114 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
115 end
116 if length(unique(servTime)) ~= K
117 line_error(mfilename,'LEPT does not support identical service time means.');
118 end
119 [servTimeSorted] = sort(unique(servTime),'descend');
120 self.nodes{ist}.schedStrategyPar = zeros(1,K);
121 for k=1:K
122 self.nodes{ist}.schedStrategyPar(k) = find(servTimeSorted == servTime(k));
123 end
124 end
125 case 'Delay'
126 hasEnabledService = false;
127 for k=1:K
128 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
129 self.nodes{ist}.serviceProcess{k} = Disabled.getInstance();
130 self.nodes{ist}.classCap(k) = 0;
131 self.nodes{ist}.dropRule(k) = DropStrategy.WAITQ;
132 self.nodes{ist}.schedStrategyPar(k) = 0;
133 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
134 else
135 % Check if this class has an enabled service
136 if ~isa(self.nodes{ist}.server.serviceProcess{k}{3}, 'Disabled')
137 hasEnabledService = true;
138 end
139 end
140 end
141 % Validate that at least one class has service configured
142 % Skip validation for models with Cache, Petri net, or Fork/Join elements
143 hasSpecialElements = false;
144 for jnode=1:self.getNumberOfNodes
145 if isa(self.nodes{jnode}, 'Cache') || isa(self.nodes{jnode}, 'Place') || isa(self.nodes{jnode}, 'Transition') || isa(self.nodes{jnode}, 'Fork') || isa(self.nodes{jnode}, 'Join')
146 hasSpecialElements = true;
147 break;
148 end
149 end
150 if ~hasEnabledService && ~hasSpecialElements
151 line_error(mfilename, sprintf('Delay ''%s'' has no service configured for any job class. Use setService() to configure service times.', self.nodes{ist}.name));
152 end
153 for k=1:K
154 if isa(self.nodes{ist}.serviceProcess{k},'Disabled')
155 self.nodes{ist}.setRouting(self.classes{k},RoutingStrategy.DISABLED);
156 end
157 end
158 switch SchedStrategy.toId(self.nodes{ist}.schedStrategy)
159 case SchedStrategy.SEPT
160 servTime = zeros(1,K);
161 for k=1:K
162 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
163 end
164 [~,self.nodes{ist}.schedStrategyPar] = sort(servTime);
165 end
166 case 'Sink'
167 for k=1:K
168 self.getSink.setRouting(self.classes{k},RoutingStrategy.DISABLED);
169 end
170 case 'Source'
171 for k=1:K
172 if k > length(self.nodes{ist}.input.sourceClasses) || isempty(self.nodes{ist}.input.sourceClasses{k})
173 self.nodes{ist}.input.sourceClasses{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
174 self.nodes{ist}.setArrival(self.classes{k},Disabled.getInstance());
175 end
176 end
177 case 'Place'
178 for k=1:K
179 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
180 self.nodes{ist}.schedStrategyPar(k) = 0;
181 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
182 end
183 end
184 case 'Transition'
185 for k=1:K
186 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
187 % self.nodes{i}.schedStrategyPar(k) = 0;
188 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
189 end
190 end
191 end
192 end
193 for ist=1:M
194 if isempty(self.getIndexSourceStation) || ist ~= self.getIndexSourceStation
195 for r=1:K
196 switch self.stations{ist}.server.className
197 case 'ServiceTunnel'
198 % do nothing
199 case 'Cache'
200 self.stations{ist}.setProbRouting(self.classes{r}, self.stations{ist}, 0.0);
201 otherwise
202 if isempty(self.stations{ist}.server.serviceProcess{r})
203 self.stations{ist}.server.serviceProcess{r} = {[],ServiceStrategy.LI,Disabled.getInstance()};
204 end
205 end
206 end
207 end
208 end
209
210 % Check if model has Cache, Petri net, or Fork/Join elements
211 hasSpecialElements = false;
212 for inode=1:self.getNumberOfNodes
213 if isa(self.nodes{inode}, 'Cache') || isa(self.nodes{inode}, 'Place') || isa(self.nodes{inode}, 'Transition') || isa(self.nodes{inode}, 'Fork') || isa(self.nodes{inode}, 'Join')
214 hasSpecialElements = true;
215 break;
216 end
217 end
218
219 % Validate that each job class has service configured at least one station
220 % Skip validation for models with Cache, Petri net, or Fork/Join elements
221 if ~hasSpecialElements
222 for k=1:K
223 hasServiceAtAnyStation = false;
224 isUsedInCacheSetRead = false;
225 isOpenClass = isa(self.classes{k}, 'OpenClass');
226
227 % Check if job class is used in setRead at a Cache node
228 for inode=1:self.getNumberOfNodes
229 if isa(self.nodes{inode}, 'Cache')
230 if size(self.nodes{inode}.popularity,2) >= k && ~isempty(self.nodes{inode}.popularity{k}) && ~isa(self.nodes{inode}.popularity{k}, 'Disabled')
231 isUsedInCacheSetRead = true;
232 break;
233 end
234 end
235 end
236
237 for ist=1:M
238 if k <= length(self.stations{ist}.server.serviceProcess) && ...
239 ~isempty(self.stations{ist}.server.serviceProcess{k}) && ...
240 length(self.stations{ist}.server.serviceProcess{k}) >= 3 && ...
241 ~isa(self.stations{ist}.server.serviceProcess{k}{3}, 'Disabled')
242 hasServiceAtAnyStation = true;
243 break;
244 end
245 end
246
247 % Only closed classes need explicit setService configuration (open classes route to Sink by design)
248 if ~hasServiceAtAnyStation && ~isUsedInCacheSetRead && ~isOpenClass
249 line_error(mfilename, sprintf('Job class ''%s'' has no service configured at any station. Every job class must have service configured at least one station using setService().', self.classes{k}.name));
250 end
251 end
252 end
253end
254end
Definition mmt.m:92