Skip to content

Fix COPT interface silently dropping nonzero quadratic objective when Q has an all-zero column - #1529

Merged
johanlofberg merged 1 commit into
yalmip:developfrom
terryweng03:fix/copt-quadratic-objective-dropped
Sep 15, 2026
Merged

johanlofberg merged 1 commit into
yalmip:developfrom
terryweng03:fix/copt-quadratic-objective-dropped

Conversation

@terryweng03

@terryweng03 terryweng03 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Hi, there is a suspected issue in COPT interface.

Problem

The COPT interface (solvers/yalmip2copt.m) uses if any to decide whether to attach the quadratic objective matrix:

if any(interfacedata.Q)        % line 133
    model.Q = interfacedata.Q;
end
...
if any(interfacedata.Q)        % line 195 (cone/expcone extension block)
    model.Q(length(model.obj),length(model.obj)) = 0;
end

interfacedata.Q is a sparse square matrix. any(Q) on a matrix returns a per-column logical row vector, not a scalar. MATLAB's if collapses a non-scalar with an implicit all(...(:)), so the guard actually tests "every column has a nonzero entry" rather than "Q has any nonzero entry".

Minimal reproducer

x = sdpvar(1,1); y = sdpvar(1,1);
F = [0 <= x <= 10, 0 <= y <= 1];
obj = (x-3)^2 + y;            % Q = diag([1,0]): y column all-zero

opt = sdpsettings('solver', 'copt', 'verbose', 1 ,'savesolverinput', 1);
opt.copt.Logging = 1;
opt.copt.LogToConsole = 1;

d = optimize(F,obj,opt);

Variable order [x; y] gives Q = diag([1, 0]) — the y column is all-zero.

  • Correct solution: x = 3, y = 0, objective 0.
  • Before the fix: COPT receives no Q, minimizes -6x + y + 9, returns x = 10, y = 0 with solver objective -51.

… all-zero column

The COPT interface used `if any(interfacedata.Q)` to decide whether to attach
the quadratic objective matrix. `interfacedata.Q` is a sparse square matrix,
and `any(Q)` on a matrix returns a logical row vector (per-column reduction),
not a scalar. MATLAB's `if` collapses a non-scalar with an implicit
`all(...(:))`, so the guard actually tests "every column has a nonzero
entry" rather than "Q has any nonzero entry."

When Q is nonzero but has at least one all-zero column — i.e., some variable
appears only linearly in the objective — the guard evaluates to false and
`model.Q` is never set, so the entire quadratic objective is silently
dropped. The model is solved as a linear program while YALMIP still reports
success, and the original YALMIP objective disagrees with the COPT-reported
objective at the returned point.

This is not an edge case in conic models: `interfacedata.Q` spans all
variables including cone-auxiliary columns, which never appear in the
quadratic objective and are therefore always all-zero. Hence any SOCP model
with a nonzero quadratic objective trips the same guard at both call sites,
even when the Q block on the original variables is full rank.

The same guard appears twice and both are changed:

  - Line 133: the initial `model.Q = interfacedata.Q` assignment.
  - Line 195: the dimension-extension `model.Q(n,n) = 0` performed after
    appending cone/expcone variables. Skipping this would also leave
    `model.Q` smaller than `ncol`, which COPT rejects (coptinit.c requires
    problem.Q to be ncol-by-ncol).

The fix replaces the non-scalar `any(interfacedata.Q)` with the scalar
`nnz(interfacedata.Q)`, which is consistent with how the rest of YALMIP's
solver interfaces test for a nonzero quadratic objective (e.g.
yalmip2quadprog uses `nnz(Q)==0`, yalmip2cplex uses `nnz(H)==0`,
yalmip2gurobi assigns unconditionally, yalmip2mosek uses
`find(tril(sparse(2*Q)))`). `nnz` always returns a scalar, so the `if`
behaves correctly under all sparsity patterns.

Minimal example: min (x-3)^2 + y, 0<=x<=10, 0<=y<=1. Correct solution is
x=3, y=0, objective 0. Before the fix the quadratic term is dropped, COPT
returns x=10, y=0 with solver objective -51, while the original objective
evaluates to 49.
@johanlofberg
johanlofberg merged commit 7588319 into yalmip:develop Sep 15, 2026
0 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants