Skip to content

New cones: DD, SDD, DSOS, SDSOS. #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 84 commits into
base: main
Choose a base branch
from
Open

New cones: DD, SDD, DSOS, SDSOS. #76

wants to merge 84 commits into from

Conversation

renatoloureiro
Copy link
Contributor

@renatoloureiro renatoloureiro commented Jul 19, 2024

This pull request introduces and implements NEW cone options:

  • Diagonally Dominant (DD), dd
  • Scaled Diagonally Dominant (SDD), sdd
  • Diagonally Scaled Sum-of-Squares (DSOS), dsos
  • Scaled Diagonally Scaled Sum-of-Squares (SDSOS). sdsos

Pseudo example of cones definition while using casos.sdpsol:

opts.Kx = struct('lin', 4, 'psd', 2, 'dd', 3, 'sdd', 5);
opts.Kc = struct('psd', 3, 'dd', 4, 'sdd', 2);

In the case of relaxing a SOS program using casos.sossol:

opts.Kx = struct('lin', 4,  'sdsos', 1);
opts.Kc = struct('dsos', 3);

Note: This pull request answers issue #74

Key contributions include:

  • DD/SDD Cone Implementation: Added support for DD/SDD cones in decision variables and constraints, along with multiple DD/SDD cones handling and bug fixes.
  • Additional Cone Options: Added DSOS and SDSOS cones to the polynomial cone class, including methods for their dimensions and mappings.
  • Enhanced Demos: Updated demos to incorporate DD, SDD, DSOS, and SDSOS cones, showcasing their optimal solutions and integration into the problem-solving process.
    (see the attached file debug_dd_sdd.zip that contains additional programs to verify the correct performance of the new cones)
  • Bug Fixes and Enhancements: Addressed bugs related to the new cones, optimized dimensions, and refined plot outputs.
  • Documentation and Readme: Updated README to document the new cone options and their functionalities.

Example of new cones:

  1. Polynomial optimization with dsos or sdsos:
x = casos.Indeterminates('x');                % indeterminate variable
f = x^4 + 1*x;                                % some polynomial
g = casos.PS.sym('g');                        % scalar decision variable
sos = struct('x',g,'f',g,'g',f+g);            % define SOS problem
opts = struct('Kc',struct('dsos',1));         % constraint is scalar SOS cone
S = casos.sossol('S','mosek',sos,opts);       % solve by relaxation to SDP
sol = S();                                    % evaluate
  1. Relaxed SDP problem with DD (dd) or SDD (sdd):
ndim = 5;                      % dimension of SDP
x = casadi.SX.sym('x', 4, 1);  % decision variables
A = randn(ndim, ndim);         % create random SDP data
B = randn(ndim, ndim);
A = A+A';
B = B+B';

% define SDP problem
sdp.f = x(1);
sdp.g = eye(ndim) + x(1)*A + x(2)*B;
sdp.g = [sdp.g(:)];
sdp.x = x;

% define cones
opts = struct;
opts.Kx = struct('lin', 4);
opts.Kc = struct('psd', ndim);

S = casos.sdpsol('S','mosek',sdp,opts); % initialize solver
sol = S('lbx',-inf,'ubx',+inf); % solve

renatoloureiro and others added 30 commits April 4, 2024 10:20
1. Current implementation only allows a single DD cone in the free variables, not the constraints
2. Due to the creation of new variables and a change of cones, two additional properties in options class were added: they inherit the upper and lower bounds of the new linear cones that are not requested for the user to define
1. Fix bug related to DD cone of size 1.
2. Ensure that only SX is leveraged
The cones description added were DD, SDD and for the polynomial cones DSOS and SDSOS
Add to sdpopt the cones used at the sdp level, namely, the 'lin', 'psd' and 'dd'
Due to the additional decision variables created from building the DD and constraints, a mapping is defined and is used aposteriori to extract the solution of the original problem formulation from the solved sdp
Make both problems use the same solver for a coherent comparison
1. add comments; 2. add SDD feasible region; 3. add SDD program
This file in this commit is similar to the one related to the DD. currently this file performs SDD to linear as if the constraint was DD.
The function sdd_reduced is added in the file to call the re-building to LP/SOCP.
Check instead if the variable args which is only nonempty if either DD/SDD was present in the original problem formulation.
The solutions obtained are from solving the SDP by either using PSD, DD or SDD cones.
This versio works for a single SDD constraint on the SDP level.
@renatoloureiro renatoloureiro marked this pull request as ready for review August 7, 2024 07:55
@renatoloureiro
Copy link
Contributor Author

