LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
mmt.m
1function [nonfjmodel, fjclassmap, fjforkmap, fanout] = mmt(model, forkLambda)
2% build a model with fork-joins replaced by routers and delays and
3% parallelism simulated by artificial classes
4% forkLambda(s) is the arrival rate of artificial class s
5% s = fjclassmap(r) for auxiliary class r gives the index s of the original class
6% f = fjforkmap(r) for auxiliary class r gives the index of the associated fork node f
7% fo = fanout(r) is the number of output jobs across all links for the (fork f,class s) pair modelled by auxiliary class r
8
9%% this has been migrated to Java inside FJ.java as mmt
10
11sn = model.getStruct;
12if nargin < 2
13 forkLambda = GlobalConstants.FineTol * ones(1,sn.nclasses);
14end
15fjclassmap = [];
16fjforkmap = [];
17fanout = [];
18% we create an equivalent model without fj stations
19nonfjmodel = model.copy();
20nonfjmodel.allowReplace = true;
21P = nonfjmodel.getLinkedRoutingMatrix;
22nonfjmodel.resetNetwork(true);
23nonfjmodel.resetStruct();
24if isempty(P)
25 line_error(mfilename,'SolverMVA can process fork-join networks only if their routing topology has been generated using Network.link.');
26end
27Vnodes = cellsum(sn.nodevisits);
28forkedClasses = {};
29forkIndexes = find(sn.nodetype == NodeType.Fork)';
30% replaces forks and joins with routers
31fanout = [];
32for f=forkIndexes
33 for r=1:size(P,1)
34 if length(model.nodes{f}.output.outputStrategy{r})>2
35 origfanout(f,r) = length(model.nodes{f}.output.outputStrategy{r}{3});
36 for s=1:size(P,2)
37 P{r,s}(f,:) = P{r,s}(f,:) / origfanout(f,r);
38 end
39 else
40 origfanout(f,r) = 0;
41 end
42 end
43 % replace Join with a Router
44 nonfjmodel.nodes{f} = Router(nonfjmodel, nonfjmodel.nodes{f}.name);
45 % replace Fork with a StatelessClassSwitcher that doesn't
46 % change the classes
47 %nonfjmodel.nodes{f} = ClassSwitch(nonfjmodel, nonfjmodel.nodes{f}.name, eye(sn.nclasses));
48 forkedClasses{f,1} = find(Vnodes(f,:)>0); %#ok<AGROW>
49end
50for j=find(sn.nodetype == NodeType.Join)'
51 % replace Join with an Infinite Server
52 nonfjmodel.nodes{j} = Delay(nonfjmodel, nonfjmodel.nodes{j}.name);
53 nonfjmodel.stations{model.nodes{j}.stationIndex} = nonfjmodel.nodes{j};
54 for c=1:length(nonfjmodel.classes)
55 nonfjmodel.nodes{j}.setService(nonfjmodel.classes{c},Immediate());
56 end
57end
58nonfjmodel.stations={nonfjmodel.stations{1:model.getNumberOfStations}}'; % remove automatically added station and put it where the join was
59% if they don't exist already, add source and sink
60if nonfjmodel.hasOpenClasses
61 source = nonfjmodel.getSource;
62 sink = nonfjmodel.getSink;
63else
64 source = Source(nonfjmodel,'Source');
65 sink = Sink(nonfjmodel,'Sink');
66end
67for r=1:size(P,1)
68 for s=1:size(P,2)
69 P{r,s}(length(nonfjmodel.nodes),length(nonfjmodel.nodes)) = 0;
70 P{s,r}(length(nonfjmodel.nodes),length(nonfjmodel.nodes)) = 0;
71 end
72end
73nonfjmodel.connections = zeros(length(nonfjmodel.nodes));
74oclass = {};
75all_aux_class_indices = []; % aux class indices for post-relink routing fix
76for f=forkIndexes
77 % find join associated to fork f
78 joinIdx = find(sn.fj(f,:));
79 if length(joinIdx)>1
80 line_error(mfilename,'SolverMVA supports at present only a single join station per fork node.');
81 end
82 % find chains associated to classes forked by f
83 forkedChains = find(sum(sn.chains(:,forkedClasses{f}),2));
84 for fc=forkedChains'
85 % create a new open class for each class in forkedChains
86 oclass = {};
87 inchain = find(sn.chains(fc,:)); inchain = inchain(:)';
88 for r=inchain
89 oclass{end+1} = OpenClass(nonfjmodel,[nonfjmodel.classes{r}.name,'.',nonfjmodel.nodes{f}.name]); %#ok<AGROW>
90 fjclassmap(oclass{end}.index) = nonfjmodel.classes{r}.index;
91 fjforkmap(oclass{end}.index) = f;
92 s = fjclassmap(oclass{end}.index); % auxiliary class index
93 if model.nodes{f}.output.tasksPerLink > 1
94 line_warning(mfilename, 'There are no synchronisation delays implemented in MMT for multiple tasks per link. Results may be inaccurate.');
95 end
96 fanout(oclass{end}.index) = origfanout(f,r)*model.nodes{f}.output.tasksPerLink;
97 all_aux_class_indices(end+1) = oclass{end}.index; %#ok<AGROW>
98 if origfanout(f,r) == 0 || sn.nodevisits{fc}(f,r) == 0
99 source.setArrival(oclass{end},Disabled.getInstance);
100 else
101 source.setArrival(oclass{end},Exp(forkLambda(r)));
102 end
103 % joins are now Delays, let us set their service time
104 for i=1:sn.nnodes
105 if sn.isstation(i)
106 switch sn.nodetype(i)
107 case NodeType.Join
108 nonfjmodel.nodes{i}.setService(oclass{end},Immediate());
109 case {NodeType.Source, NodeType.Fork}
110 %no-op
111 otherwise
112 nonfjmodel.nodes{i}.setService(oclass{end},model.nodes{i}.getService(model.classes{r}).copy());
113 end
114 else
115 end
116 end
117 end
118
119 for r=inchain
120 for s=inchain
121 P{oclass{find(r==inchain,1)},oclass{find(s==inchain,1)}} = P{r,s};
122 end
123 end
124 for r=inchain
125 for s=inchain
126 P{oclass{find(r==inchain,1)},oclass{find(s==inchain,1)}}(source,:) = 0.0;
127 if ~isempty(joinIdx)
128 P{oclass{find(r==inchain,1)},oclass{find(s==inchain,1)}}(nonfjmodel.nodes{joinIdx},:) = 0.0;
129 end
130 end
131 if origfanout(f,r) > 0
132 P{oclass{find(r==inchain,1)},oclass{find(r==inchain,1)}}(source, nonfjmodel.nodes{f}) = 1.0;
133 if ~isempty(joinIdx)
134 P{oclass{find(r==inchain,1)},oclass{find(r==inchain,1)}}(nonfjmodel.nodes{joinIdx},sink) = 1.0;
135 end
136 end
137 end
138 % Check if all classes in this chain have non-zero fanout at this fork.
139 % BFS scope clearing only applies when all classes are actually forked;
140 % when some classes pass through the fork via class-switching (origfanout=0),
141 % clearing would disconnect their aux chain and break MVA convergence.
142 all_forked = true;
143 for ri = 1:length(inchain)
144 if origfanout(f, inchain(ri)) == 0
145 all_forked = false;
146 break;
147 end
148 end
149 if all_forked
150 % Determine fork-join scope via class-aware BFS from Fork, stopping
151 % at Join. Tracks (node, class) pairs so that shared stations (e.g.
152 % processors serving both fork-branch and return-path classes) are
153 % correctly handled: only fork-branch class routing is followed.
154 pnnodes = size(P{inchain(1),inchain(1)}, 1);
155 maxclass = max(inchain);
156 fj_node_mask = false(1, pnnodes);
157 fj_node_mask(f) = true;
158 if ~isempty(joinIdx), fj_node_mask(joinIdx) = true; end
159 % Source and Sink are mmt infrastructure, always in scope
160 for nd = 1:pnnodes
161 if isa(nonfjmodel.nodes{nd}, 'Source') || isa(nonfjmodel.nodes{nd}, 'Sink')
162 fj_node_mask(nd) = true;
163 end
164 end
165 % Class-aware BFS: find all (node, class) pairs reachable from Fork
166 visited = false(pnnodes, maxclass);
167 bfs_q = zeros(0, 2); % [node, class] pairs
168 % Seed: classes entering the fork
169 for ri = 1:length(inchain)
170 r = inchain(ri);
171 for si = 1:length(inchain)
172 s = inchain(si);
173 if any(P{r,s}(f,:) > 0) && ~visited(f, r)
174 visited(f, r) = true;
175 bfs_q(end+1,:) = [f, r]; %#ok<AGROW>
176 end
177 end
178 end
179 while ~isempty(bfs_q)
180 cn = bfs_q(1,1); cc = bfs_q(1,2);
181 bfs_q(1,:) = [];
182 for si = 1:length(inchain)
183 s = inchain(si);
184 for nd = 1:pnnodes
185 if P{cc,s}(cn, nd) > 0 && ~visited(nd, s)
186 visited(nd, s) = true;
187 fj_node_mask(nd) = true;
188 % Continue BFS unless this is Join
189 if isempty(joinIdx) || nd ~= joinIdx
190 bfs_q(end+1,:) = [nd, s]; %#ok<AGROW>
191 end
192 end
193 end
194 end
195 end
196 % Clear outgoing aux routing at nodes not in fork-join scope.
197 % Only clear rows (outgoing routes), not columns (incoming),
198 % because dead incoming routes are harmless and clearing columns
199 % can break the routing matrix structure for class-switching models.
200 for nd = 1:pnnodes
201 if ~fj_node_mask(nd)
202 for ri = 1:length(inchain)
203 for si = 1:length(inchain)
204 P{oclass{ri}, oclass{si}}(nd, :) = 0.0;
205 end
206 end
207 end
208 end
209 end
210 end
211end
212nonfjmodel.relink(P);
213% Fix spurious routing for aux classes at non-scope nodes and CS nodes.
214% relink() only calls setProbRouting for non-zero P entries, so nodes
215% where P was zeroed retain default RAND routing that inherits physical
216% connections from original classes. CS nodes created by link() for
217% cross-class routing also get default RAND for aux classes. Override
218% all non-PROB routing for aux classes to PROB with empty destinations.
219for nd = 1:length(nonfjmodel.nodes)
220 os = nonfjmodel.nodes{nd}.output.outputStrategy;
221 for ci = all_aux_class_indices
222 if ci <= length(os)
223 if isempty(os{ci}) || ~strcmp(os{ci}{2}, 'Probabilities')
224 os{ci} = {nonfjmodel.classes{ci}.name, 'Probabilities', {}};
225 end
226 end
227 end
228 nonfjmodel.nodes{nd}.output.outputStrategy = os;
229end
230for f=forkIndexes
231 for r=1:length(nonfjmodel.nodes{f}.output.outputStrategy)
232 if strcmp(nonfjmodel.nodes{f}.output.outputStrategy{r}{2},RoutingStrategy.RAND)
233 nonfjmodel.nodes{f}.output.outputStrategy{r}{1} = nonfjmodel.classes{r}.name;
234 nonfjmodel.nodes{f}.output.outputStrategy{r}{2} = RoutingStrategy.DISABLED;
235 end
236 end
237end
238end