1function renv_lqn_twostages()
2% RENV_LQN_TWOSTAGES LayeredNetwork operating in a two-stage random environment.
4% Demonstrates SolverENV running over a LayeredNetwork (LQN) base model,
using
5%
the uniform model/solver interface (no branching in ENV). The environment
6% alternates between an UP stage (fast database) and a DOWN stage (slow
7% database);
the environment-averaged total throughput must lie between
the two
8% single-stage LQN solutions.
12%% Two structurally identical LQN stages, differing only in DB host demand.
13upModel = buildLQN(
'LQN_UP', 0.8); % fast DB activity
14downModel = buildLQN(
'LQN_DOWN', 3.0); % slow DB activity (degraded)
16%% Random environment: UP <-> DOWN
17env = Environment(
'DBReliability');
18env.addStage(
'UP',
'operational', upModel);
19env.addStage(
'DOWN',
'degraded', downModel);
20env.addTransition(
'UP',
'DOWN', Exp(0.2)); % mean UP time = 5
21env.addTransition(
'DOWN',
'UP', Exp(1.0)); % mean DOWN time = 1
24%% Solve with SolverENV over SolverLN(.,@SolverFluid)
25% The transient window
is set on SolverLN (not
the layer factory):
the layered
26% fixed-point iteration solves each layer in steady state, and SolverLN applies
27%
the timespan only to
the per-layer transient getTranAvg call.
29fldFactory = @(mm) SolverFluid(mm,
'verbose',
false);
30lnFactory = @(m) SolverLN(m, fldFactory,
'timespan', [0, T],
'verbose',
false);
32options = SolverENV.defaultOptions;
34options.iter_tol = 0.02;
35options.verbose =
false;
37envSolver = SolverENV(env, lnFactory, options);
38[QN, UN, RN, TN] = envSolver.getAvg(); %#ok<ASGLU>
39fprintf(
'ENV over LQN ran: aggregate size = %d x %d\n', size(QN,1), size(QN,2));
40envAvgTable = envSolver.getAvgTable(); %#ok<NASGU>
43%% Single-stage aggregate references (same block-diagonal layout)
44[upQ, upT] = stageAggregate(upModel, fldFactory, T);
45[downQ, downT] = stageAggregate(downModel, fldFactory, T);
48% (1) The solver runs and returns finite, population-conserving metrics.
49assert(all(isfinite(QN(:))),
'ENV aggregate Q contains non-finite values');
50assert(abs(sum(QN(:)) - sum(upQ(:))) < 1e-2 && abs(sum(QN(:)) - sum(downQ(:))) < 1e-2, ...
51 'ENV aggregate does not conserve the closed population of the stages');
53% (2) A physically monotone scalar (total throughput) must lie between
the two
54% single-stage solutions: a slower database slows
the whole system, so
the
55% environment-averaged throughput brackets
the UP and DOWN values.
56% Disabled station/
class cells carry NaN (no throughput defined); they
57% contribute zero to
the system total, matching
the stage references.
58Xup = sum(upT(:)); Xdown = sum(downT(:)); Xenv = sum(TN(isfinite(TN)));
59lo = min(Xup, Xdown); hi = max(Xup, Xdown);
60tol = 1e-2 * max(1, hi);
61fprintf(
'Total throughput UP=%.4f DOWN=%.4f ENV=%.4f\n', Xup, Xdown, Xenv);
62fprintf(
'Aggregate Q (sum) UP=%.4f DOWN=%.4f ENV=%.4f\n', sum(upQ(:)), sum(downQ(:)), sum(QN(:)));
63assert(Xenv >= lo - tol && Xenv <= hi + tol, ...
64 'ENV-averaged throughput is not bracketed by the single-stage solutions');
66% (3) Quantitative coupling:
the env-averaged throughput increases with
the
67% stationary probability of
the fast UP stage,
P(UP)=b/(a+b) for switch rates
68% a (UP->DOWN) and b (DOWN->UP). Both settings remain within
the bracket.
69Xlow = env2StageTput(1.0, 0.2, T); %
P(UP)=0.167 (mostly slow DOWN)
70Xhigh = env2StageTput(0.2, 1.0, T); %
P(UP)=0.833 (mostly fast UP)
71fprintf(
'Monotonicity P(UP)=0.167 -> %.4f P(UP)=0.833 -> %.4f\n', Xlow, Xhigh);
72assert(Xhigh > Xlow + 1e-3,
'ENV throughput is not monotone in P(UP)');
73assert(Xlow >= lo - tol && Xhigh <= hi + tol,
'ENV throughputs escape the single-stage bracket');
75% (4) Three-stage environment (UP/MID/DOWN) stays bracketed by
the extreme
76% single-stage solutions (exercises
the E>2 coupling).
78fprintf(
'Three-stage ENV throughput = %.4f (bracket [%.4f, %.4f])\n', X3, lo, hi);
79assert(X3 >= lo - tol && X3 <= hi + tol,
'Three-stage ENV-averaged throughput is not bracketed');
81fprintf(
'PASS: ENV-over-LQN meanfield ran; throughput bracketed, monotone in P(UP), 3-stage bracketed.\n');
84function X = env2StageTput(a, b, T)
85% Env-averaged total throughput
for the two-stage UP/DOWN environment at
switch
86% rates a (UP->DOWN) and b (DOWN->UP).
87upModel = buildLQN('UP', 0.8);
88downModel = buildLQN('DOWN', 3.0);
89env = Environment('R');
90env.addStage('UP', 'operational', upModel);
91env.addStage('DOWN', 'degraded', downModel);
92env.addTransition('UP', 'DOWN', Exp(a));
93env.addTransition('DOWN', 'UP', Exp(b));
95X = envTputSum(env, T);
98function X = env3StageTput(T)
99% Env-averaged total throughput for a three-stage UP/MID/DOWN environment.
100upModel = buildLQN('UP', 0.8);
101midModel = buildLQN('MID', 1.6);
102downModel = buildLQN('DOWN', 3.0);
103env = Environment('R3');
104env.addStage('UP', 'operational', upModel);
105env.addStage('MID', 'degraded', midModel);
106env.addStage('DOWN', 'failed', downModel);
107env.addTransition('UP', 'MID', Exp(0.3));
108env.addTransition('MID', 'DOWN', Exp(0.3));
109env.addTransition('DOWN', 'MID', Exp(0.6));
110env.addTransition('MID', 'UP', Exp(0.6));
112X = envTputSum(env, T);
115function X = envTputSum(env, T)
116% Solve an LQN-in-ENV and return
the finite aggregate throughput sum.
117fldFactory = @(mm) SolverFluid(mm, 'verbose', false);
118lnFactory = @(m) SolverLN(m, fldFactory, 'timespan', [0, T], 'verbose', false);
119opt = SolverENV.defaultOptions;
120opt.iter_max = 10; opt.iter_tol = 0.03; opt.verbose = false;
121s = SolverENV(env, lnFactory, opt);
122[~,~,~,TN] = s.getAvg();
123X = sum(TN(isfinite(TN)));
126function model = buildLQN(name, dbMean)
127model = LayeredNetwork(name);
128P1 = Processor(model, 'ClientProcessor', 1, SchedStrategy.PS); %
#ok<NASGU>
129P2 = Processor(model,
'DBProcessor', 1, SchedStrategy.PS); %#ok<NASGU>
130T1 = Task(model,
'ClientTask', 5, SchedStrategy.REF).on(P1);
131T1.setThinkTime(Exp.fitMean(5.0));
132T2 = Task(model,
'DBTask', Inf, SchedStrategy.INF).on(P2);
133E1 = Entry(model,
'ClientEntry').on(T1);
134E2 = Entry(model,
'DBEntry').on(T2);
135A1 = Activity(model,
'ClientActivity', Exp.fitMean(1.0)).on(T1);
136A1.boundTo(E1).synchCall(E2, 2.5);
137A2 = Activity(model,
'DBActivity', Exp.fitMean(dbMean)).on(T2);
138A2.boundTo(E2).repliesTo(E2);
141function [Q, T] = stageAggregate(model, fldFactory, Tspan)
142% Steady transient aggregate (block-diagonal M x K) queue lengths and
143% throughputs
for one LQN stage,
using the same SolverLN layout SolverENV
145s = SolverLN(model, fldFactory,
'timespan', [0, Tspan],
'verbose',
false);
146[Qt,~,Tt] = s.getTranAvg();
147M = size(Qt,1); K = size(Qt,2);
148Q = zeros(M,K); T = zeros(M,K);
151 Q(i,k) = tailValue(Qt{i,k});
152 T(i,k) = tailValue(Tt{i,k});
157function v = tailValue(cell_ik)
159if isstruct(cell_ik) && isfield(cell_ik,
'metric') && ~isempty(cell_ik.metric)
160 v = cell_ik.metric(end);