1classdef SolverLQNS < Solver
2 % A solver that interfaces
the LQNS to LINE.
4 % Copyright (c) 2012-2026, Imperial College London
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);']);
25 function sn = getStruct(self)
26 %GETSTRUCT Retrieve
the model structure
27 sn = self.model.getStruct();
30 function varargout = getAvg(varargin)
31 %GETAVG Proxy to getEnsembleAvg
32 [varargout{1:nargout}] = getEnsembleAvg(varargin{:});
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([]);
43 avgTable = self.obj.getEnsembleAvg();
44 [QN,UN,RN,WN,AN,TN] = JLINE.arrayListToResults(avgTable);
46 [QN,UN,RN,TN,AN,WN] = getAvg(self);
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;
56 [QN, UN, RN, TN, AN, WN] = deal(variables{:}); % Assign
the modified values back to
the original variables
59 lqn = self.model.getStruct;
60 Node = label(lqn.names);
62 NodeType = label(O,1);
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'});
71 NodeType(o,1) = label({
'Task'});
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'});
82 QT = Table(Node,QLen);
84 UT = Table(Node,Util);
86 RT = Table(Node,RespT);
88 TT = Table(Node,Tput);
90 %ST = Table(Node,SvcT);
92 %PT = Table(Node,ProcUtil);
94 WT = Table(Node,ResidT);
96 AT = Table(Node,ArvR);
97 AvgTable = Table(Node, NodeType, QLen, Util, RespT, ResidT, ArvR, Tput);%, ProcUtil, SvcT);
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);
107 function allMethods = listValidMethods(self)
108 %LISTVALIDMETHODS List valid solving methods
for LQNS
109 sn = self.model.getStruct();
111 'default',
'lqns',
'srvn',
'exactmva', ...
112 'srvn.exactmva',
'sim',
'lqsim',
'lqnsdefault'
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'}));
127 function
bool = hasLocalBinary()
128 %HASLOCALBINARY True
if the native lqns command
is on
the system path
130 [~, ret] = dos(
'lqns -V -H');
131 bool = ~contains(ret,
'not recognized',
'IgnoreCase',
true);
133 [~, ret] = unix(
'lqns -V -H');
134 bool = ~contains(ret,
'command not found',
'IgnoreCase',
true);
138 function img = getDockerImage(requested)
139 %GETDOCKERIMAGE Resolve an LQNS Docker image to run, or
'' if unavailable
142 %
'' or
'auto' or
true -> pick a known imperialqore/lqns image
143 % an
explicit image name/tag -> use that image
if present locally
145 % Returns
'' when Docker
is not usable or no matching image exists.
147 if nargin < 1 || isempty(requested)
150 if islogical(requested)
157 % Docker bind-mount dispatch
is supported on unix hosts only.
161 % Is
the Docker daemon reachable?
162 if unix(
'docker info >/dev/null 2>&1') ~= 0
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'};
171 candidates = {requested};
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))
182 function
bool = isAvailable()
183 %ISAVAILABLE Check
if LQNS
is available natively or via Docker
186 [~, ret] = dos(
'lqns -V -H');
187 if contains(ret,
'not recognized',
'IgnoreCase',
true)
188 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
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.');
200 [~, ret] = unix('lqns -V -H');
201 if contains(ret, 'command not found', 'IgnoreCase', true)
202 bool = ~isempty(SolverLQNS.getDockerImage('auto'));
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.');
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({ ...
233 'SchedStrategy_PS', ...
234 'SchedStrategy_FCFS', ...
239 numLayers = model.getNumberOfLayers();
240 for idx = 1:numLayers
241 bool =
bool && SolverFeatureSet.supports( ...
242 featSupported, featUsed{idx} ...
247 function options = defaultOptions()
248 %DEFAULTOPTIONS Return
default options
for SolverLQNS
249 options = SolverOptions(
'LQNS');