-
Notifications
You must be signed in to change notification settings - Fork 11
/
lsq_classopath.m
774 lines (672 loc) · 29.2 KB
/
lsq_classopath.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
function [rhoPath, betaPath, dfPath, objValPath] = ...
lsq_classopath(X, y, A, b, Aeq, beq, varargin)
% LSQ_CLASSOPATH Constrained lasso solution path
%
% [RHO_PATH, BETA_PATH] = LSQ_CLASSOPATH(X, y, A, b, Aeq, beq) computes the
% solution path for the constrained lasso problem using the predictor
% matrix X and response y. The constrained lasso solves the standard
% lasso (Tibshirani, 1996) subject to the linear equality constraints
% Aeq*beta = beq and linear inequality constraints A*beta <= b. The
% result RHO_PATH contains the values of the tuning parameter rho along
% the solution path. The result BETA_PATH has a vector of the estimated
% regression coefficients for each value of rho. If the design matrix is
%
%
% [RHO_PATH, BETA_PATH] = LSQ_CLASSOPATH(X, y, A, b, Aeq, beq, ...
% 'PARAM1', val1, 'PARAM2', val2, ...) allows you to specify optional
% parameter name/value pairs to control the model fit.
%
%
% INPUTS:
% X: n-by-p design matrix
% y: n-by-1 response vector
% A: inequality constraint matrix
% b: inequality constraint vector
% Aeq: equality constraint matrix
% beq: equality constraint vector
%
% OPTIONAL NAME-VALUE PAIRS:
%
% 'qp_solver': 'matlab' (default), 'gurobi' currently is not working
% 'penidx': a logical vector indicating penalized coefficients
% 'init_method': 'qp' (default) or 'lp' method to initialize. 'lp is
% recommended only when it's reasonable to assume that all
% coefficient estimates initialize at zero.
% 'epsilon': tuning parameter for ridge penalty. Default is 1e-4.
%
% OUTPUTS:
%
% rhoPath: vector of the tuning parameter values along the solution path
% betaPath: matrix with estimated regression coefficients for each value
% of rho
% dfPath: vector with estimate of degrees of freedom along the solution path
% objValPath: vector of values of the objective function at each value of rho
% along the solution path
%
% EXAMPLE
% See tutorial examples at https://github.com/Hua-Zhou/SparseReg
%
% REFERENCES
% Gaines, Brian and Zhou, Hua (2016). On Fitting the Constrained Lasso.
%
%
% Copyright 2014-2017 University of California at Los Angeles and North Carolina State University
% Hua Zhou ([email protected]) and Brian Gaines ([email protected])
%
% input parsing rule
[n, p] = size(X);
argin = inputParser;
argin.addRequired('X', @isnumeric);
argin.addRequired('y', @(x) length(y)==n);
argin.addRequired('A', @(x) size(x,2)==p || isempty(x));
argin.addRequired('b', @(x) isnumeric(x) || isempty(x));
argin.addRequired('Aeq', @(x) size(x,2)==p || isempty(x));
argin.addRequired('beq', @(x) isnumeric(x) || isempty(x));
argin.addParamValue('qp_solver', 'matlab', @ischar);
argin.addParamValue('init_method', 'qp', @ischar);
argin.addParamValue('penidx', true(p,1), @(x) islogical(x) && length(x)==p);
argin.addParamValue('epsilon', 1e-4, @isnumeric);
% parse inputs
y = reshape(y, n, 1);
argin.parse(X, y, A, b, Aeq, beq, varargin{:});
qp_solver = argin.Results.qp_solver;
init_method = argin.Results.init_method;
penidx = reshape(argin.Results.penidx,p,1);
epsilon = argin.Results.epsilon;
% check validity of qp_solver
if ~(strcmpi(qp_solver, 'matlab'))% || strcmpi(qp_solver, 'GUROBI'))
error('sparsereg:lsq_classopath:qp_solver', ...
'qp_solver not recognized');
end
% check validity of initialization method
if ~(strcmpi(init_method, 'qp') || strcmpi(init_method, 'lp'))
error('sparsereg:lsq_classopath:init_method', ...
'init_method not recognized');
end
% issue warning if LP is used for initialization
if strcmpi(init_method, 'lp')
warning('LP used for initialization, assumes initial solution is unique')
end
% save original number of observations (for when ridge penalty is used)
n_orig = n;
% see if ridge penalty needs to be included
if n < p
warning('Adding a small ridge penalty (default is 1e-4) since n < p')
if epsilon <= 0
warning('epsilon must be positive, switching to default value (1e-4)')
epsilon = 1e-4;
end
% create augmented data
y = [y; zeros(p, 1)];
X = [X; sqrt(epsilon)*eye(p)];
% record original number of observations
n_orig = n;
else
% make sure X is full column rank
[~, R] = qr(X, 0);
rankX = sum(abs(diag(R)) > abs(R(1))*max(n, p)*eps(class(R)));
if (rankX ~= p)
warning(['Adding a small ridge penalty (default is 1e-4) since X ' ...
'is rank deficient']);
if epsilon <= 0
warning(['epsilon must be positive, switching to default value' ...
' (1e-4)'])
epsilon = 1e-4;
end
% create augmented data
y = [y; zeros(p, 1)];
X = [X; sqrt(epsilon)*eye(p)];
% record original number of observations
n_orig = n;
end
end
m1 = size(Aeq, 1); % # equality constraints
if isempty(Aeq)
Aeq = zeros(0,p);
beq = zeros(0,1);
end
m2 = size(A, 1); % # inequality constraints
if isempty(A)
A = zeros(0,p);
b = zeros(0,1);
end
maxiters = 5*(p+m2); % max number of path segments to consider
betaPath = zeros(p, maxiters);
dualpathEq = zeros(m1, maxiters);
dualpathIneq = zeros(m2, maxiters);
rhoPath = zeros(1, maxiters);
dfPath = Inf(1, maxiters);
objValPath = zeros(1, maxiters);
violationsPath = Inf(1, maxiters);
%# intialization
H = X'*X;
% if strcmpi(qp_solver, 'matlab')
% using matlab
if strcmpi(init_method, 'lp')
% use Matlab lsqlin
[x,~,~,~,lambda] = ...
linprog(ones(2*p,1), [A -A], b, [Aeq -Aeq], beq, ...
zeros(2*p,1), inf(2*p,1));
betaPath(:,1) = x(1:p) - x(p+1:end);
dualpathEq(:,1) = lambda.eqlin;
dualpathIneq(:,1) = lambda.ineqlin;
elseif strcmpi(init_method, 'qp')
%# First use LP to find rho_max
% solve LP problem
[x,~,~,~,lambda] = ...
linprog(ones(2*p,1),[A -A],b,[Aeq -Aeq],beq, ...
zeros(2*p,1), inf(2*p,1));
betaPath(:,1) = x(1:p) - x(p+1:end);
dualpathEq(:,1) = lambda.eqlin;
dualpathIneq(:,1) = lambda.ineqlin;
% initialize sets
dualpathIneq(dualpathIneq(:,1) < 0,1) = 0; % fix negative dual variables
setActive = abs(betaPath(:,1))>1e-4 | ~penidx;
betaPath(~setActive,1) = 0;
% find the maximum rho and initialize subgradient vector
resid = y - X*betaPath(:, 1);
subgrad = X'*resid - Aeq'*dualpathEq(:,1) - A'*dualpathIneq(:,1);
rho_max = max(abs(subgrad));
%# Use QP at rho_max to initialize
[betaPath(:,1), stats] = lsq_constrsparsereg(X, y, ...
(rho_max*1), ...
'method', 'qp', 'qp_solver', 'matlab', 'Aeq', Aeq,...
'beq', beq, 'A', A, 'b', b);
dualpathEq(:,1) = stats.qp_dualEq;
dualpathIneq(:,1) = stats.qp_dualIneq;
end
% elseif strcmpi(qp_solver, 'GUROBI')
% % use GUROBI solver if possible
% if strcmpi(init_method, 'lp')
% % linear programming
% gmodel.obj = ones(2*p,1);
% gmodel.A = sparse([A -A; Aeq -Aeq]);
% gmodel.sense = [repmat('<', m2, 1); repmat('=', m1, 1)];
% gmodel.rhs = [b; beq];
% gmodel.lb = zeros(2*p,1);
% gparam.OutputFlag = 0;
% gresult = gurobi(gmodel, gparam);
% betaPath(:,1) = gresult.x(1:p) - gresult.x(p+1:end);
% dualpathEq(:,1) = reshape(gresult.pi(m2+1:end), m1, 1);
% dualpathIneq(:,1) = reshape(gresult.pi(1:m2), m2, 1);
% elseif strcmpi(init_method, 'qp')
% %# First use LP to find rho_max
% % solve LP problem
% gmodel.obj = ones(2*p,1);
% gmodel.A = sparse([A -A; Aeq -Aeq]);
% gmodel.sense = [repmat('<', m2, 1); repmat('=', m1, 1)];
% gmodel.rhs = [b; beq];
% gmodel.lb = zeros(2*p,1);
% gparam.OutputFlag = 0;
% gresult = gurobi(gmodel, gparam);
% betaPath(:,1) = gresult.x(1:p) - gresult.x(p+1:end);
% dualpathEq(:,1) = reshape(gresult.pi(m2+1:end), m1, 1);
% dualpathIneq(:,1) = reshape(gresult.pi(1:m2), m2, 1);
% % initialize sets
% dualpathIneq(dualpathIneq(:,1) < 0,1) = 0; % fix negative dual variables
% setActive = abs(betaPath(:,1))>1e-4 | ~penidx;
% betaPath(~setActive,1) = 0;
% % find the maximum rho and initialize subgradient vector
% resid = y - X*betaPath(:, 1);
% subgrad = X'*resid - Aeq'*dualpathEq(:,1) - A'*dualpathIneq(:,1);
% rho_max = max(abs(subgrad));
%
%
% % quadratic programming
% [betaPath(:,1), stats] = lsq_constrsparsereg(X, y, ...
% rho_max,...
% 'method','qp','qp_solver','gurobi','Aeq', Aeq,...
% 'beq', beq, 'A',A,'b',b);
% dualpathEq(:,1) = stats.qp_dualEq;
% dualpathIneq(:,1) = stats.qp_dualIneq;
% end
% end
% may wanna switch this so the first rho isn't re-calculated?
% initialize sets
dualpathIneq(dualpathIneq(:,1) < 0,1) = 0; % fix Gurobi negative dual variables
setActive = abs(betaPath(:,1))>1e-4 | ~penidx;
betaPath(~setActive,1) = 0;
% setIneqBorder = dualpathIneq(:,1)>0;
residIneq = A*betaPath(:,1) - b;
setIneqBorder = residIneq == 0;
nIneqBorder = nnz(setIneqBorder);
% find the maximum rho and initialize subgradient vector
resid = y - X*betaPath(:, 1);
subgrad = X'*resid - Aeq'*dualpathEq(:,1) - A'*dualpathIneq(:,1);
% subgrad(setActive) = 0;
[rhoPath(1), idx] = max(abs(subgrad));
subgrad(setActive) = sign(betaPath(setActive,1));
subgrad(~setActive) = subgrad(~setActive)/rhoPath(1);
setActive(idx) = true;
nActive = nnz(setActive);
% calculate value for objective function
objValPath(1) = norm(y-X*betaPath(:,1))^2/2 + ...
rhoPath(1)*sum(abs(betaPath(:,1)));
% calculate degrees of freedom
rankAeq = rank(Aeq); % should do this more efficiently
dfPath(1) = nActive - rankAeq - nIneqBorder;
% set initial violations counter to 0
violationsPath(1) = 0;
% sign for path direction (originally went both ways, but increasing was
% retired)
dirsgn = -1;
%####################################%
%### main loop for path following ###%
%####################################%
s = warning('error', 'MATLAB:nearlySingularMatrix'); %#ok<CTPCT>
s2 = warning('error', 'MATLAB:singularMatrix'); %#ok<CTPCT>
for k = 2:maxiters
% threshold near-zero rhos to zero and stop algorithm
if rhoPath(k-1) <= (0 + 1e-3)
rhoPath(k-1) = 0;
break;
end
%# Calculate derivative for coefficients and multipliers #%
% construct matrix
M = [H(setActive, setActive) Aeq(:,setActive)' ...
A(setIneqBorder,setActive)'];
M(end+1:end+m1+nIneqBorder, 1:nActive) = ...
[Aeq(:,setActive); A(setIneqBorder,setActive)];
% calculate derivative
try
% try using a regular inverse first
dir = dirsgn ...
* (M \ [subgrad(setActive); zeros(m1+nIneqBorder,1)]);
catch
% otherwise use the moore-penrose inverse
dir = -(IMqrginv(M) * ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
end
%# calculate derivative for rho*subgradient #%
dirSubgrad = ...
- [H(~setActive, setActive) Aeq(:,~setActive)' ...
A(setIneqBorder,~setActive)'] * dir;
%## check additional events related to potential subgradient violations ##%
%# Inactive coefficients moving too slowly #%
% Negative subgradient
inactSlowNegIdx = find((1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (1*dirsgn + 1e-8) & 1*dirsgn < dirSubgrad);
% Positive subgradient
inactSlowPosIdx = find((-1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (-1*dirsgn + 1e-8) & dirSubgrad < -1*dirsgn);
%# "Active" coeficients estimated as 0 with potential sign mismatch #%
% Positive subgrad but negative derivative
signMismatchPosIdx = find((0 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (1 + 1e-8) & ...
dirsgn*dir(1:nActive) <= (0 - 1e-8) & ...
betaPath(setActive, k-1) == 0);
% Negative subgradient but positive derivative
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% reset violation counter (to avoid infinite loops)
violateCounter = 0;
%# Outer while loop for checking all conditions together #%
while ~isempty(inactSlowNegIdx) || ~isempty(inactSlowPosIdx) || ...
~isempty(signMismatchPosIdx) || ~isempty(signMismatchNegIdx)
% Monitor & fix condition 1 violations
while ~isempty(inactSlowNegIdx)
%# Identify & move problem coefficient #%
% indices corresponding to inactive coefficients
inactiveCoeffs = find(setActive == 0);
% identify prblem coefficient
viol_coeff = inactiveCoeffs(inactSlowNegIdx);
% put problem coefficient back into active set;
setActive(viol_coeff) = true;
% determine new number of active coefficients
nActive = nnz(setActive);
% determine number of active/binding inequality constraints
nIneqBorder = nnz(setIneqBorder);
%# Recalculate derivative for coefficients & multipliers #%
% construct matrix
M = [H(setActive, setActive) Aeq(:,setActive)' ...
A(setIneqBorder,setActive)'];
M(end+1:end+m1+nIneqBorder, 1:nActive) = ...
[Aeq(:,setActive); A(setIneqBorder,setActive)];
% calculate derivative
try
% try using a regular inverse first
dir = dirsgn ...
* (M \ ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
catch
% otherwise use moore-penrose inverse
dir = -(IMqrginv(M) * ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
end
%# calculate derivative for rho*subgradient #%
dirSubgrad = ...
- [H(~setActive, setActive) Aeq(:,~setActive)' ...
A(setIneqBorder,~setActive)'] * dir;
%# Misc. housekeeping #%
% check for violations again
%# Inactive coefficients moving too slowly #%
% Negative subgradient
inactSlowNegIdx = ...
find((1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (1*dirsgn + 1e-8) & ...
1*dirsgn < dirSubgrad);
% Positive subgradient
inactSlowPosIdx = ...
find((-1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (-1*dirsgn + 1e-8) & ...
dirSubgrad < -1*dirsgn);
%# "Active" coeficients est'd as 0 with potential sign mismatch #%
% Positive subgrad but negative derivative
signMismatchPosIdx = find((0 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (1 + 1e-8) & ...
dirsgn*dir(1:nActive) <= (0 - 1e-8) & ...
betaPath(setActive, k-1) == 0);
% Negative subgradient but positive derivative
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% update violation counter
violateCounter = violateCounter + 1;
% break loop if needed
if violateCounter >= maxiters
break;
end
end
% Monitor & fix subgradient condition 2 violations
while ~isempty(inactSlowPosIdx)
%# Identify & move problem coefficient #%
% indices corresponding to inactive coefficients
inactiveCoeffs = find(setActive == 0);
% identify problem coefficient
viol_coeff = inactiveCoeffs(inactSlowPosIdx);
% put problem coefficient back into active set;
setActive(viol_coeff) = true;
% determine new number of active coefficients
nActive = nnz(setActive);
% determine number of active/binding inequality constraints
nIneqBorder = nnz(setIneqBorder);
%# Recalculate derivative for coefficients & multiplier #%
% construct matrix
M = [H(setActive, setActive) Aeq(:,setActive)' ...
A(setIneqBorder,setActive)'];
M(end+1:end+m1+nIneqBorder, 1:nActive) = ...
[Aeq(:,setActive); A(setIneqBorder,setActive)];
% calculate derivative
try
% try using a regular inverse first
dir = dirsgn ...
* (M \ ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
catch
dir = -(IMqrginv(M) * ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
end
%# calculate derivative for rho*subgradient #%
dirSubgrad = ...
- [H(~setActive, setActive) Aeq(:,~setActive)' ...
A(setIneqBorder,~setActive)'] * dir;
%# Misc. housekeeping #%
% check for violations again
inactSlowPosIdx = find((-1*dirsgn - 1e-8) <= ...
subgrad(~setActive) & ...
subgrad(~setActive) <= (-1*dirsgn + 1e-8) & ...
dirSubgrad < -1*dirsgn);
%# "Active" coeficients est'd as 0 with potential sign mismatch #%
% Positive subgrad but negative derivative
signMismatchPosIdx = find((0 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (1 + 1e-8) & ...
dirsgn*dir(1:nActive) <= (0 - 1e-8) & ...
betaPath(setActive, k-1) == 0);
% Negative subgradient but positive derivative
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% update violation counter
violateCounter = violateCounter + 1;
% break loop if needed
if violateCounter >= maxiters
break;
end
end
% Monitor & fix condition 3 violations
while ~isempty(signMismatchPosIdx)
%# Identify & move problem coefficient #%
% indices corresponding to active coefficients
activeCoeffs = find(setActive == 1);
% identify prblem coefficient
viol_coeff = activeCoeffs(signMismatchPosIdx);
% put problem coefficient back into inactive set;
setActive(viol_coeff) = false;
% determine new number of active coefficients
nActive = nnz(setActive);
% determine number of active/binding inequality constraints
nIneqBorder = nnz(setIneqBorder);
%# Recalculate derivative for coefficients & multipliers #%
% construct matrix
M = [H(setActive, setActive) Aeq(:,setActive)' ...
A(setIneqBorder,setActive)'];
M(end+1:end+m1+nIneqBorder, 1:nActive) = ...
[Aeq(:,setActive); A(setIneqBorder,setActive)];
% calculate derivative
try
% try using a regular inverse first
dir = dirsgn ...
* (M \ ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
catch
dir = -(IMqrginv(M) * ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
end
%# calculate derivative for rho*subgradient (Eq. 10) #%
dirSubgrad = ...
- [H(~setActive, setActive) Aeq(:,~setActive)' ...
A(setIneqBorder,~setActive)'] * dir;
%# Misc. housekeeping #%
% check for violations again
signMismatchPosIdx = find((0 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (1 + 1e-8) & ...
dirsgn*dir(1:nActive) <= (0 - 1e-8) & ...
betaPath(setActive, k-1) == 0);
% Negative subgradient but positive derivative
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% update violation counter
violateCounter = violateCounter + 1;
% break loop if needed
if violateCounter >= maxiters
break;
end
end
% Monitor & fix condition 4 violations
while ~isempty(signMismatchNegIdx)
%# Identify & move problem coefficient #%
% indices corresponding to active coefficients
activeCoeffs = find(setActive == 1);
% identify prblem coefficient
viol_coeff = activeCoeffs(signMismatchNegIdx);
% put problem coefficient back into inactive set;
setActive(viol_coeff) = false;
% determine new number of active coefficients
nActive = nnz(setActive);
% determine number of active/binding inequality constraints
nIneqBorder = nnz(setIneqBorder);
%# Recalculate derivative for coefficients & multipliers #%
% construct matrix
M = [H(setActive, setActive) Aeq(:,setActive)' ...
A(setIneqBorder,setActive)'];
M(end+1:end+m1+nIneqBorder, 1:nActive) = ...
[Aeq(:,setActive); A(setIneqBorder,setActive)];
% calculate derivative
try
% try using a regular inverse first
dir = dirsgn ...
* (M \ ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
catch
dir = -(IMqrginv(M) * ...
[subgrad(setActive); zeros(m1+nIneqBorder,1)]);
end
%# calculate derivative for rho*subgradient #%
dirSubgrad = ...
- [H(~setActive, setActive) Aeq(:,~setActive)' ...
A(setIneqBorder,~setActive)'] * dir;
%# Recheck for violations #%
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% update violation counter
violateCounter = violateCounter + 1;
% break loop if needed
if violateCounter >= maxiters
break;
end
end
%## update violation trackers to see if any issues persist ##%
%# Inactive coefficients moving too slowly #%
% Negative subgradient
inactSlowNegIdx = ...
find((1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (1*dirsgn + 1e-8) & ...
1*dirsgn < dirSubgrad);
% % Positive subgradient
inactSlowPosIdx = find((-1*dirsgn - 1e-8) <= subgrad(~setActive) & ...
subgrad(~setActive) <= (-1*dirsgn + 1e-8) & dirSubgrad < -1*dirsgn);
%# "Active" coeficients estimated as 0 with potential sign mismatch #%
% Positive subgrad but negative derivative
signMismatchPosIdx = find((0 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (1 + 1e-8) & ...
dirsgn*dir(1:nActive) <= (0 - 1e-8) & ...
betaPath(setActive, k-1) == 0);
% Negative subgradient but positive derivative
signMismatchNegIdx = find((-1 - 1e-8) <= subgrad(setActive) & ...
subgrad(setActive) <= (0 + 1e-8) & ...
(0 + 1e-8) <= dirsgn*dir(1:nActive) & ...
betaPath(setActive, k-1) == 0);
% break loop if needed
if violateCounter >= maxiters
break;
end
end % end of outer while loop
% store number of violations
violationsPath(k) = violateCounter;
% calculate derivative for residual inequality
dirResidIneq = A(~setIneqBorder, setActive)*dir(1:nActive);
%### Determine rho for next event (via delta rho) ###%
%## Events based on coefficients changing activation status ##%
% clear previous values for delta rho
nextrhoBeta = inf(p, 1);
%# Active coefficient going inactive #%
nextrhoBeta(setActive) = -dirsgn*betaPath(setActive, k-1) ...
./ dir(1:nActive);
%# Inactive coefficient becoming positive #%
t1 = dirsgn*rhoPath(k-1)*(1 - subgrad(~setActive)) ./ (dirSubgrad - 1);
% threshold values hitting ceiling
t1(t1 <= (0 + 1e-8)) = inf;
%# Inactive coefficient becoming negative #%
t2 = -dirsgn*rhoPath(k-1)*(1 + subgrad(~setActive)) ...
./ (dirSubgrad + 1);
% threshold values hitting ceiling
t2(t2 <= (0 + 1e-8)) = inf;
% choose smaller delta rho out of t1 and t2
nextrhoBeta(~setActive) = min(t1, t2);
% ignore delta rhos numerically equal to zero
nextrhoBeta(nextrhoBeta <= 1e-8 | ~penidx) = inf;
%## Events based inequality constraints ##%
% clear previous values
nextrhoIneq = inf(m2, 1);
%# Inactive inequality constraint becoming active #%
nextrhoIneq(~setIneqBorder) = reshape(-dirsgn*residIneq(~setIneqBorder), ...
nnz(~setIneqBorder), 1)./ reshape(dirResidIneq, nnz(~setIneqBorder), 1);
%# Active inequality constraint becoming deactive #%
nextrhoIneq(setIneqBorder) = - dirsgn*dualpathIneq(setIneqBorder, k-1) ...
./ reshape(dir(nActive+m1+1:end), nIneqBorder,1);
% ignore delta rhos equal to zero
nextrhoIneq(nextrhoIneq <= 1e-8) = inf;
%# determine next rho ##
% find smallest rho
chgrho = min([nextrhoBeta; nextrhoIneq]);
% find all indices corresponding to this chgrho
idx = find(([nextrhoBeta; nextrhoIneq] - chgrho) <= 1e-8);
% terminate path following if no new event found
if isinf(chgrho)
chgrho = rhoPath(k-1);
end
%## Update values at new rho ##%
%# move to next rho #%
% make sure next rho isn't negative
if rhoPath(k-1) + dirsgn*chgrho < 0
chgrho = rhoPath(k-1);
end
% calculate new value of rho
rhoPath(k) = rhoPath(k-1) + dirsgn*chgrho;
%# Update parameter and subgradient values #%
% new coefficient estimates
betaPath(setActive, k) = betaPath(setActive, k-1) ...
+ dirsgn*chgrho*dir(1:nActive);
% force near-zero coefficients to be zero (helps with numerical issues)
betaPath(abs(betaPath(:, k)) < 1e-12, k) = 0;
% new subgradient estimates
subgrad(~setActive) = ...
(rhoPath(k-1)*subgrad(~setActive) + ...
dirsgn*chgrho*dirSubgrad)/rhoPath(k);
%# Update dual variables #%
% update lambda (lagrange multipliers for equality constraints)
dualpathEq(:, k) = dualpathEq(:, k-1) ...
+ dirsgn*chgrho*reshape(dir(nActive+1:nActive+m1), m1, 1);
% update mu (lagrange multipliers for inequality constraints)
dualpathIneq(setIneqBorder, k) = dualpathIneq(setIneqBorder, k-1) ...
+ dirsgn*chgrho*reshape(dir(nActive+m1+1:end), nIneqBorder, 1);
% update residual inequality
residIneq = A*betaPath(:, k) - b;
%## update sets ##%
for j = 1:length(idx)
curidx = idx(j);
if curidx <= p && setActive(curidx)
% an active coefficient hits 0, or
setActive(curidx) = false;
elseif curidx <= p && ~setActive(curidx)
% a zero coefficient becomes nonzero
setActive(curidx) = true;
elseif curidx > p
% an ineq on boundary becomes strict, or
% a strict ineq hits boundary
setIneqBorder(curidx-p) = ~setIneqBorder(curidx-p);
end
end
% determine new number of active coefficients
nActive = nnz(setActive);
% determine number of active/binding inequality constraints
nIneqBorder = nnz(setIneqBorder);
%# Calcuate and store values of interest along the path #%
% calculate value of objective function
objValPath(k) = norm(y - X*betaPath(:, k))^2/2 + ...
rhoPath(k)*sum(abs(betaPath(:, k)));
% calculate degrees of freedom
dfPath(k) = nActive - rankAeq - nIneqBorder;
% break algorithm when df are exhausted
if dfPath(k) >= n_orig
break;
end
end
% clean up output
warning(s);
warning(s2);
betaPath(:, k:end) = [];
rhoPath(k:end) = [];
objValPath(k:end) = [];
dfPath(k:end) = [];
dfPath(dfPath < 0) = 0;
end
function [ IMqrginv ] = IMqrginv( A )
%IMqrginv Fast computation of a Moore-Penrose inverse
% Source: Ataei (2014), "Improved Qrginv Algorithm for Computing
% Moore-Penrose Inverse Matrices"
% [m, n] = size(A);
[Q, R, P] = qr(A);
r = sum(any(abs(R) > 1e-05, 2));
Q = Q(:, 1:r);
R = R(1:r, :);
IMqrginv = P*(R'/(R*R')*Q');
end