1function x0 = qrf_noblo_start(nonlcon, n)
2% QRF_NOBLO_START A point of
the QRF no-blocking polytope,
for fmincon
's start.
4% X0 = QRF_NOBLO_START(NONLCON, N) recovers the affine form of the
5% constraint callback NONLCON (which returns [C, CEQ] with C <= 0 and
6% CEQ == 0 for a decision vector of length N) and returns a point of the
7% polytope obtained by a phase-1 LP.
9% The all-zero vector that these methods used as a start violates ONE
10% (normalization) by a full unit and COR1 by N^2. fmincon then spends its
11% iteration budget restoring feasibility and terminates at a point that is
12% still infeasible: on a 2-station cyclic MAP/MAP network at N=2 it returned
13% a utilization ABOVE the LP maximum of the same polytope, and the queue
14% lengths did not conserve the population. Every constraint of this model is
15% linear, so a phase-1 LP lands on the polytope exactly and costs a fraction
16% of one fmincon iteration.
18% The phase 1 minimises 0.5*||x||^2 over the polytope rather than a null
19% objective, so the returned point is the unique minimum-norm feasible point
20% instead of an arbitrary vertex. (It is solved with QUADPROG, not LINPROG:
21% LINPROG errors with "Unrecognized field name optimstatus" on R2025a even
22% for a two-variable problem.)
24% The polytope is nonempty for every well-posed instance, so infeasibility
25% here is a modelling error and is raised rather than replaced by an
28[c0, ceq0] = nonlcon(zeros(n,1));
31A = zeros(numel(c0), n);
32Aeq = zeros(numel(ceq0), n);
36 [c1, ceq1] = nonlcon(e);
37 A(:,col) = full(c1(:)) - c0;
38 Aeq(:,col) = full(ceq1(:)) - ceq0;
43% Drop linearly dependent equality rows before the phase 1. The block is
44% heavily redundant (SYMMETRY states every station pair twice, and the ZERO,
45% MARGINALS and UEFF families overlap), carrying roughly twice as many rows as
46% its rank, and quadprog stalls on the rank-deficient system: on a 2-station
47% Erlang-2/Erlang-2 network at N=3 it stops at an equality residual of 2e-5
48% instead of machine precision. Dropping dependent rows changes no feasible
49% point, since they are exact linear combinations of the retained ones.
50keep = qrf_independent_rows(Aeq);
53if rank([AeqR beqR]) > numel(keep)
54 error('qrf_noblo_start:inconsistent
', ...
55 'QRF no-blocking equality system
is inconsistent
');
58opts = optimset('Display
','off
','MaxIter
',1000,'TolFun
',1e-12,'TolCon
',1e-12);
59x0 = quadprog(speye(n), zeros(n,1), A, b, AeqR, beqR, ...
60 zeros(n,1), ones(n,1), [], opts);
62% Judge the phase 1 by the residual of the point it returned, not by the
63% exit flag: quadprog reports flag 0 (iteration limit) on instances where it
64% has nevertheless landed on the polytope to machine precision.
66 error('qrf_noblo_start:infeasible
', ...
67 'QRF no-blocking polytope phase 1 returned no point
');
71if ~isempty(Aeq), req = max(abs(Aeq*x0 - beq)); end
72if ~isempty(A), rub = max(A*x0 - b); end
73if req > 1e-8 || rub > 1e-8
74 error('qrf_noblo_start:infeasible
', ...
75 ['QRF no-blocking polytope phase 1 did not reach feasibility
' ...
76 '(max equality residual %.3e, max inequality residual %.3e)
'], req, rub);