1function [x, pi, Q, stats] = solver_mam_autocat(R, AP, varargin)
2% SOLVER_MAM_AUTOCAT Native MATLAB implementation of autocat
4% [X, PI, Q, STATS] = SOLVER_MAM_AUTOCAT(R, AP, OPTIONS)
6% Searches
for RCAT product-form solutions
using various relaxation methods.
7% Native MATLAB implementation without YALMIP dependency.
10% R - Cell array of rate matrices
11% AP - Action-Process mapping (A x 2)
12% OPTIONS (name-value pairs):
13%
'relaxation' - Relaxation method (default:
'auto')
14%
'lpr' - LP relaxation with McCormick envelopes
15%
'tlpr0inf' - Tightened LP with 0-level + infinity
16%
'tlpr1inf' - Tightened LP with 1-level + infinity
17%
'tlprUinf' - Tightened LP with U-level + infinity
18%
'ens' - Exact nonlinear system (fmincon)
19%
'qcp' - Quadratically constrained (fmincon)
20%
'ma' - Mean Approximation (first moment matching)
21%
'va' - Variance Approximation (slack variables)
22%
'minres' - Minimum Residual (L1-norm of RC3)
23%
'inap' - Iterative fixed-point approximation
24%
'tma1inf' - Mean Approx + Tightened LP with 1-level + infinity
25%
'zpr' - Zero potential relaxation with cutting planes
26%
'tzpr0inf' - Tightened zero potential (0-level)
27%
'tzpr1inf' - Tightened zero potential (1-level)
28%
'auto' - Automatic selection
29%
'policy' - Bound selection policy (default:
'vol')
30%
'vol',
'lu',
'l',
'u'
31%
'maxiter' - Maximum iterations (default: 100)
32%
'tol' - Convergence tolerance (default: 1e-4)
33%
'verbose' - Verbosity level 0/1/2 (default: 1)
36% x - Action rate parameters
37% pi - Equilibrium distributions
38% Q - Generator matrices
39% stats - Statistics struct
41% Copyright (c) 2012-2025, Imperial College London
46addParameter(p,
'relaxation',
'auto');
47addParameter(p,
'policy',
'vol');
48addParameter(p,
'maxiter', 100);
49addParameter(p,
'tol', 1e-4);
50addParameter(p,
'verbose', 1);
54RELAXATION = opts.relaxation;
56MAXITER = opts.maxiter;
59verbose = opts.verbose;
61%% Extract model parameters
67% Extract rate matrices
86%% Scale rates
for numerical stability
88if ismember(RELAXATION, {
'auto',
'tlpr0inf',
'tlpr1inf',
'tlprUinf',
'tzpr0inf',
'tzpr1inf',
'zpr',
'tma1inf'})
90 scale = max([scale, max(sum(abs(Aa{a}), 2))]);
93 scale = max([scale, max(sum(abs(L{k}), 2))]);
99Aa_scaled = cell(1, A);
100L_scaled = cell(1, M);
102 Aa_scaled{a} = Aa{a} / scale;
105 L_scaled{k} = L{k} / scale;
112 xU(a) = max(Aa_scaled{a}(:)) * 1.1;
113 xL(a) = max(PFTOL, min(Aa_scaled{a}(Aa_scaled{a} > 0)) * 0.1);
119 piL{k} = zeros(1, N(k));
120 piU{k} = ones(1, N(k));
126stats.action_seq = [];
129stats.relaxation_used = {};
131%% Main iteration loop
133currentRelaxation = RELAXATION;
138 % Select relaxation based on iteration (
for 'auto' mode)
139 if strcmpi(RELAXATION,
'auto')
141 currentRelaxation = 'lpr';
143 currentRelaxation = 'lpr';
145 currentRelaxation = 'tlpr1inf';
147 currentRelaxation = 'tlpr1inf';
151 % Select action and bound type using policy
152 [targetAction, boundType] = select_action_bound(stats, A, xL, xU, POLICY);
153 stats.action_seq(end+1) = targetAction;
154 stats.bound_seq(end+1) = boundType;
155 stats.relaxation_used{end+1} = currentRelaxation;
159 % Solve
using selected relaxation
161 switch currentRelaxation
163 [xOpt, piOpt, fval, exitflag] = solve_lpr(targetAction, boundType, ...
164 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
165 case {
'tlpr0inf',
'tlpr1inf',
'tlprUinf'}
166 U = 1; % Level
for tightening
167 if strcmpi(currentRelaxation,
'tlpr0inf')
169 elseif strcmpi(currentRelaxation, 'tlprUinf')
170 U = min(2, floor(iter/A));
172 [xOpt, piOpt, fval, exitflag] = solve_tlpr(targetAction, boundType, ...
173 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL, U);
175 [xOpt, piOpt, fval, exitflag] = solve_ens(targetAction, boundType, ...
176 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, PFTOL);
178 [xOpt, piOpt, fval, exitflag] = solve_qcp(...
179 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, PFTOL);
181 [xOpt, piOpt, fval, exitflag] = solve_ma(targetAction, boundType, ...
182 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
184 [xOpt, piOpt, fval, exitflag] = solve_va(...
185 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, PFTOL);
187 [xOpt, piOpt, fval, exitflag] = solve_minres(targetAction, boundType, ...
188 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
190 [xOpt, piOpt, fval, exitflag] = solve_inap(...
191 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, PFTOL);
193 [xOpt, piOpt, fval, exitflag] = solve_tma1inf(targetAction, boundType, ...
194 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
196 [xOpt, piOpt, fval, exitflag] = solve_zpr(targetAction, boundType, ...
197 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
198 case {
'tzpr0inf',
'tzpr1inf'}
199 U = 1; % Level
for tightening
200 if strcmpi(currentRelaxation,
'tzpr0inf')
203 [xOpt, piOpt, fval, exitflag] = solve_tzpr(targetAction, boundType, ...
204 Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL, U);
206 error('Unknown relaxation: %s', currentRelaxation);
209 stats.tot_time = stats.tot_time + toc(T0);
213 fprintf('iter %3d: x(%d) %c - infeasible (%s)\n', ...
214 iter, targetAction,
char('L' + boundType*('U'-'L')), currentRelaxation);
220 newBnd = abs(xOpt(targetAction));
221 if boundType == 0 % Lower bound
222 oldBnd = xL(targetAction);
223 xL(targetAction) = max(xL(targetAction), min(newBnd, xU(targetAction)));
225 oldBnd = xU(targetAction);
226 xU(targetAction) = min(xU(targetAction), max(newBnd, xL(targetAction)));
229 % Update pi bounds from LP solution (tightens McCormick envelopes)
231 piTol = 0.1; % Relaxed tolerance for pi bounds
233 if ~isempty(piOpt{k})
234 piL{k} = max(piL{k}, piOpt{k} - piTol);
235 piU{k} = min(piU{k}, piOpt{k} + piTol);
240 gap(targetAction) = abs(1 - xU(targetAction)/xL(targetAction));
243 fprintf(
'iter %3d: x(%d) %c = %8.5f gap = %8.5f [%s]\n', ...
244 iter, targetAction,
char(
'L' + boundType*(
'U'-
'L')), ...
245 newBnd * scale, gap(targetAction), currentRelaxation);
251 fprintf(
'\nConverged: max gap = %.6f\n', max(gap));
257%% Compute
final solution
258x = (xL + xU) / 2 * scale;
259[pi, Q] = compute_equilibrium(x/scale, Aa_scaled, Pb, L_scaled, ACT, PSV, M, A, N);
266%% Check product-form conditions
267rc3 = check_rc3(x/scale, pi, Aa_scaled, ACT, A, N) * scale;
269 fprintf(
'\nFinal solution:\n');
271 fprintf(
' x(%d) = %.6f [%.6f, %.6f]\n', a, x(a), xL(a)*scale, xU(a)*scale);
273 fprintf(
' RC3 residual = %.6f\n', rc3);
274 fprintf(
' Total time = %.2f sec\n', stats.tot_time);
277stats.xL = xL * scale;
278stats.xU = xU * scale;
285%% Action/Bound Selection Policy
286function [action, boundType] = select_action_bound(stats, A, xL, xU, policy)
287 if isempty(stats.action_seq)
295 % Volume-based: prioritize largest gap, alternate L/U
296 if stats.bound_seq(end) == 0
297 action = stats.action_seq(end);
300 [~, pos] = sort(xU - xL, 'descend');
302 recent = stats.action_seq(max(1, end-2*(A-1)+1):end);
303 if isempty(find(a == recent, 1))
310 action = mod(stats.action_seq(end), A) + 1;
314 % Alternate lower/upper for each action
315 if stats.bound_seq(end) == 0
316 action = stats.action_seq(end);
319 action = mod(stats.action_seq(end), A) + 1;
324 action = mod(stats.action_seq(end), A) + 1;
328 action = mod(stats.action_seq(end), A) + 1;
331 action = mod(stats.action_seq(end), A) + 1;
332 boundType = mod(stats.bound_seq(end) + 1, 2);
336%% LP Relaxation (lpr)
337function [xOpt, piOpt, fval, exitflag] = solve_lpr(targetAction, boundType, ...
338 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL)
340% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...; z{1,1}(:); z{1,2}(:); ...]
345 nZ = nZ + N(ACT(a)) + N(PSV(a));
354 piIdx{k} = offset + (1:N(k));
355 offset = offset + N(k);
359 zIdx{a, 1} = offset + (1:N(ACT(a)));
360 offset = offset + N(ACT(a));
361 zIdx{a, 2} = offset + (1:N(PSV(a)));
362 offset = offset + N(PSV(a));
370 f(targetAction) = -1;
381 row = zeros(1, nVar);
387% sum(z{a,type}) = x(a)
389 row = zeros(1, nVar);
395 row = zeros(1, nVar);
402% pi{k} * Q{k} = 0 (balance equations)
404 Qlocal = L{k} - diag(sum(L{k}, 2));
407 row = zeros(1, nVar);
408 row(piIdx{k}) = Qlocal(:, s)
';
412 Qp = Pb{a} - diag(sum(Pb{a}, 2));
413 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)';
415 Qa = Aa{a} - diag(sum(Aa{a}, 2));
416 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)
';
420 Aineq = [Aineq; row; -row];
421 bineq = [bineq; PFTOL; PFTOL];
432 xLa = xL(a); xUa = xU(a);
433 pL = piL{k_act}(i); pU = piU{k_act}(i);
435 row = zeros(1, nVar);
436 row(zIdx{a, 1}(i)) = -1;
438 row(piIdx{k_act}(i)) = xLa;
439 Aineq = [Aineq; row];
440 bineq = [bineq; pL * xLa];
442 row = zeros(1, nVar);
443 row(zIdx{a, 1}(i)) = 1;
445 row(piIdx{k_act}(i)) = -xUa;
446 Aineq = [Aineq; row];
447 bineq = [bineq; -pL * xUa];
449 row = zeros(1, nVar);
450 row(zIdx{a, 1}(i)) = 1;
452 row(piIdx{k_act}(i)) = -xLa;
453 Aineq = [Aineq; row];
454 bineq = [bineq; -pU * xLa];
456 row = zeros(1, nVar);
457 row(zIdx{a, 1}(i)) = -1;
459 row(piIdx{k_act}(i)) = xUa;
460 Aineq = [Aineq; row];
461 bineq = [bineq; pU * xUa];
466 xLa = xL(a); xUa = xU(a);
467 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
469 row = zeros(1, nVar);
470 row(zIdx{a, 2}(i)) = -1;
472 row(piIdx{k_psv}(i)) = xLa;
473 Aineq = [Aineq; row];
474 bineq = [bineq; pL * xLa];
476 row = zeros(1, nVar);
477 row(zIdx{a, 2}(i)) = 1;
479 row(piIdx{k_psv}(i)) = -xUa;
480 Aineq = [Aineq; row];
481 bineq = [bineq; -pL * xUa];
483 row = zeros(1, nVar);
484 row(zIdx{a, 2}(i)) = 1;
486 row(piIdx{k_psv}(i)) = -xLa;
487 Aineq = [Aineq; row];
488 bineq = [bineq; -pU * xLa];
490 row = zeros(1, nVar);
491 row(zIdx{a, 2}(i)) = -1;
493 row(piIdx{k_psv}(i)) = xUa;
494 Aineq = [Aineq; row];
495 bineq = [bineq; pU * xUa];
505 lb(piIdx{k}) = piL{k};
506 ub(piIdx{k}) = piU{k};
510 ub(zIdx{a, 1}) = xU(a);
512 ub(zIdx{a, 2}) = xU(a);
516options = optimoptions('linprog
', 'Algorithm
', 'interior-point
', 'Display
', 'off
');
517[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
523 piOpt{k} = sol(piIdx{k})';
532%% Tightened LP Relaxation (tlpr)
533function [xOpt, piOpt, fval, exitflag] = solve_tlpr(targetAction, boundType, ...
534 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL, U)
536% Start with basic LPR
537[xOpt, piOpt, fval, exitflag] = solve_lpr(targetAction, boundType, ...
538 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
544% Add tightening constraints iteratively
545% The tightening uses pi*Q*A^u = 0 and pi*Q*(I-A)^{-1} = 0
546% For simplicity, we iterate the basic LPR with updated bounds
549 % Update pi bounds based on current solution
552 piL{k} = max(piL{k}, piOpt{k} - PFTOL);
553 piU{k} = min(piU{k}, piOpt{k} + PFTOL);
557 % Re-solve with tightened bounds
558 [xOpt, piOpt, fval, exitflag] = solve_lpr(targetAction, boundType, ...
559 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
568%% Exact Nonlinear System (ens)
using fmincon
569function [xOpt, piOpt, fval, exitflag] = solve_ens(targetAction, boundType, ...
570 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, PFTOL)
572% ENS finds product-form solutions by minimizing RC3 violations
573% Uses penalty method: minimize |pi*A - x*pi|^2 subject to normalization and balance
575% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...]
585 piIdx{k} = offset + (1:N(k));
586 offset = offset + N(k);
589% Better initial guess: use geometric distribution approximation
590% For M/M/1 queue, pi(n) = (1-rho) * rho^n where rho = lambda/mu
592x0(xIdx) = (xL + xU) / 2;
594 % Estimate utilization from rate matrices
595 rho_est = 0.5; % Default estimate
596 % Try to extract arrival/service rates from L
597 arrivals = sum(diag(L{k}, 1));
598 departures = sum(diag(L{k}, -1));
599 if arrivals > 0 && departures > 0
600 rho_est = min(0.95, arrivals / departures);
602 % Geometric distribution
603 pi_init = (1 - rho_est) * rho_est.^(0:(N(k)-1));
604 pi_init = pi_init / sum(pi_init); % Normalize
605 x0(piIdx{k}) = pi_init;
614 lb(piIdx{k}) = 1e-12; % Small positive to avoid log issues
618% Objective: minimize RC3 violation (squared sum of residuals)
619% This
is better conditioned than
using as hard constraints
620 function obj = objfun_penalty(v)
627 % RC3: pi * Aa = x(a) * pi for active processes
630 residual = piv * Aa{aa} - xv(aa) * piv;
631 rc3_penalty = rc3_penalty + sum(residual.^2);
636 % Add optimization direction as small perturbation
637 if boundType == 0 % minimizing
638 obj = rc3_penalty + 1e-6 * v(targetAction);
640 obj = rc3_penalty - 1e-6 * v(targetAction);
644% Equality constraints: only normalization and balance (not RC3)
645 function [c, ceq] = nlcon(v)
653 % Normalization: sum(pi) = 1
654 ceq = [ceq; sum(piv) - 1];
656 % Balance: pi * Q = 0 (only N-1 are independent, but fmincon handles
this)
657 Qk = L{kk} - diag(sum(L{kk}, 2));
660 Qk = Qk + xv(aa) * (Pb{aa} - diag(sum(Pb{aa}, 2)));
662 Qk = Qk + Aa{aa} - diag(sum(Aa{aa}, 2));
665 % Use only first N-1 balance equations (last
is dependent on normalization)
667 ceq = [ceq; balance(1:end-1)
'];
671% Solve with relaxed tolerances and interior-point algorithm
672options = optimoptions('fmincon
', 'Algorithm
', 'interior-point
', 'Display
', 'off
', ...
673 'MaxIterations
', 2000, 'MaxFunctionEvaluations
', 50000, ...
674 'OptimalityTolerance
', 1e-8, 'ConstraintTolerance
', 1e-6, ...
675 'StepTolerance
', 1e-12);
677[sol, fval, exitflag] = fmincon(@objfun_penalty, x0, [], [], [], [], lb, ub, @nlcon, options);
679% Check if RC3 is satisfied (exitflag > 0 only indicates convergence)
680if exitflag > 0 && fval < 1e-4 % RC3 residual is small enough
684 piOpt{k} = sol(piIdx{k})';
687 % Converged but RC3 not satisfied - still
return result
for analysis
691 piOpt{k} = sol(piIdx{k})
';
700%% Quadratically Constrained Program (qcp) using fmincon
701function [xOpt, piOpt, fval, exitflag] = solve_qcp(...
702 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, PFTOL)
704% QCP minimizes RC3 violations directly (no slack variables needed)
705% This is a simpler reformulation that works better with fmincon
707% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...]
717 piIdx{k} = offset + (1:N(k));
718 offset = offset + N(k);
721% Better initial guess: use geometric distribution approximation
723x0(xIdx) = (xL + xU) / 2;
725 % Estimate utilization from rate matrices
727 arrivals = sum(diag(L{k}, 1));
728 departures = sum(diag(L{k}, -1));
729 if arrivals > 0 && departures > 0
730 rho_est = min(0.95, arrivals / departures);
732 pi_init = (1 - rho_est) * rho_est.^(0:(N(k)-1));
733 pi_init = pi_init / sum(pi_init);
734 x0(piIdx{k}) = pi_init;
743 lb(piIdx{k}) = 1e-12;
747% Objective: minimize sum of squared RC3 violations (quadratic form)
748 function obj = objfun_qcp(v)
757 residual = piv * Aa{aa} - xv(aa) * piv;
758 rc3_sum = rc3_sum + sum(residual.^2);
765% Equality constraints: normalization and balance only
766 function [c, ceq] = nlcon(v)
774 % Normalization: sum(pi) = 1
775 ceq = [ceq; sum(piv) - 1];
777 % Balance: pi * Q = 0
778 Qk = L{kk} - diag(sum(L{kk}, 2));
781 Qk = Qk + xv(aa) * (Pb{aa} - diag(sum(Pb{aa}, 2)));
783 Qk = Qk + Aa{aa} - diag(sum(Aa{aa}, 2));
786 % Use only first N-1 balance equations
788 ceq = [ceq; balance(1:end-1)'];
792% Solve with interior-point (better
for quadratic objectives)
793options = optimoptions(
'fmincon',
'Algorithm',
'interior-point',
'Display',
'off', ...
794 'MaxIterations', 2000,
'MaxFunctionEvaluations', 50000, ...
795 'OptimalityTolerance', 1e-8,
'ConstraintTolerance', 1e-6, ...
796 'StepTolerance', 1e-12);
798[sol, fval, exitflag] = fmincon(@objfun_qcp, x0, [], [], [], [], lb, ub, @nlcon, options);
804 piOpt{k} = sol(piIdx{k})
';
813%% Compute equilibrium
814function [pi, Q] = compute_equilibrium(x, Aa, Pb, L, ACT, PSV, M, A, N)
819 Qk = L{k} - diag(sum(L{k}, 2));
823 Qk = Qk + x(a) * (Pb{a} - diag(sum(Pb{a}, 2)));
825 Qk = Qk + Aa{a} - diag(sum(Aa{a}, 2));
830 Qk_inf = ctmc_makeinfgen(Qk);
831 pi{k} = ctmc_solve(Qk_inf);
835%% Check RC3 condition
836function rc3 = check_rc3(x, pi, Aa, ACT, A, N)
840 residual = abs(pi{k} * Aa{a} - x(a) * pi{k});
841 rc3 = rc3 + mean(residual);
846%% Mean Approximation (ma)
847% Relaxes RC3 to only enforce first moment matching: sum(z) = sum(pi*Aa)
848% This is equivalent to x = E[eigenvalue] from the active rate matrix
849function [xOpt, piOpt, fval, exitflag] = solve_ma(targetAction, boundType, ...
850 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL)
852% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...; z{1,1}(:); z{1,2}(:); ...]
857 nZ = nZ + N(ACT(a)) + N(PSV(a));
866 piIdx{k} = offset + (1:N(k));
867 offset = offset + N(k);
871 zIdx{a, 1} = offset + (1:N(ACT(a))); % z_active
872 offset = offset + N(ACT(a));
873 zIdx{a, 2} = offset + (1:N(PSV(a))); % z_passive
874 offset = offset + N(PSV(a));
882 f(targetAction) = -1;
891% pi{k} * 1 = 1 (normalization)
893 row = zeros(1, nVar);
899% Mean approximation: sum(z{a,active}) = sum(pi*Aa) = x(a) * sum(pi) = x(a)
900% and sum(z{a,passive}) = x(a)
903 row = zeros(1, nVar);
909 % z_passive sums to x
910 row = zeros(1, nVar);
917% Balance equations: pi{k} * Q{k} = 0 (relaxed to tolerance)
919 Qlocal = L{k} - diag(sum(L{k}, 2));
922 row = zeros(1, nVar);
923 row(piIdx{k}) = Qlocal(:, s)';
927 Qp = Pb{a} - diag(sum(Pb{a}, 2));
928 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)
';
930 Qa = Aa{a} - diag(sum(Aa{a}, 2));
931 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)';
935 Aineq = [Aineq; row; -row];
936 bineq = [bineq; PFTOL; PFTOL];
940% McCormick envelopes
for z = x * pi
947 xLa = xL(a); xUa = xU(a);
948 pL = piL{k_act}(i); pU = piU{k_act}(i);
950 % z >= pL*x + xL*pi - pL*xL
951 row = zeros(1, nVar);
952 row(zIdx{a, 1}(i)) = -1;
954 row(piIdx{k_act}(i)) = xLa;
955 Aineq = [Aineq; row];
956 bineq = [bineq; pL * xLa];
958 % z <= pL*x + xU*pi - pL*xU
959 row = zeros(1, nVar);
960 row(zIdx{a, 1}(i)) = 1;
962 row(piIdx{k_act}(i)) = -xUa;
963 Aineq = [Aineq; row];
964 bineq = [bineq; -pL * xUa];
966 % z <= pU*x + xL*pi - pU*xL
967 row = zeros(1, nVar);
968 row(zIdx{a, 1}(i)) = 1;
970 row(piIdx{k_act}(i)) = -xLa;
971 Aineq = [Aineq; row];
972 bineq = [bineq; -pU * xLa];
974 % z >= pU*x + xU*pi - pU*xU
975 row = zeros(1, nVar);
976 row(zIdx{a, 1}(i)) = -1;
978 row(piIdx{k_act}(i)) = xUa;
979 Aineq = [Aineq; row];
980 bineq = [bineq; pU * xUa];
985 xLa = xL(a); xUa = xU(a);
986 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
988 row = zeros(1, nVar);
989 row(zIdx{a, 2}(i)) = -1;
991 row(piIdx{k_psv}(i)) = xLa;
992 Aineq = [Aineq; row];
993 bineq = [bineq; pL * xLa];
995 row = zeros(1, nVar);
996 row(zIdx{a, 2}(i)) = 1;
998 row(piIdx{k_psv}(i)) = -xUa;
999 Aineq = [Aineq; row];
1000 bineq = [bineq; -pL * xUa];
1002 row = zeros(1, nVar);
1003 row(zIdx{a, 2}(i)) = 1;
1005 row(piIdx{k_psv}(i)) = -xLa;
1006 Aineq = [Aineq; row];
1007 bineq = [bineq; -pU * xLa];
1009 row = zeros(1, nVar);
1010 row(zIdx{a, 2}(i)) = -1;
1012 row(piIdx{k_psv}(i)) = xUa;
1013 Aineq = [Aineq; row];
1014 bineq = [bineq; pU * xUa];
1018% Relaxed RC3 (mean approximation): sum(z*I - pi*Aa) = 0
1019% This only enforces first moment, not pointwise equality
1022 % sum(z{a,active}) - sum(pi * Aa) should be close to 0
1023 % but sum(z{a,active}) = x(a), so
this is: x(a) - sum(pi*Aa) close to 0
1024 row = zeros(1, nVar);
1026 row(piIdx{k}) = -sum(Aa{a}, 2)
';
1027 Aineq = [Aineq; row; -row];
1028 bineq = [bineq; PFTOL; PFTOL];
1037 lb(piIdx{k}) = piL{k};
1038 ub(piIdx{k}) = piU{k};
1042 ub(zIdx{a, 1}) = xU(a);
1044 ub(zIdx{a, 2}) = xU(a);
1048options = optimoptions('linprog
', 'Algorithm
', 'interior-point
', 'Display
', 'off
');
1049[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
1055 piOpt{k} = sol(piIdx{k})';
1064%% Variance Approximation (va)
1065% Uses fixed x from bounds, solves
for pi with slack variables on RC3
1066function [xOpt, piOpt, fval, exitflag] = solve_va(...
1067 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, PFTOL)
1069% Use midpoint of bounds as fixed x
1070x_fixed = (xL + xU) / 2;
1072% Variables: [pi{1}(:); pi{2}(:); ...; slack{1}; slack{2}; ...]
1076 nSlack = nSlack + N(ACT(a));
1084 piIdx{k} = offset + (1:N(k));
1085 offset = offset + N(k);
1087slackIdx = cell(A, 1);
1089 slackIdx{a} = offset + (1:N(ACT(a)));
1090 offset = offset + N(ACT(a));
1093% Objective: minimize sum of absolute slacks (L1 norm)
1096 f(slackIdx{a}) = 1; % Minimize slack
1105% pi{k} * 1 = 1 (normalization)
1107 row = zeros(1, nVar);
1113% Balance equations: pi{k} * Q{k} = 0
1115 Qk = L{k} - diag(sum(L{k}, 2));
1118 Qk = Qk + x_fixed(a) * (Pb{a} - diag(sum(Pb{a}, 2)));
1120 Qk = Qk + Aa{a} - diag(sum(Aa{a}, 2));
1124 for s = 1:(N(k)-1) % Only N-1 are independent
1125 row = zeros(1, nVar);
1126 row(piIdx{k}) = Qk(:, s)
';
1132% RC3 with slack: pi*Aa - x*pi + slack >= 0, pi*Aa - x*pi - slack <= 0
1133% This means |pi*Aa - x*pi| <= slack
1137 % (pi*Aa - x*pi)_i + slack_i >= 0
1138 row = zeros(1, nVar);
1139 row(piIdx{k}) = Aa{a}(:, i)' - x_fixed(a) * (1:N(k) == i);
1140 row(slackIdx{a}(i)) = 1;
1141 Aineq = [Aineq; -row]; % -(...) <= 0
1144 % (pi*Aa - x*pi)_i - slack_i <= 0
1145 row = zeros(1, nVar);
1146 row(piIdx{k}) = Aa{a}(:, i)
' - x_fixed(a) * (1:N(k) == i);
1147 row(slackIdx{a}(i)) = -1;
1148 Aineq = [Aineq; row];
1161 lb(slackIdx{a}) = 0; % Slack must be non-negative
1165options = optimoptions('linprog
', 'Algorithm
', 'interior-point
', 'Display
', 'off
');
1166[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
1169 xOpt = x_fixed; % Return the fixed x
1172 piOpt{k} = sol(piIdx{k})';
1181%% Minimum Residual (minres)
1182% Minimizes L1 norm of RC3 violations with McCormick envelopes
1183function [xOpt, piOpt, fval, exitflag] = solve_minres(targetAction, boundType, ...
1184 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL)
1186% Variables: [x; pi{1}(:); ...; z{1,1}(:); z{1,2}(:); ...; slack_pos; slack_neg]
1191 nZ = nZ + N(ACT(a)) + N(PSV(a));
1195 nSlack = nSlack + 2 * N(ACT(a)); % Positive and negative slack
for each RC3 component
1197nVar = nX + nPi + nZ + nSlack;
1204 piIdx{k} = offset + (1:N(k));
1205 offset = offset + N(k);
1209 zIdx{a, 1} = offset + (1:N(ACT(a)));
1210 offset = offset + N(ACT(a));
1211 zIdx{a, 2} = offset + (1:N(PSV(a)));
1212 offset = offset + N(PSV(a));
1214slackPosIdx = cell(A, 1);
1215slackNegIdx = cell(A, 1);
1217 slackPosIdx{a} = offset + (1:N(ACT(a)));
1218 offset = offset + N(ACT(a));
1219 slackNegIdx{a} = offset + (1:N(ACT(a)));
1220 offset = offset + N(ACT(a));
1223% Objective: minimize L1 norm of slack (minimize RC3 residuals)
1226 f(slackPosIdx{a}) = 1;
1227 f(slackNegIdx{a}) = 1;
1238 row = zeros(1, nVar);
1244% sum(z{a,type}) = x(a)
1246 row = zeros(1, nVar);
1247 row(zIdx{a, 1}) = 1;
1252 row = zeros(1, nVar);
1253 row(zIdx{a, 2}) = 1;
1259% Balance equations: pi{k} * Q{k} = 0
1261 Qlocal = L{k} - diag(sum(L{k}, 2));
1264 row = zeros(1, nVar);
1265 row(piIdx{k}) = Qlocal(:, s)
';
1269 Qp = Pb{a} - diag(sum(Pb{a}, 2));
1270 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)';
1272 Qa = Aa{a} - diag(sum(Aa{a}, 2));
1273 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)
';
1277 Aineq = [Aineq; row; -row];
1278 bineq = [bineq; PFTOL; PFTOL];
1282% McCormick envelopes for z = x * pi
1288 xLa = xL(a); xUa = xU(a);
1289 pL = piL{k_act}(i); pU = piU{k_act}(i);
1291 row = zeros(1, nVar);
1292 row(zIdx{a, 1}(i)) = -1;
1294 row(piIdx{k_act}(i)) = xLa;
1295 Aineq = [Aineq; row];
1296 bineq = [bineq; pL * xLa];
1298 row = zeros(1, nVar);
1299 row(zIdx{a, 1}(i)) = 1;
1301 row(piIdx{k_act}(i)) = -xUa;
1302 Aineq = [Aineq; row];
1303 bineq = [bineq; -pL * xUa];
1305 row = zeros(1, nVar);
1306 row(zIdx{a, 1}(i)) = 1;
1308 row(piIdx{k_act}(i)) = -xLa;
1309 Aineq = [Aineq; row];
1310 bineq = [bineq; -pU * xLa];
1312 row = zeros(1, nVar);
1313 row(zIdx{a, 1}(i)) = -1;
1315 row(piIdx{k_act}(i)) = xUa;
1316 Aineq = [Aineq; row];
1317 bineq = [bineq; pU * xUa];
1321 xLa = xL(a); xUa = xU(a);
1322 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
1324 row = zeros(1, nVar);
1325 row(zIdx{a, 2}(i)) = -1;
1327 row(piIdx{k_psv}(i)) = xLa;
1328 Aineq = [Aineq; row];
1329 bineq = [bineq; pL * xLa];
1331 row = zeros(1, nVar);
1332 row(zIdx{a, 2}(i)) = 1;
1334 row(piIdx{k_psv}(i)) = -xUa;
1335 Aineq = [Aineq; row];
1336 bineq = [bineq; -pL * xUa];
1338 row = zeros(1, nVar);
1339 row(zIdx{a, 2}(i)) = 1;
1341 row(piIdx{k_psv}(i)) = -xLa;
1342 Aineq = [Aineq; row];
1343 bineq = [bineq; -pU * xLa];
1345 row = zeros(1, nVar);
1346 row(zIdx{a, 2}(i)) = -1;
1348 row(piIdx{k_psv}(i)) = xUa;
1349 Aineq = [Aineq; row];
1350 bineq = [bineq; pU * xUa];
1354% RC3 with slack: z_i - (pi*Aa)_i = slack_pos_i - slack_neg_i
1355% We use z{a,active} as the bilinear term x*pi
1359 % z_i - (pi*Aa)_i - slack_pos + slack_neg = 0
1360 row = zeros(1, nVar);
1361 row(zIdx{a, 1}(i)) = 1;
1362 row(piIdx{k}) = -Aa{a}(:, i)';
1363 row(slackPosIdx{a}(i)) = -1;
1364 row(slackNegIdx{a}(i)) = 1;
1376 lb(piIdx{k}) = piL{k};
1377 ub(piIdx{k}) = piU{k};
1381 ub(zIdx{a, 1}) = xU(a);
1383 ub(zIdx{a, 2}) = xU(a);
1384 lb(slackPosIdx{a}) = 0;
1385 lb(slackNegIdx{a}) = 0;
1389options = optimoptions(
'linprog',
'Algorithm',
'interior-point',
'Display',
'off');
1390[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
1396 piOpt{k} = sol(piIdx{k})
';
1405%% Iterative Fixed-Point Approximation (inap)
1406% Uses fixed-point iteration: x(a) = mean(pi*Aa ./ pi)
1407function [xOpt, piOpt, fval, exitflag] = solve_inap(...
1408 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, PFTOL)
1410% Initialize x randomly within bounds
1411x = xL + (xU - xL) .* rand(A, 1);
1413% Compute initial equilibrium
1414[pi, Q] = compute_eq_internal(x, Aa, Pb, L, ACT, PSV, M, A, N);
1416% Fixed-point iteration
1425 % LAMBDA = (pi * Aa) ./ pi (element-wise)
1426 piAa = pi{k} * Aa{a};
1427 LAMBDA = piAa ./ pi{k};
1428 LAMBDA = LAMBDA(~isnan(LAMBDA) & ~isinf(LAMBDA) & LAMBDA > 0);
1431 x(a) = mean(LAMBDA);
1433 x(a) = max(xL(a), min(xU(a), x(a)));
1437 % Recompute equilibrium
1438 [pi, Q] = compute_eq_internal(x, Aa, Pb, L, ACT, PSV, M, A, N);
1441 if norm(x - xprev, inf) < tol
1446% Check if RC3 is satisfied
1447rc3_satisfied = true;
1450 piAa = pi{k} * Aa{a};
1451 LAMBDA = piAa ./ pi{k};
1452 LAMBDA = LAMBDA(~isnan(LAMBDA) & ~isinf(LAMBDA) & LAMBDA > 0);
1453 if ~isempty(LAMBDA) && (max(LAMBDA) - min(LAMBDA)) > 0.1
1454 rc3_satisfied = false;
1466 fval = 1; % Indicates approximate solution
1467 exitflag = 1; % Still return solution for use
1472%% Internal equilibrium computation for inap
1473function [pi, Q] = compute_eq_internal(x, Aa, Pb, L, ACT, PSV, M, A, N)
1478 Qk = L{k} - diag(sum(L{k}, 2));
1482 Qk = Qk + x(a) * (Pb{a} - diag(sum(Pb{a}, 2)));
1484 Qk = Qk + Aa{a} - diag(sum(Aa{a}, 2));
1489 Qk_inf = ctmc_makeinfgen(Qk);
1490 pi{k} = ctmc_solve(Qk_inf);
1494%% Tightened Mean Approximation with 1-level + infinity (tma1inf)
1495% Combines mean approximation (RC3 first moment only) with tightened LP constraints
1496function [xOpt, piOpt, fval, exitflag] = solve_tma1inf(targetAction, boundType, ...
1497 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL)
1499% First run mean approximation to get initial solution
1500[xMA, piMA, ~, exitflagMA] = solve_ma(targetAction, boundType, ...
1501 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL);
1511% Use MA solution to warm-start tightened LP with additional constraints
1512% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...; z{1,1}(:); z{1,2}(:); ...]
1517 nZ = nZ + N(ACT(a)) + N(PSV(a));
1519nVar = nX + nPi + nZ;
1526 piIdx{k} = offset + (1:N(k));
1527 offset = offset + N(k);
1531 zIdx{a, 1} = offset + (1:N(ACT(a)));
1532 offset = offset + N(ACT(a));
1533 zIdx{a, 2} = offset + (1:N(PSV(a)));
1534 offset = offset + N(PSV(a));
1540 f(targetAction) = 1;
1542 f(targetAction) = -1;
1553 row = zeros(1, nVar);
1559% RC3 first moment constraint: sum(z{a,active}) = x(a) (mean approximation)
1561 row = zeros(1, nVar);
1562 row(zIdx{a, 1}) = 1;
1567 row = zeros(1, nVar);
1568 row(zIdx{a, 2}) = 1;
1574% pi{k} * Q{k} = 0 (balance equations)
1576 Qlocal = L{k} - diag(sum(L{k}, 2));
1579 row = zeros(1, nVar);
1580 row(piIdx{k}) = Qlocal(:, s)';
1584 Qp = Pb{a} - diag(sum(Pb{a}, 2));
1585 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)
';
1587 Qa = Aa{a} - diag(sum(Aa{a}, 2));
1588 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)';
1592 Aineq = [Aineq; row; -row];
1593 bineq = [bineq; PFTOL; PFTOL];
1597% Tightened LP constraints: pi*Q*Aa^u = 0
for u=0,1,...,U and pi*Q*Aa^inf = 0
1598U = 1; % Tightening level
1602 Qlocal = L{k} - diag(sum(L{k}, 2));
1604 % Add constraints
for u = 1
1609 row = zeros(1, nVar);
1611 % z{b,active} * Aa^(u-1) * Q_local
1612 coeffZ = (Aa{b}^(u-1)) * Qlocal;
1613 row(zIdx{b, 1}) = coeffZ(:, s)
';
1617 Qp = Pb{c} - diag(sum(Pb{c}, 2));
1618 coeffZp = Aa_pow * Qp;
1619 row(zIdx{c, 2}) = row(zIdx{c, 2}) + coeffZp(:, s)';
1621 Qa = Aa{c} - diag(sum(Aa{c}, 2));
1622 coeffZa = (Aa{b}^(u-1)) * Qa;
1623 row(zIdx{b, 1}) = row(zIdx{b, 1}) + coeffZa(:, s)
';
1627 Aineq = [Aineq; row; -row];
1628 bineq = [bineq; PFTOL; PFTOL];
1632 % Add infinity constraint: z * (I-Aa)^{-1} * Q = 0
1634 Aa_inv = inv(eye(N(k)) - Aa{b});
1637 row = zeros(1, nVar);
1639 coeffZ = Aa_inv * Qlocal;
1640 row(zIdx{b, 1}) = coeffZ(:, s)';
1644 Qp = Pb{c} - diag(sum(Pb{c}, 2));
1645 coeffZp = Aa{b} * Aa_inv * Qp;
1646 row(zIdx{c, 2}) = row(zIdx{c, 2}) + coeffZp(:, s)
';
1648 Qa = Aa{c} - diag(sum(Aa{c}, 2));
1649 coeffZa = Aa_inv * Qa;
1650 row(zIdx{b, 1}) = row(zIdx{b, 1}) + coeffZa(:, s)';
1654 Aineq = [Aineq; row; -row];
1655 bineq = [bineq; PFTOL; PFTOL];
1658 % Matrix not invertible, skip infinity constraints
1664% McCormick envelopes
1671 xLa = xL(a); xUa = xU(a);
1672 pL = piL{k_act}(i); pU = piU{k_act}(i);
1674 row = zeros(1, nVar);
1675 row(zIdx{a, 1}(i)) = -1;
1677 row(piIdx{k_act}(i)) = xLa;
1678 Aineq = [Aineq; row];
1679 bineq = [bineq; pL * xLa];
1681 row = zeros(1, nVar);
1682 row(zIdx{a, 1}(i)) = 1;
1684 row(piIdx{k_act}(i)) = -xUa;
1685 Aineq = [Aineq; row];
1686 bineq = [bineq; -pL * xUa];
1688 row = zeros(1, nVar);
1689 row(zIdx{a, 1}(i)) = 1;
1691 row(piIdx{k_act}(i)) = -xLa;
1692 Aineq = [Aineq; row];
1693 bineq = [bineq; -pU * xLa];
1695 row = zeros(1, nVar);
1696 row(zIdx{a, 1}(i)) = -1;
1698 row(piIdx{k_act}(i)) = xUa;
1699 Aineq = [Aineq; row];
1700 bineq = [bineq; pU * xUa];
1705 xLa = xL(a); xUa = xU(a);
1706 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
1708 row = zeros(1, nVar);
1709 row(zIdx{a, 2}(i)) = -1;
1711 row(piIdx{k_psv}(i)) = xLa;
1712 Aineq = [Aineq; row];
1713 bineq = [bineq; pL * xLa];
1715 row = zeros(1, nVar);
1716 row(zIdx{a, 2}(i)) = 1;
1718 row(piIdx{k_psv}(i)) = -xUa;
1719 Aineq = [Aineq; row];
1720 bineq = [bineq; -pL * xUa];
1722 row = zeros(1, nVar);
1723 row(zIdx{a, 2}(i)) = 1;
1725 row(piIdx{k_psv}(i)) = -xLa;
1726 Aineq = [Aineq; row];
1727 bineq = [bineq; -pU * xLa];
1729 row = zeros(1, nVar);
1730 row(zIdx{a, 2}(i)) = -1;
1732 row(piIdx{k_psv}(i)) = xUa;
1733 Aineq = [Aineq; row];
1734 bineq = [bineq; pU * xUa];
1744 lb(piIdx{k}) = piL{k};
1745 ub(piIdx{k}) = piU{k};
1749 ub(zIdx{a, 1}) = xU(a);
1751 ub(zIdx{a, 2}) = xU(a);
1755options = optimoptions(
'linprog',
'Algorithm',
'interior-point',
'Display',
'off');
1756[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
1762 piOpt{k} = sol(piIdx{k})
';
1771%% Zero Potential Relaxation (zpr)
1772% Uses potential matrix to add cutting plane constraints
1773function [xOpt, piOpt, fval, exitflag] = solve_zpr(targetAction, boundType, ...
1774 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL)
1776% First get initial solution using inap or from midpoint
1778[pi0, Q0] = compute_eq_zpr(x0, Aa, Pb, L, ACT, PSV, M, A, N);
1780% Compute potential matrices using Jacobi iteration
1784 f_vec = zeros(N(k), 1);
1786 g{k, n} = potential_jacobi(Q0{k}, pi0{k}, f_vec);
1790% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...; z{1,1}(:); z{1,2}(:); ...]
1795 nZ = nZ + N(ACT(a)) + N(PSV(a));
1797nVar = nX + nPi + nZ;
1804 piIdx{k} = offset + (1:N(k));
1805 offset = offset + N(k);
1809 zIdx{a, 1} = offset + (1:N(ACT(a)));
1810 offset = offset + N(ACT(a));
1811 zIdx{a, 2} = offset + (1:N(PSV(a)));
1812 offset = offset + N(PSV(a));
1818 f(targetAction) = 1;
1820 f(targetAction) = -1;
1831 row = zeros(1, nVar);
1837% sum(z{a,type}) = x(a)
1839 row = zeros(1, nVar);
1840 row(zIdx{a, 1}) = 1;
1845 row = zeros(1, nVar);
1846 row(zIdx{a, 2}) = 1;
1852% pi{k} * Q{k} = 0 (balance equations)
1854 Qlocal = L{k} - diag(sum(L{k}, 2));
1857 row = zeros(1, nVar);
1858 row(piIdx{k}) = Qlocal(:, s)';
1862 Qp = Pb{a} - diag(sum(Pb{a}, 2));
1863 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)
';
1865 Qa = Aa{a} - diag(sum(Aa{a}, 2));
1866 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)';
1870 Aineq = [Aineq; row; -row];
1871 bineq = [bineq; PFTOL; PFTOL];
1875% RC3 constraint: z{a,active}*I - pi*Aa = 0
1879 row = zeros(1, nVar);
1880 row(zIdx{a, 1}(i)) = 1;
1881 row(piIdx{k}) = -Aa{a}(:, i)
';
1883 Aineq = [Aineq; row; -row];
1884 bineq = [bineq; PFTOL; PFTOL];
1888% Potential constraints (cutting planes from zpr)
1891 % delta constraint: pi0 - pi + correction terms = 0
1892 row = zeros(1, nVar);
1893 row(piIdx{k}(n)) = -1;
1898 Qp = Pb{b} - diag(sum(Pb{b}, 2));
1899 % z{b,passive} * Qp * g{k,n}
1900 coeff = Qp * g{k, n};
1901 row(zIdx{b, 2}) = row(zIdx{b, 2}) + coeff';
1902 % -x0(b) * pi * Qp * g{k,n}
1903 coeff2 = Qp * g{k, n};
1904 row(piIdx{k}) = row(piIdx{k}) - x0(b) * coeff2
';
1908 Aineq = [Aineq; row; -row];
1909 bineq = [bineq; rhs + PFTOL; -rhs + PFTOL];
1913% McCormick envelopes
1919 xLa = xL(a); xUa = xU(a);
1920 pL = piL{k_act}(i); pU = piU{k_act}(i);
1922 row = zeros(1, nVar);
1923 row(zIdx{a, 1}(i)) = -1;
1925 row(piIdx{k_act}(i)) = xLa;
1926 Aineq = [Aineq; row];
1927 bineq = [bineq; pL * xLa];
1929 row = zeros(1, nVar);
1930 row(zIdx{a, 1}(i)) = 1;
1932 row(piIdx{k_act}(i)) = -xUa;
1933 Aineq = [Aineq; row];
1934 bineq = [bineq; -pL * xUa];
1936 row = zeros(1, nVar);
1937 row(zIdx{a, 1}(i)) = 1;
1939 row(piIdx{k_act}(i)) = -xLa;
1940 Aineq = [Aineq; row];
1941 bineq = [bineq; -pU * xLa];
1943 row = zeros(1, nVar);
1944 row(zIdx{a, 1}(i)) = -1;
1946 row(piIdx{k_act}(i)) = xUa;
1947 Aineq = [Aineq; row];
1948 bineq = [bineq; pU * xUa];
1952 xLa = xL(a); xUa = xU(a);
1953 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
1955 row = zeros(1, nVar);
1956 row(zIdx{a, 2}(i)) = -1;
1958 row(piIdx{k_psv}(i)) = xLa;
1959 Aineq = [Aineq; row];
1960 bineq = [bineq; pL * xLa];
1962 row = zeros(1, nVar);
1963 row(zIdx{a, 2}(i)) = 1;
1965 row(piIdx{k_psv}(i)) = -xUa;
1966 Aineq = [Aineq; row];
1967 bineq = [bineq; -pL * xUa];
1969 row = zeros(1, nVar);
1970 row(zIdx{a, 2}(i)) = 1;
1972 row(piIdx{k_psv}(i)) = -xLa;
1973 Aineq = [Aineq; row];
1974 bineq = [bineq; -pU * xLa];
1976 row = zeros(1, nVar);
1977 row(zIdx{a, 2}(i)) = -1;
1979 row(piIdx{k_psv}(i)) = xUa;
1980 Aineq = [Aineq; row];
1981 bineq = [bineq; pU * xUa];
1991 lb(piIdx{k}) = piL{k};
1992 ub(piIdx{k}) = piU{k};
1996 ub(zIdx{a, 1}) = xU(a);
1998 ub(zIdx{a, 2}) = xU(a);
2002options = optimoptions('linprog
', 'Algorithm
', 'interior-point
', 'Display
', 'off
');
2003[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
2009 piOpt{k} = sol(piIdx{k})';
2018%% Tightened Zero Potential Relaxation (tzpr0inf/tzpr1inf)
2019% Combines zero potential relaxation with tightened LP constraints
2020% U = tightening level (0
for tzpr0inf, 1
for tzpr1inf)
2021function [xOpt, piOpt, fval, exitflag] = solve_tzpr(targetAction, boundType, ...
2022 Aa, Pb, L, ACT, PSV, M, A, N, xL, xU, piL, piU, PFTOL, U)
2024% First get initial solution from midpoint
2026[pi0, Q0] = compute_eq_zpr(x0, Aa, Pb, L, ACT, PSV, M, A, N);
2028% Compute potential matrices
2032 f_vec = zeros(N(k), 1);
2034 g{k, n} = potential_jacobi(Q0{k}, pi0{k}, f_vec);
2038% Variables: [x(1:A); pi{1}(:); pi{2}(:); ...; z{1,1}(:); z{1,2}(:); ...]
2043 nZ = nZ + N(ACT(a)) + N(PSV(a));
2045nVar = nX + nPi + nZ;
2052 piIdx{k} = offset + (1:N(k));
2053 offset = offset + N(k);
2057 zIdx{a, 1} = offset + (1:N(ACT(a)));
2058 offset = offset + N(ACT(a));
2059 zIdx{a, 2} = offset + (1:N(PSV(a)));
2060 offset = offset + N(PSV(a));
2066 f(targetAction) = 1;
2068 f(targetAction) = -1;
2079 row = zeros(1, nVar);
2085% sum(z{a,type}) = x(a)
2087 row = zeros(1, nVar);
2088 row(zIdx{a, 1}) = 1;
2093 row = zeros(1, nVar);
2094 row(zIdx{a, 2}) = 1;
2100% pi{k} * Q{k} = 0 (balance equations)
2102 Qlocal = L{k} - diag(sum(L{k}, 2));
2105 row = zeros(1, nVar);
2106 row(piIdx{k}) = Qlocal(:, s)
';
2110 Qp = Pb{a} - diag(sum(Pb{a}, 2));
2111 row(zIdx{a, 2}) = row(zIdx{a, 2}) + Qp(:, s)';
2113 Qa = Aa{a} - diag(sum(Aa{a}, 2));
2114 row(piIdx{k}) = row(piIdx{k}) + Qa(:, s)
';
2118 Aineq = [Aineq; row; -row];
2119 bineq = [bineq; PFTOL; PFTOL];
2127 row = zeros(1, nVar);
2128 row(zIdx{a, 1}(i)) = 1;
2129 row(piIdx{k}) = -Aa{a}(:, i)';
2131 Aineq = [Aineq; row; -row];
2132 bineq = [bineq; PFTOL; PFTOL];
2136% Potential constraints (from zpr)
2139 row = zeros(1, nVar);
2140 row(piIdx{k}(n)) = -1;
2145 Qp = Pb{b} - diag(sum(Pb{b}, 2));
2146 coeff = Qp * g{k, n};
2147 row(zIdx{b, 2}) = row(zIdx{b, 2}) + coeff
';
2148 coeff2 = Qp * g{k, n};
2149 row(piIdx{k}) = row(piIdx{k}) - x0(b) * coeff2';
2153 Aineq = [Aineq; row; -row];
2154 bineq = [bineq; rhs + PFTOL; -rhs + PFTOL];
2158% Tightened LP constraints (from tlpr)
2159% U
is passed as parameter: 0
for tzpr0inf, 1
for tzpr1inf
2163 Qlocal = L{k} - diag(sum(L{k}, 2));
2169 row = zeros(1, nVar);
2170 coeffZ = (Aa{b}^(u-1)) * Qlocal;
2171 row(zIdx{b, 1}) = coeffZ(:, s)
';
2175 Qp = Pb{c} - diag(sum(Pb{c}, 2));
2176 coeffZp = Aa_pow * Qp;
2177 row(zIdx{c, 2}) = row(zIdx{c, 2}) + coeffZp(:, s)';
2179 Qa = Aa{c} - diag(sum(Aa{c}, 2));
2180 coeffZa = (Aa{b}^(u-1)) * Qa;
2181 row(zIdx{b, 1}) = row(zIdx{b, 1}) + coeffZa(:, s)
';
2185 Aineq = [Aineq; row; -row];
2186 bineq = [bineq; PFTOL; PFTOL];
2190 % Infinity constraint
2192 Aa_inv = inv(eye(N(k)) - Aa{b});
2195 row = zeros(1, nVar);
2196 coeffZ = Aa_inv * Qlocal;
2197 row(zIdx{b, 1}) = coeffZ(:, s)';
2201 Qp = Pb{c} - diag(sum(Pb{c}, 2));
2202 coeffZp = Aa{b} * Aa_inv * Qp;
2203 row(zIdx{c, 2}) = row(zIdx{c, 2}) + coeffZp(:, s)
';
2205 Qa = Aa{c} - diag(sum(Aa{c}, 2));
2206 coeffZa = Aa_inv * Qa;
2207 row(zIdx{b, 1}) = row(zIdx{b, 1}) + coeffZa(:, s)';
2211 Aineq = [Aineq; row; -row];
2212 bineq = [bineq; PFTOL; PFTOL];
2215 % Matrix not invertible
2221% McCormick envelopes
2227 xLa = xL(a); xUa = xU(a);
2228 pL = piL{k_act}(i); pU = piU{k_act}(i);
2230 row = zeros(1, nVar);
2231 row(zIdx{a, 1}(i)) = -1;
2233 row(piIdx{k_act}(i)) = xLa;
2234 Aineq = [Aineq; row];
2235 bineq = [bineq; pL * xLa];
2237 row = zeros(1, nVar);
2238 row(zIdx{a, 1}(i)) = 1;
2240 row(piIdx{k_act}(i)) = -xUa;
2241 Aineq = [Aineq; row];
2242 bineq = [bineq; -pL * xUa];
2244 row = zeros(1, nVar);
2245 row(zIdx{a, 1}(i)) = 1;
2247 row(piIdx{k_act}(i)) = -xLa;
2248 Aineq = [Aineq; row];
2249 bineq = [bineq; -pU * xLa];
2251 row = zeros(1, nVar);
2252 row(zIdx{a, 1}(i)) = -1;
2254 row(piIdx{k_act}(i)) = xUa;
2255 Aineq = [Aineq; row];
2256 bineq = [bineq; pU * xUa];
2260 xLa = xL(a); xUa = xU(a);
2261 pL = piL{k_psv}(i); pU = piU{k_psv}(i);
2263 row = zeros(1, nVar);
2264 row(zIdx{a, 2}(i)) = -1;
2266 row(piIdx{k_psv}(i)) = xLa;
2267 Aineq = [Aineq; row];
2268 bineq = [bineq; pL * xLa];
2270 row = zeros(1, nVar);
2271 row(zIdx{a, 2}(i)) = 1;
2273 row(piIdx{k_psv}(i)) = -xUa;
2274 Aineq = [Aineq; row];
2275 bineq = [bineq; -pL * xUa];
2277 row = zeros(1, nVar);
2278 row(zIdx{a, 2}(i)) = 1;
2280 row(piIdx{k_psv}(i)) = -xLa;
2281 Aineq = [Aineq; row];
2282 bineq = [bineq; -pU * xLa];
2284 row = zeros(1, nVar);
2285 row(zIdx{a, 2}(i)) = -1;
2287 row(piIdx{k_psv}(i)) = xUa;
2288 Aineq = [Aineq; row];
2289 bineq = [bineq; pU * xUa];
2299 lb(piIdx{k}) = piL{k};
2300 ub(piIdx{k}) = piU{k};
2304 ub(zIdx{a, 1}) = xU(a);
2306 ub(zIdx{a, 2}) = xU(a);
2310options = optimoptions(
'linprog',
'Algorithm',
'interior-point',
'Display',
'off');
2311[sol, fval, exitflag] = linprog(f, Aineq, bineq, Aeq, beq, lb, ub, options);
2317 piOpt{k} = sol(piIdx{k})
';
2326%% Compute equilibrium for zpr
2327function [pi, Q] = compute_eq_zpr(x, Aa, Pb, L, ACT, PSV, M, A, N)
2332 Qk = L{k} - diag(sum(L{k}, 2));
2336 Qk = Qk + x(a) * (Pb{a} - diag(sum(Pb{a}, 2)));
2338 Qk = Qk + Aa{a} - diag(sum(Aa{a}, 2));
2343 Qk_inf = ctmc_makeinfgen(Qk);
2344 pi{k} = ctmc_solve(Qk_inf);
2348%% Potential matrix computation using Jacobi iteration
2349function g = potential_jacobi(Q, pi, f)
2350% Compute potential g such that Q*g = f - pi*f*e
2351% Uses Jacobi iteration method
2358b = f - (pi * f) * ones(n, 1);
2371 if abs(d(i)) > 1e-12
2372 sum_term = Q(i, :) * g_old - d(i) * g_old(i);
2373 g(i) = (b(i) - sum_term) / d(i);
2377 if norm(g - g_old, inf) < tol
2382% Normalize to satisfy pi*g = 0