I have fixed the bugs that existed before and created two new demos:

  1. /pendulum: generated the dynamics of a controlled n-link inverted pendulum and its polynomial approximation. A region of attraction program using the SOS, DSOS, or SDSOS is given.
    Screenshot from 2024-08-07 10-07-19

  2. /stats_poly: runs a simple polynomial optimization task for increasing free variable dimension for the different polynomial cones, i.e. SOS, DSOS, SDSOS. Plots the elapsed time and result achieved.
    Screenshot from 2024-08-07 10-08-28
    Screenshot from 2024-08-07 10-08-19

@tcunis tcunis removed the bug Something isn't working label Aug 14, 2024
@@ -61,19 +119,18 @@
% constraint function (vectorized)
sdp_g = sdp.g(:);

if isfield(sdp,'derivatives')
if isfield(sdp,'derivatives') && isempty(fieldnames(args))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the second clause works as intended, for even if there are no DD/SDD constraints, the struct args has the fields dd_lbx etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see commit (f245d36) for the requested changes

Copy link
Contributor

@tcunis tcunis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks, but there are a few critical issues with the interface of the SDP solver and its embedding into the SOS solver that must be addressed.

if Nsds~=0 || Msds~=0
sdpopt.Kx.sdd = [Ksdp_x_s(Nds+Ns+1:end); Ksdp_g_s(Mds+Ms+1:end)];
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you also have to reorder the SDP variables and constraints to ensure PSD < DD < SDD?

@@ -61,19 +118,18 @@
% constraint function (vectorized)
sdp_g = sdp.g(:);

if isfield(sdp,'derivatives')
if isfield(sdp,'derivatives') && ~flag_reduced
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use the map above to update pre-computed derivatives?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can:
Screenshot from 2024-08-21 18-10-46
The inner if-statement is used cause at the moment I don't find an elegant and short way of doing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about A? Why can't we do the same here?

% output function
obj.ghan = casadi.Function('g', ...
{p x cost lam_a lam_x}, ...
{x fx gx lam_x lam_a nan(size(p))}, ...
{obj.map.x*x fx obj.map.g*gx obj.map.x*lam_x obj.map.g*lam_a nan(size(p))}, ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check whether the dual variables can simply be mapped by the same mapping as the primal variables/constraints.

opts.Kc = setfield(opts.Kc,'psd', [Mpsd, repmat(2, 1, length(vertcat(M_g{:}))/3+length(vertcat(M_x{:}))/3)]);

% map from new sdp.x to old
map.x = jacobian(x_original, sdp.x);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the structure of sdp.x, isn't that map simply blkdiag(speye(Nlin), sparse(0,m), speye(n-Nlin), where m is the number of elements in vertcat(M_g{:}, M_x{:}) and n is the number of elements in the original sdp.x.

opts.Kc = setfield(opts.Kc,'lin',Mlin);

% map from new sdp.x to old
map.x = jacobian(x_original, sdp.x);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the structure of sdp.x, isn't that map simply blkdiag(speye(Nlin), sparse(0,m), speye(n-Nlin), where m is the number of elements in vertcat(M_g{:}, M_x{:}) and n is the number of elements in the original sdp.x.

@tcunis
Copy link
Contributor

tcunis commented Aug 21, 2024

It appears that map.x and map.g are the same in dd_reduce and sdd_reduce. Is that correct?

@renatoloureiro
Copy link
Contributor Author

Screenshot from 2024-08-21 17-03-56

The current implementation uses a mapping from what was previously done. That is, the sdd_reduce receives the SDP already altered in the dd_reduced step. This is done mainly due to the reordering, otherwise I would have a harder time afterwards.

@tcunis
Copy link
Contributor

tcunis commented Aug 22, 2024

Screenshot from 2024-08-21 17-03-56

The current implementation uses a mapping from what was previously done. That is, the sdd_reduce receives the SDP already altered in the dd_reduced step. This is done mainly due to the reordering, otherwise I would have a harder time afterwards.

I was referring to the construction of map_DD_2_ORIG_x and map_SDD_2_ORIG_x (or their respective variants for the constraints) individually, viz. (for DD):

sdp.x = [sdp.x(1:Nlin); vertcat(M_g{:}); vertcat(M_x{:}); sdp.x(Nlin+1:end)];

% ...

map.x = jacobian(x_original, sdp.x);
map.g = [speye(length(g_original)), sparse(length(g_original), length(sdp.g)-length(g_original))];

vs. (for SDD):

sdp.x = [sdp.x(1:Nlin); vertcat(M_g{:}); vertcat(M_x{:}); sdp.x(Nlin+1:end)];

% ...

map.x = jacobian(x_original, sdp.x);
map.g = [speye(length(g_original)), sparse(length(g_original), length(sdp.g)-length(g_original))];

It appears that the maps in both cases are equal, which seems counterintuitive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for DD and SDD matrix and SOS
2 participants