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 - 'qiu'
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[isHomogeneous, fjInfo] = fj_is_homogeneous(sn);
31
32if ~isHomogeneous
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 = 'qiu';
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 % FJ effective service = max of K servers; exponential case uses the
112 % harmonic-sum approximation E[R] = sum_k 1/(mu*k)
113 if service{r}.SerChoice == 1 % Exponential
114 mean_service_time = sum(1./(mu * (1:K)));
115 else
116 % For general distributions, use the 50th percentile as rough estimate
117 % or compute from distribution moments
118 mean_service_time = 1/mu * sum(1./(1:K)); % Rough approximation
119 end
120
121 % Get station indices for queues between fork and join
122 queueIdx = fjInfo.queueIdx;
123 forkIdx = fjInfo.forkIdx;
124 joinIdx = fjInfo.joinIdx;
125 sourceIdx = find(sn.nodetype == NodeType.Source, 1);
126 sinkIdx = find(sn.nodetype == NodeType.Sink, 1);
127
128 % Throughput: arrival rate (assuming stable system)
129 throughput = lambda;
130
131 % Compute metrics for each queue
132 for k = 1:K
133 queueStat = sn.nodeToStation(queueIdx(k));
134
135 % Each queue sees arrival rate lambda and has service rate mu
136 % Utilization: rho = lambda/mu
137 rho_k = lambda / mu;
138 UN(queueStat, r) = rho_k;
139
140 % Throughput at each queue
141 TN(queueStat, r) = lambda;
142
143 % Mean queue length: Using M/M/1 approximation
144 QN(queueStat, r) = rho_k / (1 - rho_k);
145
146 % Mean response time at queue: Using M/M/1 formula
147 RN(queueStat, r) = 1 / (mu - lambda);
148 end
149
150 % Fork node (no queueing)
151 if ~isnan(sn.nodeToStation(forkIdx))
152 forkStat = sn.nodeToStation(forkIdx);
153 TN(forkStat, r) = throughput;
154 UN(forkStat, r) = 0;
155 QN(forkStat, r) = 0;
156 RN(forkStat, r) = 0;
157 end
158
159 % Join node (synchronization delay captured in FJ analysis)
160 if ~isnan(sn.nodeToStation(joinIdx))
161 joinStat = sn.nodeToStation(joinIdx);
162 TN(joinStat, r) = throughput;
163 UN(joinStat, r) = 0;
164
165 % Join contribution = total FJ RT minus mean individual queue RT
166 % (FJ_codes returns fork-to-join total including sync delay)
167 mean_individual_queue_rt = 1 / (mu - lambda);
168 synchronization_delay = mean_fj_rt - mean_individual_queue_rt;
169
170 % Mean response time at Join: synchronization overhead only
171 % This represents the additional delay due to waiting for the slowest of K parallel tasks
172 RN(joinStat, r) = synchronization_delay;
173
174 % Mean queue length at Join using Little's Law: L = lambda * W
175 QN(joinStat, r) = throughput * synchronization_delay;
176 end
177
178 % Source node
179 if ~isnan(sn.nodeToStation(sourceIdx))
180 sourceStat = sn.nodeToStation(sourceIdx);
181 TN(sourceStat, r) = throughput;
182 UN(sourceStat, r) = 0;
183 QN(sourceStat, r) = 0;
184 RN(sourceStat, r) = 0;
185 end
186
187 % Sink node
188 if ~isnan(sn.nodeToStation(sinkIdx))
189 sinkStat = sn.nodeToStation(sinkIdx);
190 TN(sinkStat, r) = throughput;
191 UN(sinkStat, r) = 0;
192 QN(sinkStat, r) = 0;
193 RN(sinkStat, r) = 0;
194 end
195end
196
197% No iterations for FJ_codes (it's an analytical/approximation method)
198totiter = 0;
199
200end