LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_mam_fj.m
1function [QN,UN,RN,TN,CN,XN,totiter,percResults] = solver_mam_fj(sn, options)
2% [QN,UN,RN,TN,CN,XN,TOTITER,PERCRESULTS] = SOLVER_MAM_FJ(SN, OPTIONS)
3%
4% Analyze Fork-Join network using FJ_codes percentile approximation
5%
6% Parameters:
7% sn - Network structure from getStruct()
8% options - Solver options with FJ-specific fields:
9% - percentiles: percentile levels (default [0.90, 0.95, 0.99])
10% - fj_accuracy: C parameter for approximation (default 100)
11% - fj_tmode: 'NARE' or 'Sylves' for T matrix computation (default 'NARE')
12%
13% Returns:
14% QN, UN, RN, TN, CN, XN - Standard LINE performance metrics matrices
15% totiter - Number of iterations (N/A for FJ, returns 0)
16% percResults - Percentile results structure with fields:
17% .RT - cell array of percentile structs per class
18% .K - number of parallel queues
19% .method - 'fj_codes'
20%
21% Reference:
22% Z. Qiu, J.F. Pérez, and P. Harrison, "Beyond the Mean in Fork-Join Queues:
23% Efficient Approximation for Response-Time Tails", IFIP Performance 2015.
24% Copyright 2015 Imperial College London
25%
26% Copyright (c) 2012-2026, Imperial College London
27% All rights reserved.
28
29% Validate FJ topology
30[isFJ, fjInfo] = fj_isfj(sn);
31
32if ~isFJ
33 line_error(mfilename, 'Network is not a valid Fork-Join topology for FJ_codes: %s', fjInfo.errorMsg);
34end
35
36% Extract FJ parameters
37[arrival, service, K, fjInfo] = fj_extract_params(sn, fjInfo);
38
39% Get FJ-specific options from config
40% Request dense percentiles for accurate mean calculation via numerical integration
41% E[X] = integral_0^inf (1 - F(x)) dx, approximated using trapezoidal rule
42pers_dense = [0.01:0.05:0.95, 0.99, 0.999]; % Dense percentiles for mean calculation
43pers_stored = [0.50, 0.90, 0.95, 0.99]; % Percentiles stored for user retrieval
44
45if isfield(options.config, 'fj_accuracy')
46 Cs = options.config.fj_accuracy;
47else
48 Cs = 100; % Default accuracy parameter
49end
50
51if isfield(options.config, 'fj_tmode')
52 T_Mode = options.config.fj_tmode;
53else
54 T_Mode = 'NARE'; % Default method for T matrix
55end
56
57% Initialize result matrices
58M = sn.nstations;
59R = sn.nclasses;
60QN = zeros(M, R);
61UN = zeros(M, R);
62RN = zeros(M, R);
63TN = zeros(M, R);
64CN = zeros(M, R);
65XN = zeros(M, R);
66
67% Initialize percentile results
68percResults = struct();
69percResults.RT = cell(1, R);
70percResults.K = K;
71percResults.method = 'fj_codes';
72percResults.percentiles = pers_stored;
73percResults.accuracy = Cs;
74percResults.tmode = T_Mode;
75
76% Analyze each class independently
77for r = 1:R
78 % Call FJ_codes mainFJ function with dense percentiles for mean calculation
79 percentileRT_dense = mainFJ(arrival{r}, service{r}, pers_dense, K, Cs, T_Mode);
80
81 % Call FJ_codes again with stored percentiles for user retrieval
82 percentileRT_stored = mainFJ(arrival{r}, service{r}, pers_stored, K, Cs, T_Mode);
83
84 % Store percentile results for this class (stored percentiles only)
85 percResults.RT{r} = percentileRT_stored{1}; % mainFJ returns cell array, get first element
86
87 % ===== Compute mean response time from percentile distribution =====
88 % Use numerical integration: E[X] = integral_0^inf (1 - F(x)) dx
89 % Approximated using trapezoidal rule on the inverse CDF (percentile function)
90
91 % Extract percentile values and probabilities
92 percentile_probs = percentileRT_dense{1}.percentiles / 100; % Convert to [0,1]
93 percentile_values = percentileRT_dense{1}.RTp;
94
95 % Add boundary points for better integration
96 % At p=0, x=0 (assuming response time starts at 0)
97 % At p=1, extrapolate or use last value
98 probs_extended = [0; percentile_probs(:); 1];
99 values_extended = [0; percentile_values(:); percentile_values(end) * 1.1]; % Small extrapolation at tail
100
101 % Compute mean using trapezoidal integration of inverse CDF
102 % E[X] = integral_0^1 Q(p) dp, where Q(p) is the inverse CDF (percentile function)
103 mean_fj_rt = trapz(probs_extended, values_extended);
104
105 % ===== Compute average metrics from FJ_codes results =====
106
107 % Get arrival rate and service rate
108 lambda = arrival{r}.lambda;
109 mu = service{r}.mu;
110
111 % For Fork-Join, the effective service time is the max of K parallel servers
112 % We can use the percentile data to estimate mean response time
113 % Or compute it analytically for exponential case
114
115 % Mean response time for FJ queue (approximation using first-order moment)
116 % For exponential service: E[R] ≈ sum(1/(mu*k)) for k=1 to K
117 if service{r}.SerChoice == 1 % Exponential
118 mean_service_time = sum(1./(mu * (1:K)));
119 else
120 % For general distributions, use the 50th percentile as rough estimate
121 % or compute from distribution moments
122 mean_service_time = 1/mu * sum(1./(1:K)); % Rough approximation
123 end
124
125 % Get station indices for queues between fork and join
126 queueIdx = fjInfo.queueIdx;
127 forkIdx = fjInfo.forkIdx;
128 joinIdx = fjInfo.joinIdx;
129 sourceIdx = find(sn.nodetype == NodeType.Source, 1);
130 sinkIdx = find(sn.nodetype == NodeType.Sink, 1);
131
132 % Throughput: arrival rate (assuming stable system)
133 throughput = lambda;
134
135 % Compute metrics for each queue
136 for k = 1:K
137 queueStat = sn.nodeToStation(queueIdx(k));
138
139 % Each queue sees arrival rate lambda and has service rate mu
140 % Utilization: rho = lambda/mu
141 rho_k = lambda / mu;
142 UN(queueStat, r) = rho_k;
143
144 % Throughput at each queue
145 TN(queueStat, r) = lambda;
146
147 % Mean queue length: Using M/M/1 approximation
148 QN(queueStat, r) = rho_k / (1 - rho_k);
149
150 % Mean response time at queue: Using M/M/1 formula
151 RN(queueStat, r) = 1 / (mu - lambda);
152 end
153
154 % Fork node (no queueing)
155 if ~isnan(sn.nodeToStation(forkIdx))
156 forkStat = sn.nodeToStation(forkIdx);
157 TN(forkStat, r) = throughput;
158 UN(forkStat, r) = 0;
159 QN(forkStat, r) = 0;
160 RN(forkStat, r) = 0;
161 end
162
163 % Join node (synchronization delay captured in FJ analysis)
164 if ~isnan(sn.nodeToStation(joinIdx))
165 joinStat = sn.nodeToStation(joinIdx);
166 TN(joinStat, r) = throughput;
167 UN(joinStat, r) = 0;
168
169 % FJ_codes computes the TOTAL Fork-Join response time (from fork to join completion).
170 % This includes both the individual queue service time AND the synchronization delay.
171 % To report Join's contribution separately, we subtract the mean individual queue RT.
172 mean_individual_queue_rt = 1 / (mu - lambda);
173 synchronization_delay = mean_fj_rt - mean_individual_queue_rt;
174
175 % Mean response time at Join: synchronization overhead only
176 % This represents the additional delay due to waiting for the slowest of K parallel tasks
177 RN(joinStat, r) = synchronization_delay;
178
179 % Mean queue length at Join using Little's Law: L = lambda * W
180 QN(joinStat, r) = throughput * synchronization_delay;
181 end
182
183 % Source node
184 if ~isnan(sn.nodeToStation(sourceIdx))
185 sourceStat = sn.nodeToStation(sourceIdx);
186 TN(sourceStat, r) = throughput;
187 UN(sourceStat, r) = 0;
188 QN(sourceStat, r) = 0;
189 RN(sourceStat, r) = 0;
190 end
191
192 % Sink node
193 if ~isnan(sn.nodeToStation(sinkIdx))
194 sinkStat = sn.nodeToStation(sinkIdx);
195 TN(sinkStat, r) = throughput;
196 UN(sinkStat, r) = 0;
197 QN(sinkStat, r) = 0;
198 RN(sinkStat, r) = 0;
199 end
200end
201
202% No iterations for FJ_codes (it's an analytical/approximation method)
203totiter = 0;
204
205end