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)
4% Analyze Fork-Join network
using FJ_codes percentile approximation
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')
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
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
26% Copyright (c) 2012-2026, Imperial College London
30[isFJ, fjInfo] = fj_isfj(sn);
33 line_error(mfilename,
'Network is not a valid Fork-Join topology for FJ_codes: %s', fjInfo.errorMsg);
36% Extract FJ parameters
37[arrival, service, K, fjInfo] = fj_extract_params(sn, fjInfo);
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
45if isfield(options.config,
'fj_accuracy')
46 Cs = options.config.fj_accuracy;
48 Cs = 100; % Default accuracy parameter
51if isfield(options.config, 'fj_tmode')
52 T_Mode = options.config.fj_tmode;
54 T_Mode = 'NARE'; % Default method for T matrix
57% Initialize result matrices
67% Initialize percentile results
68percResults = struct();
69percResults.
RT = cell(1, R);
71percResults.method = 'fj_codes';
72percResults.percentiles = pers_stored;
73percResults.accuracy = Cs;
74percResults.tmode = T_Mode;
76% Analyze each class independently
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);
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);
84 % Store percentile results
for this class (stored percentiles only)
85 percResults.RT{r} = percentileRT_stored{1}; % mainFJ returns cell array, get first element
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)
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;
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
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);
105 % ===== Compute average metrics from FJ_codes results =====
107 % Get arrival rate and service rate
108 lambda = arrival{r}.lambda;
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
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)));
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
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);
132 % Throughput: arrival rate (assuming stable system)
135 % Compute metrics
for each queue
137 queueStat = sn.nodeToStation(queueIdx(k));
139 % Each queue sees arrival rate lambda and has service rate mu
140 % Utilization: rho = lambda/mu
142 UN(queueStat, r) = rho_k;
144 % Throughput at each queue
145 TN(queueStat, r) = lambda;
147 % Mean queue length: Using M/M/1 approximation
148 QN(queueStat, r) = rho_k / (1 - rho_k);
150 % Mean response time at queue: Using M/M/1 formula
151 RN(queueStat, r) = 1 / (mu - lambda);
154 % Fork node (no queueing)
155 if ~isnan(sn.nodeToStation(forkIdx))
156 forkStat = sn.nodeToStation(forkIdx);
157 TN(forkStat, r) = throughput;
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;
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;
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;
179 % Mean queue length at Join using Little's Law: L = lambda * W
180 QN(joinStat, r) = throughput * synchronization_delay;
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;
193 if ~isnan(sn.nodeToStation(sinkIdx))
194 sinkStat = sn.nodeToStation(sinkIdx);
195 TN(sinkStat, r) = throughput;
202% No iterations
for FJ_codes (it
's an analytical/approximation method)