LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
fj_isfj.m
1%{ @file fj_isfj.m
2 % @brief Checks if network is a valid Fork-Join topology for FJ_codes analysis
3 %
4 % @author LINE Development Team
5%}
6
7%{
8 % @brief Checks if network is a valid Fork-Join topology for FJ_codes analysis
9 %
10 % @details
11 % Validates that the network structure matches the requirements for FJ_codes:
12 % - Single Fork-Join pair
13 % - K parallel queues between Fork and Join
14 % - Homogeneous service distributions across parallel queues
15 % - Supported distributions (Exp, HyperExp(2), Erlang(2), MAP(2))
16 % - Open classes only
17 %
18 % @par Syntax:
19 % @code
20 % [isFJ, fjInfo] = fj_isfj(sn)
21 % @endcode
22 %
23 % @par Parameters:
24 % <table>
25 % <tr><th>Name<th>Description
26 % <tr><td>sn<td>Network structure
27 % </table>
28 %
29 % @par Returns:
30 % <table>
31 % <tr><th>Name<th>Description
32 % <tr><td>isFJ<td>True if network is valid FJ topology for FJ_codes
33 % <tr><td>fjInfo<td>Struct with fields: forkIdx, joinIdx, queueIdx, K, errorMsg
34 % </table>
35 %
36 % @par Reference:
37 % Z. Qiu, J.F. Pérez, and P. Harrison, "Beyond the Mean in Fork-Join Queues:
38 % Efficient Approximation for Response-Time Tails", IFIP Performance 2015.
39 % Copyright 2015 Imperial College London
40%}
41function [isFJ, fjInfo] = fj_isfj(sn)
42
43% Initialize output
44isFJ = false;
45fjInfo = struct();
46fjInfo.forkIdx = [];
47fjInfo.joinIdx = [];
48fjInfo.queueIdx = [];
49fjInfo.K = 0;
50fjInfo.errorMsg = '';
51
52% Check if model has open classes only
53if ~sn_is_open_model(sn)
54 fjInfo.errorMsg = 'FJ_codes only supports open queueing models.';
55 return;
56end
57
58% Check if network has fork-join
59if ~sn_has_fork_join(sn)
60 fjInfo.errorMsg = 'Network does not contain Fork-Join structure.';
61 return;
62end
63
64% Find Fork and Join nodes
65forkIndices = find(sn.nodetype == NodeType.Fork);
66joinIndices = find(sn.nodetype == NodeType.Join);
67
68if isempty(forkIndices) || isempty(joinIndices)
69 fjInfo.errorMsg = 'Network must contain both Fork and Join nodes.';
70 return;
71end
72
73% FJ_codes supports single Fork-Join pair
74if length(forkIndices) > 1
75 fjInfo.errorMsg = 'FJ_codes only supports a single Fork-Join pair. Found multiple Fork nodes.';
76 return;
77end
78
79if length(joinIndices) > 1
80 fjInfo.errorMsg = 'FJ_codes only supports a single Fork-Join pair. Found multiple Join nodes.';
81 return;
82end
83
84forkIdx = forkIndices(1);
85joinIdx = joinIndices(1);
86
87% Check if Fork and Join are paired using sn.fj matrix
88if sn.fj(forkIdx, joinIdx) == 0
89 fjInfo.errorMsg = sprintf('Fork node %d and Join node %d are not paired.', forkIdx, joinIdx);
90 return;
91end
92
93fjInfo.forkIdx = forkIdx;
94fjInfo.joinIdx = joinIdx;
95
96% Find queues between Fork and Join
97% These are nodes that receive routing from Fork and route to Join
98queueIdx = [];
99for i = 1:sn.nnodes
100 if sn.nodetype(i) == NodeType.Queue
101 % Check if this queue is in a path from Fork to Join
102 % Use rtnodes which is indexed by node indices (not station indices)
103 hasForkInput = false;
104 hasJoinOutput = false;
105
106 % Check if Fork routes to this queue (using rtnodes for node-based routing)
107 if sn.rtnodes(forkIdx, i) > 0
108 hasForkInput = true;
109 end
110 % Check if this queue routes to Join
111 if sn.rtnodes(i, joinIdx) > 0
112 hasJoinOutput = true;
113 end
114
115 if hasForkInput && hasJoinOutput
116 queueIdx = [queueIdx, i];
117 end
118 end
119end
120
121if isempty(queueIdx)
122 fjInfo.errorMsg = 'No Queue nodes found between Fork and Join.';
123 return;
124end
125
126K = length(queueIdx);
127fjInfo.queueIdx = queueIdx;
128fjInfo.K = K;
129
130% Validate homogeneous service distributions across parallel queues
131% For each class, all K queues must have the same service distribution
132for r = 1:sn.nclasses
133 % Get PH representation of first queue's service distribution
134 firstQueueIdx = queueIdx(1);
135 firstPH = sn.proc{sn.nodeToStation(firstQueueIdx)}{r};
136
137 if isempty(firstPH) || isnan(firstPH{1}(1))
138 fjInfo.errorMsg = sprintf('Queue %d has no valid service distribution for class %d.', ...
139 firstQueueIdx, r);
140 return;
141 end
142
143 % Check all other queues have the same distribution
144 for k = 2:K
145 queueIdx_k = queueIdx(k);
146 ph_k = sn.proc{sn.nodeToStation(queueIdx_k)}{r};
147
148 if isempty(ph_k) || isnan(ph_k{1}(1))
149 fjInfo.errorMsg = sprintf('Queue %d has no valid service distribution for class %d.', ...
150 queueIdx_k, r);
151 return;
152 end
153
154 % Compare PH representations (must be identical)
155 % Compare number of phases
156 if length(ph_k{1}) ~= length(firstPH{1})
157 fjInfo.errorMsg = sprintf('Queues have heterogeneous service distributions for class %d. FJ_codes requires homogeneous servers.', r);
158 return;
159 end
160
161 % Compare initial probability vector and rate matrix
162 if ~isequal(size(ph_k{1}), size(firstPH{1})) || ...
163 ~isequal(size(ph_k{2}), size(firstPH{2})) || ...
164 max(abs(ph_k{1} - firstPH{1})) > GlobalConstants.FineTol || ...
165 max(max(abs(ph_k{2} - firstPH{2}))) > GlobalConstants.FineTol
166 fjInfo.errorMsg = sprintf('Queues have heterogeneous service distributions for class %d. FJ_codes requires homogeneous servers.', r);
167 return;
168 end
169 end
170end
171
172% Validate supported scheduling strategies (FCFS or PS)
173for k = 1:K
174 queueSt = sn.nodeToStation(queueIdx(k));
175 if sn.sched(queueSt) ~= SchedStrategy.FCFS && sn.sched(queueSt) ~= SchedStrategy.PS
176 fjInfo.errorMsg = sprintf('Queue %d has unsupported scheduling strategy. FJ_codes supports FCFS or PS only.', queueIdx(k));
177 return;
178 end
179end
180
181% All validations passed
182isFJ = true;
183
184end
Definition mmt.m:92