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/line-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 % see _kb/06-solver-catalog.md (Wrappers: LQNS Docker image candidate list)
167 candidates = {'imperialqore/line-lqns-rest:latest', 'imperialqore/line-lqns-rest', ...
168 'imperialqore/lqns:6.2.28', 'imperialqore/lqns:latest', 'imperialqore/lqns'};
169 else
170 candidates = {requested};
171 end
172 for i = 1:numel(candidates)
173 [st, out] = unix(['docker images -q ', candidates{i}, ' 2>/dev/null']);
174 if st == 0 && ~isempty(strtrim(out))
175 img = candidates{i};
176 return;
177 end
178 end
179 end
180
181 function bool = isAvailable()
182 %ISAVAILABLE Check if LQNS is available natively or via Docker
183 bool = true;
184 if ispc
185 [~, ret] = dos('lqns -V -H');
186 if contains(ret, 'not recognized', 'IgnoreCase', true)
187 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
188 return;
189 end
190 if contains(ret, 'Version 5', 'IgnoreCase', true) || ...
191 contains(ret, 'Version 4', 'IgnoreCase', true) || ...
192 contains(ret, 'Version 3', 'IgnoreCase', true) || ...
193 contains(ret, 'Version 2', 'IgnoreCase', true) || ...
194 contains(ret, 'Version 1', 'IgnoreCase', true)
195 line_warning(mfilename, ...
196 'Unsupported LQNS version. LINE requires Version 6.0 or greater.');
197 end
198 else
199 [~, ret] = unix('lqns -V -H');
200 if contains(ret, 'command not found', 'IgnoreCase', true)
201 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
202 return;
203 end
204 if contains(ret, 'Version 5', 'IgnoreCase', true) || ...
205 contains(ret, 'Version 4', 'IgnoreCase', true) || ...
206 contains(ret, 'Version 3', 'IgnoreCase', true) || ...
207 contains(ret, 'Version 2', 'IgnoreCase', true) || ...
208 contains(ret, 'Version 1', 'IgnoreCase', true)
209 line_warning(mfilename, ...
210 'Unsupported LQNS version. LINE requires Version 6.0 or greater.');
211 end
212 end
213 end
214
215 function [bool, featSupported] = supports(model)
216 %SUPPORTS Check if the used features are supported
217 featUsed = model.getUsedLangFeatures();
218 featSupported = SolverFeatureSet;
219 featSupported.setTrue({ ...
220 'Sink', ...
221 'Source', ...
222 'Queue', ...
223 'Coxian', ...
224 'Erlang', ...
225 'Exp', ...
226 'HyperExp', ...
227 'Buffer', ...
228 'Server', ...
229 'JobSink', ...
230 'RandomSource', ...
231 'ServiceTunnel', ...
232 'SchedStrategy_PS', ...
233 'SchedStrategy_FCFS', ...
234 'ClosedClass' ...
235 });
236
237 bool = true;
238 numLayers = model.getNumberOfLayers();
239 for idx = 1:numLayers
240 bool = bool && SolverFeatureSet.supports( ...
241 featSupported, featUsed{idx} ...
242 );
243 end
244 end
245
246 function options = defaultOptions()
247 %DEFAULTOPTIONS Return default options for SolverLQNS
248 options = SolverOptions('LQNS');
249 end
250
251 end
252end