LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
SolverLQNS.m
1classdef SolverLQNS < Solver
2 % A solver that interfaces the LQNS to LINE.
3 %
4 % Copyright (c) 2012-2026, Imperial College London
5 % All rights reserved.
6
7 methods
8 function self = SolverLQNS(model, varargin)
9 % SELF = SOLVERLQNS(MODEL, VARARGIN)
10 self@Solver(model, mfilename);
11 self.setOptions(Solver.parseOptions(varargin, self.defaultOptions));
12 if ~SolverLQNS.isAvailable() && ~self.options.config.remote
13 line_error(mfilename,['SolverLQNS requires the lqns and lqsim commands to be available on the system path.\n' ...
14 'You can install them from: http://www.sce.carleton.ca/rads/lqns/\n\n' ...
15 'Alternatively, use remote execution via Docker:\n' ...
16 ' 1. Pull and run: docker run -d -p 8080:8080 imperialqore/lqns-rest:latest\n' ...
17 ' 2. Configure remote execution in MATLAB:\n' ...
18 ' options = SolverOptions(@()SolverLQNS);\n' ...
19 ' options.config.remote = true;\n' ...
20 ' options.config.remote_url = ''http://localhost:8080'';\n' ...
21 ' solver = SolverLQNS(model, options);']);
22 end
23 end
24
25 function sn = getStruct(self)
26 %GETSTRUCT Retrieve the model structure
27 sn = self.model.getStruct();
28 end
29
30 function varargout = getAvg(varargin)
31 %GETAVG Proxy to getEnsembleAvg
32 [varargout{1:nargout}] = getEnsembleAvg(varargin{:});
33 end
34
35 function [AvgTable,QT,UT,RT,WT,AT,TT] = getAvgTable(self)
36 % [AVGTABLE,QT,UT,RT,WT,TT] = GETAVGTABLE()
37 if (GlobalConstants.DummyMode)
38 [AvgTable, QT, UT, RT, TT, WT] = deal([]);
39 return
40 end
41
42 if ~isempty(self.obj)
43 avgTable = self.obj.getEnsembleAvg();
44 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(avgTable);
45 else
46 [QN,UN,RN,TN,AN,WN] = getAvg(self);
47 end
48
49 % attempt to sanitize small numerical perturbations
50 variables = {QN, UN, RN, TN, AN, WN}; % Put all variables in a cell array
51 for i = 1:length(variables)
52 rVar = round(variables{i} * 10);
53 toRound = abs(variables{i} * 10 - rVar) < GlobalConstants.CoarseTol * variables{i} * 10;
54 variables{i}(toRound) = rVar(toRound) / 10;
55 end
56 [QN, UN, RN, TN, AN, WN] = deal(variables{:}); % Assign the modified values back to the original variables
57
58 %%
59 lqn = self.model.getStruct;
60 Node = label(lqn.names);
61 O = length(Node);
62 NodeType = label(O,1);
63 for o = 1:O
64 switch lqn.type(o)
65 case LayeredNetworkElement.PROCESSOR
66 NodeType(o,1) = label({'Processor'});
67 case LayeredNetworkElement.TASK
68 if self.model.getStruct.isref(o)
69 NodeType(o,1) = label({'RefTask'});
70 else
71 NodeType(o,1) = label({'Task'});
72 end
73 case LayeredNetworkElement.ENTRY
74 NodeType(o,1) = label({'Entry'});
75 case LayeredNetworkElement.ACTIVITY
76 NodeType(o,1) = label({'Activity'});
77 case LayeredNetworkElement.CALL
78 NodeType(o,1) = label({'Call'});
79 end
80 end
81 QLen = QN;
82 QT = Table(Node,QLen);
83 Util = UN;
84 UT = Table(Node,Util);
85 RespT = RN;
86 RT = Table(Node,RespT);
87 Tput = TN;
88 TT = Table(Node,Tput);
89 %SvcT = SN;
90 %ST = Table(Node,SvcT);
91 %ProcUtil = PN;
92 %PT = Table(Node,ProcUtil);
93 ResidT = WN;
94 WT = Table(Node,ResidT);
95 ArvR = AN;
96 AT = Table(Node,ArvR);
97 AvgTable = Table(Node, NodeType, QLen, Util, RespT, ResidT, ArvR, Tput);%, ProcUtil, SvcT);
98 end
99 end
100
101 methods % implemented in .m files
102 runtime = runAnalyzer(self, options);
103 [result, iterations] = parseXMLResults(self, filename);
104 [QN,UN,RN,TN,AN,WN] = getEnsembleAvg(self);
105 savedfname = plot(model);
106
107 function allMethods = listValidMethods(self)
108 %LISTVALIDMETHODS List valid solving methods for LQNS
109 sn = self.model.getStruct();
110 allMethods = {
111 'default', 'lqns', 'srvn', 'exactmva', ...
112 'srvn.exactmva', 'sim', 'lqsim', 'lqnsdefault'
113 };
114 end
115
116 function bool = isStochasticMethod(self, method) %#ok<INUSL>
117 % BOOL = ISSTOCHASTICMETHOD(METHOD)
118 % The lqsim simulator is stochastic; the analytical lqns/srvn
119 % methods are deterministic.
120 bool = any(strcmpi(method, {'sim','lqsim'}));
121 end
122 end
123
124 methods (Static)
125
126
127 function bool = hasLocalBinary()
128 %HASLOCALBINARY True if the native lqns command is on the system path
129 if ispc
130 [~, ret] = dos('lqns -V -H');
131 bool = ~contains(ret, 'not recognized', 'IgnoreCase', true);
132 else
133 [~, ret] = unix('lqns -V -H');
134 bool = ~contains(ret, 'command not found', 'IgnoreCase', true);
135 end
136 end
137
138 function img = getDockerImage(requested)
139 %GETDOCKERIMAGE Resolve an LQNS Docker image to run, or '' if unavailable
140 %
141 % REQUESTED may be:
142 % '' or 'auto' or true -> pick a known imperialqore/lqns image
143 % an explicit image name/tag -> use that image if present locally
144 %
145 % Returns '' when Docker is not usable or no matching image exists.
146 img = '';
147 if nargin < 1 || isempty(requested)
148 requested = 'auto';
149 end
150 if islogical(requested)
151 if requested
152 requested = 'auto';
153 else
154 return;
155 end
156 end
157 % Docker bind-mount dispatch is supported on unix hosts only.
158 if ispc
159 return;
160 end
161 % Is the Docker daemon reachable?
162 if unix('docker info >/dev/null 2>&1') ~= 0
163 return;
164 end
165 if strcmpi(requested, 'auto') || strcmpi(requested, 'true')
166 % Prefer the pinned 6.2.28 tag: it matches the native lqns used
167 % to generate the LQNS regression goldens bit-for-bit (6.2.31
168 % shifts entry/call utilization). Fall back to newer tags.
169 candidates = {'imperialqore/lqns:6.2.28', 'imperialqore/lqns:latest', 'imperialqore/lqns'};
170 else
171 candidates = {requested};
172 end
173 for i = 1:numel(candidates)
174 [st, out] = unix(['docker images -q ', candidates{i}, ' 2>/dev/null']);
175 if st == 0 && ~isempty(strtrim(out))
176 img = candidates{i};
177 return;
178 end
179 end
180 end
181
182 function bool = isAvailable()
183 %ISAVAILABLE Check if LQNS is available natively or via Docker
184 bool = true;
185 if ispc
186 [~, ret] = dos('lqns -V -H');
187 if contains(ret, 'not recognized', 'IgnoreCase', true)
188 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
189 return;
190 end
191 if contains(ret, 'Version 5', 'IgnoreCase', true) || ...
192 contains(ret, 'Version 4', 'IgnoreCase', true) || ...
193 contains(ret, 'Version 3', 'IgnoreCase', true) || ...
194 contains(ret, 'Version 2', 'IgnoreCase', true) || ...
195 contains(ret, 'Version 1', 'IgnoreCase', true)
196 line_warning(mfilename, ...
197 'Unsupported LQNS version. LINE requires Version 6.0 or greater.');
198 end
199 else
200 [~, ret] = unix('lqns -V -H');
201 if contains(ret, 'command not found', 'IgnoreCase', true)
202 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
203 return;
204 end
205 if contains(ret, 'Version 5', 'IgnoreCase', true) || ...
206 contains(ret, 'Version 4', 'IgnoreCase', true) || ...
207 contains(ret, 'Version 3', 'IgnoreCase', true) || ...
208 contains(ret, 'Version 2', 'IgnoreCase', true) || ...
209 contains(ret, 'Version 1', 'IgnoreCase', true)
210 line_warning(mfilename, ...
211 'Unsupported LQNS version. LINE requires Version 6.0 or greater.');
212 end
213 end
214 end
215
216 function [bool, featSupported] = supports(model)
217 %SUPPORTS Check if the used features are supported
218 featUsed = model.getUsedLangFeatures();
219 featSupported = SolverFeatureSet;
220 featSupported.setTrue({ ...
221 'Sink', ...
222 'Source', ...
223 'Queue', ...
224 'Coxian', ...
225 'Erlang', ...
226 'Exp', ...
227 'HyperExp', ...
228 'Buffer', ...
229 'Server', ...
230 'JobSink', ...
231 'RandomSource', ...
232 'ServiceTunnel', ...
233 'SchedStrategy_PS', ...
234 'SchedStrategy_FCFS', ...
235 'ClosedClass' ...
236 });
237
238 bool = true;
239 numLayers = model.getNumberOfLayers();
240 for idx = 1:numLayers
241 bool = bool && SolverFeatureSet.supports( ...
242 featSupported, featUsed{idx} ...
243 );
244 end
245 end
246
247 function options = defaultOptions()
248 %DEFAULTOPTIONS Return default options for SolverLQNS
249 options = SolverOptions('LQNS');
250 end
251
252 end
253end
Definition Station.m:245