1function [m, v] = fj_quorum_moments(branchMeans, branchVars, k)
2% FJ_QUORUM_MOMENTS Mean and variance of a k-of-n (quorum) join completion time.
4% [M, V] = FJ_QUORUM_MOMENTS(BRANCHMEANS, BRANCHVARS, K) returns
the first two
5% moments of
the K-th smallest of N independent branch completion times, each
6% branch being given by its mean and variance.
8% Each branch
is expanded into a discrete step CDF by a two-moment fit. At each
9% time
the number of completed branches
is Poisson-binomial, so its distribution
10%
is built by
the recurrence q_j <- q_{j-1}*F_i + q_j*(1-F_i) over branches i, and
11%
the K-th order statistic
is the upper tail sum_{j>=K} q_j. For K = N
this
12% reduces to
the product of
the branch CDFs, i.e.
the ordinary AND-join, and
for
13% K = 1 to 1 - prod(1 - F_i), i.e.
the minimum.
15% This
is the same quantity as
the inclusion-exclusion identity
16% F_(k)(t) = sum_{i=k..n} (-1)^(i-k) * C(i-1, k-1) * e_i(F_1(t), ..., F_n(t))
17% used by LQNS, but every term here
is a probability in [0,1] and none
is
18% subtracted, so it avoids
the catastrophic cancellation
the alternating binomial
19% sum incurs as N grows.
21% Follows
the formulation of Omari, Franks, Woodside and Pan, as implemented in
22% LQNS 6.x (randomvar.cc). Mirrors jline.api.fj.FJ_quorum in
the Java runtime.
26n = numel(branchMeans);
27if n ~= numel(branchVars)
28 line_error(mfilename,
'branchMeans and branchVars must have the same number of elements.');
34 line_error(mfilename, sprintf(
'k must satisfy 1 <= k <= n. Got k=%d, n=%d.', k, n));
37 % Evaluation
is cubic in
the branch
count; fail immediately rather than hang.
38 line_error(mfilename, sprintf([
'quorum join has %d branches, above the supported maximum ' ...
39 'of %d; evaluation is cubic in the branch count.'], n, MAX_BRANCHES));
42% Fit each branch to a three-point discrete distribution matching its moments.
45 branches{i} = local_threePointFit(branchMeans(i), branchVars(i));
46 if isempty(branches{i}.t)
47 % A branch with no mass completes instantly, so that it neither delays
the
48 % join nor suppresses
the completion counts below.
49 branches{i} =
struct(
't', 0,
'A', 1);
53% Evaluate
the order statistic pointwise on
the union of
the branch grids.
56 grid = [grid, branches{i}.t]; %#ok<AGROW>
62% Tabulate each branch along
the merged grid by a single monotone walk, so
the
63% evaluation below
is linear rather than quadratic in
the grid size.
66 bt = branches{i}.t; bA = branches{i}.A;
69 while p <= numel(bt) && bt(p) <= grid(g)
77total =
struct(
't', grid,
'A', zeros(1, ngrid));
80 q(1) = 1; % q(j+1) holds
P(exactly j branches complete)
83 for j = min(i, n):-1:1
84 q(j + 1) = q(j) * f + q(j + 1) * (1 - f);
86 q(1) = q(1) * (1 - f);
88 total.A(g) = sum(q((k + 1):(n + 1)));
92v = local_variance(total, m);
95function v = local_at(f, x)
96% Value of
the step function at time x.
106function f = local_threePointFit(mu, var)
107% Two-moment fit to a three-point discrete distribution.
109 line_error(mfilename, 'mean and variance must be non-negative.');
112 f = struct('t', [], 'A', []); return;
116 f = struct('t', mu, 'A', 1); return;
125 t3 = mu + 2 * var / mu;
129delta = t1^2 * (t3 - t2) + t2^2 * (t1 - t3) + t3^2 * (t2 - t1);
131 % The abscissae are not distinct, so
the fit
is undetermined.
132 f = struct('t', mu, 'A', 1); return;
135a1 = (temp * (t3 - t2) + t2^2 * (mu - t3) + t3^2 * (t2 - mu)) / delta;
136a3 = (t1^2 * (mu - t2) + t2^2 * (t1 - mu) + temp * (t2 - t1)) / delta;
137f = struct('t', [t1, t2, t3], 'A', [a1, 1 - a3, 1]);
140function m = local_mean(f)
143 m = m + (f.A(i) - prev) * f.t(i);
148function v = local_variance(f, m)
151 v = v + (f.A(i) - prev) * (f.t(i) - m)^2;