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[isHomogeneous, fjInfo] = fj_is_homogeneous(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 = 'qiu';
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 % 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)));
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
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);
128 % Throughput: arrival rate (assuming stable system)
131 % Compute metrics
for each queue
133 queueStat = sn.nodeToStation(queueIdx(k));
135 % Each queue sees arrival rate lambda and has service rate mu
136 % Utilization: rho = lambda/mu
138 UN(queueStat, r) = rho_k;
140 % Throughput at each queue
141 TN(queueStat, r) = lambda;
143 % Mean queue length: Using M/M/1 approximation
144 QN(queueStat, r) = rho_k / (1 - rho_k);
146 % Mean response time at queue: Using M/M/1 formula
147 RN(queueStat, r) = 1 / (mu - lambda);
150 % Fork node (no queueing)
151 if ~isnan(sn.nodeToStation(forkIdx))
152 forkStat = sn.nodeToStation(forkIdx);
153 TN(forkStat, r) = throughput;
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;
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;
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;
174 % Mean queue length at Join
using Little
's Law: L = lambda * W
175 QN(joinStat, r) = throughput * synchronization_delay;
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;
188 if ~isnan(sn.nodeToStation(sinkIdx))
189 sinkStat = sn.nodeToStation(sinkIdx);
190 TN(sinkStat, r) = throughput;
197% No iterations for FJ_codes (it's an analytical/approximation method)