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 % Also check for heterogeneous services
77 if ~hasEnabledService && isprop(self.nodes{ist}, 'heteroServiceDistributions') && ~isempty(self.nodes{ist}.heteroServiceDistributions)
78 if self.nodes{ist}.heteroServiceDistributions.Count > 0
79 hasEnabledService = true;
80 end
81 end
82 % Validate that at least one class has service configured
83 % Skip validation for models with Cache, Petri net, or Fork/Join elements
84 hasSpecialElements = false;
85 for jnode=1:self.getNumberOfNodes
86 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')
87 hasSpecialElements = true;
88 break;
89 end
90 end
91 if ~hasEnabledService && ~hasSpecialElements
92 line_error(mfilename, sprintf('Queue ''%s'' has no service configured for any job class. Use setService() to configure service times.', self.nodes{ist}.name));
93 end
94 for k=1:K
95 if isa(self.nodes{ist}.serviceProcess{k},'Disabled')
96 self.nodes{ist}.setRouting(self.classes{k},RoutingStrategy.DISABLED);
97 end
98 end
99 switch SchedStrategy.toId(self.nodes{ist}.schedStrategy)
100 case SchedStrategy.SEPT
101 servTime = zeros(1,K);
102 for k=1:K
103 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
104 end
105 if length(unique(servTime)) ~= K
106 line_error(mfilename,'SEPT does not support identical service time means.');
107 end
108 [servTimeSorted] = sort(unique(servTime));
109 self.nodes{ist}.schedStrategyPar = zeros(1,K);
110 for k=1:K
111 if ~isnan(servTime(k))
112 self.nodes{ist}.schedStrategyPar(k) = find(servTimeSorted == servTime(k));
113 else
114 self.nodes{ist}.schedStrategyPar(k) = find(isnan(servTimeSorted));
115 end
116 end
117 case SchedStrategy.LEPT
118 servTime = zeros(1,K);
119 for k=1:K
120 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
121 end
122 if length(unique(servTime)) ~= K
123 line_error(mfilename,'LEPT does not support identical service time means.');
124 end
125 [servTimeSorted] = sort(unique(servTime),'descend');
126 self.nodes{ist}.schedStrategyPar = zeros(1,K);
127 for k=1:K
128 self.nodes{ist}.schedStrategyPar(k) = find(servTimeSorted == servTime(k));
129 end
130 end
131 case 'Delay'
132 hasEnabledService = false;
133 for k=1:K
134 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
135 self.nodes{ist}.serviceProcess{k} = Disabled.getInstance();
136 self.nodes{ist}.classCap(k) = 0;
137 self.nodes{ist}.dropRule(k) = DropStrategy.WAITQ;
138 self.nodes{ist}.schedStrategyPar(k) = 0;
139 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
140 else
141 % Check if this class has an enabled service
142 if ~isa(self.nodes{ist}.server.serviceProcess{k}{3}, 'Disabled')
143 hasEnabledService = true;
144 end
145 end
146 end
147 % Validate that at least one class has service configured
148 % Skip validation for models with Cache, Petri net, or Fork/Join elements
149 hasSpecialElements = false;
150 for jnode=1:self.getNumberOfNodes
151 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')
152 hasSpecialElements = true;
153 break;
154 end
155 end
156 if ~hasEnabledService && ~hasSpecialElements
157 line_error(mfilename, sprintf('Delay ''%s'' has no service configured for any job class. Use setService() to configure service times.', self.nodes{ist}.name));
158 end
159 for k=1:K
160 if isa(self.nodes{ist}.serviceProcess{k},'Disabled')
161 self.nodes{ist}.setRouting(self.classes{k},RoutingStrategy.DISABLED);
162 end
163 end
164 switch SchedStrategy.toId(self.nodes{ist}.schedStrategy)
165 case SchedStrategy.SEPT
166 servTime = zeros(1,K);
167 for k=1:K
168 servTime(k) = self.nodes{ist}.serviceProcess{k}.getMean;
169 end
170 [~,self.nodes{ist}.schedStrategyPar] = sort(servTime);
171 end
172 case 'Sink'
173 for k=1:K
174 self.getSink.setRouting(self.classes{k},RoutingStrategy.DISABLED);
175 end
176 case 'Source'
177 for k=1:K
178 if k > length(self.nodes{ist}.input.sourceClasses) || isempty(self.nodes{ist}.input.sourceClasses{k})
179 self.nodes{ist}.input.sourceClasses{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
180 self.nodes{ist}.setArrival(self.classes{k},Disabled.getInstance());
181 end
182 end
183 case 'Place'
184 for k=1:K
185 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
186 self.nodes{ist}.schedStrategyPar(k) = 0;
187 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
188 end
189 end
190 case 'Transition'
191 for k=1:K
192 if k > length(self.nodes{ist}.server.serviceProcess) || isempty(self.nodes{ist}.server.serviceProcess{k})
193 % self.nodes{i}.schedStrategyPar(k) = 0;
194 self.nodes{ist}.server.serviceProcess{k} = {[],ServiceStrategy.LI,Disabled.getInstance()};
195 end
196 end
197 end
198 end
199 for ist=1:M
200 if isempty(self.getIndexSourceStation) || ist ~= self.getIndexSourceStation
201 for r=1:K
202 switch self.stations{ist}.server.className
203 case 'ServiceTunnel'
204 % do nothing
205 case 'Cache'
206 self.stations{ist}.setProbRouting(self.classes{r}, self.stations{ist}, 0.0);
207 otherwise
208 if isempty(self.stations{ist}.server.serviceProcess{r})
209 self.stations{ist}.server.serviceProcess{r} = {[],ServiceStrategy.LI,Disabled.getInstance()};
210 end
211 end
212 end
213 end
214 end
215
216 % Check if model has Cache, Petri net, or Fork/Join elements
217 hasSpecialElements = false;
218 for inode=1:self.getNumberOfNodes
219 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')
220 hasSpecialElements = true;
221 break;
222 end
223 end
224
225 % Validate that each job class has service configured at least one station
226 % Skip validation for models with Cache, Petri net, or Fork/Join elements
227 if ~hasSpecialElements
228 for k=1:K
229 hasServiceAtAnyStation = false;
230 isUsedInCacheSetRead = false;
231 isOpenClass = isa(self.classes{k}, 'OpenClass');
232
233 % Check if job class is used in setRead at a Cache node
234 for inode=1:self.getNumberOfNodes
235 if isa(self.nodes{inode}, 'Cache')
236 if size(self.nodes{inode}.popularity,2) >= k && ~isempty(self.nodes{inode}.popularity{k}) && ~isa(self.nodes{inode}.popularity{k}, 'Disabled')
237 isUsedInCacheSetRead = true;
238 break;
239 end
240 end
241 end
242
243 for ist=1:M
244 if k <= length(self.stations{ist}.server.serviceProcess) && ...
245 ~isempty(self.stations{ist}.server.serviceProcess{k}) && ...
246 length(self.stations{ist}.server.serviceProcess{k}) >= 3 && ...
247 ~isa(self.stations{ist}.server.serviceProcess{k}{3}, 'Disabled')
248 hasServiceAtAnyStation = true;
249 break;
250 end
251 end
252
253 % Only closed classes need explicit setService configuration (open classes route to Sink by design)
254 if ~hasServiceAtAnyStation && ~isUsedInCacheSetRead && ~isOpenClass
255 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));
256 end
257 end
258 end
259end
260end
Definition mmt.m:92