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