├── .gitattributes ├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── alfonso.bib ├── examples ├── README.md ├── exp_design │ └── e_design.m ├── poly_opt │ ├── ChebInterval.m │ ├── FeketeCube.m │ ├── PaduaSquare.m │ ├── README.md │ ├── demo_polyEnv.m │ ├── demo_polyOpt.m │ ├── polyEnv.m │ └── polyOpt.m ├── portfolio │ └── portfolio.m └── random_lp │ ├── random_lp.m │ └── random_lp_simple.m └── src ├── README.md ├── alfonso.m ├── alfonso_simple.m ├── computeAHiAt.m ├── computeAHiAti.m ├── gH_DGPow.m ├── gH_Exp.m ├── gH_GPow.m ├── gH_LP.m ├── gH_RSOC.m ├── gH_SOCP.m ├── gH_rk1LMI.m ├── linsolveA.m └── linsolveB.m /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default EOL behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !/.gitignore 3 | !/.gitattributes 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | alfonso was created at North Carolina State University (NCSU) and at the The Statistical and Applied Mathematical Sciences Institute (SAMSI), with partial support from the National Science Foundation grants NSF-DMS-1638521, NSF-DMS-1719828, and NSF-DMS-1847865. 2 | 3 | David Papp 4 | Sercan Yildiz 5 | 6 | Recent (2023-) improvements of alfonso are supported in part by the Air Force Office of Scientific Research grant FA9550-23-1-0370. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018-2020, David Papp and Sercan Yildiz. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alfonso: ALgorithm FOr Non-Symmetric Optimization 2 | 3 | ## A fast, open-source Matlab solver for conic optimization 4 | 5 | `alfonso` is an open-source Matlab package for solving convex optimization problems in conic form, created by Dávid Papp and Sercan Yıldız. It enables optimization over any convex cone as long as a suitable barrier is available for either the cone or its dual. This includes many nonsymmetric cones, for example, hyperbolicity cones and their duals (such as sum-of-squares cones), semidefinite and second-order cone representable cones, power cones, and the exponential cone. It also offers performance advantages for problems whose symmetric cone programming representation requires a large number of auxiliary variables or has a special structure that can be exploited in the barrier computation. An example of this is *sums-of-squares optimization*. 6 | 7 | The interfaces of the original version of the software are described in the accompanying software paper: 8 | > D. Papp and S. Yıldız. alfonso: Matlab package for nonsymmetric conic optimization. arXiv:2101.04274 [https://arxiv.org/abs/2101.04274](https://arxiv.org/abs/2101.04274) 9 | 10 | The "oracle interface" of the current version is slightly different; see any of the built-in barriers (e.g., gH_LP.m) for details. (*TBD: updating the preprint above.*) 11 | 12 | The package also includes an implementation of the sum-of-squares optimization algorithm based on non-symmetric conic optimization and polynomial interpolants presented in: 13 | 14 | > D. Papp and S. Yıldız. Sum-of-squares optimization without semidefinite programming. *SIAM Journal on Optimization* 29(1), 2019, pp. 822-851. [https://doi.org/10.1137/17M1160124](https://doi.org/10.1137/17M1160124) 15 | 16 | The code is distributed under the [2-Clause BSD License](LICENSE). 17 | 18 | ## Citing alfonso 19 | 20 | To cite alfonso, please mention the [research article](https://doi.org/10.1137/17M1160124) for which the code was originally developed and the [software paper](https://doi.org/10.1287/ijoc.2021.1058). Here's a [BibTeX file](alfonso.bib) with these references. 21 | 22 | ## What's new? 23 | July 2024: The `alfonso_simple` interface has a built-in barrier function for *very* efficient optimization over sets defined by the types of linear matrix inequalities that are characteristic of *sums-of-squares* optimization and *design of experiments*. 24 | 25 | July 2024: The custom barrier functions for `alfonso` are implemented using a slightly different interface: rather than returning the Hessian matrix H and its Cholesky factor L, they return a function handles representing the actions of the inverse Hessian and inverse Cholesky factor. See one of the built-in barrier functions, e.g., `gH_LP.m` for details. 26 | 27 | Big thanks to [Giovanni Fantuzzi](https://dcn.nat.fau.eu/giovanni-fantuzzi/) for his helpful discussions and the occasional beta (alpha?) testing prior to this update. 28 | 29 | ## Installation 30 | 31 | `alfonso` is entirely written in Matlab m-code. To install, unzip the downloaded files in any directory and add the `src` subdirectory to the Matlab path. 32 | 33 | The polynomial optimization (sum-of-squares) examples in `examples/poly_opt/` require the following additional software: 34 | 35 | 1. `Padua2DM` (a Matlab/Octave package by M. Caliari, S. De Marchi, A. Sommariva, and M. Vianello for interpolation and 36 | cubature at the Padua points): [download](http://profs.sci.univr.it/~caliari/software.htm) to any directory and add its directory to the Matlab path. 37 | 38 | 2. `Chebfun` (an open-source package for numerical computing with functions): [download](http://www.chebfun.org/download/) and install following the installation instructions. 39 | Our code has been developed and tested with Chebfun version 5.5.0. 40 | 41 | 3. `partitions` (a function by John D'Errico to compute all partitions of an integer): [download](https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer) to any directory and add its directory to the Matlab path. 42 | 43 | ## Contents 44 | 45 | * `src`: source files for alfonso. Use `alfonso.m` for the oracle interface, and `alfonso_simple` for the simple interface, which lets you specify complicated cones from simple, built-in building blocks. 46 | * `examples`: contains a number of optimization problems solved with `alfonso` or `alfonso_simple`: 47 | * `examples/random_lp`: solves a random linear programming problem using either interface. 48 | * `examples/exp_design`: solves an optimal design of experiments problem. 49 | * `examples/poly_opt`: solves various polynomial optimization problems using sum-of-squares optimization. 50 | * `examples/portfolio`: solves a mean-risk portfolio optimization problem with a factor risk model and market impact. 51 | 52 | ## Additional information 53 | 54 | This package is based on a infeasible-start primal-dual interior-point algorithm that originally appeared in: 55 | 56 | > A. Skajaa and Y. Ye, A homogeneous interior-point algorithm for nonsymmetric convex conic optimization, *Mathematical Programming Ser. A* 150 (2015), pp. 391-422. [https://doi.org/10.1007/s10107-014-0773-1](https://doi.org/10.1007/s10107-014-0773-1) 57 | 58 | The alfonso implementation is derived from the corrected analysis of this algorithm published in 2017: 59 | 60 | > D. Papp and S. Yıldız. On "A homogeneous interior-point algorithm for nonsymmetric convex conic optimization". [https://arxiv.org/abs/1712.00492](https://arxiv.org/abs/1712.00492) 61 | 62 | The Matlab software was first published here: 63 | > D. Papp and S. Yıldız. alfonso: Matlab package for nonsymmetric conic optimization, 64 | *INFORMS Journal on Computing* 34(1) (2022), pp. 11-19. [https://doi.org/10.1287/ijoc.2021.1058](https://doi.org/10.1287/ijoc.2021.1058) 65 | 66 | The first version of alfonso was created at North Carolina State University (NCSU) and at the The Statistical and Applied Mathematical Sciences Institute (SAMSI), with partial support from the National Science Foundation. Recent (2023-) improvements of alfonso are supported in part by the Air Force Office of Scientific Research. Grant numbers: 67 | * NSF: 68 | * DMS-1638521 69 | * DMS-1719828 70 | * DMS-1847865 71 | * AFOSR: 72 | * FA9550-23-1-0370 73 | -------------------------------------------------------------------------------- /alfonso.bib: -------------------------------------------------------------------------------- 1 | @article{PappYildiz2019sos-without-sdp, 2 | title={Sum-of-squares optimization without semidefinite programming}, 3 | author={Papp, D{\'a}vid and Y{\i}ld{\i}z, Sercan}, 4 | journal={SIAM Journal on Optimization}, 5 | volume={29}, 6 | number={1}, 7 | pages={822--851}, 8 | year={2019}, 9 | url={https://doi.org/10.1137/17M1160124}, 10 | doi={10.1137/17M1160124} 11 | } 12 | 13 | @article{PappYildiz2022alfonso, 14 | title={{alfonso}: {M}atlab package for nonsymmetric conic optimization}, 15 | author={Papp, D{\'a}vid and Y{\i}ld{\i}z, Sercan}, 16 | journal={INFORMS Journal on Computing}, 17 | volume={34}, 18 | number={1}, 19 | pages={11--19}, 20 | year={2022}, 21 | url={https://doi.org/10.1287/ijoc.2021.1058}, 22 | doi={10.1287/ijoc.2021.1058} 23 | } 24 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | Each subdirectory contains a number of optimization problems solved with `alfonso` or `alfonso_simple`: 4 | * `examples/random_lp`: solves a random linear programming problem using either interface. 5 | * `examples/exp_design`: solves an optimal design of experiments problem. 6 | * `examples/poly_opt`: solves various polynomial optimization problems using sum-of-squares optimization. 7 | * `examples/portfolio`: solves a mean-risk portfolio optimization problem with a factor risk model and market impact. 8 | 9 | -------------------------------------------------------------------------------- /examples/exp_design/e_design.m: -------------------------------------------------------------------------------- 1 | function e_design() 2 | % Numerical experiments with E-optimal design problems comparing alfonso, 3 | % Mosek 9.2.6 and SCS 2.1.7 4 | % 5 | % ------------------------------------------------------------------------- 6 | % EXTERNAL PACKAGES CALLED IN THIS FILE: YALMIP, Mosek, SCS 7 | % ------------------------------------------------------------------------- 8 | 9 | datetime() 10 | rng(2020,'twister'); 11 | 12 | for i=1:5 % warning: i=6:10 is too slow for SCS 13 | 14 | n = 50*i; 15 | p = 2*n; 16 | 17 | V = randn(n,p); 18 | 19 | 20 | % ALFONSO 21 | probData = struct('c',[-1;zeros(p,1)],'A',[0,ones(1,p)],'b',1); 22 | x0 = [0; ones(p,1)/p]; 23 | pars = struct('ext', true, 'nonneg', true, 'Vs', {{V}}, 'ws', ones(p,1)); % cone parameters 24 | opts = struct('optimTol',1e-8,'linSolveFun',@linsolveB); % solver options 25 | 26 | res = alfonso(probData,x0,@gH_rk1LMI,pars,opts); 27 | % print # of iterations and solver time 28 | fprintf('alf: %d %d %.2f\n\n', n, res.nIterations, res.time); 29 | 30 | 31 | % YALMIP PREP FOR MOSEK AND SCS 32 | x = sdpvar(p,1); 33 | t = sdpvar(1); 34 | cons = [sum(x)==1, x>=0, -t*eye(n)+V*diag(x)*V' >= 0]; 35 | 36 | % MOSEK 37 | opts = sdpsettings('solver','mosek','savesolveroutput',1); 38 | res = optimize(cons,-t,opts); % maximize t 39 | % print only solver (not yalmip) time 40 | fprintf('mos: %d %d %.2f\n\n', n, res.solveroutput.res.info.MSK_IINF_INTPNT_ITER, res.solvertime); 41 | 42 | %%% SCS 43 | %%opts = sdpsettings('solver','scs','savesolveroutput',1,'scs.max_iters',100000,'scs.eps',1e-3); 44 | %%res = optimize(cons,-t,opts); % maximize t 45 | %%% print only solver (not yalmip) time 46 | %%fprintf('scs: %d %d %.2f\n\n', n, res.solveroutput.info.iter, res.solvertime); 47 | end 48 | 49 | disp('Done!'); 50 | 51 | return 52 | 53 | -------------------------------------------------------------------------------- /examples/poly_opt/ChebInterval.m: -------------------------------------------------------------------------------- 1 | % This code generates parameters for the interpolant basis representation 2 | % of univariate sum-of-squares polynomials. 3 | % ------------------------------------------------------------------------- 4 | % Copyright (C) 2018-2020 David Papp and Sercan Yildiz. 5 | % 6 | % Redistribution and use of this software are subject to the terms of the 7 | % 2-Clause BSD License. You should have received a copy of the license along 8 | % with this program. If not, see . 9 | % 10 | % Authors: 11 | % David Papp 12 | % Sercan Yildiz 13 | % 14 | % Version: 06/14/2018 15 | % 16 | % This code has been developed and tested with Matlab R2016b. 17 | % ------------------------------------------------------------------------- 18 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 19 | % chebpts, chebpolyval from Chebfun. Chebfun is an open-source package for 20 | % numerical computing with functions: http://www.chebfun.org/. 21 | % Our code has been developed and tested with Chebfun version 5.5.0. 22 | % The latest version of the Chebfun package can be downloaded from 23 | % http://www.chebfun.org/download/. 24 | % ------------------------------------------------------------------------- 25 | 26 | 27 | function intParams = ChebInterval(d) 28 | % This method generates parameters for the interpolant basis representation 29 | % of univariate sum-of-squares polynomials. 30 | % -------------------------------------------------------------------------- 31 | % USAGE of "ChebInterval" 32 | % intParams = ChebInterval(d) 33 | % -------------------------------------------------------------------------- 34 | % INPUT 35 | % d: degree of polynomials to be squared 36 | % 37 | % OUTPUT 38 | % intParams: interpolation parameters 39 | % - intParams.n: number of arguments to the polynomials. intParams.n = 1 40 | % in the univariate case. 41 | % - intParams.d: degree of polynomials to be squared 42 | % - intParams.L: dimension of the space of (intParams.n)-variate 43 | % degree-d polynomials. intParams.L = d+1 in the 44 | % univariate case. 45 | % - intParams.U: dimension of the space of (intParams.n)-variate 46 | % degree-(2*d) polynomials. intParams.U = 2*d+1 in the 47 | % univariate case. 48 | % - intParams.pts: Chebyshev points of the second kind for degree-(2*d) 49 | % polynomial interpolation. (intParams.U x 1) array. 50 | % - intParams.w: (scaled) weights for Clenshaw-Curtis quadrature 51 | % - intParams.P0: evaluations of Chebyshev polynomials of the first kind 52 | % up to degree d at the points intParams.pts. 53 | % (intParams.U x intParams.L) array. 54 | % - intParams.P: evaluations of a basis for the space of 55 | % (intParams.n)-variate degree-d polynomials at the points 56 | % intParams.pts. (intParams.U x intParams.L) array with 57 | % orthonormal columns. 58 | % -------------------------------------------------------------------------- 59 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 60 | % chebpts, chebpolyval from Chebfun. Chebfun is an open-source package for 61 | % numerical computing with functions: http://www.chebfun.org/. 62 | % Our code has been developed and tested with Chebfun version 5.5.0. 63 | % The latest version of the Chebfun package can be downloaded from 64 | % http://www.chebfun.org/download/. 65 | % ------------------------------------------------------------------------- 66 | 67 | n = 1; % univariate polynomials 68 | 69 | intParams.n = n; 70 | intParams.d = d; 71 | intParams.L = nchoosek(n+d, n); 72 | intParams.U = nchoosek(n+2*d, n); 73 | [intParams.pts, intParams.w] = chebpts(intParams.U); 74 | intParams.w = intParams.w(:); 75 | intParams.P = zeros(intParams.U, intParams.L); 76 | 77 | col = 0; 78 | lrEye = fliplr(eye(d+1)); 79 | for t = 0:d % polynomials with degree up to d 80 | col = col+1; 81 | intParams.P(:,col) = chebpolyval(lrEye(:,t+1), intParams.pts); 82 | end 83 | 84 | intParams.P0 = intParams.P; 85 | [intParams.P, ~] = qr(intParams.P, 0); 86 | 87 | return 88 | -------------------------------------------------------------------------------- /examples/poly_opt/FeketeCube.m: -------------------------------------------------------------------------------- 1 | % This code generates parameters for the interpolant basis representation 2 | % of sum-of-squares polynomials with an arbitrary number of variables. 3 | % It follows the approach described in: 4 | % 5 | % A. Sommariva and M. Vianello, Computing approximate Fekete points by QR 6 | % factorizations of Vandermonde matrices, Computers & Mathematics with 7 | % Applications, 57 (2009), pp. 1324-1336. Available at 8 | % https://doi.org/10.1016/j.camwa.2008.11.011. 9 | % ------------------------------------------------------------------------- 10 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 11 | % 12 | % Authors: 13 | % David Papp 14 | % Sercan Yildiz 15 | % 16 | % Date: 06/14/2018 17 | % 18 | % This code has been developed and tested with Matlab R2016b. 19 | % ------------------------------------------------------------------------- 20 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 21 | % chebpolyval from Chebfun. Chebfun is an open-source package for 22 | % numerical computing with functions: http://www.chebfun.org/. 23 | % Our code has been developed and tested with Chebfun version 5.5.0. 24 | % The latest version of the Chebfun package can be downloaded from 25 | % http://www.chebfun.org/download/. 26 | % 27 | % partitions. The partitions function computes all partitions of an integer. 28 | % We use the implementation of John D'Errico from a MATLAB Central File 29 | % Exchange post which is available at 30 | % https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer. 31 | % ------------------------------------------------------------------------- 32 | 33 | 34 | function intParams = FeketeCube(n,d) 35 | % This method generates parameters for the interpolant basis representation 36 | % of sum-of-squares polynomials with an arbitrary number of variables. 37 | % -------------------------------------------------------------------------- 38 | % USAGE of "FeketeCube" 39 | % intParams = FeketeCube(n,d) 40 | % -------------------------------------------------------------------------- 41 | % INPUT 42 | % n: number of arguments to the polynomials 43 | % d: degree of polynomials to be squared 44 | % 45 | % OUTPUT 46 | % intParams: interpolation parameters 47 | % - intParams.n: number of arguments to the polynomials 48 | % - intParams.d: degree of polynomials to be squared 49 | % - intParams.L: dimension of the space of (intParams.n)-variate 50 | % degree-d polynomials 51 | % - intParams.U: dimension of the space of (intParams.n)-variate 52 | % degree-(2*d) polynomials 53 | % - intParams.pts: approximate Fekete points for degree-(2*d) 54 | % polynomial interpolation. (intParams.U x 1) array. 55 | % - intParams.w: (scaled) weights for Clenshaw-Curtis quadrature 56 | % - intParams.P0: evaluations of n-variate product Chebyshev 57 | % polynomials of the first kind up to degree d at 58 | % the points intParams.pts 59 | % (intParams.U x intParams.L) array. 60 | % - intParams.P: evaluations of a basis for the space of 61 | % (intParams.n)-variate degree-d polynomials 62 | % at the points intParams.pts. 63 | % (intParams.U x intParams.L) array with 64 | % orthonormal columns. 65 | % - intParams.nrPoints1D: smallest number of points along a side of the 66 | % initial interpolation grid 67 | % - intParams.nrPoints: number of points in the initial interpolation 68 | % grid 69 | % -------------------------------------------------------------------------- 70 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 71 | % chebpolyval from Chebfun. Chebfun is an open-source package for 72 | % numerical computing with functions: http://www.chebfun.org/. 73 | % Our code has been developed and tested with Chebfun version 5.5.0. 74 | % The latest version of the Chebfun package can be downloaded from 75 | % http://www.chebfun.org/download/. 76 | % 77 | % partitions. The partitions function computes all partitions of an integer. 78 | % We use the implementation of John D'Errico from a MATLAB Central File 79 | % Exchange post which is available at 80 | % https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer. 81 | % ------------------------------------------------------------------------- 82 | 83 | intParams.n = n; 84 | intParams.d = d; 85 | intParams.L = nchoosek(n+d,n); 86 | intParams.U = nchoosek(n+2*d,n); 87 | 88 | intParams.nrPoints1D = 2*d+1; 89 | 90 | intParams.nrPoints = prod(intParams.nrPoints1D:intParams.nrPoints1D+n-1); 91 | pts = zeros(intParams.nrPoints,n); 92 | for j = 1:n 93 | temp = 1; 94 | for i = 1:j-1; temp = kron(temp,ones(intParams.nrPoints1D+i-1,1)); end 95 | temp = kron(temp,chebpts(intParams.nrPoints1D+j-1)); 96 | for i = j+1:n; temp = kron(temp,ones(intParams.nrPoints1D+i-1,1)); end 97 | pts(:,j) = temp; 98 | end 99 | 100 | P = ones(intParams.nrPoints,intParams.U); 101 | m = ones(intParams.U,1); 102 | 103 | col = 0; 104 | lrEye = fliplr(eye(2*d+1)); 105 | for t = 0:2*d % polynomials with total degree up to 2*d 106 | allDegs = partitions(t, ones(1,n)); 107 | [nrDegs,~] = size(allDegs); 108 | for i = 1:nrDegs 109 | col = col+1; 110 | for j = 1:n 111 | dj = allDegs(i,j); 112 | P(:,col) = P(:,col).*chebpolyval(lrEye(:,dj+1),pts(:,j)); 113 | if dj == 1; m(col) = 0; 114 | else; m(col) = m(col)*(((-1)^dj+1)/(1-dj^2)); end; 115 | end 116 | end 117 | end 118 | 119 | w = P'\m; 120 | ind = abs(w)>0; 121 | 122 | % extracts the positive entries of w 123 | w = w(ind); 124 | % extracts the subset of points indexed with the support of w 125 | pts = pts(ind,:); 126 | % extracts the subset of polynomials up to total degree d 127 | P = P(ind,1:intParams.L); 128 | 129 | intParams.w = w; 130 | intParams.pts = pts; 131 | intParams.P0 = P; 132 | [intParams.P,~] = qr(P,0); 133 | 134 | return 135 | -------------------------------------------------------------------------------- /examples/poly_opt/PaduaSquare.m: -------------------------------------------------------------------------------- 1 | % This code generates parameters for the interpolant basis representation 2 | % of bivariate sum-of-squares polynomials. 3 | % ------------------------------------------------------------------------- 4 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 5 | % 6 | % Authors: 7 | % David Papp 8 | % Sercan Yildiz 9 | % 10 | % Date: 06/14/2018 11 | % 12 | % This code has been developed and tested with Matlab R2023b. 13 | % ------------------------------------------------------------------------- 14 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 15 | % pdpts, pdwtsMM from Padua2DM. Padua2DM is a Matlab package from M. Caliari, 16 | % S. De Marchi, A. Sommariva, and M. Vianello for interpolation and 17 | % cubature at the Padua points. It can be downloaded from 18 | % http://profs.sci.univr.it/~caliari/software.htm. 19 | % 20 | % chebpolyval from Chebfun. Chebfun is an open-source package for 21 | % numerical computing with functions: http://www.chebfun.org/. 22 | % Our code has been developed and tested with Chebfun version 5.5.0. 23 | % The latest version of the Chebfun package can be downloaded from 24 | % http://www.chebfun.org/download/. 25 | % 26 | % partitions. The partitions function computes all partitions of an integer. 27 | % We use the implementation of John D'Errico from a MATLAB Central File 28 | % Exchange post which is available at 29 | % https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer. 30 | % ------------------------------------------------------------------------- 31 | 32 | 33 | function intParams = PaduaSquare(d) 34 | % This method generates parameters for the interpolant basis representation 35 | % of bivariate sum-of-squares polynomials. 36 | % -------------------------------------------------------------------------- 37 | % USAGE of "PaduaSquare" 38 | % intParams = PaduaSquare(d) 39 | % -------------------------------------------------------------------------- 40 | % INPUT 41 | % d: degree of polynomials to be squared 42 | % 43 | % OUTPUT 44 | % intParams: interpolation parameters 45 | % - intParams.n: number of arguments to the polynomials. intParams.n = 2 46 | % in the bivariate case. 47 | % - intParams.d: degree of polynomials to be squared 48 | % - intParams.L: dimension of the space of (intParams.n)-variate 49 | % degree-d polynomials. intParams.L = nchoosek(d+2,2) in 50 | % the bivariate case. 51 | % - intParams.U: dimension of the space of (intParams.n)-variate 52 | % degree-(2*d) polynomials. intParams.U = nchoosek(2*d+2,2) 53 | % in the bivariate case. 54 | % - intParams.pts: Padua points for degree-(2*d) polynomial interpolation. 55 | % (intParams.U x 1) array. 56 | % - intParams.w: (scaled) weights for Clenshaw-Curtis quadrature 57 | % - intParams.P0: evaluations of bivariate product Chebyshev polynomials 58 | % of the first kind up to degree d at the points 59 | % intParams.pts. (intParams.U x intParams.L) array. 60 | % - intParams.P: evaluations of a basis for the space of 61 | % (intParams.n)-variate degree-d polynomials at the points 62 | % intParams.pts. (intParams.U x intParams.L) array with 63 | % orthonormal columns. 64 | % -------------------------------------------------------------------------- 65 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 66 | % pdpts, pdwtsMM from Padua2DM. Padua2DM is a Matlab package from M. Caliari, 67 | % S. De Marchi, A. Sommariva, and M. Vianello for 68 | % interpolation and cubature at the Padua points. It can be downloaded from 69 | % http://profs.sci.univr.it/~caliari/software.htm. 70 | % 71 | % chebpolyval from Chebfun. Chebfun is an open-source package for 72 | % numerical computing with functions: http://www.chebfun.org/. 73 | % Our code has been developed and tested with Chebfun version 5.5.0. 74 | % The latest version of the Chebfun package can be downloaded from 75 | % http://www.chebfun.org/download/. 76 | % 77 | % partitions. The partitions function computes all partitions of an integer. 78 | % We use the implementation of John D'Errico from a MATLAB Central File 79 | % Exchange post which is available at 80 | % https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer. 81 | % ------------------------------------------------------------------------- 82 | 83 | n = 2; % bivariate polynomials 84 | 85 | intParams.n = n; 86 | intParams.d = d; 87 | intParams.L = nchoosek(n+d,n); 88 | intParams.U = nchoosek(n+2*d,n); 89 | intParams.pts = pdpts(2*d); 90 | intParams.w = pdwtsMM(2*d); 91 | intParams.P = ones(intParams.U,intParams.L); 92 | 93 | col = 0; 94 | lrEye = fliplr(eye(d+1)); 95 | for t = 0:d % polynomials with total degree up to d 96 | allDegs = partitions(t, ones(1,n)); 97 | [nrDegs,~] = size(allDegs); 98 | for i = 1:nrDegs 99 | col = col+1; 100 | for j = 1:n 101 | dj = allDegs(i,j); 102 | intParams.P(:,col) = ... 103 | intParams.P(:,col).*chebpolyval(lrEye(:,dj+1),intParams.pts(:,j)); 104 | end 105 | end 106 | end 107 | 108 | intParams.P0 = intParams.P; 109 | [intParams.P,~] = qr(intParams.P,0); 110 | 111 | return 112 | -------------------------------------------------------------------------------- /examples/poly_opt/README.md: -------------------------------------------------------------------------------- 1 | # examples/poly_opt 2 | 3 | * `polyOpt.m` 4 | This code formulates and solves the *polynomial optimization* problem 5 | using a sum-of-squares optimization approach and polynomial interpolants 6 | as described in: 7 | 8 | > Papp, D; Yildiz, S: Sum-of-squares optimization without semidefinite 9 | > programming. *SIAM Journal on Optimization* 29(1), 2019, pp. 822-851. 10 | > URL: https://doi.org/10.1137/17M1160124 11 | 12 | 13 | * `demo_polyOpt.m` 14 | This script demonstrates how to use the provided methods to solve the 15 | polynomial optimization problems described in the above paper. 16 | 17 | 18 | * `polyEnv.m` 19 | This code formulates and solves the *polynomial envelope* problem 20 | using a sum-of-squares optimization approach and polynomial interpolants 21 | as described in: 22 | 23 | > Papp, D; Yildiz, S: Sum-of-squares optimization without semidefinite 24 | > programming. *SIAM Journal on Optimization* 29(1), 2019, pp. 822-851. 25 | > URL: https://doi.org/10.1137/17M1160124 26 | 27 | 28 | * `demo_polyEnv.m` 29 | This script demonstrates how to use the provided methods to solve the 30 | polynomial envelope problem also described in the above paper. 31 | 32 | 33 | * `ChebInterval.m` 34 | This code generates parameters for the interpolant basis representation 35 | of univariate sum-of-squares polynomials. 36 | 37 | 38 | * `PaduaSquare.m` 39 | This code generates parameters for the interpolant basis representation 40 | of bivariate sum-of-squares polynomials. 41 | 42 | 43 | * `FeketeCube.m` 44 | This code generates parameters for the interpolant basis representation 45 | of sum-of-squares polynomials with three or more variables. It follows 46 | the approach described in: 47 | 48 | > A. Sommariva and M. Vianello, Computing approximate Fekete points by 49 | > QR factorizations of Vandermonde matrices, *Computers & Mathematics* 50 | > *with Applications*, 57 (2009), pp. 1324-1336. URL: 51 | > https://doi.org/10.1016/j.camwa.2008.11.011. 52 | 53 | 54 | EXTERNAL FUNCTIONS REQUIRED BY THESE EXAMPLES: 55 | 56 | * `chebpts`, `chebpolyval` from Chebfun. Chebfun is an open-source package for 57 | numerical computing with functions: http://www.chebfun.org/. 58 | Our code has been developed and tested with Chebfun version 5.5.0. 59 | The latest version of the Chebfun package can be downloaded from 60 | http://www.chebfun.org/download/. 61 | 62 | * `pdpts`, `pdwtsMM` from Padua2DM. Padua2DM is a Matlab package from M. Caliari, 63 | S. De Marchi, A. Sommariva, and M. Vianello for interpolation and 64 | cubature at the Padua points. It can be downloaded from 65 | http://profs.sci.univr.it/~caliari/software.htm. 66 | 67 | * `partitions`. The partitions function computes all partitions of an integer. 68 | We use the implementation of John D'Errico from a MATLAB Central File 69 | Exchange post which is available at 70 | https://www.mathworks.com/matlabcentral/fileexchange/12009-partitions-of-an-integer. -------------------------------------------------------------------------------- /examples/poly_opt/demo_polyEnv.m: -------------------------------------------------------------------------------- 1 | % This script demonstrates how to use the provided methods to solve the 2 | % polynomial envelope problem described in: 3 | % 4 | % D. Papp and S. Yildiz. Sum-of-squares optimization without semidefinite 5 | % programming. Available at https://arxiv.org/abs/1712.01792. 6 | % ------------------------------------------------------------------------- 7 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 8 | % 9 | % Authors: 10 | % David Papp 11 | % Sercan Yildiz 12 | % 13 | % Date: 06/14/2018 14 | % 15 | % This code has been developed and tested with Matlab R2016b. 16 | % ------------------------------------------------------------------------- 17 | 18 | seed = 2017; 19 | 20 | tol = 1e-08; 21 | 22 | %intParams = ChebInterval(100); % in the case of univariate polynomials 23 | %intParams = PaduaSquare(10); % in the case of bivariate polynomials 24 | intParams = FeketeCube(3, 6); % in the case of n-variate polynomials 25 | 26 | results = polyEnv(intParams, 2, 5, tol, seed); 27 | -------------------------------------------------------------------------------- /examples/poly_opt/demo_polyOpt.m: -------------------------------------------------------------------------------- 1 | % This script demonstrates how to use the provided methods to solve the 2 | % polynomial optimization problems described in: 3 | % 4 | % D. Papp and S. Yildiz. Sum-of-squares optimization without semidefinite 5 | % programming. Available at https://arxiv.org/abs/1712.01792. 6 | % ------------------------------------------------------------------------- 7 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 8 | % 9 | % Authors: 10 | % David Papp 11 | % Sercan Yildiz 12 | % 13 | % Date: 06/14/2018 14 | % 15 | % This code has been developed and tested with Matlab R2016b. 16 | % ------------------------------------------------------------------------- 17 | 18 | tol = 1e-07; 19 | 20 | % intParams = PaduaSquare(10); % in the case of bivariate polynomials 21 | intParams = FeketeCube(4, 2); % in the case of n-variate polynomials; the 22 | % first argument must be n 23 | 24 | % results = polyOpt(intParams, 'robinson', tol); % bivariate example 25 | results = polyOpt(intParams, 'caprasse', tol); % 4-variate example 26 | -------------------------------------------------------------------------------- /examples/poly_opt/polyEnv.m: -------------------------------------------------------------------------------- 1 | % This code is an implementation of the sum-of-squares optimization approach 2 | % based on non-symmetric conic optimization and polynomial interpolants 3 | % presented in: 4 | % 5 | % D. Papp and S. Yildiz. Sum-of-squares optimization without semidefinite 6 | % programming. Available at https://arxiv.org/abs/1712.01792. 7 | % 8 | % The implementation formulates and solves the polynomial envelope problem 9 | % described in the same reference. 10 | % 11 | % Note: the implementation follows the paper above. With the current 12 | % version of alfonso, using alfonso_simple and the `rk1LMI' cone is 13 | % recommended, as it is far simpler and more efficient. 14 | % ------------------------------------------------------------------------- 15 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 16 | % 17 | % Authors: 18 | % David Papp 19 | % Sercan Yildiz 20 | % 21 | % Date: 2024/07/15 22 | % 23 | % This code has been developed and tested with Matlab R2023b. 24 | % ------------------------------------------------------------------------- 25 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 26 | % None. 27 | % ------------------------------------------------------------------------- 28 | 29 | 30 | function results = polyEnv(intParams, numPolys, degPolys, tol, seed) 31 | % This is the main method for the sum-of-squares optimization approach to 32 | % to the polynomial envelope problem. 33 | % -------------------------------------------------------------------------- 34 | % USAGE of "polyEnv" 35 | % results = polyEnv(intParams, numPolys, degPolys, tol, seed) 36 | % Use seed = 2017 to reproduce the results in the reference above. 37 | % -------------------------------------------------------------------------- 38 | % INPUT 39 | % intParams: data for the interpolant basis (as generated in 40 | % ChebInterval.m, for instance) 41 | % - intParams.n: number of arguments to the polynomials 42 | % - intParams.d: largest degree of polynomials to be squared 43 | % - intParams.L: dimension of the space of (intParams.n)-variate 44 | % degree-(intParams.d) polynomials 45 | % - intParams.U: dimension of the space of (intParams.n)-variate 46 | % degree-(2*intParams.d) polynomials 47 | % - intParams.pts: interpolation points. (intParams.U x intParams.n) array. 48 | % - intParams.w: quadrature weights for the interpolation points 49 | % - intParams.P0: evaluations of a basis for the space of 50 | % (intParams.n)-variate degree-(intParams.d) 51 | % polynomials at the interpolation points. 52 | % (intParams.U x intParams.L) array. columns are indexed 53 | % with the basis polynomials. it is assumed that the first 54 | % nchoosek(intParams.n+k,intParams.n) columns are a 55 | % basis for the space of (intParams.n)-variate degree-k 56 | % polynomials for k = 1,...,intParams.d. 57 | % - intParams.P: similar to intParams.P0 58 | % numPolys: number of approximated polynomials 59 | % degPolys: degree of approximated polynomials. it is assumed that 60 | % degPolys <= intParams.d. 61 | % tol: tolerance parameter for optimization 62 | % seed: seed for the random number generator 63 | % 64 | % OUTPUT 65 | % results: final solution and iteration statistics 66 | % - results.nIterations: total number of iterations 67 | % - results.alphaPred: predictor step size at each iteration 68 | % - results.betaPred: neighborhood parameter at the end of the 69 | % predictor phase at each iteration 70 | % - results.etaCorr: neighborhood parameter at the end of the 71 | % corrector phase at each iteration 72 | % - results.mu: complementarity gap at each iteration 73 | % - results.x: final value of the primal variables 74 | % - results.s: final value of the dual slack variables 75 | % - results.y: final value of the dual free variables 76 | % - results.tau: final value of the tau-variable 77 | % - results.kappa: final value of the kappa-variable 78 | % - results.pObj: final primal objective value 79 | % - results.dObj: final dual objective value 80 | % - results.dGap: final duality gap 81 | % - results.cGap: final complementarity gap 82 | % - results.rel_dGap: final relative duality gap 83 | % - results.rel_cGap: final relative complementarity gap 84 | % - results.pRes: final primal residuals 85 | % - results.dRes: final dual residuals 86 | % - results.pIn: final primal infeasibility 87 | % - results.dIn: final dual infeasibility 88 | % - results.rel_pIn: final relative primal infeasibility 89 | % - results.rel_dIn: final relative dual infeasibility 90 | % -------------------------------------------------------------------------- 91 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 92 | % None. 93 | % ------------------------------------------------------------------------- 94 | 95 | rng(seed, 'twister'); 96 | 97 | n = intParams.n; 98 | d = intParams.d; 99 | U = intParams.U; 100 | L = intParams.L; 101 | P = intParams.P; 102 | pts = intParams.pts; 103 | 104 | % ORDER OF VARIABLES 105 | % x = [x_1; x_2; ...] \in WSOS_(n,2*d)^* x WSOS_(n,2*d)^* x ... 106 | % x_1 corresponds to the 1st approximated polynomial, x_2 to the 2nd,... 107 | 108 | % WEIGHTS 109 | % weight #0: 1 110 | % degree of associated SOS multiplier: 2*d 111 | % weight #j for j = 1,...,n: (1-t_j^2) 112 | % degree of associated SOS multiplier: 2*d-2 113 | 114 | LWts = repmat(nchoosek(n+d-1,n),n,1); 115 | 116 | % PARAMETERS ASSOCIATED WITH THE METHOD gH_polyEnv 117 | % see input description for gH_polyEnv for details 118 | gH_Params.n = n; 119 | gH_Params.d = d; 120 | gH_Params.U = U; 121 | gH_Params.numPolys = numPolys; 122 | 123 | gH_Params.L = L; 124 | gH_Params.LWts = LWts; 125 | nu = numPolys*(L+sum(LWts)); 126 | gH_Params.bnu = nu+1; 127 | 128 | % evaluations of the weights 1-t_j^2 at the interpolation points: 129 | % the first column is for the weight 1-t_1^2, the second is for 130 | % 1-t_2^2,... 131 | wtVals = 1-pts.^2; 132 | 133 | % P has orthonormal columns: 134 | gH_Params.P = P; 135 | % associated positive semidefinite cone constraints: 136 | % P'*diag(x_1)*P >= 0, 137 | % P'*diag(x_2)*P >= 0,... 138 | PWts = cell(n,1); 139 | for j = 1:n 140 | PWts{j} = diag(sqrt(wtVals(:,j)))*P(:,1:LWts(j)); 141 | [PWts{j}, ~] = qr(PWts{j}, 0); 142 | % associated positive semidefinite cone constraints: 143 | % PWts{j}'*diag(x_1)*PWts{j} >= 0, 144 | % PWts{j}'*diag(x_2)*PWts{j} >= 0,... 145 | end 146 | gH_Params.PWts = PWts; 147 | 148 | % x_1 \in WSOS_(n,2*d)^* <=> 149 | % P'*diag(x_1)*P >= 0, PWts{1}'*diag(x_1)*PWts{1} >= 0, PWts{2}'*diag(x_1)*PWts{2} >= 0,... 150 | % x_2 \in WSOS_(n,2*d)^* <=> 151 | % P'*diag(x_1)*P >= 0, PWts{1}'*diag(x_2)*PWts{1} >= 0, PWts{2}'*diag(x_2)*PWts{2} >= 0,... 152 | 153 | % DATA FOR THE CONIC OPTIMIZATION PROBLEM 154 | probData.A = sparse(repmat(eye(U),1,numPolys)); 155 | probData.b = intParams.w; 156 | probData.c = genRandPolyVals(n, numPolys, degPolys, intParams.P0); 157 | 158 | tic 159 | 160 | % INITIAL PRIMAL ITERATE 161 | x0 = ones(numPolys*U,1); 162 | [~, g0, ~, ~] = gH_polyEnv(x0, gH_Params); 163 | % scaling factor for the primal problem 164 | rP = max((1+abs(probData.b))./(1+abs(probData.A*x0))); 165 | % scaling factor for the dual problem 166 | rD = max((1+abs(g0))./(1+abs(probData.c))); 167 | % initial primal iterate 168 | x0 = repmat(sqrt(rP*rD),numPolys*U,1); 169 | 170 | % CUSTOM ALGORITHMIC OPTIONS 171 | opts.optimTol = tol; 172 | 173 | % CALL TO alfonso 174 | results = alfonso(probData, x0, @gH_polyEnv, gH_Params, opts); 175 | fprintf('alfonso is done.\n'); 176 | 177 | toc 178 | 179 | % prints error statistics 180 | fprintf('\n'); 181 | fprintf('FINAL:\n'); 182 | fprintf('Relative primal infeasibility: %d\n', results.rel_pIn); 183 | fprintf('Relative dual infeasibility: %d\n', results.rel_dIn); 184 | fprintf('Relative duality gap: %d\n', results.rel_dGap); 185 | fprintf('Relative complementarity gap: %d\n\n', results.rel_cGap); 186 | 187 | return 188 | 189 | function [in, g, Hi, Li] = gH_polyEnv(x, params) 190 | % This method computes the gradient and Hessian of the barrier function for 191 | % the problem of polynomial envelopes. 192 | % -------------------------------------------------------------------------- 193 | % USAGE of "gH_polyEnv" 194 | % [in, g, H, L] = gH_polyEnv(x, params) 195 | % -------------------------------------------------------------------------- 196 | % INPUT 197 | % x: primal iterate 198 | % params: parameters associated with the method gH 199 | % - params.n: number of arguments to the polynomials 200 | % - params.d: largest degree of polynomials to be squared 201 | % - params.U: dimension of the space of (params.n)-variate 202 | % degree-(2*params.d) polynomials 203 | % - params.numPolys: number of approximated polynomials 204 | % - params.L: dimension of the space of (params.n)-variate 205 | % degree-(params.d) polynomials. 206 | % - params.LWts: dimensions of the "weighted" polynomial spaces. 207 | % params.LWts(j) is the dimension of the space of 208 | % (params.n)-variate degree-(params.d-1) polynomials 209 | % for j = 1,...,n. 210 | % - params.bnu: complexity parameter of the augmented barrier (nu-bar) 211 | % - params.P: evaluations of the basis for the space of 212 | % (params.n)-variate degree-(params.d) polynomials 213 | % at the interpolation points 214 | % - params.PWts: evaluations of "weighted" basis polynomials at the 215 | % interpolation points. params.PWts{j} is 216 | % the evaluations of the basis for the "weighted" 217 | % space of (params.n)-variate degree-(params.d-1) 218 | % polynomials at the interpolation points for 219 | % j = 1,...,n. the weight corresponding to 220 | % params.PWts{j} is sqrt(1-t_j^2). 221 | % 222 | % OUTPUT 223 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 224 | % interior of the cone. 225 | % g: gradient of the barrier function at x 226 | % Hi: function representing the inverse Hessian action at x 227 | % Li: function representing the inverse Cholesky action or similar 228 | % (see the built-in barrier functions or the documentation for more info) 229 | % -------------------------------------------------------------------------- 230 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 231 | % None. 232 | % ------------------------------------------------------------------------- 233 | 234 | in = 1; 235 | n = params.n; 236 | U = params.U; 237 | numPolys = params.numPolys; 238 | P = params.P; 239 | PWts = params.PWts; 240 | 241 | g = zeros(numPolys*U,1); 242 | L = cell(numPolys,1); 243 | 244 | % ORDER OF VARIABLES 245 | % x = [x_1; x_2; ...] \in WSOS_(n,2*d)^* x WSOS_(n,2*d)^* x ... 246 | % x_1 corresponds to the 1st approximated polynomial, x_2 to the 2nd,... 247 | 248 | off = 0; 249 | for polyId = 1:numPolys 250 | xPoly = x(off+(1:U)); 251 | % for the weight 1 252 | [inPoly, gPoly, HPoly] = gH_SOSWt(xPoly,P); 253 | 254 | if inPoly == 1 255 | for j = 1:n 256 | % for the weight 1-t_j^2 257 | [inPolyWt, gPolyWt, HPolyWt] = gH_SOSWt(xPoly,PWts{j}); 258 | inPoly = inPoly & inPolyWt; 259 | if inPoly == 1 260 | gPoly = gPoly+gPolyWt; 261 | HPoly = HPoly+HPolyWt; 262 | else 263 | gPoly = NaN; 264 | HPoly = NaN; 265 | break; 266 | end 267 | end 268 | end 269 | 270 | if inPoly == 1 271 | % checks positive semidefiniteness of HPoly one more time. 272 | % HPoly may fail Cholesky factorization even if inPoly == 1 273 | % due to numerical errors in summing up HPolyWt's above. 274 | [LPoly, err] = chol(HPoly,'lower'); 275 | inPoly = inPoly & (err == 0); 276 | end 277 | 278 | if inPoly == 1 279 | g(off+(1:U)) = gPoly; 280 | L{polyId} = LPoly; 281 | off = off + U; 282 | else 283 | in = 0; 284 | g = NaN; 285 | Li = NaN; 286 | Hi = NaN; 287 | return; 288 | end 289 | end 290 | 291 | if nargout >= 3 292 | Hi = @(v)(concatH(L,v)); 293 | if nargout == 4 294 | Li = @(M)(concatL(L,M)); 295 | end 296 | end 297 | 298 | return 299 | 300 | function LiM = concatL(Ls, M) 301 | 302 | U = size(Ls{1},1); 303 | 304 | LiMs = cell(length(Ls),1); 305 | idx = 0; % x subvector index 306 | for i=1:length(Ls) 307 | LiMs{i} = Ls{i} \ M(idx+1:idx+U, :); 308 | idx = idx + U; 309 | end 310 | LiM = vertcat(LiMs{:}); 311 | 312 | return 313 | 314 | function Hiv = concatH(Ls, v) 315 | 316 | U = size(Ls{1},1); 317 | 318 | Hiv = zeros(size(v)); 319 | idx = 0; % x subvector index 320 | for i=1:length(Ls) 321 | Hiv(idx+1:idx+U) = Ls{i}'\(Ls{i}\v(idx+1:idx+U)); 322 | idx = idx + U; 323 | end 324 | 325 | return 326 | 327 | 328 | 329 | function [in, g, H] = gH_SOSWt(x, P) 330 | % This method computes the gradient and Hessian of a barrier function term 331 | % corresponding to a single weight and single approximated polynomial for 332 | % the problem of polynomial envelopes. 333 | % -------------------------------------------------------------------------- 334 | % USAGE of "gH_SOSWt" 335 | % [in, g, H] = gH_SOSWt(x, P) 336 | % -------------------------------------------------------------------------- 337 | % INPUT 338 | % x: subvector of the primal iterate corresponding to a single 339 | % approximated polynomial 340 | % P: evaluations of "weighted" basis polynomials at the 341 | % interpolation points 342 | % 343 | % OUTPUT 344 | % in: 0 if P'*diag(x)*P is not positive definite. 1 if P'*diag(x)*P is 345 | % positive definite. 346 | % g: gradient of the barrier function term corresponding to a single 347 | % weight and single approximated polynomial at x 348 | % H: Hessian of the barrier function term corresponding to a single 349 | % weight and single approximated polynomial at x 350 | % -------------------------------------------------------------------------- 351 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 352 | % None. 353 | % ------------------------------------------------------------------------- 354 | 355 | Y = P'*diag(x)*P; 356 | 357 | if ~issymmetric(Y) 358 | Y = (Y+Y')/2; 359 | end 360 | 361 | [L, err] = chol(Y, 'lower'); 362 | if err > 0 363 | in = 0; 364 | g = NaN; 365 | H = NaN; 366 | else 367 | in = 1; 368 | V = L\P'; 369 | H = V'*V; % VtV; not the Hessian yet, but below we compute the Hessian in-place from this. 370 | 371 | g = -diag(H); 372 | H = H.^2; 373 | end 374 | 375 | return 376 | 377 | function vals = genRandPolyVals(n, numPolys, degPolys, P) 378 | % This method computes evaluations of randomly generated polynomials at 379 | % the interpolation points. 380 | % -------------------------------------------------------------------------- 381 | % USAGE of "genRandPolyVals" 382 | % vals = genRandPolyVals(n, numPolys, degPolys, P) 383 | % -------------------------------------------------------------------------- 384 | % INPUT 385 | % n: number of arguments to the polynomials 386 | % numPolys: number of polynomials to be generated 387 | % degPolys: degree of polynomials to be generated. it is assumed that 388 | % degPolys <= d. 389 | % P: evaluations of a basis for the space of n-variate degree-d 390 | % polynomials at the interpolation points. it is assumed that 391 | % the first nchoosek(n+degPolys,degPolys) columns are a basis 392 | % for the space of n-variate degree-(degPolys) polynomials. 393 | % 394 | % OUTPUT 395 | % vals: evaluations of numPolys randomly generated n-variate 396 | % degree-(degPolys) polynomials at the interpolation points. 397 | % the evaluation vectors are stacked vertically. 398 | % -------------------------------------------------------------------------- 399 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 400 | % None. 401 | % ------------------------------------------------------------------------- 402 | 403 | % generates coefficients in the basis that indexes the columns of P 404 | LDegs = nchoosek(n+degPolys, n); 405 | coeffs = randi([-9,9], LDegs, numPolys); 406 | vals = P(:,1:LDegs)*coeffs; 407 | 408 | vals = vals(:); 409 | 410 | return 411 | -------------------------------------------------------------------------------- /examples/poly_opt/polyOpt.m: -------------------------------------------------------------------------------- 1 | % This code is an implementation of the sum-of-squares optimization approach 2 | % based on non-symmetric conic optimization and polynomial interpolants 3 | % presented in: 4 | % 5 | % D. Papp and S. Yildiz. Sum-of-squares optimization without semidefinite 6 | % programming. Available at https://arxiv.org/abs/1712.01792. 7 | % 8 | % The implementation formulates and solves the polynomial optimization 9 | % problems described in the same reference. 10 | % 11 | % Note: the implementation follows the paper above. With the current 12 | % version of alfonso, using alfonso_simple and the `rk1LMI' cone is 13 | % recommended, as it is far simpler and more efficient. 14 | % ------------------------------------------------------------------------- 15 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 16 | % 17 | % Authors: 18 | % David Papp 19 | % Sercan Yildiz 20 | % 21 | % Date: 2024/07/15 22 | % 23 | % This code has been developed and tested with Matlab R2023b. 24 | % ------------------------------------------------------------------------- 25 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 26 | % None. 27 | % ------------------------------------------------------------------------- 28 | 29 | function sol = polyOpt(intParams, polyName, tol) 30 | % This is the main method for the sum-of-squares optimization approach to 31 | % to the polynomial optimization problem. 32 | % -------------------------------------------------------------------------- 33 | % USAGE of "polyOpt" 34 | % results = polyOpt(intParams, polyName, tol) 35 | % -------------------------------------------------------------------------- 36 | % INPUT 37 | % intParams: data for the interpolant basis (as generated in 38 | % ChebInterval.m, for instance) 39 | % - intParams.n: number of arguments to the polynomials 40 | % - intParams.d: largest degree of polynomials to be squared 41 | % - intParams.L: dimension of the space of (intParams.n)-variate 42 | % degree-(intParams.d) polynomials 43 | % - intParams.U: dimension of the space of (intParams.n)-variate 44 | % degree-(2*intParams.d) polynomials 45 | % - intParams.pts: interpolation points. (intParams.U x intParams.n) array. 46 | % - intParams.w: quadrature weights for the interpolation points 47 | % - intParams.P0: evaluations of a basis for the space of 48 | % (intParams.n)-variate degree-(intParams.d) 49 | % polynomials at the interpolation points. 50 | % (intParams.U x intParams.L) array. columns are indexed 51 | % with the basis polynomials. it is assumed that the first 52 | % nchoosek(intParams.n+k,intParams.n) columns are a 53 | % basis for the space of (intParams.n)-variate degree-k 54 | % polynomials for k = 1,...,intParams.d. 55 | % - intParams.P: similar to intParams.P0 56 | % polyName: name of polynomial to be minimized. see the function 57 | % setPolyParams below for options. 58 | % tol: tolerance parameter for optimization 59 | % 60 | % OUTPUT 61 | % results: final solution and iteration statistics 62 | % - results.nIterations: total number of iterations 63 | % - results.alphaPred: predictor step size at each iteration 64 | % - results.betaPred: neighborhood parameter at the end of the 65 | % predictor phase at each iteration 66 | % - results.etaCorr: neighborhood parameter at the end of the 67 | % corrector phase at each iteration 68 | % - results.mu: complementarity gap at each iteration 69 | % - results.x: final value of the primal variables 70 | % - results.s: final value of the dual slack variables 71 | % - results.y: final value of the dual free variables 72 | % - results.tau: final value of the tau-variable 73 | % - results.kappa: final value of the kappa-variable 74 | % - results.pObj: final primal objective value 75 | % - results.dObj: final dual objective value 76 | % - results.dGap: final duality gap 77 | % - results.cGap: final complementarity gap 78 | % - results.rel_dGap: final relative duality gap 79 | % - results.rel_cGap: final relative complementarity gap 80 | % - results.pRes: final primal residuals 81 | % - results.dRes: final dual residuals 82 | % - results.pIn: final primal infeasibility 83 | % - results.dIn: final dual infeasibility 84 | % - results.rel_pIn: final relative primal infeasibility 85 | % - results.rel_dIn: final relative dual infeasibility 86 | % -------------------------------------------------------------------------- 87 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 88 | % None. 89 | % ------------------------------------------------------------------------- 90 | 91 | n = intParams.n; 92 | d = intParams.d; 93 | U = intParams.U; 94 | L = intParams.L; 95 | P0 = intParams.P0; 96 | pts = intParams.pts; 97 | 98 | [polyDeg, lb, ub] = setPolyParams(polyName, n); 99 | 100 | if d < ceil(polyDeg/2) 101 | error(strcat(polyName,' requires d >= ',num2str(ceil(polyDeg/2)),'.')); 102 | end 103 | 104 | % transforms points to fit the domain 105 | scale = (ub-lb)/2; 106 | shift = (lb+ub)/2; 107 | pts = bsxfun(@plus,bsxfun(@times,pts,scale'),shift'); 108 | wtVals = bsxfun(@minus,pts,lb').*bsxfun(@minus,ub',pts); 109 | 110 | % ORDER OF VARIABLES 111 | % x \in WSOS_(n,2*d)^* 112 | 113 | % WEIGHTS 114 | % weight #0: 1 115 | % degree of associated SOS multiplier: 2*d 116 | % weight #j for j = 1,...,n: (lb_j+t_j)(ub_j-t_j) 117 | % degree of associated SOS multiplier: 2*d-2 118 | 119 | LWts = nchoosek(n+d-1,n)*ones(n,1); 120 | 121 | % PARAMETERS ASSOCIATED WITH THE METHOD gH_polyOpt 122 | % see input description for gH_polyOpt for details 123 | 124 | gH_Params.n = n; 125 | gH_Params.d = d; 126 | gH_Params.U = U; 127 | 128 | gH_Params.L = L; 129 | gH_Params.LWts = LWts; 130 | nu = L + sum(LWts); 131 | gH_Params.bnu = nu+1; 132 | 133 | % P0 has columns of Chebyshev polynomial evaluations: 134 | gH_Params.P = P0; 135 | % associated positive semidefinite cone constraint: 136 | % P0'*diag(x)*P0 >= 0 137 | PWts = cell(n,1); 138 | for j = 1:n 139 | PWts{j} = diag(sqrt(wtVals(:,j)))*P0(:,1:LWts(j)); 140 | % associated positive semidefinite cone constraint: 141 | % PWts{j}'*diag(x)*PWts{j} >= 0 142 | end 143 | gH_Params.PWts = PWts; 144 | 145 | % x \in WSOS_(n,2*d)^* <=> 146 | % P'*diag(x)*P >= 0, PWts{1}'*diag(x)*PWts{1} >= 0, PWts{2}'*diag(x)*PWts{2} >= 0,... 147 | 148 | % DATA FOR THE CONIC OPTIMIZATION PROBLEM 149 | probData.A = ones(1,U); 150 | probData.b = 1; 151 | probData.c = setPolyVals(polyName, pts); 152 | 153 | tic 154 | 155 | % INITIAL PRIMAL ITERATE 156 | x0 = ones(U,1); 157 | [~, g0, ~, ~] = gH_polyOpt(x0, gH_Params); 158 | % scaling factor for the primal problem 159 | rP = max((1+abs(probData.b))./(1+abs(probData.A*x0))); 160 | % scaling factor for the dual problem 161 | rD = max((1+abs(g0))./(1+abs(probData.c))); 162 | % initial primal iterate 163 | x0 = repmat(sqrt(rP*rD), U, 1); 164 | 165 | % CUSTOM ALGORITHMIC OPTIONS 166 | opts.optimTol = tol; 167 | 168 | % CALL TO alfonso 169 | sol = alfonso(probData,x0,@gH_polyOpt,gH_Params,opts); 170 | fprintf('alfonso is done.\n'); 171 | 172 | toc 173 | 174 | % prints error statistics 175 | fprintf('\n'); 176 | fprintf('FINAL:\n'); 177 | fprintf('Relative primal infeasibility: %d\n', sol.rel_pIn); 178 | fprintf('Relative dual infeasibility: %d\n', sol.rel_dIn); 179 | fprintf('Relative duality gap: %d\n', sol.rel_dGap); 180 | fprintf('Relative complementarity gap: %d\n\n', sol.rel_cGap); 181 | 182 | return 183 | 184 | function [in, g, Hi, Li] = gH_polyOpt(x, params) 185 | % This method computes the gradient and Hessian of the barrier function for 186 | % the polynomial optimization problem. 187 | % -------------------------------------------------------------------------- 188 | % USAGE of "gH_polyOpt" 189 | % [in, g, H, L] = gH_polyOpt(x, params) 190 | % -------------------------------------------------------------------------- 191 | % INPUT 192 | % x: primal iterate 193 | % params: parameters associated with the method gH 194 | % - params.n: number of arguments to the polynomials 195 | % - params.d: largest degree of polynomials to be squared 196 | % - params.U: dimension of the space of (params.n)-variate 197 | % degree-(2*params.d) polynomials 198 | % - params.L: dimension of the space of (params.n)-variate 199 | % degree-(params.d) polynomials. 200 | % - params.LWts: dimensions of the "weighted" polynomial spaces. 201 | % params.LWts(j) is the dimension of the space of 202 | % (params.n)-variate degree-(params.d-1) polynomials 203 | % for j = 1,...,n. 204 | % - params.bnu: complexity parameter of the augmented barrier (nu-bar) 205 | % - params.P: evaluations of the basis for the space of 206 | % (params.n)-variate degree-(params.d) polynomials 207 | % at the interpolation points 208 | % - params.PWts: evaluations of "weighted" basis polynomials at the 209 | % interpolation points. params.PWts{j} is 210 | % the evaluations of the basis for the "weighted" 211 | % space of (params.n)-variate degree-(params.d-1) 212 | % polynomials at the interpolation points for 213 | % j = 1,...,n. the weight corresponding to 214 | % params.PWts{j} is sqrt((lb_j+t_j)(ub_j-t_j)). 215 | % 216 | % OUTPUT 217 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 218 | % interior of the cone. 219 | % g: gradient of the barrier function at x 220 | % H: Hessian of the barrier function at x 221 | % L: Cholesky factor of H 222 | % -------------------------------------------------------------------------- 223 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 224 | % None. 225 | % ------------------------------------------------------------------------- 226 | 227 | n = params.n; 228 | P = params.P; 229 | PWts = params.PWts; 230 | 231 | % in case we return somewhere with in == 0 232 | Li = []; 233 | Hi = []; 234 | 235 | % ORDER OF VARIABLES 236 | % x \in WSOS_(n,2*d)^* 237 | 238 | % for the weight 1 239 | [in, g, H] = gH_SOSWt(x,P); 240 | 241 | if in 242 | for j = 1:n 243 | % for the weight (lb_j+t_j)(ub_j-t_j) 244 | [inWt, gWt, HWt] = gH_SOSWt(x,PWts{j}); 245 | in = in & inWt; 246 | if in 247 | g = g+gWt; 248 | H = H+HWt; 249 | else 250 | return; 251 | end 252 | end 253 | end 254 | 255 | if in 256 | % checks positive semidefiniteness of H one more time. 257 | % H may fail Cholesky factorization even if in == 1 258 | % due to numerical errors in summing up HWt's above. 259 | [L, err] = chol(H,'lower'); 260 | in = in & (err == 0); 261 | 262 | if in 263 | % Now we are really done. 264 | %Hi = @(v)(vHiv(L,v,symmflag)); 265 | Hi = @(v)(L'\(L\v)); 266 | if nargout == 4 267 | Li = @(M)(L\M); 268 | end 269 | end 270 | end 271 | 272 | return 273 | 274 | % auxiliary function for Hi 275 | function res = vHiv(L, v, symmflag) 276 | 277 | if ~symmflag 278 | % inv(H)*v 279 | res = L'\(L\v); 280 | else 281 | % v'*inv(H)*v 282 | Liv = L\v; 283 | res = Liv.' * Liv; 284 | end 285 | 286 | return 287 | 288 | function [in, g, H] = gH_SOSWt(x, P) 289 | % This method computes the gradient and Hessian of a barrier function term 290 | % corresponding to a single weight for the polynomial optimization problem. 291 | % -------------------------------------------------------------------------- 292 | % USAGE of "gH_SOSWt" 293 | % [in, g, H] = gH_SOSWt(x, P) 294 | % -------------------------------------------------------------------------- 295 | % INPUT 296 | % x: primal iterate 297 | % P: evaluations of "weighted" basis polynomials at the 298 | % interpolation points 299 | % 300 | % OUTPUT 301 | % in: 0 if P'*diag(x)*P is not positive definite. 1 if P'*diag(x)*P is 302 | % positive definite. 303 | % g: gradient of the barrier function term corresponding to a single 304 | % weight at x 305 | % H: Hessian of the barrier function term corresponding to a single 306 | % weight at x 307 | % -------------------------------------------------------------------------- 308 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 309 | % None. 310 | % ------------------------------------------------------------------------- 311 | 312 | Y = P'*diag(x)*P; 313 | 314 | if ~issymmetric(Y) 315 | Y = (Y+Y')/2; 316 | end 317 | 318 | [L,err] = chol(Y,'lower'); 319 | if err > 0 320 | in = 0; 321 | g = NaN; 322 | H = NaN; 323 | else 324 | in = 1; 325 | V = L\P'; 326 | VtV = V'*V; 327 | 328 | g = -diag(VtV); 329 | H = VtV.^2; 330 | end 331 | 332 | return 333 | 334 | function [polyDeg, lb, ub] = setPolyParams(polyName, n) 335 | % This method sets the values of certain parameters associated with the 336 | % polynomial optimization problem. 337 | % -------------------------------------------------------------------------- 338 | % USAGE of "setPolyParams" 339 | % [polyDeg, lb, ub] = setPolyParams(polyName, n) 340 | % -------------------------------------------------------------------------- 341 | % INPUT 342 | % polyName: name of polynomial to be minimized 343 | % n: number of arguments to the polynomials 344 | % 345 | % OUTPUT 346 | % polyDeg: degree of polynomial to be minimized 347 | % lb: lower bounds defining the polynomial's standard domain 348 | % ub: upper bounds defining the polynomial's standard domain 349 | % -------------------------------------------------------------------------- 350 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 351 | % None. 352 | % ------------------------------------------------------------------------- 353 | 354 | switch polyName 355 | case 'robinson' 356 | if n ~= 2; error('robinson requires 2 arguments.'); end 357 | polyDeg = 6; 358 | lb = repmat(-1, n, 1); 359 | ub = repmat(1, n, 1); 360 | case 'rosenbrock' 361 | if n ~= 2; error('rosenbrock requires 2 arguments.'); end 362 | polyDeg = 4; 363 | lb = repmat(-1, n, 1); 364 | ub = repmat(1, n, 1); 365 | case 'motzkin' 366 | if n ~= 2; error('motzkin requires 2 arguments.'); end 367 | polyDeg = 6; 368 | lb = repmat(-1, n, 1); 369 | ub = repmat(1, n, 1); 370 | case 'schwefel' 371 | if n ~= 3; error('schwefel requires 3 arguments.'); end 372 | polyDeg = 4; 373 | lb = repmat(-10, n, 1); 374 | ub = repmat(10, n, 1); 375 | case 'reaction-diffusion' 376 | if n ~= 3; error('reaction-diffusion requires 3 arguments.'); end 377 | polyDeg = 2; 378 | lb = repmat(-5, n, 1); 379 | ub = repmat(5, n, 1); 380 | case 'caprasse' 381 | if n ~= 4; error('caprasse requires 4 arguments.'); end 382 | polyDeg = 4; 383 | lb = repmat(-0.5, n, 1); 384 | ub = repmat(0.5, n, 1); 385 | case 'lotka-volterra' 386 | if n ~= 4; error('lotka-volterra requires 4 arguments.'); end 387 | polyDeg = 3; 388 | lb = repmat(-2, n, 1); 389 | ub = repmat(2, n, 1); 390 | case 'butcher' 391 | if n ~= 6; error('butcher requires 6 arguments.'); end 392 | polyDeg = 3; 393 | lb = [-1; -0.1; -0.1; -1; -0.1; -0.1]; 394 | ub = [0; 0.9; 0.5; -0.1; -0.05; -0.03]; 395 | case 'magnetism7' 396 | if n ~= 7; error('magnetism7 requires 7 arguments.'); end 397 | polyDeg = 2; 398 | lb = repmat(-1, n, 1); 399 | ub = repmat(1, n, 1); 400 | case 'heart' 401 | if n ~= 8; error('heart requires 8 arguments.'); end 402 | polyDeg = 4; 403 | lb = [-0.1; 0.4; -0.7; -0.7; 0.1; -0.1; -0.3; -1.1]; 404 | ub = [0.4; 1; -0.4; 0.4; 0.2; 0.2; 1.1; -0.3]; 405 | otherwise 406 | error('The polynomial name is not recognized.'); 407 | end 408 | %#ok<*REPMAT> 409 | 410 | return 411 | 412 | function vals = setPolyVals(polyName, pts) 413 | % This method computes evaluations of the polynomial to be optimized at 414 | % the interpolation points. 415 | % -------------------------------------------------------------------------- 416 | % USAGE of "setPolyVals" 417 | % vals = setPolyVals(polyName, pts) 418 | % -------------------------------------------------------------------------- 419 | % INPUT 420 | % polyName: name of polynomial to be minimized 421 | % pts: interpolation points 422 | % 423 | % OUTPUT 424 | % vals: evaluations of the polynomial to be optimized at 425 | % the interpolation points 426 | % -------------------------------------------------------------------------- 427 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 428 | % None. 429 | % ------------------------------------------------------------------------- 430 | 431 | switch polyName 432 | case 'robinson' 433 | vals = robinsonVals(pts); 434 | case 'rosenbrock' 435 | vals = rosenbrockVals(pts); 436 | case 'motzkin' 437 | vals = motzkinVals(pts); 438 | case 'schwefel' 439 | vals = schwefelVals(pts); 440 | case 'reaction-diffusion' 441 | vals = reactionDiffusionVals(pts); 442 | case 'caprasse' 443 | vals = caprasseVals(pts); 444 | case 'lotka-volterra' 445 | vals = lotkaVolterraVals(pts); 446 | case 'butcher' 447 | vals = butcherVals(pts); 448 | case 'magnetism7' 449 | vals = magnetism7Vals(pts); 450 | case 'heart' 451 | vals = heartVals(pts); 452 | end 453 | 454 | return 455 | 456 | function vals = robinsonVals(pts) 457 | % This method computes evaluations of Robinson's polynomial at the 458 | % interpolation points. 459 | % -------------------------------------------------------------------------- 460 | % USAGE of "robinsonVals" 461 | % vals = robinsonVals(pts) 462 | % -------------------------------------------------------------------------- 463 | % INPUT 464 | % pts: interpolation points 465 | % 466 | % OUTPUT 467 | % vals: evaluations of Robinson's polynomial at the interpolation points 468 | % -------------------------------------------------------------------------- 469 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 470 | % None. 471 | % ------------------------------------------------------------------------- 472 | 473 | vals = 1 + pts(:,1).^6 + pts(:,2).^6 - (pts(:,1).^4).*(pts(:,2).^2) +... 474 | pts(:,1).^4 - (pts(:,2).^4).*(pts(:,1).^2) + pts(:,2).^4 -... 475 | pts(:,1).^2 + pts(:,2).^2 + 3*(pts(:,1).^2).*(pts(:,2).^2); 476 | 477 | return 478 | 479 | function vals = rosenbrockVals(pts) 480 | % This method computes evaluations of Rosenbrock's polynomial at the 481 | % interpolation points. 482 | % -------------------------------------------------------------------------- 483 | % USAGE of "rosenbrockVals" 484 | % vals = rosenbrockVals(pts) 485 | % -------------------------------------------------------------------------- 486 | % INPUT 487 | % pts: interpolation points 488 | % 489 | % OUTPUT 490 | % vals: evaluations of Rosenbrock's polynomial at the interpolation points 491 | % -------------------------------------------------------------------------- 492 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 493 | % None. 494 | % ------------------------------------------------------------------------- 495 | 496 | vals = 1 - 2*pts(:,1) + pts(:,1).^2 + 100*pts(:,1).^4 -... 497 | 200*(pts(:,1).^2).*pts(:,2) + 100*pts(:,2).^2; 498 | 499 | return 500 | 501 | function vals = motzkinVals(pts) 502 | % This method computes evaluations of Motzkin's polynomial at the 503 | % interpolation points. 504 | % -------------------------------------------------------------------------- 505 | % USAGE of "motzkinVals" 506 | % vals = motzkinVals(pts) 507 | % -------------------------------------------------------------------------- 508 | % INPUT 509 | % pts: interpolation points 510 | % 511 | % OUTPUT 512 | % vals: evaluations of Motzkin's polynomial at the interpolation points 513 | % -------------------------------------------------------------------------- 514 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 515 | % None. 516 | % ------------------------------------------------------------------------- 517 | 518 | vals = 1 - 48*(pts(:,1).^2).*(pts(:,2).^2) + 64*(pts(:,1).^2).*(pts(:,2).^4) +... 519 | 64*(pts(:,1).^4).*(pts(:,2).^2); 520 | 521 | return 522 | 523 | function vals = schwefelVals(pts) 524 | % This method computes evaluations of Schwefel's polynomial at the 525 | % interpolation points. 526 | % -------------------------------------------------------------------------- 527 | % USAGE of "schwefelVals" 528 | % vals = schwefelVals(pts) 529 | % -------------------------------------------------------------------------- 530 | % INPUT 531 | % pts: interpolation points 532 | % 533 | % OUTPUT 534 | % vals: evaluations of Schwefel's polynomial at the interpolation points 535 | % -------------------------------------------------------------------------- 536 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 537 | % None. 538 | % ------------------------------------------------------------------------- 539 | 540 | vals = (pts(:,1) - pts(:,2).^2).^2 + (pts(:,2) - 1).^2 +... 541 | (pts(:,1) - pts(:,3).^2).^2 + (pts(:,3) - 1).^2; 542 | 543 | return 544 | 545 | function vals = reactionDiffusionVals(pts) 546 | % This method computes evaluations of the 3-variable reaction-diffusion 547 | % polynomial at the interpolation points. 548 | % -------------------------------------------------------------------------- 549 | % USAGE of "reactionDiffusionVals" 550 | % vals = reactionDiffusionVals(pts) 551 | % -------------------------------------------------------------------------- 552 | % INPUT 553 | % pts: interpolation points 554 | % 555 | % OUTPUT 556 | % vals: evaluations of the 3-variable reaction-diffusion polynomial at the 557 | % interpolation points 558 | % -------------------------------------------------------------------------- 559 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 560 | % None. 561 | % ------------------------------------------------------------------------- 562 | 563 | vals = -pts(:,1) + 2*pts(:,2) - pts(:,3) - 0.835634534*pts(:,2).*(1+pts(:,2)); 564 | 565 | return 566 | 567 | function vals = caprasseVals(pts) 568 | % This method computes evaluations of Caprasse's system at the interpolation 569 | % points. 570 | % -------------------------------------------------------------------------- 571 | % USAGE of "caprasseVals" 572 | % vals = caprasseVals(pts) 573 | % -------------------------------------------------------------------------- 574 | % INPUT 575 | % pts: interpolation points 576 | % 577 | % OUTPUT 578 | % vals: evaluations of Caprasse's system at the interpolation points 579 | % -------------------------------------------------------------------------- 580 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 581 | % None. 582 | % ------------------------------------------------------------------------- 583 | 584 | vals = -pts(:,1).*(pts(:,3).^3) + 4*pts(:,2).*(pts(:,3).^2).*pts(:,4) +... 585 | 4*pts(:,1).*pts(:,3).*(pts(:,4).^2) + 2*pts(:,2).*(pts(:,4).^3) +... 586 | 4*pts(:,1).*pts(:,3) + 4*(pts(:,3).^2) - 10*pts(:,2).*pts(:,4) -... 587 | 10*(pts(:,4).^2) + 2; 588 | 589 | return 590 | 591 | function vals = lotkaVolterraVals(pts) 592 | % This method computes evaluations of the adaptive Lotka-Volterra system at 593 | % the interpolation points. 594 | % -------------------------------------------------------------------------- 595 | % USAGE of "lotkaVolterraVals" 596 | % vals = lotkaVolterraVals(pts) 597 | % -------------------------------------------------------------------------- 598 | % INPUT 599 | % pts: interpolation points 600 | % 601 | % OUTPUT 602 | % vals: evaluations of the adaptive Lotka-Volterra system at the 603 | % interpolation points 604 | % -------------------------------------------------------------------------- 605 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 606 | % None. 607 | % ------------------------------------------------------------------------- 608 | 609 | vals = pts(:,1).*(pts(:,2).^2) + pts(:,1).*(pts(:,3).^2) +... 610 | pts(:,1).*(pts(:,4).^2) - 1.1*pts(:,1) + 1; 611 | 612 | return 613 | 614 | function vals = butcherVals(pts) 615 | % This method computes evaluations of Butcher's polynomial at the interpolation 616 | % points. 617 | % -------------------------------------------------------------------------- 618 | % USAGE of "butcherVals" 619 | % vals = butcherVals(pts) 620 | % -------------------------------------------------------------------------- 621 | % INPUT 622 | % pts: interpolation points 623 | % 624 | % OUTPUT 625 | % vals: evaluations of Butcher's polynomial at the interpolation points 626 | % -------------------------------------------------------------------------- 627 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 628 | % None. 629 | % ------------------------------------------------------------------------- 630 | 631 | vals = pts(:,6).*(pts(:,2).^2) + pts(:,5).*(pts(:,3).^2) - pts(:,1).*(pts(:,4).^2) +... 632 | pts(:,4).^3 + pts(:,4).^2 - (1/3)*pts(:,1) + (4/3)*pts(:,4); 633 | 634 | return 635 | 636 | function vals = magnetism7Vals(pts) 637 | % This method computes evaluations of the 7-variable magnetism polynomial at 638 | % the interpolation points. 639 | % -------------------------------------------------------------------------- 640 | % USAGE of "magnetism7Vals" 641 | % vals = magnetism7Vals(pts) 642 | % -------------------------------------------------------------------------- 643 | % INPUT 644 | % pts: interpolation points 645 | % 646 | % OUTPUT 647 | % vals: evaluations of the 7-variable magnetism polynomial at the 648 | % interpolation points 649 | % -------------------------------------------------------------------------- 650 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 651 | % None. 652 | % ------------------------------------------------------------------------- 653 | 654 | vals = pts(:,1).^2 + 2*(pts(:,2).^2) + 2*(pts(:,3).^2) + 2*(pts(:,4).^2) +... 655 | 2*(pts(:,5).^2) + 2*(pts(:,6).^2) + 2*(pts(:,7).^2) - pts(:,1); 656 | 657 | return 658 | 659 | function vals = heartVals(pts) 660 | % This method computes evaluations of the heart dipole polynomial at 661 | % the interpolation points. 662 | % -------------------------------------------------------------------------- 663 | % USAGE of "heartVals" 664 | % vals = heartVals(pts) 665 | % -------------------------------------------------------------------------- 666 | % INPUT 667 | % pts: interpolation points 668 | % 669 | % OUTPUT 670 | % vals: evaluations of the heart dipole polynomial at the interpolation points 671 | % -------------------------------------------------------------------------- 672 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 673 | % None. 674 | % ------------------------------------------------------------------------- 675 | 676 | vals = -pts(:,1).*(pts(:,6).^3) + 3*pts(:,1).*pts(:,6).*(pts(:,7).^2) -... 677 | pts(:,3).*(pts(:,7).^3) + 3*pts(:,3).*pts(:,7).*(pts(:,6).^2) -... 678 | pts(:,2).*(pts(:,5).^3) + 3*pts(:,2).*pts(:,5).*(pts(:,8).^2) -... 679 | pts(:,4).*(pts(:,8).^3) + 3*pts(:,4).*pts(:,8).*(pts(:,5).^2) -... 680 | 0.9563453; 681 | 682 | return 683 | -------------------------------------------------------------------------------- /examples/portfolio/portfolio.m: -------------------------------------------------------------------------------- 1 | function results = portfolio(f, n) 2 | %PORTFOLIO(f,n) formulates and solves a portfolio optimization with a 3 | % factor risk model and market impact model, using the simple interface of 4 | % alfonso. Also illustrates the linSolveFun option. 5 | % See the description of the model below. 6 | % ------------------------------------------------------------------------- 7 | % USAGE of "portfolio" 8 | % results = portfolio(f, n) 9 | % ------------------------------------------------------------------------- 10 | % INPUT 11 | % f: number of factors 12 | % n: number of assets 13 | % 14 | % OUTPUT 15 | % results: final solution and iteration statistics 16 | % see alfonso.m for details 17 | % ------------------------------------------------------------------------- 18 | % Copyright (C) 2020 David Papp and Sercan Yildiz. 19 | % 20 | % Authors: 21 | % David Papp 22 | % Sercan Yildiz 23 | % 24 | % Date: 2024/07/15 25 | % ------------------------------------------------------------------------- 26 | % BRIEF MODEL DESCRIPTION: 27 | % 28 | % The asset return covariance matrix has the factor model structure 29 | % Sigma = B Omega B' + Delta, 30 | % where B is an n x f matrix, Omega is an f x f PSD matrix, and Delta is a 31 | % diagonal positive definite matrix. 32 | % 33 | % Market model parameters: 34 | % - alpha: asset expected returns (alpha's) 35 | % - B: factor exposure matrix 36 | % - Omega: factor covariance matrix 37 | % - diagDelta: specific variance of asset returns 38 | % 39 | % Market impact model parameters: 40 | % - beta: exponent 41 | % - lambda: multipliers 42 | % 43 | % Additional model parameters: 44 | % - portfolio risk limit: gamma 45 | % - initial portfolio: h 46 | % 47 | % Decision variables: 48 | % - optimal portfolio weights: x 49 | % - transaction weights: t 50 | % 51 | % The optimization model is of the form 52 | % 53 | % maximize_{x,t} alpha'*x - sum_i (lambda_i*|t|^beta) 54 | % subject to sum(x) == 1 55 | % x >= 0 56 | % x+t = h 57 | % sqrt(x'*(B*Omega*B' + Delta*x) <= gamma 58 | % 59 | % ------------------------------------------------------------------------- 60 | 61 | 62 | % generate some realistic-looking random data 63 | [alpha, B, Omega, diagDelta, beta, lambda, gamma, h] = getRandomData(f,n); 64 | 65 | %[n, f] = size(B); 66 | 67 | % variable order: x, (z_i, 1, t_i)_{i=1:n}, (gamma, d, u) 68 | 69 | % K = R_+^n x P_{1/beta,1-1/beta}^n x Q_{n+f+1} 70 | K = cell(1,n+2); 71 | K{1} = struct('type','lp','dim',n); 72 | [K{2:n+1}] = deal(struct('type','gpow','dim',3,'lambda',[1/beta; 1-1/beta])); 73 | K{n+2} = struct('type','socp','dim',n+f+1); 74 | 75 | rows = 1+n+n+f+n+1; 76 | cols = n+3*n+(n+f+1); 77 | 78 | A = zeros(rows, cols); 79 | b = zeros(rows,1); 80 | c = zeros(cols,1); 81 | 82 | c(1:n) = -alpha; 83 | c(n+1:3:4*n-2) = lambda; 84 | 85 | % e'x = 1 86 | A(1,1:n) = 1; 87 | b(1) = 1; 88 | 89 | % x-t = h 90 | A(2:n+1,1:n) = eye(n); 91 | A(2:n+1,n+3:3:n+3*n) = -eye(n); 92 | b(2:n+1) = h; 93 | 94 | % d - Delta^{1/2}*x = 0 95 | A(n+2:2*n+1,4*n+1+(1:n)) = eye(n); 96 | A(n+2:2*n+1,1:n) = -diag(sqrt(diagDelta)); 97 | 98 | % u - Omega^{1/2}B'*x = 0 99 | A(2*n+2:2*n+1+f,5*n+1+(1:f)) = eye(f); 100 | A(2*n+2:2*n+1+f,1:n) = -sqrtm(Omega)*B'; 101 | 102 | % constants 103 | A(2*n+1+f+(1:n), n+2:3:4*n-1) = eye(n); 104 | b(2*n+1+f+(1:n)) = 1; 105 | A(2*n+1+f+n+1,4*n+1) = 1; 106 | b(2*n+1+f+n+1,1) = gamma; 107 | 108 | fprintf('A size: %dx%d; sparsity:%f\n',rows,cols,nnz(A)/numel(A)); 109 | 110 | A = sparse(A); 111 | 112 | opts.linSolveFun = @linsolveB; % also works with linsolveA, but B is considerably faster. 113 | results = alfonso_simple(c, A, b, K, [], opts); 114 | results.data = struct('h',h,'alpha',alpha,'beta',beta,'gamma',gamma,'Delta',sparse(diag(diagDelta)),'B',B,'Omega',Omega); 115 | 116 | return 117 | 118 | function [alpha, B, Omega, diagDelta, beta, lambda, gamma, h] = getRandomData(f,n) 119 | 120 | seed = 2020; 121 | rng(seed,'twister'); 122 | 123 | % market impact model parameters 124 | lambda = 0.1*ones(n,1); % multipliers 125 | beta = 5/3; % exponent 126 | 127 | % asset expected returns (alpha's) 128 | alpha = randn(n,1); 129 | 130 | % risk model parameters 131 | % asset return covariance matrix has the factor model structure 132 | % B Omega B' + Delta, 133 | % where B is an n x f matrix, Omega is an f x f PSD matrix, and Delta 134 | % is a diagonal PSD matrix 135 | 136 | B = [ones(n,1), 0.5*randn(1,f-1) + 0.2*randn(n,f-1)]; % first column represents the market intercept 137 | 138 | % generate a random orthogonal matrix Q from the Haar distribution 139 | [Q,R] = qr(randn(f)); 140 | Q = Q*diag(sign(diag(R))); 141 | 142 | Omega = Q * diag((0.15 + 0.03*randn(f,1)).^2) * Q'; 143 | diagDelta = (0.2 + 0.05*randn(n,1)).^2; % Delta = diag(diagDelta) 144 | 145 | % optimization model parameters 146 | h = 1/n * ones(n,1); % equally weighted initial portfolio 147 | Sigma = B*Omega*B' + diag(diagDelta); 148 | gamma = sqrt(h'*Sigma*h); % portfolio risk limit = current risk 149 | 150 | disp(['Portfolio risk limit (gamma) = ', num2str(gamma)]); 151 | return 152 | -------------------------------------------------------------------------------- /examples/random_lp/random_lp.m: -------------------------------------------------------------------------------- 1 | % This code formulates and solves a random linear programming problem. 2 | % ------------------------------------------------------------------------- 3 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 4 | % 5 | % Authors: 6 | % David Papp 7 | % Sercan Yildiz 8 | % 9 | % Date: 01/15/2019 10 | % 11 | % This code has been developed and tested with Matlab R2016b. 12 | % ------------------------------------------------------------------------- 13 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 14 | % None. 15 | % ------------------------------------------------------------------------- 16 | 17 | function results = random_lp(m, n, tol, seed) 18 | % This is the main method for solving a random linear programming problem. 19 | % -------------------------------------------------------------------------- 20 | % USAGE of "random_lp" 21 | % results = random_lp(m, n, tol, seed) 22 | % -------------------------------------------------------------------------- 23 | % INPUT 24 | % m: number of equality constraints 25 | % n: number of variables 26 | % tol: tolerance parameter for optimization 27 | % seed: seed for the random number generator 28 | % 29 | % OUTPUT 30 | % results: final solution and iteration statistics 31 | % - results.nIterations: total number of iterations 32 | % - results.alphaPred: predictor step size at each iteration 33 | % - results.betaPred: neighborhood parameter at the end of the 34 | % predictor phase at each iteration 35 | % - results.etaCorr: neighborhood parameter at the end of the 36 | % corrector phase at each iteration 37 | % - results.mu: complementarity gap at each iteration 38 | % - results.x: final value of the primal variables 39 | % - results.s: final value of the dual slack variables 40 | % - results.y: final value of the dual free variables 41 | % - results.tau: final value of the tau-variable 42 | % - results.kappa: final value of the kappa-variable 43 | % - results.pObj: final primal objective value 44 | % - results.dObj: final dual objective value 45 | % - results.dGap: final duality gap 46 | % - results.cGap: final complementarity gap 47 | % - results.rel_dGap: final relative duality gap 48 | % - results.rel_cGap: final relative complementarity gap 49 | % - results.pRes: final primal residuals 50 | % - results.dRes: final dual residuals 51 | % - results.pIn: final primal infeasibility 52 | % - results.dIn: final dual infeasibility 53 | % - results.rel_pIn: final relative primal infeasibility 54 | % - results.rel_dIn: final relative dual infeasibility 55 | % -------------------------------------------------------------------------- 56 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 57 | % None. 58 | % ------------------------------------------------------------------------- 59 | 60 | 61 | rng(seed, 'twister'); 62 | 63 | A = randi([-9,9],m,n); 64 | b = A*ones(n, 1); 65 | c = randi(9,n,1); 66 | 67 | probData = struct('c', c, 'A', A, 'b', b); 68 | 69 | tic 70 | 71 | % INITIAL PRIMAL ITERATE 72 | x0 = ones(n,1); 73 | [~, g0] = gH_LP(x0, []); 74 | % scaling factor for the primal problem 75 | rP = max((1+abs(probData.b))./(1+abs(probData.A*x0))); 76 | % scaling factor for the dual problem 77 | rD = max((1+abs(g0))./(1+abs(probData.c))); 78 | % initial primal iterate 79 | x0 = repmat(sqrt(rP*rD),n,1); 80 | 81 | % CUSTOM ALGORITHMIC OPTIONS 82 | opts.optimTol = tol; 83 | 84 | % CALL TO alfonso 85 | results = alfonso(probData, x0, @gH_LP, [], opts); 86 | fprintf('alfonso is done. '); 87 | 88 | toc 89 | 90 | % prints error statistics 91 | fprintf('\n'); 92 | fprintf('FINAL:\n'); 93 | fprintf('Relative primal infeasibility: %d\n', results.rel_pIn); 94 | fprintf('Relative dual infeasibility: %d\n', results.rel_dIn); 95 | fprintf('Relative duality gap: %d\n', results.rel_dGap); 96 | fprintf('Relative complementarity gap: %d\n\n', results.rel_cGap); 97 | 98 | end 99 | -------------------------------------------------------------------------------- /examples/random_lp/random_lp_simple.m: -------------------------------------------------------------------------------- 1 | % This code formulates and solves a random linear programming problem 2 | % utilizing the simple interface. 3 | % ------------------------------------------------------------------------- 4 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 5 | % 6 | % Authors: 7 | % David Papp 8 | % Sercan Yildiz 9 | % 10 | % Date: 2024/07/15 11 | % 12 | % This code has been developed and tested with Matlab R2016b. 13 | % ------------------------------------------------------------------------- 14 | % EXTERNAL FUNCTIONS CALLED IN THIS FILE 15 | % None. 16 | % ------------------------------------------------------------------------- 17 | 18 | function results = random_lp_simple(m, n, tol, seed) 19 | % This is the main method for solving a random linear programming problem. 20 | % -------------------------------------------------------------------------- 21 | % USAGE of "random_lp_simple" 22 | % results = random_lp_simple(m, n, tol, seed) 23 | % -------------------------------------------------------------------------- 24 | % INPUT 25 | % m: number of equality constraints 26 | % n: number of variables 27 | % tol: tolerance parameter for optimization 28 | % seed: seed for the random number generator 29 | % 30 | % OUTPUT 31 | % results: final solution and iteration statistics 32 | % - results.nIterations: total number of iterations 33 | % - results.alphaPred: predictor step size at each iteration 34 | % - results.betaPred: neighborhood parameter at the end of the 35 | % predictor phase at each iteration 36 | % - results.etaCorr: neighborhood parameter at the end of the 37 | % corrector phase at each iteration 38 | % - results.mu: complementarity gap at each iteration 39 | % - results.x: final value of the primal variables 40 | % - results.s: final value of the dual slack variables 41 | % - results.y: final value of the dual free variables 42 | % - results.tau: final value of the tau-variable 43 | % - results.kappa: final value of the kappa-variable 44 | % - results.pObj: final primal objective value 45 | % - results.dObj: final dual objective value 46 | % - results.dGap: final duality gap 47 | % - results.cGap: final complementarity gap 48 | % - results.rel_dGap: final relative duality gap 49 | % - results.rel_cGap: final relative complementarity gap 50 | % - results.pRes: final primal residuals 51 | % - results.dRes: final dual residuals 52 | % - results.pIn: final primal infeasibility 53 | % - results.dIn: final dual infeasibility 54 | % - results.rel_pIn: final relative primal infeasibility 55 | % - results.rel_dIn: final relative dual infeasibility 56 | % -------------------------------------------------------------------------- 57 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 58 | % None. 59 | % ------------------------------------------------------------------------- 60 | 61 | 62 | rng(seed, 'twister'); 63 | 64 | A = randi([-9,9],m,n); 65 | b = A*ones(n,1); 66 | c = randi(9,n,1); 67 | 68 | tic 69 | 70 | % INITIAL PRIMAL ITERATE 71 | x0 = ones(n,1); 72 | [~, g0] = gH_LP(x0, []); 73 | % scaling factor for the primal problem 74 | rP = max((1+abs(b))./(1+abs(A*x0))); 75 | % scaling factor for the dual problem 76 | rD = max((1+abs(g0))./(1+abs(c))); 77 | % initial primal iterate 78 | x0 = repmat(sqrt(rP*rD),n,1); 79 | 80 | % CUSTOM ALGORITHMIC OPTIONS 81 | opts = struct('optimTol', tol, 'preprocess', false); 82 | 83 | % CALL TO alfonso 84 | K{1} = struct('type', 'lp', 'dim', n); 85 | results = alfonso_simple(c, A, b, K, x0, opts); 86 | fprintf('alfonso is done. '); 87 | 88 | toc 89 | 90 | % prints error statistics 91 | fprintf('\n'); 92 | fprintf('FINAL:\n'); 93 | fprintf('Relative primal infeasibility: %d\n', results.rel_pIn); 94 | fprintf('Relative dual infeasibility: %d\n', results.rel_dIn); 95 | fprintf('Relative duality gap: %d\n', results.rel_dGap); 96 | fprintf('Relative complementarity gap: %d\n\n', results.rel_cGap); 97 | 98 | end 99 | 100 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # SRC 2 | 3 | The files in this directory are the Matlab source files of the alfonso solver. 4 | `alfonso.m` implements the oracle interface, `alfonso_simple.m` implements the simple interface. 5 | -------------------------------------------------------------------------------- /src/alfonso.m: -------------------------------------------------------------------------------- 1 | % This code is an implementation of the algorithm for non-symmetric conic 2 | % optimization, which was originally presented in: 3 | % 4 | % A. Skajaa and Y. Ye, A homogeneous interior-point algorithm for nonsymmetric 5 | % convex conic optimization, Mathematical Programming Ser. A, 150 (2015), 6 | % pp. 391-422. Available at https://doi.org/10.1007/s10107-014-0773-1. 7 | % 8 | % The implementation is based on the corrected analysis of the algorithm 9 | % presented in: 10 | % 11 | % D. Papp and S. Yildiz. On "A homogeneous interior-point algorithm for 12 | % nonsymmetric convex conic optimization". Available at 13 | % https://arxiv.org/abs/1712.00492. 14 | % ------------------------------------------------------------------------- 15 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 16 | % 17 | % Authors: 18 | % David Papp 19 | % Sercan Yildiz 20 | % 21 | % Version: 2024/07/15 22 | % 23 | % This code has been developed and tested with Matlab R2023b. 24 | % ------------------------------------------------------------------------- 25 | 26 | function results = alfonso(probData, x0, gH, gH_Params, opts) 27 | % ALgorithm FOr Non-Symmetric Optimization 28 | % This is the main method for the algorithm. 29 | % -------------------------------------------------------------------------- 30 | % USAGE of "alfonso" 31 | % results = alfonso(probData, x0, gH, gH_Params, opts) 32 | % -------------------------------------------------------------------------- 33 | % INPUT 34 | % probData: Data for the conic optimization problem. 35 | % - probData.A: Constraint matrix. 36 | % - probData.b: Right-hand side vector. 37 | % - probData.c: Cost vector. 38 | % x0: Initial primal iterate. 39 | % gH: Method for computing the gradient and 40 | % the bilinear map corresponding to the 41 | % Hessian inverse. (Function handle.) 42 | % gH_Params: Parameters passed to gH; may be replaced 43 | % by [] if no parameters needed. 44 | % 45 | % opts: Algorithmic options [default value]: 46 | % - opts.maxIter: Maximum number of interior-point iterations. 47 | % [10000]. 48 | % - opts.linSolveFun: Function handle for the Newton solver to use. 49 | % (You may add your own!) [@linsolveA] 50 | % - opts.predLineSearch: 0: fixed step size. 51 | % 1: line search. 52 | % 2: backtracking line search. [1] 53 | % - opts.unsafeMult: A multiplier for the safe fixed step size, 54 | % for those who like to live dangerously. 55 | % [1] 56 | % - opts.maxCorrSteps: Maximum number of corrector steps. 57 | % Possible values: 1, 2, or 4. [4] 58 | % - opts.corrCheck: 0: maxCorrSteps corrector steps are to be 59 | % performed at each corrector phase. 60 | % 1: corrector phase can be terminated before 61 | % maxCorrSteps corrector steps if the iterate 62 | % is in the eta-neighborhood. [1] 63 | % - opts.optimTol: Optimization tolerance parameter. [1e-06] 64 | % - opts.maxCorrLSIters: Maximum number of line search iterations in 65 | % each corrector step. [8] 66 | % - opts.maxSmallPredSteps: Maximum number of predictor step size 67 | % reductions allowed with respect to the safe 68 | % fixed step size. [8] 69 | % - opts.verbose: 0: suppress output. 70 | % 1: progress is printed after each iteration. [1] 71 | % 72 | % OUTPUT 73 | % results: final solution and iteration statistics 74 | % - results.status: solver status: 1 = success, 0 = infeasible problem, everything else is trouble 75 | % - results.statusString: solver status string 76 | % - results.nIterations: total number of iterations 77 | % - results.x: final value of the primal variables 78 | % - results.s: final value of the dual slack variables 79 | % - results.y: final value of the dual free variables 80 | % - results.tau: final value of the tau-variable 81 | % - results.kappa: final value of the kappa-variable 82 | % - results.pObj: final primal objective value 83 | % - results.dObj: final dual objective value 84 | % - results.options: options structure used in the computation 85 | % - results.alphaPred: predictor step size at each iteration 86 | % - results.betaPred: neighborhood parameter at the end of the 87 | % predictor phase at each iteration 88 | % - results.etaCorr: neighborhood parameter at the end of the 89 | % corrector phase at each iteration 90 | % - results.mu: complementarity gap at each iteration 91 | % - results.dGap: final duality gap 92 | % - results.cGap: final complementarity gap 93 | % - results.rel_dGap: final relative duality gap 94 | % - results.rel_cGap: final relative complementarity gap 95 | % - results.pRes: final primal residuals 96 | % - results.dRes: final dual residuals 97 | % - results.pIn: final primal infeasibility 98 | % - results.dIn: final dual infeasibility 99 | % - results.rel_pIn: final relative primal infeasibility 100 | % - results.rel_dIn: final relative dual infeasibility 101 | % -------------------------------------------------------------------------- 102 | 103 | if nargin == 4 104 | opts = struct(); 105 | elseif nargin < 4 106 | error('alfonso needs more input arguments.') 107 | elseif nargin > 5 108 | error('alfonso needs fewer input arguments.') 109 | end 110 | 111 | % sets algorithmic options 112 | opts = setOpts(opts); 113 | 114 | % say hello, alfonso 115 | sayHello(opts); 116 | 117 | % checks the problem data for consistency 118 | inputCheck(probData); 119 | 120 | stopwatch = tic; 121 | 122 | % check the initial point and compute the barrier parameter of the barrier function 123 | [in, g] = gH(x0, gH_Params); 124 | if ~in 125 | error('Specified initial point is not in the cone.'); 126 | end 127 | bnu = (-g'*x0) + 1; % nu-bar = nu+1, where nu = g(x0)'*x0 is the barrier parameter 128 | if isfield(gH_Params,'bnu') 129 | if abs(gH_Params.bnu-bnu) > 1e-12 130 | warning('Specified and computed gH_Params.bnu arguments do not agree. Specified: %d; computed: %d', gH_Params.bnu, bnu); 131 | end 132 | else 133 | gH_Params.bnu = bnu; 134 | if opts.verbose 135 | disp(['barrier parameter set to nu = ', num2str(bnu-1)]); 136 | end 137 | end 138 | 139 | 140 | [~, n] = size(probData.A); 141 | A = probData.A; 142 | b = probData.b; 143 | c = probData.c; 144 | At = A'; 145 | probData.At = At; 146 | 147 | % if opts.maxItRefineSteps > 0 148 | % probData.LHS = ... 149 | % [ sparse(m,m) A -b sparse(m,n) sparse(m,1) ; 150 | % -A' sparse(n,n) c -speye(n) sparse(n,1) ; 151 | % b' -c' 0 sparse(1,n) -1 ; 152 | % sparse(n,m) speye(n) sparse(n,1) speye(n) sparse(n,1) ; 153 | % sparse(1,m) sparse(1,n) 1 sparse(1,n) 1 ]; 154 | % end 155 | 156 | % sets the solution method for the Newton system 157 | % sets the solution method for the Newton system 158 | myLinSolve = opts.linSolveFun; 159 | 160 | % sets algorithmic parameters 161 | algParams = setAlgParams(gH_Params, opts); 162 | 163 | results.status = 1; 164 | results.statusString = ''; 165 | 166 | % creates arrays for iteration statistics 167 | results.alphaPred = zeros(algParams.maxIter, 1); 168 | results.betaPred = zeros(algParams.maxIter, 1); 169 | results.etaCorr = zeros(algParams.maxIter, 1); 170 | results.mu = zeros(algParams.maxIter, 1); 171 | 172 | % sets constants for termination criteria 173 | termConsts.pRes = max([1, norm([A,b],Inf)]); 174 | termConsts.dRes = max([1, norm([At,speye(n),-c],Inf)]); 175 | termConsts.comp = max([1, norm([-c',b',1],Inf)]); 176 | 177 | % creates the central primal-dual iterate corresponding to x0 178 | soln = initSoln(x0, probData, gH, gH_Params); 179 | 180 | if ~opts.debug 181 | warning('off','MATLAB:nearlySingularMatrix'); 182 | end 183 | termFlag = 0; 184 | numIters = 0; 185 | elapsed = toc(stopwatch); 186 | 187 | for iter = 1:algParams.maxIter+1 188 | 189 | % checks progress towards termination criteria 190 | [status, metrics] = term(soln, probData, algParams, termConsts); 191 | 192 | % prints progress metrics 193 | if mod(iter,1)==0 && opts.verbose 194 | fprintf('%3d: pObj=%.6e pIn=%#.2e dIn=%#.2e gap=%#.2e tau=%#.2e kap=%#.2e mu=%.2e t=%#.2f s\n',... 195 | iter, metrics.O, metrics.P, metrics.D, metrics.A, soln.tau, soln.kappa, soln.mu, elapsed); 196 | end 197 | 198 | if termFlag || iter == algParams.maxIter+1 || (status ~= -99 && status ~= -6) 199 | if iter == algParams.maxIter+1 200 | status = 0; % 'Number of iterations exceeded opts.maxIter.'; 201 | end 202 | 203 | numIters = iter; 204 | break; 205 | end 206 | 207 | % PREDICTOR PHASE 208 | [soln, alphaPred, betaPred, algParams, predStatus] =... 209 | pred(soln, probData, gH, gH_Params, myLinSolve, algParams, opts); 210 | 211 | results.alphaPred(iter) = alphaPred; 212 | results.betaPred(iter) = betaPred; 213 | % raises a termination flag if predictor phase was not successful 214 | 215 | if predStatus == 0 216 | if iter > 1 217 | results.betaPred(iter) = results.etaCorr(iter-1); 218 | end 219 | if opts.verbose 220 | fprintf('Predictor could not improve the solution.\n'); 221 | end 222 | termFlag = 1; 223 | else 224 | %?%soln.AHiAti = computeAHiAti(soln.Li, probData); 225 | end 226 | 227 | % CORRECTOR PHASE 228 | results.etaCorr(iter) = results.betaPred(iter); % Will be overwritten if corr phase is successful and it is measured. 229 | % Skips corrector phase if 230 | % (corrCheck == 1 AND current iterate is already in the eta-neighborhood) OR 231 | % termination flag was already raised 232 | if (~opts.corrCheck || results.etaCorr(iter) >= algParams.eta) && ~termFlag % margin? 233 | for corrIter = 1:algParams.maxCorrSteps 234 | 235 | % A single corrector step, possibly with failsafe backtracking line search. 236 | % Fails only if could not stay inside the cone at all. (Does not check neighborhoods.) 237 | [soln, corrStatus] = corr(soln, probData, gH, gH_Params, myLinSolve, algParams, opts); 238 | %?%soln.AHiAti = computeAHiAti(soln.Li, probData); 239 | 240 | % Exits corrector phase and raises a termination flag if 241 | % last corrector step was not successful. In this case, the 242 | % corrector ended outside the cone (not just the neighborhood). 243 | if corrStatus == 0 244 | if opts.verbose 245 | fprintf('Corrector could not improve the solution.\n'); 246 | end 247 | termFlag = 1; 248 | break; 249 | end 250 | % exits corrector phase if corrCheck == 1 and current 251 | % iterate is in the eta-neighborhood 252 | if (opts.corrCheck && corrIter < algParams.maxCorrSteps) || corrIter == algParams.maxCorrSteps 253 | 254 | if ~iscell(soln.Hi) 255 | v = soln.psi(1:end-1); 256 | psiHiPsi = v.'*soln.Hi(v); 257 | else 258 | idx = 0; 259 | Kdims = probData.Kdims; 260 | psiHiPsi = 0; 261 | for k=1:length(Kdims) 262 | v = soln.psi(idx+1:idx+Kdims(k)); 263 | psiHiPsi = psiHiPsi + v.'*soln.Hi{k}(v); 264 | idx = idx+Kdims(k); 265 | end 266 | end 267 | results.etaCorr(iter) = sqrt(psiHiPsi + (soln.tau*soln.psi(end))^2)/soln.mu; 268 | 269 | if results.etaCorr(iter) < algParams.eta % ? margin 270 | break; 271 | end 272 | 273 | end 274 | end 275 | 276 | % Raises a termination flag if corrector phase was not successful. 277 | % Unsafe fixed step size is exempted; in that case, all bets are off. 278 | if opts.debug && ~(opts.predLineSearch==0 && opts.unsafeMult>1) && results.etaCorr(iter) > algParams.eta 279 | if opts.verbose 280 | fprintf('Corrector phase finished outside the eta-neighborhood.\n'); 281 | end 282 | termFlag = 1; 283 | end 284 | end 285 | results.mu(iter) = soln.mu; 286 | 287 | elapsed = toc(stopwatch); 288 | end 289 | 290 | warning('on','MATLAB:nearlySingularMatrix'); 291 | 292 | % prepares final solution and iteration statistics 293 | results = prepResults(results, status, soln, probData, numIters, elapsed, opts); 294 | 295 | if opts.verbose 296 | disp(['Done in ', int2str(numIters), ' iterations.']); 297 | disp(['Status = ', results.statusString]); 298 | end 299 | return 300 | 301 | function [solnAlpha, alpha, betaAlpha, algParams, predStatus] = pred(soln, probData, gH, gH_Params, myLinSolve, algParams, opts) 302 | % This method performs a predictor step in the algorithm. 303 | % -------------------------------------------------------------------------- 304 | % USAGE of "pred" 305 | % [solnAlpha, alpha, betaAlpha, algParams, predStatus] = pred(soln, probData, gH, gH_Params, myLinSolve, algParams, opts) 306 | % -------------------------------------------------------------------------- 307 | % INPUT 308 | % soln: current iterate 309 | % probData: data for the conic optimization problem 310 | % gH: method for computing the gradient and Hessian of the barrier function 311 | % gH_Params: parameters associated with the method gH 312 | % myLinSolve: solution method for the Newton system 313 | % algParams: algorithmic parameters 314 | % opts: algorithmic options 315 | % 316 | % OUTPUT 317 | % solnAlpha: new iterate 318 | % alpha: predictor step size 319 | % betaAlpha: neighborhood parameter at the end of the predictor phase 320 | % algParams: algorithmic parameters 321 | % predStatus: 0 if predictor phase was not successful. 1 if predictor 322 | % phase was successful. 323 | % -------------------------------------------------------------------------- 324 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 325 | % None. 326 | % -------------------------------------------------------------------------- 327 | 328 | prevSolnAlpha = soln; % At least we can return the curent point if everything else fails. 329 | 330 | x = soln.x; 331 | tau = soln.tau; 332 | s = soln.s; 333 | kappa = soln.kappa; 334 | y = soln.y; 335 | 336 | A = probData.A; 337 | At = probData.At; 338 | b = probData.b; 339 | c = probData.c; 340 | [m, n] = size(A); 341 | 342 | RHS = zeros(m+2*n+2,1); 343 | RHS(1:m+n+1) = -[A*x - b*tau; -At*y + c*tau - s; b'*y - c'*x - kappa]; 344 | RHS(m+n+2:end) = -[s; kappa]; 345 | 346 | % Computes the Newton direction. 347 | newtonDir = linSolveMain(soln, probData, RHS, myLinSolve); 348 | 349 | % Cannot even compute a predictor direction (severe numerical issue) 350 | if any(isnan(newtonDir.y)) 351 | predStatus = 0; 352 | solnAlpha = soln; 353 | alpha = 0; 354 | betaAlpha = NaN; % Not worth computing. 355 | return; 356 | end 357 | 358 | switch opts.predLineSearch 359 | case 0 % Fixed step size, no neighborhood checks. 360 | alpha = algParams.alphaPred; 361 | 362 | solnAlpha.y = y + alpha*newtonDir.y; 363 | solnAlpha.x = x + alpha*newtonDir.x; 364 | solnAlpha.tau = tau + alpha*newtonDir.tau; 365 | solnAlpha.s = s + alpha*newtonDir.s; 366 | solnAlpha.kappa = kappa + alpha*newtonDir.kappa; 367 | 368 | [solnAlpha.in, solnAlpha.g, solnAlpha.Hi, solnAlpha.Li] = gH(solnAlpha.x, gH_Params); 369 | 370 | if solnAlpha.in 371 | % Primal iterate is inside the cone, as it automatically should be. 372 | predStatus = 1; 373 | 374 | solnAlpha.mu = (solnAlpha.x'*solnAlpha.s + solnAlpha.tau*solnAlpha.kappa)/gH_Params.bnu; 375 | solnAlpha.psi = [solnAlpha.s; solnAlpha.kappa] + solnAlpha.mu*[solnAlpha.g; -1/solnAlpha.tau]; 376 | else 377 | % This should not happen, except perhaps for numerical reasons. 378 | predStatus = 0; 379 | 380 | % Restore the previous iterate. 381 | solnAlpha = soln; 382 | end 383 | % Returned but not used outside of line search. 384 | betaAlpha = NaN; 385 | 386 | return; 387 | 388 | case 1 389 | nSteps = 0; 390 | alpha = algParams.alphaPred; % the initial step size or the last working one 391 | direction = 0; % +1 if increasing, -1 if decreasing, 0 if just started. 392 | 393 | % Loop until we find an acceptable step size or fail. 394 | while true 395 | nSteps = nSteps + 1; 396 | 397 | solnAlpha.y = y + alpha*newtonDir.y; 398 | solnAlpha.x = x + alpha*newtonDir.x; 399 | solnAlpha.tau = tau + alpha*newtonDir.tau; 400 | solnAlpha.s = s + alpha*newtonDir.s; 401 | solnAlpha.kappa = kappa + alpha*newtonDir.kappa; 402 | 403 | [solnAlpha.in, solnAlpha.g, solnAlpha.Hi, solnAlpha.Li] = gH(solnAlpha.x, gH_Params); 404 | 405 | % Check if we are in the cone, and if so, if we are in the 406 | % beta neighborhood. 407 | if solnAlpha.in 408 | % Primal iterate is inside the cone. 409 | 410 | % Compute the neighborhood radius. 411 | solnAlpha.mu = (solnAlpha.x'*solnAlpha.s +... 412 | solnAlpha.tau*solnAlpha.kappa)/gH_Params.bnu; 413 | solnAlpha.psi = [solnAlpha.s; solnAlpha.kappa] +... 414 | solnAlpha.mu*[solnAlpha.g; -1/solnAlpha.tau]; 415 | 416 | if ~iscell(solnAlpha.Hi) 417 | v = solnAlpha.psi(1:end-1); 418 | psiHiPsi = v.'*solnAlpha.Hi(v); 419 | else 420 | idx = 0; 421 | Kdims = probData.Kdims; 422 | psiHiPsi = 0; 423 | for k=1:length(Kdims) 424 | v = solnAlpha.psi(idx+1:idx+Kdims(k)); 425 | psiHiPsi = psiHiPsi + v.'*solnAlpha.Hi{k}(v); 426 | idx = idx+Kdims(k); 427 | end 428 | end 429 | % The neighborhood radius beta after the alpha step: 430 | betaAlpha = sqrt(psiHiPsi + (solnAlpha.tau*solnAlpha.psi(end))^2)/solnAlpha.mu; 431 | if (betaAlpha < algParams.beta) % ? margin 432 | inNbhood = true; 433 | else 434 | inNbhood = false; 435 | end 436 | else 437 | % Not in the neighborhood 438 | inNbhood = false; 439 | %solnAlpha.mu = NaN; 440 | end 441 | 442 | if inNbhood 443 | % Good step size. 444 | 445 | % Save the last good iterate. 446 | prevSolnAlpha = solnAlpha; 447 | prevBetaAlpha = betaAlpha; 448 | prevAlpha = alpha; 449 | 450 | % Reached maximum step size? 451 | maxStepSize = 0.99; 452 | if alpha >= maxStepSize 453 | %solnAlpha.mu = (solnAlpha.x'*solnAlpha.s + solnAlpha.tau*solnAlpha.kappa)/gH_Params.bnu; 454 | break; 455 | end 456 | 457 | if direction >= 0 458 | % Increase the step size if possible. 459 | direction = 1; % If we weren't increasing yet, we are now. 460 | 461 | % Can we increase? 462 | if alpha / algParams.predLS1Multi < maxStepSize 463 | alpha = alpha / algParams.predLS1Multi; 464 | continue; 465 | else 466 | alpha = maxStepSize; 467 | continue; 468 | end 469 | else 470 | % We have been decreasing, but found a good step size. 471 | break; 472 | end 473 | else 474 | % Step size too large. 475 | if direction == 1 476 | % We have been increasing, but we went too far. 477 | % Accept previous stepsize. 478 | solnAlpha = prevSolnAlpha; 479 | betaAlpha = prevBetaAlpha; 480 | alpha = prevAlpha; 481 | break; 482 | 483 | else 484 | direction = -1; % If we weren't decreasing yet, we are now. 485 | 486 | % Lower step size if possible. 487 | alpha = alpha * algParams.predLS1Multi; 488 | % Have we failed? 489 | if alpha < algParams.alphaPredThreshold 490 | predStatus = 0; % predictor has failed 491 | solnAlpha = prevSolnAlpha; 492 | betaAlpha = alpha; 493 | return; 494 | end 495 | end 496 | end 497 | 498 | end % end while 499 | 500 | % If we are here, we found the good step size. 501 | predStatus = 1; 502 | % Will start from here next time. 503 | algParams.alphaPred = alpha; 504 | 505 | return; 506 | 507 | case 2 % 508 | nSteps = 0; 509 | alpha = algParams.alphaPred; % the initial step size or the last working one 510 | 511 | % Loop until we find an acceptable step size or fail. 512 | while true 513 | nSteps = nSteps + 1; 514 | 515 | solnAlpha.y = y + alpha*newtonDir.y; 516 | solnAlpha.x = x + alpha*newtonDir.x; 517 | solnAlpha.tau = tau + alpha*newtonDir.tau; 518 | solnAlpha.s = s + alpha*newtonDir.s; 519 | solnAlpha.kappa = kappa + alpha*newtonDir.kappa; 520 | 521 | [solnAlpha.in, solnAlpha.g, solnAlpha.Hi, solnAlpha.Li] = gH(solnAlpha.x, gH_Params); 522 | 523 | if ~solnAlpha.in 524 | % Primal iterate is outside the cone. 525 | % Reduce step size and repeat if possible. 526 | 527 | alpha = alpha * algParams.predLS2Multi; 528 | % Have we failed? 529 | if alpha < algParams.alphaPredThreshold 530 | predStatus = 0; % predictor has failed 531 | %alpha = 0; 532 | % Restore the previous iterate. 533 | solnAlpha = soln; 534 | % Reported but not used. 535 | betaAlpha = NaN; 536 | return; 537 | end 538 | 539 | else 540 | % Compute the neighborhood radius. 541 | 542 | solnAlpha.mu = (solnAlpha.x'*solnAlpha.s + solnAlpha.tau*solnAlpha.kappa)/gH_Params.bnu; 543 | solnAlpha.psi = [solnAlpha.s; solnAlpha.kappa] + solnAlpha.mu*[solnAlpha.g; -1/solnAlpha.tau]; 544 | 545 | if ~iscell(solnAlpha.Hi) 546 | v = solnAlpha.psi(1:end-1); 547 | psiHiPsi = v.'*solnAlpha.Hi(v); 548 | else 549 | idx = 0; 550 | Kdims = probData.Kdims; 551 | psiHiPsi = 0; 552 | for k=1:length(Kdims) 553 | v = solnAlpha.psi(idx+1:idx+Kdims(k)); 554 | psiHiPsi = psiHiPsi + v.'*solnAlpha.Hi{k}(v); 555 | idx = idx+Kdims(k); 556 | end 557 | end 558 | % The neighborhood radius beta after the alpha step: 559 | betaAlpha = sqrt(psiHiPsi + (solnAlpha.tau*solnAlpha.psi(end))^2)/solnAlpha.mu; 560 | 561 | % If we are in the beta-neighborhood, done. 562 | % Otherwise reduce step size and continue if possible. 563 | if (betaAlpha < algParams.beta) 564 | predStatus = 1; 565 | %algParams.alphaPred = alpha; % This might need more testing to see if it's worth it. ?? 566 | return; 567 | else 568 | alpha = alpha * algParams.predLS2Multi; 569 | % Have we failed? 570 | if alpha < algParams.alphaPredThreshold 571 | predStatus = 0; % predictor has failed 572 | %alpha = 0; 573 | return; 574 | end 575 | end 576 | end % end if ~solnAlpha.in 577 | end % endwhile 578 | 579 | end % end switch 580 | 581 | return 582 | 583 | function [solnAlpha, corrStatus] = corr(soln, probData, gH, gH_Params, myLinSolve, algParams, opts) 584 | % This method performs a single corrector step in the algorithm. 585 | % It does not do neighborhood checks, which is included in the main script. 586 | % -------------------------------------------------------------------------- 587 | % USAGE of "corr" 588 | % [solnAlpha, corrStatus] = corr(soln, probData, gH, gH_Params, myLinSolve, algParams, opts) 589 | % -------------------------------------------------------------------------- 590 | % INPUT 591 | % soln: current iterate 592 | % probData: data for the conic optimization problem 593 | % gH: method for computing the gradient and Hessian 594 | % of the barrier function 595 | % gH_Params: parameters associated with the method gH 596 | % myLinSolve: solution method for the Newton system 597 | % algParams: algorithmic parameters 598 | % opts: algorithmic options 599 | % 600 | % OUTPUT 601 | % solnAlpha: new iterate 602 | % corrStatus: 0 if corrector phase was not successful. 1 if corrector 603 | % phase was successful. 604 | % -------------------------------------------------------------------------- 605 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 606 | % None. 607 | % -------------------------------------------------------------------------- 608 | 609 | [m, n] = size(probData.A); 610 | 611 | RHS = zeros(m+2*n+2,1); 612 | RHS(m+n+2:end) = -soln.psi; 613 | 614 | dsoln = linSolveMain(soln, probData, RHS, myLinSolve); 615 | 616 | % Cannot even compute a corrector direction (severe numerical issue). 617 | if any(isnan(dsoln.y)) 618 | corrStatus = 0; 619 | solnAlpha = soln; 620 | return; 621 | end 622 | 623 | alpha = algParams.alphaCorr; 624 | corrStatus = 1; 625 | 626 | % does line search to make sure next primal iterate remains inside the cone 627 | for nSteps = 1:opts.maxCorrLSIters 628 | 629 | solnAlpha.y = soln.y + alpha*dsoln.y; 630 | solnAlpha.x = soln.x + alpha*dsoln.x; 631 | solnAlpha.tau = soln.tau + alpha*dsoln.tau; 632 | solnAlpha.s = soln.s + alpha*dsoln.s; 633 | solnAlpha.kappa = soln.kappa + alpha*dsoln.kappa; 634 | 635 | [solnAlpha.in, solnAlpha.g, solnAlpha.Hi, solnAlpha.Li] = gH(solnAlpha.x, gH_Params); 636 | 637 | % terminates line search if primal iterate is inside the cone 638 | if solnAlpha.in 639 | solnAlpha.mu = (solnAlpha.x'*solnAlpha.s + solnAlpha.tau*solnAlpha.kappa)/gH_Params.bnu; 640 | solnAlpha.psi = [solnAlpha.s;solnAlpha.kappa] + solnAlpha.mu*[solnAlpha.g;-1/solnAlpha.tau]; 641 | break; 642 | end 643 | 644 | alpha = algParams.corrLSMulti*alpha; 645 | end 646 | 647 | if solnAlpha.in == 0 648 | corrStatus = 0; % corrector has failed 649 | solnAlpha = soln; 650 | end 651 | 652 | return 653 | 654 | function dsoln = linSolveMain(soln, probData, RHS, myLinSolve) 655 | % This method sets up the Newton system and computes its solution. 656 | % -------------------------------------------------------------------------- 657 | % USAGE of "linSolveMain" 658 | % dsoln = linSolveMain(soln, probData, RHS, myLinSolve, algParams, opts) 659 | % -------------------------------------------------------------------------- 660 | % INPUT 661 | % soln: current iterate 662 | % probData: data for the conic optimization problem 663 | % RHS: right-hand side of the Newton system 664 | % myLinSolve: solution method for the Newton system 665 | % 666 | % OUTPUT 667 | % dsoln: computed Newton direction 668 | % -------------------------------------------------------------------------- 669 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 670 | % None. 671 | % -------------------------------------------------------------------------- 672 | 673 | [m, n] = size(probData.A); 674 | delta = myLinSolve(soln, probData, RHS); 675 | 676 | dsoln.y = delta(1:m); 677 | dsoln.x = delta(m+(1:n)); 678 | dsoln.tau = delta(m+n+1); 679 | dsoln.s = delta(m+n+1+(1:n)); 680 | dsoln.kappa = delta(end); 681 | 682 | return 683 | 684 | 685 | function opts = setOpts(opts) 686 | % This method sets the empty algorithmic options to their default values. 687 | % -------------------------------------------------------------------------- 688 | % USAGE of "setOpts" 689 | % opts = setOpts(opts) 690 | % -------------------------------------------------------------------------- 691 | % INPUT 692 | % opts: custom algorithmic options 693 | % 694 | % OUTPUT 695 | % opts: complete algorithmic options 696 | % -------------------------------------------------------------------------- 697 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 698 | % None. 699 | % -------------------------------------------------------------------------- 700 | 701 | if ~isfield(opts, 'predLineSearch'); opts.predLineSearch = 1; end 702 | if ~isfield(opts, 'unsafeMult'); opts.unsafeMult = 1.0; end 703 | if ~isfield(opts, 'maxCorrSteps'); opts.maxCorrSteps = 4; end 704 | if ~isfield(opts, 'corrCheck'); opts.corrCheck = 1; end 705 | if ~isfield(opts, 'optimTol'); opts.optimTol = 1e-06; end 706 | if opts.optimTol < eps, opts.optimTol = eps; end 707 | if ~isfield(opts, 'debug'); opts.debug = 0; end 708 | if ~isfield(opts, 'maxCorrLSIters'); opts.maxCorrLSIters = 8; end 709 | if ~isfield(opts, 'maxPredSmallSteps'); opts.maxPredSmallSteps = 8; end 710 | if ~isfield(opts, 'verbose'); opts.verbose = 1; end 711 | if ~isfield(opts, 'maxIter'); opts.maxIter = 10000; end 712 | if ~isfield(opts, 'linSolveFun'); opts.linSolveFun = @linsolveA; end 713 | 714 | return 715 | 716 | function algParams = setAlgParams(gH_Params, opts) 717 | % This method sets the algorithmic parameters. 718 | % -------------------------------------------------------------------------- 719 | % USAGE of "setAlgParams" 720 | % algParams = setAlgParams(gH_Params, opts) 721 | % -------------------------------------------------------------------------- 722 | % INPUT 723 | % gH_Params: parameters associated with the method gH 724 | % opts: algorithmic options 725 | % 726 | % OUTPUT 727 | % algParams: algorithmic parameters 728 | % - algParams.maxIter: maximum number of iterations 729 | % - algParams.optimTol: optimization tolerance parameter 730 | % - algParams.alphaCorr: corrector step size 731 | % - algParams.predLS1Multi: predictor line search 1 step size 732 | % multiplier 733 | % - algParams.predLS2Multi: predictor line search 2 step size 734 | % multiplier 735 | % - algParams.corrLSMulti: corrector line search step size 736 | % multiplier 737 | % - algParams.maxCorrSteps: maximum number of corrector steps 738 | % - algParams.beta: large neighborhood parameter 739 | % - algParams.eta: small neighborhood parameter 740 | % - algParams.alphaPredLS: initial predictor step size with line 741 | % search 742 | % - algParams.alphaPredFix: fixed predictor step size 743 | % - algParams.alphaPred: initial predictor step size 744 | % - algParams.alphaPredThreshold: minimum predictor step size 745 | % -------------------------------------------------------------------------- 746 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 747 | % None. 748 | % -------------------------------------------------------------------------- 749 | 750 | algParams.maxIter = opts.maxIter; 751 | algParams.optimTol = opts.optimTol; 752 | 753 | algParams.alphaCorr = 1.0; 754 | algParams.predLS1Multi = 0.7; 755 | algParams.predLS2Multi = 0.7; 756 | algParams.corrLSMulti = 0.5; 757 | 758 | % parameters are chosen to make sure that each predictor 759 | % step takes the current iterate from the eta-neighborhood to the 760 | % beta-neighborhood and each corrector phase takes the current 761 | % iterate from the beta-neighborhood to the eta-neighborhood. 762 | % extra corrector steps are allowed to mitigate the effects of 763 | % finite precision. 764 | algParams.maxCorrSteps = 10*opts.maxCorrSteps; 765 | 766 | % precomputed safe parameters 767 | switch opts.maxCorrSteps 768 | case 1 769 | if gH_Params.bnu < 10 770 | algParams.beta = 0.1810; 771 | algParams.eta = 0.0733; 772 | cPredFix = 0.0225; 773 | elseif gH_Params.bnu < 100 774 | algParams.beta = 0.2054; 775 | algParams.eta = 0.0806; 776 | cPredFix = 0.0263; 777 | else 778 | algParams.beta = 0.2190; 779 | algParams.eta = 0.0836; 780 | cPredFix = 0.0288; 781 | end 782 | case 2 783 | if gH_Params.bnu < 10 784 | algParams.beta = 0.2084; 785 | algParams.eta = 0.0502; 786 | cPredFix = 0.0328; 787 | elseif gH_Params.bnu < 100 788 | algParams.beta = 0.2356; 789 | algParams.eta = 0.0544; 790 | cPredFix = 0.0380; 791 | else 792 | algParams.beta = 0.2506; 793 | algParams.eta = 0.0558; 794 | cPredFix = 0.0411; 795 | end 796 | case 4 797 | if gH_Params.bnu < 10 798 | algParams.beta = 0.2387; 799 | algParams.eta = 0.0305; 800 | cPredFix = 0.0429; 801 | elseif gH_Params.bnu < 100 802 | algParams.beta = 0.2683; 803 | algParams.eta = 0.0327; 804 | cPredFix = 0.0489; 805 | else 806 | algParams.beta = 0.2844; 807 | algParams.eta = 0.0332; 808 | cPredFix = 0.0525; 809 | end 810 | otherwise 811 | error('The maximum number of corrector steps can be 1, 2, or 4.'); 812 | end 813 | 814 | kx = algParams.eta + sqrt(2*algParams.eta^2 + gH_Params.bnu); 815 | algParams.alphaPredFix = cPredFix/kx; 816 | algParams.alphaPredLS = min(100 * algParams.alphaPredFix, 0.99); 817 | 818 | switch opts.predLineSearch 819 | case 0 820 | % Fixed predictor step size. 821 | % The safe step size is usually "too safe". 822 | algParams.alphaPred = opts.unsafeMult*algParams.alphaPredFix; 823 | case 1 824 | % Initial and minimum predictor step size for full line search. 825 | algParams.alphaPred = algParams.alphaPredLS; 826 | algParams.alphaPredThreshold = 1e-04; 827 | case 2 828 | % Initial and minimum predictor step size for backtracking line search. 829 | algParams.alphaPred = 0.99; 830 | algParams.alphaPredThreshold = 1e-04; 831 | end 832 | 833 | return 834 | 835 | function soln = initSoln(x0, probData, gH, gH_Params) 836 | % This method creates the central primal-dual iterate corresponding to x0. 837 | % -------------------------------------------------------------------------- 838 | % USAGE of "initSoln" 839 | % soln = initSoln(x0, probData, gH, gH_Params) 840 | % -------------------------------------------------------------------------- 841 | % INPUT 842 | % x0: initial primal iterate 843 | % probData: data for the conic optimization problem 844 | % gH: method for computing the gradient and Hessian of the 845 | % barrier function 846 | % gH_Params: parameters associated with the method gH 847 | % 848 | % OUTPUT 849 | % soln: initial primal-dual iteratesoln = str 850 | % -------------------------------------------------------------------------- 851 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 852 | % None. 853 | % -------------------------------------------------------------------------- 854 | 855 | [m, ~] = size(probData.A); 856 | [in, g, Hi, Li] = gH(x0, gH_Params); 857 | %?%AHiAti = computeAHiAti(Li, probData); 858 | 859 | % Packing it up for the soln structure. 860 | if iscell(Li) 861 | Li = {Li}; 862 | Hi = {Hi}; 863 | end 864 | 865 | soln = struct( ... 866 | 'in', in, ... 867 | 'g', g, ... 868 | 'Li', Li, ... 869 | 'Hi', Hi, ..., 870 | 'x', x0, ... 871 | 'y', zeros(m, 1), ... 872 | 'tau', 1, ... 873 | 's', -g, ... 874 | 'kappa', 1, ... 875 | 'mu', 1 ... 876 | ); 877 | 878 | return 879 | 880 | function [status, metrics] = term(soln, probData, algParams, termConsts) 881 | % This method checks the termination criteria. 882 | % -------------------------------------------------------------------------- 883 | % USAGE of "term" 884 | % [status, statusString, metrics] = term(soln, probData, algParams, termConsts) 885 | % -------------------------------------------------------------------------- 886 | % INPUT 887 | % soln: current iterate 888 | % probData: data for the conic optimization problem 889 | % algParams: algorithmic parameters 890 | % termConsts: constants for termination criteria 891 | % 892 | % OUTPUT 893 | % status: problem status code 894 | % statusString: problem status as an interpretable string 895 | % metrics: convergence metrics 896 | % -------------------------------------------------------------------------- 897 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 898 | % None. 899 | % -------------------------------------------------------------------------- 900 | 901 | x = soln.x; 902 | tau = soln.tau; 903 | s = soln.s; 904 | kappa = soln.kappa; 905 | y = soln.y; 906 | mu = soln.mu; 907 | 908 | A = probData.A; 909 | At = probData.At; 910 | b = probData.b; 911 | c = probData.c; 912 | 913 | cx = c'*x; 914 | by = b'*y; 915 | 916 | % convergence metrics 917 | metrics.P = norm(A*x - tau*b,Inf) / termConsts.pRes; 918 | metrics.D = norm(At*y + s - tau*c,Inf) / termConsts.dRes; 919 | metrics.G = abs(cx - by + kappa) / termConsts.comp; 920 | metrics.A = abs(cx - by) / (tau + abs(by)); 921 | metrics.O = cx/tau; 922 | 923 | % complementarity gap of the initial iterate 924 | mu0 = 1; 925 | 926 | % termination criteria 927 | tol = algParams.optimTol; 928 | P = metrics.P <= tol; 929 | D = metrics.D <= tol; 930 | G = metrics.G <= tol; 931 | AA = metrics.A <= tol; 932 | T = tau <= tol * 1e-02 * max(1, kappa); 933 | K = tau <= tol * 1e-02 * min(1, kappa); 934 | M = mu <= tol * 1e-02 * mu0; 935 | 936 | % are we at least in the ballpark? 937 | Papx = metrics.P <= sqrt(tol); 938 | Dapx = metrics.D <= sqrt(tol); 939 | Aapx = metrics.A <= sqrt(tol); 940 | 941 | if P && D && AA 942 | status = 1; % success! 943 | elseif P && D && G && T 944 | if by > -tol && cx > -tol 945 | status = -1; % P is infeasible 946 | elseif by < tol && cx < tol 947 | status = -2; % D is infeasible 948 | elseif by > -tol && cx < tol 949 | status = -3; % P and D both infeasible 950 | else 951 | status = -4; % P or D is near infeasible 952 | end 953 | elseif K && M 954 | status = -8; % ill-posed, likely no strong duality 955 | elseif Papx && Dapx && Aapx 956 | status = -6; % no progress, but we are in the ballpark 957 | else 958 | status = -99; % unknown error 959 | end 960 | 961 | return 962 | 963 | function results = prepResults(results, status, soln, probData, iter, time, opts) 964 | % This method prepares the final solution and iteration statistics. 965 | % -------------------------------------------------------------------------- 966 | % USAGE of "prepResults" 967 | % results = prepResults(results, soln, probData, iter, time, opts) 968 | % -------------------------------------------------------------------------- 969 | % INPUT 970 | % results: iteration statistics 971 | % - results.alphaPred: predictor step size at each iteration 972 | % - results.betaPred: neighborhood parameter at the end of the predictor 973 | % phase at each iteration 974 | % - results.etaCorr: neighborhood parameter at the end of the corrector 975 | % phase at each iteration 976 | % - results.mu: complementarity gap at each iteration 977 | % status: final problem status 978 | % statusString: final problem status 979 | % soln: current iterate 980 | % probData: data for the conic optimization problem 981 | % iter: iteration count 982 | % time: elapsed time in seconds 983 | % opts: alfonso options 984 | % 985 | % OUTPUT 986 | % results: final solution and iteration statistics 987 | % -------------------------------------------------------------------------- 988 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 989 | % None. 990 | % -------------------------------------------------------------------------- 991 | 992 | results.nIterations = iter; 993 | results.time = time; 994 | 995 | % Truncates or removes the arrays for iteration statistics. 996 | if opts.debug 997 | results.alphaPred = results.alphaPred(1:iter); 998 | results.betaPred = results.betaPred(1:iter); 999 | results.etaCorr = results.etaCorr(1:iter); 1000 | results.mu = results.mu(1:iter); 1001 | else 1002 | results = rmfield(results,{'alphaPred','betaPred','etaCorr','mu'}); 1003 | end 1004 | 1005 | % final solution 1006 | results.x = soln.x/soln.tau; 1007 | results.s = soln.s/soln.tau; 1008 | results.y = soln.y/soln.tau; 1009 | results.tau = soln.tau; 1010 | results.kappa = soln.kappa; 1011 | 1012 | % final primal and dual objective values 1013 | results.pObj = probData.c'*results.x; 1014 | results.dObj = probData.b'*results.y; 1015 | 1016 | % final duality and complementarity gaps 1017 | results.dGap = results.pObj - results.dObj; 1018 | results.cGap = results.s'*results.x; 1019 | 1020 | % final primal and dual linear infeasibilities 1021 | results.pRes = probData.b - probData.A*results.x; 1022 | results.dRes = probData.c - probData.At*results.y - results.s; 1023 | 1024 | % final primal and dual infeasibilities 1025 | results.pIn = norm(results.pRes); 1026 | results.dIn = norm(results.dRes); 1027 | 1028 | % final relative primal and dual linear infeasibilities 1029 | results.rel_pIn = results.pIn/(1+norm(probData.b,Inf)); 1030 | results.rel_dIn = results.dIn/(1+norm(probData.c,Inf)); 1031 | 1032 | % final relative duality and complementarity gaps 1033 | results.rel_dGap = results.dGap/(1 + abs(results.pObj) + abs(results.dObj)); 1034 | results.rel_cGap = results.cGap/(1 + abs(results.pObj) + abs(results.dObj)); 1035 | 1036 | results.status = status; 1037 | 1038 | switch status 1039 | case 0 1040 | results.statusString = 'Number of iterations exceeded opts.maxIter.'; 1041 | case 1 1042 | results.statusString = 'Optimal solution found.'; 1043 | case -1 1044 | results.statusString = 'Primal infeasibility detected.'; 1045 | case -2 1046 | results.statusString = 'Dual infeasibility detected.'; 1047 | case -3 1048 | results.statusString = 'Primal and dual infeasibility detected.'; 1049 | case -4 1050 | results.statusString = 'Problem is nearly primal or dual infeasible.'; 1051 | case -6 1052 | results.statusString = 'Inaccurate solution found.'; 1053 | case -8 1054 | results.statusString = 'Problem is ill-posed.'; 1055 | otherwise 1056 | results.statusString = 'UNKNOWN'; 1057 | end 1058 | 1059 | return 1060 | 1061 | function [] = inputCheck(probData) 1062 | % This method checks the problem data for consistency. 1063 | % -------------------------------------------------------------------------- 1064 | % USAGE of "inputCheck" 1065 | % inputCheck(probData) 1066 | % -------------------------------------------------------------------------- 1067 | % INPUT 1068 | % probData: data for the conic optimization problem 1069 | % 1070 | % OUTPUT 1071 | % None. 1072 | % -------------------------------------------------------------------------- 1073 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 1074 | % None. 1075 | % -------------------------------------------------------------------------- 1076 | 1077 | [m, n] = size(probData.A); 1078 | 1079 | if m <= 0 || n <= 0 1080 | error('Input matrix A must be nontrivial.'); 1081 | end 1082 | if m ~= size(probData.b,1) 1083 | error('Dimension of (column) vector b must match the number of rows in A.'); 1084 | end 1085 | if n ~= size(probData.c,1) 1086 | error('Dimension of (column) vector c must match the number of columns in A.'); 1087 | end 1088 | 1089 | return 1090 | 1091 | function sayHello(opts) 1092 | 1093 | if opts.verbose 1094 | fprintf('\n*** alfonso (ver. 2024/07/15) by David Papp and Sercan Yildiz, (c) 2018.\n'); 1095 | switch opts.predLineSearch 1096 | case 0 1097 | fprintf('step size: safe fixed, '); 1098 | case 1 1099 | fprintf('step size: line search, '); 1100 | case 2 1101 | fprintf('step size: backtracking, '); 1102 | end 1103 | fprintf('optimality tolerance: %#.2e\n', opts.optimTol); 1104 | end 1105 | 1106 | return 1107 | -------------------------------------------------------------------------------- /src/alfonso_simple.m: -------------------------------------------------------------------------------- 1 | %ALFONSO_SIMPLE is a simple, user-exandable interface for alfonso for 2 | % solving standard form problems involving pre-defined primitive cones 3 | % 4 | % ADD NEW CONES BY ADDING NEW CASES TO THE FUNCTIONS 5 | % x0_K, gH_K, and (if necessary) dimPreprocess IN THIS FILE 6 | % 7 | % ------------------------------------------------------------------------- 8 | % 9 | % USAGE of "alfonso_simple" 10 | % results = alfonso_simple(c, A, b, K, x0, opts) 11 | % ------------------------------------------------------------------------- 12 | % INPUT 13 | % c: cost vector 14 | % A: constraint matrix 15 | % b: right-hand side vector 16 | % K: cone, given by a cell array with fields defining 17 | % the product of primitive cones to optimize over. 18 | % Example: 19 | % K{1}.type = 'socp'; % second-order cone 20 | % K{1}.dim = 10; 21 | % K{2}.type = 'exp'; % exponential cone, always 3-dimensional 22 | % K{3}.type = 'lp'; % nonnegative orthant 23 | % K{3}.dim = 10; 24 | % x0: initial primal iterate, pass [] to be chosen by alfonso 25 | % opts: algorithmic options, see alfonso.m for details 26 | % - opts.preprocess: 1 (true) to clean up the input and identify sparse structures, 27 | % 0 (false) otherwise. Default value: 1 28 | % - opts.ignorelp: 1 (true) to leave orthants alone in preprocessing. 29 | % Default value: 0 (false) 30 | % 31 | % OUTPUT 32 | % results: final solution and iteration statistics 33 | % see alfonso.m for details 34 | % 35 | % ------------------------------------------------------------------------- 36 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 37 | % 38 | % Authors: 39 | % David Papp 40 | % Sercan Yildiz 41 | % 42 | % Version: 2024/07/15 43 | % 44 | % This code has been developed and tested with Matlab R2023b. 45 | % ------------------------------------------------------------------------- 46 | 47 | 48 | function results = alfonso_simple(c, A, b, K, x0, opts) 49 | 50 | switch nargin 51 | case 4 52 | x0 = []; 53 | opts = struct('preprocess', true); 54 | case 5 55 | opts = struct('preprocess', true); 56 | case 6 57 | if ~isfield(opts, 'preprocess') 58 | opts.preprocess = true; 59 | end 60 | otherwise 61 | error('alfonso_simple needs 4-6 input arguments'); 62 | end 63 | if ~isfield(opts, 'ignorelp') 64 | opts.ignorelp = false; 65 | end 66 | 67 | % clean up the cone 68 | if opts.preprocess 69 | K = dimPreprocess(K); 70 | end 71 | 72 | if isempty(x0) 73 | x0 = x0_K(K); 74 | end 75 | 76 | inputCheck(A, b, c, K); 77 | 78 | % basic preprocessing 79 | if opts.preprocess 80 | [c, A, b, K, Kdims, x0, backTrafo] = problemPreprocess(c, A, b, K, x0, opts.ignorelp); 81 | if issparse(A) 82 | [rB, frB, As] = sparsePreprocess(A, Kdims); 83 | probData = struct('c', c, 'A', A, 'b', b, 'Kdims', Kdims, 'rB', rB, 'frB', {frB}, 'As', {As}); 84 | param.sparseA = true; 85 | else 86 | probData = struct('c', c, 'A', A, 'b', b, 'Kdims', Kdims); 87 | param.sparseA = false; 88 | end 89 | else 90 | Kdims = dims(K); 91 | probData = struct('c', c, 'A', A, 'b', b, 'Kdims', Kdims); 92 | param.sparseA = false; 93 | end 94 | 95 | % solve the problem with alfonso 96 | param.K = K; 97 | param.dims = Kdims; 98 | results = alfonso(probData, x0, @gH_K, param, opts); 99 | 100 | % transform the solution back 101 | if opts.preprocess 102 | results.x = results.x(backTrafo); 103 | results.s = results.s(backTrafo); 104 | end 105 | 106 | return 107 | 108 | 109 | % x0_K(K) returns a central vector in K: a concatenation of pre-determined 110 | % central points of its component simple cones 111 | function x0 = x0_K(K) 112 | 113 | n = 0; 114 | for i = 1:length(K) 115 | n = n + K{i}.dim; 116 | end 117 | 118 | x0 = zeros(n,1); 119 | n = 0; 120 | for i = 1:length(K) 121 | switch K{i}.type 122 | case {'l', 'lp'} 123 | x = ones(K{i}.dim,1); 124 | case {'free'} 125 | x = zeros(K{i}.dim,1); 126 | case {'soc', 'socp'} 127 | x = [1; zeros(K{i}.dim-1,1)]; 128 | case {'rsoc'} 129 | x = [sqrt(1/2); sqrt(1/2); zeros(K{i}.dim-2,1)]; 130 | case {'gpow'} 131 | x = [ones(length(K{i}.lambda),1); 0]; % not quite sure this is the best choice 132 | case {'dgpow'} 133 | x = [ones(length(K{i}.lambda),1); 0]; % not quite sure this is the best choice 134 | case {'exp'} 135 | x = [0.7633336255892224; 0.4910129724669193; -0.4197952321239648]; % what else? 136 | case {'grk1lmi'} 137 | x = K{i}.x0; 138 | otherwise 139 | error(['unsupported cone type: ', K{i}.type]); 140 | end 141 | x0(n+1 : n+K{i}.dim) = x; 142 | n = n + K{i}.dim; 143 | end 144 | 145 | return 146 | 147 | % Composite gH function computed from the constituent gH functions. Used as 148 | % the main argument of alfonso(). 149 | function [in, g, Hi, Li] = gH_K(x, params) 150 | 151 | K = params.K; 152 | nK = length(K); 153 | 154 | in = true; 155 | g = zeros(size(x)); 156 | Li = cell(nK,1); % doesn't do much, but helps with a warning 157 | Hi = cell(nK,1); % doesn't do much, but helps with a warning 158 | 159 | idx = 0; % x subvector index 160 | for i = 1:nK 161 | ni = K{i}.dim; 162 | xi = x(idx+1:idx+ni); 163 | 164 | switch K{i}.type 165 | case {'l', 'lp'} 166 | gH = @gH_LP; 167 | par0 = []; % the dimension is the only parameter, but that is obtained directly from the length of the vector 168 | case {'soc', 'socp'} 169 | gH = @gH_SOCP; 170 | par0 = []; % the dimension is the only parameter, but that is obtained directly from the length of the vector 171 | case {'rsoc'} 172 | gH = @gH_RSOC; 173 | par0 = []; % the dimension is the only parameter, but that is obtained directly from the length of the vector 174 | case {'gpow'} 175 | gH = @gH_GPow; 176 | par0 = K{i}.lambda; 177 | case {'dgpow'} 178 | gH = @gH_DGPow; 179 | par0 = K{i}.lambda; 180 | case {'exp'} 181 | gH = @gH_Exp; 182 | par0 = []; 183 | case {'grk1lmi'} 184 | gH = @gH_rk1LMI; 185 | par0 = K{i}; % struct('Vs', K{i}.Vs, 'ws', K{i}.ws, 'nonneg', K{i}.nonneg, 'ext', K{i}.ext); 186 | otherwise 187 | error(['unsupported cone type: ', K{i}.type]); 188 | end 189 | 190 | switch nargout 191 | case 4 192 | [in0,g0,Hi0,Li0] = gH(xi, par0); 193 | case 3 194 | [in0,g0,Hi0] = gH(xi, par0); 195 | case {1,2} 196 | [in0,g0] = gH(xi, par0); 197 | end 198 | 199 | if in0 200 | g(idx+1:idx+ni) = g0; 201 | if nargout >= 3 202 | Hi{i} = Hi0; 203 | if nargout == 4 204 | Li{i} = Li0; 205 | end 206 | end 207 | else 208 | in = 0; 209 | g = NaN; 210 | Hi = NaN; 211 | Li = NaN; 212 | return; 213 | end 214 | 215 | idx = idx+ni; 216 | end 217 | 218 | if ~params.sparseA 219 | if nargout > 2 220 | Hi = @(v)(concatF(Hi,params.dims,v)); 221 | if nargout > 3 222 | Li = @(M)(concatF(Li,params.dims,M)); 223 | end 224 | end 225 | end 226 | 227 | return 228 | 229 | function FofM = concatF(Fs, Kdims, M) 230 | 231 | if issparse(M) 232 | 233 | FMs = cell(length(Kdims),1); 234 | idx = 0; % x subvector index 235 | for i=1:length(Kdims) 236 | FMs{i} = Fs{i}(M(idx+1:idx+Kdims(i), :)); 237 | idx = idx + Kdims(i); 238 | end 239 | FofM = vertcat(FMs{:}); 240 | 241 | else 242 | 243 | FofM = zeros(size(M)); 244 | idx = 0; % x subvector index 245 | for i=1:length(Kdims) 246 | FofM(idx+1:idx+Kdims(i), :) = Fs{i}(M(idx+1:idx+Kdims(i), :)); 247 | idx = idx + Kdims(i); 248 | end 249 | 250 | end 251 | 252 | return 253 | 254 | 255 | function K = dimPreprocess(K) 256 | % This method performs some basic preprocessing on the cone structure K for 257 | % efficiency and compatibility with other functions. It should NOT be 258 | % called outside of alfonso_simple(). 259 | % 260 | % Included functionality: 261 | % - add missing 'dim' fields to cones for which the dimension is not the 262 | % natural parameter (e.g., exponential cone, semidefinite cone) 263 | % - remove 0-dimensional cones 264 | % 265 | % -------------------------------------------------------------------------- 266 | % USAGE of "dimPreprocess" 267 | % K = dimPreprocess(K) 268 | % -------------------------------------------------------------------------- 269 | % INPUT 270 | % K: cone structure 271 | % 272 | % OUTPUT 273 | % K: updated cone structure 274 | % -------------------------------------------------------------------------- 275 | 276 | for i=length(K):-1:1 277 | if ~isfield(K{i},'dim') 278 | switch K{i}.type 279 | case 'exp' 280 | K{i}.dim = 3; 281 | case 'grk1lmi' 282 | if K{i}.ext 283 | K{i}.dim = size(K{i}.ws,1) + 1; 284 | else 285 | K{i}.dim = size(K{i}.ws,1); 286 | end 287 | otherwise 288 | error(['missing dimension in cone ', int2str(i), ', type: ', K{i}.type]); 289 | end 290 | end 291 | if K{i}.dim <= 0 292 | K(i) = []; 293 | end 294 | end 295 | 296 | return 297 | 298 | function [c, A, b, K, Kdims, x0, backTrafo] = problemPreprocess(c, A, b, K, x0, ignoreLP) 299 | % This method performs some basic preprocessing on the primal problem for 300 | % efficiency and compatibility with other functions. It should NOT be 301 | % called outside of alfonso_simple(). 302 | % 303 | % Included functionality: 304 | % - move free variables into a single second-order cone 305 | % - move nonnegative variables into a single orthant 306 | % 307 | % -------------------------------------------------------------------------- 308 | % USAGE of "problemPreprocess" 309 | % [c, A, b, K, Kdims, x0, backTrafo] = problemPreprocess(c, A, b, K, x0, ignore) 310 | % -------------------------------------------------------------------------- 311 | % INPUT 312 | % c, A, b, K: problem data and cone structure 313 | % x0: initial point 314 | % ignoreLP: if true (1), it does not mess with the orthants 315 | % 316 | % OUTPUT 317 | % c, A, b, K: updated problem data and cone structure 318 | % x0: updated initial point 319 | % Kdims: array of integers, contains the dimensions of K{i} 320 | % backTrafo: variable permutation to obtain the solution of the original 321 | % problem from the solution of the preprocessed problem 322 | % -------------------------------------------------------------------------- 323 | 324 | [m,n] = size(A); 325 | freeVars = false(n,1); 326 | nonnegVars = false(n,1); 327 | keepCones = false(length(K),1); 328 | 329 | % find all the variables we might want to move 330 | idx = 0; 331 | for i=1:length(K) 332 | if strcmpi(K{i}.type, 'free') 333 | freeVars(idx+1 : idx+K{i}.dim) = true; 334 | elseif ~ignoreLP && (strcmpi(K{i}.type, 'l') || strcmpi(K{i}.type, 'lp')) 335 | nonnegVars(idx+1 : idx+K{i}.dim) = true; 336 | else 337 | keepCones(i) = true; 338 | end 339 | idx = idx + K{i}.dim; 340 | end 341 | 342 | otherVars = ~(freeVars | nonnegVars); 343 | dummy = any(freeVars); 344 | 345 | % new variable order: dummy variable, free variables, nonnegatives, rest 346 | c = [zeros(1,dummy); c(freeVars); c(nonnegVars); c(otherVars)]; 347 | A = [zeros(m,dummy), A(:,freeVars), A(:,nonnegVars), A(:,otherVars)]; 348 | 349 | xf = x0(freeVars) ; 350 | x0 = [zeros(1,dummy); xf; x0(nonnegVars); x0(otherVars)]; 351 | if dummy 352 | x0(1) = 1 + norm(xf); 353 | end 354 | 355 | K0 = {}; 356 | if any(freeVars) 357 | K0 = horzcat(K0, struct('type', 'socp', 'dim', sum(freeVars)+1)); 358 | end 359 | if any(nonnegVars) 360 | K0 = horzcat(K0, struct('type', 'lp', 'dim', sum(nonnegVars))); 361 | end 362 | K = horzcat(K0, K(keepCones)); 363 | 364 | % compute the permutation of variables that arranges everything back 365 | ind = 1:n; 366 | ind = [ind(freeVars), ind(nonnegVars), ind(otherVars)]; 367 | if dummy 368 | ind = [n+1, ind]; 369 | ind(ind) = 1:n+1; 370 | ind = ind(1:end-1); 371 | else 372 | ind(ind) = 1:n; 373 | end 374 | backTrafo = ind; 375 | 376 | Kdims = dims(K); 377 | 378 | return 379 | 380 | % Identifies the block structure of A. 381 | % The As output is optional in case we don't really want to store the dense 382 | % components?? 383 | % As: dense components of A' (transpose!) 384 | % rB: "row blocks", logical array (number of rows of A x number of blocks) 385 | % frB: columnwise find(rB) 386 | 387 | function [rB, frB, As] = sparsePreprocess(A, Kdims) 388 | 389 | nK = length(Kdims); 390 | if nargout == 3 391 | As = cell(nK,1); 392 | end 393 | 394 | rB = zeros(size(A,1), nK, 'logical'); 395 | frB = cell(nK,1); 396 | sidx = 1; % starting index of the column block 397 | for i=1:nK 398 | eidx = sidx - 1 + Kdims(i); % ending index of the column block 399 | rB(:,i) = any(A(:,sidx:eidx), 2); 400 | frB{i} = find(rB(:,i)); 401 | if nargout == 3 402 | As{i} = full(A(frB{i},sidx:eidx).'); 403 | end 404 | sidx = eidx + 1; 405 | end 406 | 407 | return 408 | 409 | 410 | function Kdims = dims(K) 411 | 412 | Kdims = zeros(length(K),1); 413 | for i=1:length(K) 414 | Kdims(i) = K{i}.dim; 415 | end 416 | 417 | return 418 | 419 | 420 | function inputCheck(A, b, c, K) 421 | 422 | [m, n] = size(A); 423 | 424 | if m <= 0 || n <= 0 425 | error('Input matrix A must be nontrivial.'); 426 | end 427 | if m ~= size(b,1) 428 | error('Dimension of (column) vector b must match the number of rows in A.'); 429 | end 430 | if n ~= size(c,1) 431 | error('Dimension of (column) vector c must match the number of columns in A.'); 432 | end 433 | 434 | if n ~= sum(dims(K)) 435 | error('Dimension of (column) vector c must match the dimension of the cone K'); 436 | end 437 | 438 | return 439 | 440 | 441 | -------------------------------------------------------------------------------- /src/computeAHiAt.m: -------------------------------------------------------------------------------- 1 | % Computes the Schur complement matrix 2 | % AHiAt = A*inv(H)*A' 3 | function AHiAt = computeAHiAt(Li, probData) 4 | 5 | % Relies on matlab for a single block, but "manually" handles 6 | % block diagonal Hessians in a for loop. 7 | if ~iscell(Li) 8 | LiAt = Li(probData.At); % L'\A' 9 | AHiAt = LiAt.'*LiAt; 10 | else 11 | % This is the readable version of what is happening below. But the code below is more efficient. 12 | % AHiAt = zeros(size(probData.A,1)); 13 | % idx = 0; 14 | % for i=1:length(probData.As) 15 | % ni = size(probData.As{i},1); 16 | % Si = Li{i}(probData.As{i}); 17 | % AHiAt(probData.rB(:,i),probData.rB(:,i)) = AHiAt(probData.rB(:,i),probData.rB(:,i)) + Si.'*Si; 18 | % idx = idx+ni; 19 | % end 20 | % AHiAt = sparse(AHiAt); 21 | 22 | % Allocate space for sparse array. 23 | N = sum(sum(probData.rB,1).^2); 24 | ijv = zeros(N,3); 25 | 26 | ijvshift = 0; % current starting ijv array position (minus 1) 27 | sidx = 1; % starting index of the column block 28 | for i=1:length(probData.Kdims) 29 | eidx = sidx - 1 + probData.Kdims(i); % ending index of the column block 30 | 31 | % Compute the small Ai*inv(Hi)*Ai' block as Si'*Si, with Si = L'\A(i)'. 32 | rBi = probData.frB{i}; 33 | Si = Li{i}(probData.As{i}); 34 | % Distribute the entries in the full A*inv(H)*A' matrix, as in 35 | % AHiAt(rBi,rBi) = AHiAt(rBi,rBi) + Si.'*Si; 36 | ni = length(rBi); 37 | c = repmat(rBi,ni,1); 38 | r = kron(rBi,ones(ni,1)); 39 | M = Si.'*Si; 40 | ijv(ijvshift + (1:ni^2),:) = [r c M(:)]; 41 | 42 | sidx = eidx + 1; 43 | ijvshift = ijvshift + ni^2; 44 | end 45 | 46 | % Assemble the final result. 47 | % Relies on the property of sparse() that repeated entries are added. 48 | m = size(probData.A,1); 49 | AHiAt = sparse(ijv(:,1), ijv(:,2), ijv(:,3), m, m); 50 | end 51 | 52 | return 53 | -------------------------------------------------------------------------------- /src/computeAHiAti.m: -------------------------------------------------------------------------------- 1 | % Computes the action of the inverse Schur complement on a vector: 2 | % AHiAti = @(v)( inv(A*inv(H)*A')*v ) 3 | function AHiAti = computeAHiAti(Li, probData) 4 | 5 | % Assemble the A*inv(H)*A' matrix. 6 | AHiAt = computeAHiAt(Li, probData); 7 | 8 | [L,D,P] = ldl(AHiAt); 9 | AHiAti = @(v)( P*(L'\(D\(L\(P'*v)))) ); 10 | 11 | return 12 | -------------------------------------------------------------------------------- /src/gH_DGPow.m: -------------------------------------------------------------------------------- 1 | %GH_DGPOW implements a membership and barrier function oracle for the dual 2 | % of the generalized power cone 3 | % P^*_{a} = {(x,z)\in R_+^n x R | prod((x/a).^a) >= abs(z)}, 4 | % also known as the SONC (sum-of-nonnegative-circuit-polynomials cone) 5 | % 6 | % It is parametrized by the vector a which must satisfy sum(a) = 1 and 7 | % a(:) > 0. 8 | % 9 | % -------------------------------------------------------------------------- 10 | % USAGE of "gH_DGPow" 11 | % [in, g, Hi, Li] = gH_DGPow(xz, a) 12 | % -------------------------------------------------------------------------- 13 | % INPUT 14 | % xz: primal iterate xz = [x; z] 15 | % a: parameter vector satisfying sum(a) = 1 and a(:) > 0 16 | % 17 | % OUTPUT 18 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 19 | % interior of the cone. 20 | % g: gradient of the barrier function at x 21 | % Hi: function representing the inverse Hessian action at x 22 | % Li: function representing the inverse Cholesky action or similar 23 | % 24 | % The last 3 output may be anything if in==0. 25 | % -------------------------------------------------------------------------- 26 | % Details on the last two outputs: denoting the Hessian at x by H, 27 | % - Li(M) returns L\M, where L is any matrix satisfying LL' = inv(H) 28 | % Needs to work for a matrix. 29 | % - Hi(v) returns H\v. 30 | % Here, v can be assumed to be a column vector (not a matrix). 31 | % 32 | % It is often sensible, but not always necessary or most efficient, to 33 | % implement Hi using Li or an explicitly computed Cholesky factor. 34 | % -------------------------------------------------------------------------- 35 | % Copyright (C) 2020 David Papp and Sercan Yildiz. 36 | % 37 | % Authors: 38 | % David Papp 39 | % Sercan Yildiz 40 | % -------------------------------------------------------------------------- 41 | 42 | 43 | function [in, g, Hi, Li] = gH_DGPow(xz, a) 44 | 45 | xz = xz ./ [a;1]; % only for the dual cone 46 | x = xz(1:end-1); 47 | z = xz(end); 48 | 49 | %alogx = a'*log(x); 50 | %in = all(x>0) & alogx > log(abs(z)); 51 | xpa = prod(x.^a); 52 | in = all(x>0) & xpa > abs(z); 53 | 54 | g = NaN; Hi = NaN; Li = NaN; % will be overwritten if in==1 55 | 56 | if in 57 | %xpa = exp(alogx); 58 | x2amz2 = xpa^2-z^2; %(xpa+z)*(xpa-z); 59 | 60 | % gradient; simple formula 61 | g = [-(1-a)./x - 2*(xpa^2.*a./x)/x2amz2; 2*z/x2amz2]; 62 | g = g./[a;1]; % only for the dual cone 63 | 64 | % Hessian 65 | if nargout > 2 66 | % actual H 67 | H = 4*x2amz2^(-2)*xpa^2*([z*a;-1]*[z*a;-1]')./([x;1]*[x;1]') + ... 68 | diag( [(z^2*(-1+a)+xpa^2*(1+a))./(x2amz2*x.^2) ; -2/x2amz2] ); 69 | H = H./([a;1]*[a;1]'); % only for the dual cone 70 | 71 | [L,err] = chol(H,'lower'); 72 | if err > 0 73 | in = false; 74 | else 75 | Hi = @(v)( L'\(L\v) ); 76 | Li = @(M)( L\M ); 77 | end 78 | 79 | end % if nargout > 2 80 | end % if in 81 | 82 | return 83 | 84 | -------------------------------------------------------------------------------- /src/gH_Exp.m: -------------------------------------------------------------------------------- 1 | %GH_EXP implements a membership and barrier function oracle for the 2 | % exponential cone 3 | % K_{exp} := cl { x \in R_+^2 x R | x1 > x2*exp(x3/x2) } 4 | % 5 | % It requires no parameters and it is always three-dimensional. 6 | % -------------------------------------------------------------------------- 7 | % USAGE of "gH_Exp" 8 | % [in, g, Hi, Li] = gH_Exp(x) 9 | % -------------------------------------------------------------------------- 10 | % INPUT 11 | % x: primal iterate 12 | % 13 | % OUTPUT 14 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 15 | % interior of the cone. 16 | % g: gradient of the barrier function at x 17 | % Hi: function representing the inverse Hessian action at x 18 | % Li: function representing the inverse Cholesky action or similar 19 | % 20 | % The last 3 output may be anything if in==0. 21 | % -------------------------------------------------------------------------- 22 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 23 | % None. 24 | % ------------------------------------------------------------------------- 25 | % Copyright (C) 2020 David Papp and Sercan Yildiz. 26 | % 27 | % Authors: 28 | % David Papp 29 | % Sercan Yildiz 30 | % -------------------------------------------------------------------------- 31 | 32 | function [in, g, Hi, Li] = gH_Exp(x, ~) 33 | 34 | x1 = x(1); 35 | x2 = x(2); 36 | x3 = x(3); 37 | 38 | logx1px2 = log(x1/x2); 39 | den = -x3 + x2*logx1px2; 40 | 41 | g = NaN; Hi = NaN; Li = NaN; % will be overwritten if in==1 42 | 43 | in = x1 > 0 && x2 > 0 && den > 0; 44 | 45 | if in 46 | g = [-(1 + x2/den)/x1; (1-x3/x2)/den - 2/x2; 1/den]; 47 | 48 | if nargout > 2 49 | % not really H yet 50 | H = [ (x2^2 + x2*den + den^2)/(x1^2), (-x2+x3)/x1, -x2/x1; 51 | (-x2+x3)/x1, ((x2-x3)^2 + 2*den^2- (x2-2*x3)*den)/(x2^2) , 1-logx1px2; 52 | -x2/x1, 1-logx1px2, 1]; 53 | end 54 | 55 | if nargout >= 3 56 | [L, err] = chol(sparse(H), 'lower'); 57 | 58 | if err > 0 59 | in = false; 60 | return; 61 | else 62 | %H = den^(-2)*sparse(H); 63 | %L = den^(-1)*L; 64 | Hi = @(v)( den^2 * (H\v) ); 65 | Li = @(M)( den * (L\M) ); 66 | end 67 | end 68 | end 69 | 70 | return 71 | -------------------------------------------------------------------------------- /src/gH_GPow.m: -------------------------------------------------------------------------------- 1 | %GH_GPOW implements a membership and barrier function oracle for the 2 | % generalized power cone 3 | % P_{a} = {(x,z)\in R_+^n x R | prod(x.^a) >= abs(z)} 4 | % 5 | % It is parametrized by the vector a which must satisfy \sum(a) = 1 and 6 | % a > 0. 7 | % -------------------------------------------------------------------------- 8 | % USAGE of "gH_GPow" 9 | % [in, g, Hi, Li] = gH_GPow(xz, a) 10 | % -------------------------------------------------------------------------- 11 | % INPUT 12 | % xz: primal iterate xz = [x; z] 13 | % a: parameter vector satisfying sum(a) = 1 and a(:) > 0 14 | % 15 | % OUTPUT 16 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 17 | % interior of the cone. 18 | % g: gradient of the barrier function at x 19 | % Hi: function representing the inverse Hessian action at x 20 | % Li: function representing the inverse Cholesky action or similar 21 | % 22 | % The last 3 output may be anything if in==0. 23 | % -------------------------------------------------------------------------- 24 | % Details on the last two outputs: denoting the Hessian at x by H, 25 | % - Li(M) returns L\M, where L is any matrix satisfying LL' = inv(H) 26 | % Needs to work for a matrix. 27 | % - Hi(v) returns H\v. 28 | % Here, v can be assumed to be a column vector (not a matrix). 29 | % 30 | % It is often sensible, but not always necessary or most efficient, to 31 | % implement Hi using Li or an explicitly computed Cholesky factor. 32 | % -------------------------------------------------------------------------- 33 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 34 | % 35 | % Authors: 36 | % David Papp 37 | % Sercan Yildiz 38 | % 39 | % -------------------------------------------------------------------------- 40 | 41 | function [in,g,Hi,Li] = gH_GPow(xz, a) 42 | 43 | x = xz(1:end-1); 44 | z = xz(end); 45 | 46 | g = NaN; Hi = NaN; Li = NaN; % will be overwritten if in==1 47 | 48 | xpa = prod(x.^a); 49 | in = all(x>0) & xpa > abs(z); 50 | 51 | if in 52 | x2amz2 = xpa^2-z^2; %(xpa+z)*(xpa-z); 53 | 54 | % gradient; simple formula 55 | g = [-(1-a)./x - 2*(xpa^2.*a./x)/x2amz2; 2*z/x2amz2]; 56 | 57 | % Hessian 58 | if nargout >= 3 59 | H = 4*x2amz2^(-2)*xpa^2*([z*a;-1]*[z*a;-1]')./([x;1]*[x;1]') + ... 60 | diag( [(z^2*(-1+a)+xpa^2*(1+a))./(x2amz2*x.^2) ; -2/x2amz2] ); 61 | 62 | % direct solve with Cholesky 63 | [L,err] = chol(H,'lower'); 64 | if err 65 | in = 0; 66 | else 67 | Hi = @(v)(L'\(L\v)); 68 | if nargout == 4 69 | Li = @(M) L\M; 70 | end % if nargout == 4 71 | end 72 | end 73 | end 74 | 75 | return 76 | -------------------------------------------------------------------------------- /src/gH_LP.m: -------------------------------------------------------------------------------- 1 | %GH_LP implements a membership and barrier function oracle for the 2 | % nonnegative orthant, using the standard logarithmic barrier. 3 | % -------------------------------------------------------------------------- 4 | % USAGE of "gH_LP" 5 | % [in, g, Hi, Li] = gH_LP(x) 6 | % -------------------------------------------------------------------------- 7 | % INPUT 8 | % x: primal iterate 9 | % 10 | % OUTPUT 11 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 12 | % interior of the cone. 13 | % g: gradient of the barrier function at x 14 | % Hi: function representing the inverse Hessian action at x 15 | % Li: function representing the inverse Cholesky action or similar 16 | % 17 | % The last 3 output may be anything if in==0. 18 | % -------------------------------------------------------------------------- 19 | % Details on the last two outputs: denoting the Hessian at x by H, 20 | % - Li(M) returns L\M, where L is any matrix satisfying LL' = inv(H) 21 | % Needs to work for a matrix. 22 | % - Hi(v) returns H\v. 23 | % Here, v can be assumed to be a column vector (not a matrix). 24 | % 25 | % It is often sensible, but not always necessary or most efficient, to 26 | % implement Hi using Li or an explicitly computed Cholesky factor. 27 | % -------------------------------------------------------------------------- 28 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 29 | % 30 | % Authors: 31 | % David Papp 32 | % Sercan Yildiz 33 | % 34 | % -------------------------------------------------------------------------- 35 | 36 | function [in, g, Hi, Li] = gH_LP(x, ~) 37 | 38 | in = min(x)>0; 39 | 40 | if in 41 | % The gradient in closed form. 42 | g = -1./x; 43 | 44 | % Hessian-related functions in closed form. 45 | if nargout >= 3 46 | Hi = @(v)( (x.^2).*v ); 47 | if nargout == 4 48 | Li = @(v)( v .* x ); 49 | end 50 | end 51 | else 52 | g = NaN; Li = NaN; Hi = NaN; 53 | end 54 | 55 | return 56 | 57 | -------------------------------------------------------------------------------- /src/gH_RSOC.m: -------------------------------------------------------------------------------- 1 | %GH_RSOC implements a membership and barrier function oracle for the rotated 2 | % second-order cone 3 | % { x \in R_+^2 x R^n | x(1)*x(2) >= ||x(3:end)||^2 } 4 | % by a transformation to the second-order cone. 5 | % It requires no parameters. 6 | % -------------------------------------------------------------------------- 7 | % USAGE of "gH_RSOC" 8 | % [in, g, Hi, Li] = gH_RSOC(x) 9 | % -------------------------------------------------------------------------- 10 | % INPUT 11 | % x: primal iterate 12 | % 13 | % OUTPUT 14 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 15 | % interior of the cone. 16 | % g: gradient of the barrier function at x 17 | % Hi: function representing the inverse Hessian action at x 18 | % Li: function representing the inverse Cholesky action or similar 19 | % 20 | % The last 3 output may be anything if in==0. 21 | % -------------------------------------------------------------------------- 22 | % EXTERNAL FUNCTIONS CALLED IN THIS FUNCTION 23 | % gH_SOCP: membership and barrier function oracle for second-order cones. 24 | % ------------------------------------------------------------------------- 25 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 26 | % 27 | % Authors: 28 | % David Papp 29 | % Sercan Yildiz 30 | % -------------------------------------------------------------------------- 31 | 32 | function [in, g, Hi, Li] = gH_RSOC(x, ~) 33 | 34 | n = length(x); 35 | p = x(1); 36 | q = x(2); 37 | xbar = x(3:end); 38 | 39 | in = p > 0 && q > 0 && 2*p*q > xbar'*xbar; 40 | 41 | if in 42 | if nargout == 1 43 | return; 44 | end 45 | 46 | T = blkdiag([sqrt(1/2), sqrt(1/2); sqrt(1/2), -sqrt(1/2)], speye(n-2)); % note: equals its inverse 47 | switch nargout 48 | case 2 49 | [~, g] = gH_SOCP(T*x, []); 50 | case 3 51 | [~, g, Hi] = gH_SOCP(T*x, []); 52 | case 4 53 | [~, g, Hi, Li] = gH_SOCP(T*x, []); 54 | end 55 | 56 | g = T*g; 57 | 58 | if nargout > 2 59 | %H = T*H*T; 60 | Hi = @(v)(T*Hi(T*v)); 61 | end 62 | 63 | if nargout > 3 64 | %L = T*L; 65 | Li = @(M)(Li(T*M)); 66 | end 67 | 68 | else 69 | g = NaN; Hi = NaN; Li = NaN; 70 | end 71 | 72 | return 73 | -------------------------------------------------------------------------------- /src/gH_SOCP.m: -------------------------------------------------------------------------------- 1 | %GH_SOCP implements a membership and barrier function oracle for the 2 | % second-order cone 3 | % { x \in R^n | x(1) >= norm(x(2:end),2) }, 4 | % using the standard logarithmic barrier. 5 | % -------------------------------------------------------------------------- 6 | % USAGE of "gH_SOCP" 7 | % [in, g, Hi, Li] = gH_SOCP(x) 8 | % -------------------------------------------------------------------------- 9 | % INPUT 10 | % x: primal iterate 11 | % 12 | % OUTPUT 13 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 14 | % interior of the cone. 15 | % g: gradient of the barrier function at x 16 | % Hi: function representing the inverse Hessian action at x 17 | % Li: function representing the inverse Cholesky action or similar 18 | % 19 | % The last 3 output may be anything if in==0. 20 | % -------------------------------------------------------------------------- 21 | % Details on the last two outputs: denoting the Hessian at x by H, 22 | % - Li(M) returns L\M, where L is any matrix satisfying LL' = inv(H) 23 | % Needs to work for a matrix. 24 | % - Hi(v) returns H\v. 25 | % Here, v can be assumed to be a column vector (not a matrix). 26 | % 27 | % It is often sensible, but not always necessary or most efficient, to 28 | % implement Hi using Li or an explicitly computed Cholesky factor. 29 | % -------------------------------------------------------------------------- 30 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 31 | % 32 | % Authors: 33 | % David Papp 34 | % Sercan Yildiz 35 | % -------------------------------------------------------------------------- 36 | 37 | function [in, g, Hi, Li] = gH_SOCP(x, ~) 38 | 39 | n = size(x,1); 40 | xbar = x(2:n); 41 | det = x(1)^2 - xbar'*xbar; 42 | 43 | g = NaN; Hi = NaN; Li = NaN; % will be overwritten if in==1 44 | 45 | in = x(1) > 0 && det > 0; 46 | 47 | if in 48 | g = 2*[-x(1); xbar] / det; 49 | 50 | if nargout >= 3 51 | Hi = @(v)( x*(x'*v) - det/2 * [v(1,:); -v(2:end,:)] ); 52 | if nargout == 4 53 | R = diag( repmat(sqrt(2/det),n,1) ); 54 | R = cholupdate(R, g); 55 | [R, err] = cholupdate(R, [2/sqrt(det); zeros(n-1,1)], '-'); 56 | if err > 0 57 | in = false; 58 | else 59 | L = R.'; 60 | Li = @(M)(L\M); 61 | end 62 | end 63 | end 64 | end 65 | 66 | return 67 | -------------------------------------------------------------------------------- /src/gH_rk1LMI.m: -------------------------------------------------------------------------------- 1 | %GH_RK1LMI implements a membership and barrier function oracle for linear 2 | % matrix inequalities (LMIs), in the variables x or (z,x), of the form 3 | % V_i*diag(w_i \circ x)*V_i' \succcurlyeq 0 \forall i = 1, ..., k, 4 | % or 5 | % V_i*diag(w_i \circ x)*V_i' \succcurlyeq z*I_m \forall i = 1, ..., k, 6 | % where each V_i is an m \times n_i matrix and each w_i is an n-vector. 7 | % Equivalently, the latter can be written as 8 | % \sum_{j=1}^n x(j) * w_i(j) * V(:,j)*V(:,j)^T \succcurlyeq z*I_m 9 | % 10 | % Optionally, x >= 0 may also be required. 11 | % 12 | % The extended cone and the sign constraints are toggled using the input 13 | % parameters (see INPUT below). 14 | % 15 | % Notes: 16 | % 1) Order of variables in the extended cone is [z,x] \in R x R^n. 17 | % 2) If each V_i is full row-rank and w>0, then x = ones(n,1) is strictly 18 | % feasible with z=0. In general, z=0 is not feasible, but for every x 19 | % there is a z that works. 20 | % 3) Assumes each function x -> V_i*diag(w \circ x)*V_i' is injective. 21 | % 22 | % -------------------------------------------------------------------------- 23 | % USAGE of "gH_rk1LMI" 24 | % [in, g, Hi, Li] = gH_rk1LMI(x, param) 25 | % -------------------------------------------------------------------------- 26 | % INPUT 27 | % x: primal iterate 28 | % param.ws: a matrix whose ith column is the vector w_i above 29 | % param.Vs: cell array with the matrices V_i above 30 | % param.nonneg: true (1) if x >= 0 is also required, false (0) if x is 31 | % not sign-constrained 32 | % param.ext: true (1) if the right-hand side has z*I, false (0) if 33 | % there is no z variable, and the right-hand side is 0 34 | % 35 | % OUTPUT 36 | % in: 0 if x is not in the interior of the cone. 1 if x is in the 37 | % interior of the cone. 38 | % g: gradient of the barrier function at x 39 | % Hi: function representing the inverse Hessian action at x 40 | % Li: function representing the inverse Cholesky action or similar 41 | % 42 | % The last 3 output may be anything if in==0. 43 | % -------------------------------------------------------------------------- 44 | % Details on the last two outputs: denoting the Hessian at x by H, 45 | % - Li(M) returns L\M, where L is any matrix satisfying LL' = inv(H) 46 | % Needs to work for a matrix. 47 | % - Hi(v) returns H\v. 48 | % Here, v can be assumed to be a column vector (not a matrix). 49 | % 50 | % It is often sensible, but not always necessary or most efficient, to 51 | % implement Hi using Li or an explicitly computed Cholesky factor. 52 | % -------------------------------------------------------------------------- 53 | % Copyright (C) 2024 David Papp 54 | % -------------------------------------------------------------------------- 55 | 56 | function [in, g, Hi, Li] = gH_rk1LMI(zx, param) 57 | 58 | % The first input is [z;x] or x. 59 | if param.ext 60 | z = zx(1); 61 | x = zx(2:end); 62 | else 63 | x = zx; 64 | end 65 | 66 | g = NaN; Hi = NaN; Li = NaN; % will be overwritten if in==1 67 | 68 | % In the nonnegative cone? 69 | if param.nonneg && any(x <= 0) 70 | in = 0; return; 71 | end 72 | 73 | [n, numVs] = size(param.ws); 74 | 75 | % Initialize g and H (if needed). 76 | % For simplicity, we treat the dimension as (n+1), and will drop the first 77 | % component later if needed. 78 | if nargout > 1 79 | if param.nonneg 80 | g = [0; -1./x]; 81 | else 82 | g = zeros(n+1,1); % +1 for the z component 83 | end 84 | if nargout > 2 85 | % Variables to accumulate each block of the Hessian. 86 | H11 = 0; 87 | H1end = zeros(n,1); 88 | if param.nonneg 89 | % Variable to accumulate the Hessian. 90 | H2end = diag(x.^(-2)); 91 | else 92 | H2end = zeros(n,n); 93 | end 94 | end 95 | end 96 | 97 | % THE REST IS ALL ABOUT THE LMIs. 98 | 99 | in = 1; % innocent until proven guilty 100 | 101 | % barrier = sum of individual barriers; accumulate g and H in for i=... loop 102 | for i=1:numVs 103 | % compute M := V_i*diag(w_i \circ x)*V_i' - z*eye(n) 104 | V = param.Vs{i}; 105 | w = param.ws(:,i); 106 | 107 | [m,n] = size(V); 108 | M = V*diag(w.*x)*V.'; % not the final M yet! 109 | 110 | % compute M without vectorization 111 | % Vx = zeros(m,n); 112 | % for u = 1:n 113 | % Vx(:,u) = w(u)*x(u)*V(:,u); 114 | % end 115 | % M = Vx*V'; % not the final M yet! Also, not symmetric! 116 | % Vx = zeros(m,n); 117 | % for u = 1:n 118 | % Vx(:,u) = sqrt(w(u)*x(u))*V(:,u); 119 | % end 120 | % M = Vx*Vx.'; % not the final M yet! 121 | 122 | % -zI? 123 | if param.ext 124 | for u=1:m 125 | M(u,u) = M(u,u) - z; 126 | end 127 | end 128 | % M is now computed. 129 | 130 | % factor M to test feasibility 131 | [L,err] = chol(M,'lower'); 132 | if err 133 | in = 0; 134 | return; 135 | end 136 | 137 | if nargout == 1 && (i==numVs) 138 | % if we are here, we still have in = 1 139 | return; 140 | end 141 | 142 | if nargout > 1 143 | 144 | LinvV = L \ V; 145 | if param.ext 146 | Linv = inv(L); 147 | g(1) = g(1) + Linv(:)'*Linv(:); % this is surprising; any way to avoid explicit inversion? 148 | % tr(M^{-1}) = tr(L^{-T}*L^{-1}) = sum of squared entries of L^{-1} 149 | end 150 | if nargout == 2 151 | % accumulate gradient without the remaining Hessian elements 152 | g(2:end) = g(2:end) - w .* vecnorm(LinvV,2,1)'.^2; 153 | if (i==numVs) 154 | if ~param.ext 155 | g = g(2:end); 156 | end 157 | return; 158 | end 159 | end 160 | 161 | if nargout >= 3 162 | 163 | % First (z) column of the Hessian. 164 | if param.ext 165 | % H_zz: multivariate Faà di Bruno: \nabla_{zz} f(z,x) = 166 | % = tr(M^{-2}) = sum of squared entries of M^{-1} 167 | invM = Linv.'*Linv; 168 | H11 = H11 + invM(:)'*invM(:); 169 | 170 | % H_{zx}: even uglier calculus vi' * M^{-2} * vi 171 | MinvV = L' \ LinvV; 172 | for u=1:n 173 | H1end(u) = H1end(u,1) - w(u) * MinvV(:,u)' * MinvV(:,u); 174 | end 175 | end 176 | 177 | % H_{xx}: 178 | % gradient wrt x is computed from Hessian diagonal 179 | Hi = (LinvV.'*LinvV); % not the Hessian yet! only V.'*(M\V) but we need the diag 180 | g(2:end) = g(2:end) - w .* diag(Hi); % extract diag for gradient 181 | 182 | % Hessian wrt x. 183 | H2end = H2end + (w*w.').*(Hi.^2); 184 | %H2end = H2end + w.' .* Hi.^2 .* w; % a bit faster, not automatically symmetric. 185 | 186 | if (i==numVs) 187 | if param.ext 188 | H = [H11, H1end'; H1end, H2end]; 189 | else 190 | g = g(2:end); 191 | H = H2end; 192 | end 193 | 194 | %Hi = @(v)(H\v); 195 | 196 | %Hi = decomposition(H); 197 | %Hi = @(v) Hi\v; 198 | 199 | %[L,D,P] = ldl(H); 200 | %Hi = @(v)( P*(L'\(D\(L\(P'*v)))) ); 201 | 202 | % direct solve with Cholesky 203 | [L,err] = chol(H,'lower'); 204 | if err 205 | in = 0; 206 | return; 207 | else 208 | Hi = @(v)(L'\(L\v)); 209 | if nargout == 4 210 | Li = @(M) L\M; 211 | end % if nargout == 4 212 | return; 213 | end 214 | end 215 | 216 | end % if nargout >= 3 217 | end % if nargout > 1 218 | end % for i=1:k 219 | return 220 | 221 | -------------------------------------------------------------------------------- /src/linsolveA.m: -------------------------------------------------------------------------------- 1 | function [delta, probData] = linsolveA(soln, probData, RHS) 2 | % This method implements a block linear solver for the Newton systems. 3 | % -------------------------------------------------------------------------- 4 | % USAGE of "linSolveA" 5 | % delta = linSolveA(soln, probData, RHS) 6 | % -------------------------------------------------------------------------- 7 | % INPUT 8 | % soln: current iterate 9 | % probData: data for the conic optimization problem 10 | % RHS: right-hand side of the Newton system 11 | % 12 | % OUTPUT 13 | % delta: computed Newton direction 14 | % -------------------------------------------------------------------------- 15 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 16 | % 17 | % Version: 2024/07/15 18 | % 19 | % This code has been developed and tested with Matlab R2023b. 20 | % ------------------------------------------------------------------------- 21 | 22 | A = probData.A; 23 | At = probData.At; 24 | b = probData.b; 25 | c = probData.c; 26 | [m, n] = size(A); 27 | 28 | if issparse(A) 29 | b = sparse(b); 30 | c = sparse(c); 31 | RHS = sparse(RHS); 32 | end 33 | 34 | mu = soln.mu; 35 | tau = soln.tau; 36 | Hi = soln.Hi; 37 | Li = soln.Li; 38 | %AHiAti = soln.AHiAti; 39 | AHiAt = computeAHiAt(Li, probData); 40 | 41 | ry = RHS(1:m); 42 | rx = RHS(m+(1:n)); 43 | rtau = RHS(m+n+1); 44 | rs = RHS(m+n+1+(1:n)); 45 | rkappa = RHS(end); 46 | 47 | if ~iscell(Hi) 48 | %LiAt = Li(At); 49 | Lic = Li(c); 50 | Hic = Hi(c); 51 | Hirxrs = Hi(rx+rs); 52 | else 53 | Kdims = probData.Kdims; 54 | 55 | idx = 0; 56 | Lic = zeros(n,1); 57 | Hic = zeros(n,1); 58 | Hirxrs = zeros(n,1); 59 | 60 | z = rx+rs; 61 | for k=1:length(Kdims) 62 | ck = c(idx+1:idx+Kdims(k)); 63 | zk = z(idx+1:idx+Kdims(k)); 64 | Lic(idx+1:idx+Kdims(k)) = Li{k}(ck); 65 | Hic(idx+1:idx+Kdims(k)) = Hi{k}(ck); 66 | Hirxrs(idx+1:idx+Kdims(k)) = Hi{k}(zk); 67 | idx = idx+Kdims(k); 68 | end 69 | end 70 | AHic = A*Hic; 71 | 72 | RHSdydtau = [ry; rtau+rkappa] - [A; -c']*Hirxrs/soln.mu; 73 | 74 | x = RHSdydtau(1:end-1); 75 | y = RHSdydtau(end); 76 | 77 | dinv = (mu/tau^2 + Lic.'*Lic/mu)^(-1); 78 | %dy = (LiAt'*LiAt/mu + (b+AHic/mu)*dinv*(b-AHic/mu)') \ (x+(b+AHic/mu)*dinv*y); 79 | dy = (AHiAt/mu + (b+AHic/mu)*dinv*(b-AHic/mu)') \ (x+(b+AHic/mu)*dinv*y); 80 | dtau = dinv*((-b + AHic/mu)'*dy + y); 81 | 82 | %dx = (Hirxrs + L'\([LiAt, -Lic]*[dy; dtau]))/soln.mu; 83 | if ~iscell(Hi) 84 | HiAtdy = Hi(At*dy); 85 | else 86 | idx = 0; 87 | HiAtdy = zeros(n,1); 88 | 89 | z = At*dy; 90 | for k=1:length(Kdims) 91 | zk = z(idx+1:idx+Kdims(k)); 92 | HiAtdy(idx+1:idx+Kdims(k)) = Hi{k}(zk); 93 | idx = idx+Kdims(k); 94 | end 95 | end 96 | dx = (Hirxrs + HiAtdy - Hic*dtau)/soln.mu; 97 | 98 | delta = zeros(m+2*n+2, 1); 99 | delta(1:m) = dy; 100 | delta(m+n+1) = dtau; 101 | delta(m+(1:n)) = dx; 102 | delta(m+n+1+(1:n)) = -rx - [At, -c]*[dy; dtau]; 103 | delta(end) = -rtau + b'*dy - c'*dx; 104 | 105 | return 106 | -------------------------------------------------------------------------------- /src/linsolveB.m: -------------------------------------------------------------------------------- 1 | function [delta, probData] = linsolveB(soln, probData, RHS) 2 | % This method implements a block linear solver for the Newton systems. 3 | % -------------------------------------------------------------------------- 4 | % USAGE of "linsolveB" 5 | % delta = linsolveB(soln, probData, RHS) 6 | % -------------------------------------------------------------------------- 7 | % INPUT 8 | % soln: current iterate 9 | % probData: data for the conic optimization problem 10 | % RHS: right-hand side of the Newton system 11 | % 12 | % OUTPUT 13 | % delta: computed Newton direction 14 | % -------------------------------------------------------------------------- 15 | % Copyright (C) 2018 David Papp and Sercan Yildiz. 16 | % 17 | % Version: 2024/07/15 18 | % 19 | % This code has been developed and tested with Matlab R2023b. 20 | % ------------------------------------------------------------------------- 21 | 22 | A = probData.A; 23 | At = probData.At; 24 | b = probData.b; 25 | c = probData.c; 26 | [m, n] = size(A); 27 | 28 | mu = soln.mu; 29 | tau = soln.tau; 30 | Hi = soln.Hi; 31 | AHiAti = computeAHiAti(soln.Li, probData); 32 | %?%AHiAti = soln.AHiAti; 33 | 34 | ry = RHS(1:m); 35 | rx = RHS(m+(1:n)); 36 | rtau = RHS(m+n+1); 37 | rs = RHS(m+n+1+(1:n)); 38 | rkappa = RHS(end); 39 | 40 | % Schur complement solver for the system 41 | % [0 A -b] [dy] [ry] 42 | % [-A' H c] [dx] = [rx+rs] 43 | % [b' -c' h] [dtau] [rtau+rkappa] 44 | 45 | % equivalently 46 | % [ H -A' c] [dx] [rx+rs] 47 | % [-A 0 b] [dy] = [-ry] 48 | % [ c' -b' -h] [dtau] [-rtau-rkappa] 49 | 50 | % The next few steps: dx, dy, dtau are modified step-by-step. 51 | % Notation for the comments below: inv(LHS) = UDL block factorization 52 | dx = rx+rs; 53 | dy = -ry; 54 | dtau = -rtau-rkappa; 55 | 56 | % Precompute dense rows/columns of L and U. 57 | % Slightly wasteful for better modularity. (Hi(c) is computed twice.) 58 | [u13, u23] = HAA0sol(c, b, probData, mu, Hi, AHiAti); 59 | [l31, l32] = HAA0sol(c, -b, probData, mu, Hi, AHiAti); 60 | %l31 = u13; not true, although l32 = -u23; ? 61 | 62 | alpha = -mu/tau^2 - c'*u13 + b'*u23; 63 | 64 | % Multiply by L. 65 | dtau = dtau - l31'*dx - l32'*dy; 66 | 67 | % Multiply by D. 68 | [dx, dy] = HAA0sol(dx, dy, probData, mu, Hi, AHiAti); 69 | dtau = dtau/alpha; 70 | 71 | % Multiply by U. 72 | dx = dx - u13*dtau; 73 | dy = dy - u23*dtau; 74 | 75 | delta = zeros(m+2*n+2, 1); 76 | delta(1:m) = dy; 77 | delta(m+n+1) = dtau; 78 | delta(m+(1:n)) = dx; 79 | delta(m+n+1+(1:n)) = -rx - At*dy + dtau*c; % ds 80 | delta(end) = -rtau + b'*dy - c'*dx; % dkappa 81 | 82 | return 83 | 84 | % Computes [H -A'; -A 0] \ [u;v] 85 | function [u,v] = HAA0sol(u, v, probData, mu, Hi, AHiAti) 86 | 87 | % Notation for the comments below: inv([H -A'; -A 0]) = UDL block factorization 88 | 89 | % Multiply by L. 90 | if ~iscell(Hi) 91 | Hiu = Hi(u)/mu; 92 | else 93 | Kdims = probData.Kdims; 94 | 95 | idx = 0; 96 | Hiu = zeros(length(u),1); 97 | for k=1:length(Kdims) 98 | Hiu(idx+1:idx+Kdims(k)) = Hi{k}(u(idx+1:idx+Kdims(k)))/mu; 99 | idx = idx+Kdims(k); 100 | end 101 | end 102 | v = v + probData.A*Hiu; 103 | 104 | % Multiply by D. 105 | u = Hiu; 106 | v = -AHiAti(v)*mu; 107 | 108 | % Multiply by U. 109 | if ~iscell(Hi) 110 | u = u + Hi(probData.At*v)/mu; 111 | else 112 | idx = 0; 113 | for k=1:length(Kdims) 114 | u(idx+1:idx+Kdims(k)) = u(idx+1:idx+Kdims(k)) + Hi{k}(probData.As{k}*v(probData.rB(:,k)))/mu; 115 | idx = idx+Kdims(k); 116 | end 117 | end 118 | %v = v*mu; 119 | 120 | return 121 | --------------------------------------------------------------------------------