Skip to content

Conversation

@odow
Copy link
Member

@odow odow commented Oct 19, 2025

No description provided.

@codecov
Copy link

codecov bot commented Oct 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.46%. Comparing base (45f1507) to head (b5bc9dd).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #327      +/-   ##
==========================================
+ Coverage   91.01%   91.46%   +0.45%     
==========================================
  Files          10       11       +1     
  Lines         601      633      +32     
==========================================
+ Hits          547      579      +32     
  Misses         54       54              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@odow
Copy link
Member Author

odow commented Oct 19, 2025

So this passes some of the upstream examples: https://github.com/dance858/scs/blob/master/test/spectral_cones_problems/exp_design.h

julia> begin
       A_v = [
           -1.0, 1.0, -1.0, 3.24, 0.16, 1.0, 4.84, 3.61, 1.0, -1.0, 2.54558441,
           -0.11313708, -0.14142136, 1.24450793, 0.26870058, -2.12132034, -1.0,
           2.03646753, 0.05656854, 0.56568542, 0.93338095, 4.03050865, 0.28284271,
           -1.0, 1.0, 0.04, 0.01, 0.16, 0.01, 2.25, -1.0, 1.13137085, -0.02828427,
           -0.05656854, 0.16970563, 0.21213203, -0.42426407, -1.0, 0.64, 0.01, 0.16,
           0.09, 2.25, 0.04, -1.0,
       ]
       A_i = [
           7, 0, 8, 1, 2, 3, 4, 5, 6, 9, 1, 2, 3, 4, 5, 6, 10, 1, 2, 3, 4, 5, 6, 11, 1,
           2, 3, 4, 5, 6, 12, 1, 2, 3, 4, 5, 6, 13, 1, 2, 3, 4, 5, 6, 14,
       ]
       A_p = [0, 1, 3, 10, 17, 24, 31, 38, 45]
       b = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
       A = SparseMatrixCSC(15, 8, A_p.+1, A_i.+1, A_v)
       end;

julia> using JuMP, SCS

julia> model = Model(SCS.Optimizer)
A JuMP Model
├ solver: SCS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> @variable(model, x[1:8])
8-element Vector{VariableRef}:
 x[1]
 x[2]
 x[3]
 x[4]
 x[5]
 x[6]
 x[7]
 x[8]

julia> @objective(model, Min, 1.0 * x[1])
x[1]

julia> @constraint(model, b[1:1] - A[1:1, :] * x in Zeros())
[-x[2] + 1]  Zeros()

julia> @constraint(model, b[2:7] - A[2:7, :] * x in Nonnegatives())
[-3.24 x[3] - 2.54558441 x[4] - 2.03646753 x[5] - x[6] - 1.13137085 x[7] - 0.64 x[8] + 1, -0.16 x[3] + 0.11313708 x[4] - 0.05656854 x[5] - 0.04 x[6] + 0.02828427 x[7] - 0.01 x[8] + 1, -x[3] + 0.14142136 x[4] - 0.56568542 x[5] - 0.01 x[6] + 0.05656854 x[7] - 0.16 x[8] + 1, -4.84 x[3] - 1.24450793 x[4] - 0.93338095 x[5] - 0.16 x[6] - 0.16970563 x[7] - 0.09 x[8] + 1, -3.61 x[3] - 0.26870058 x[4] - 4.03050865 x[5] - 0.01 x[6] - 0.21213203 x[7] - 2.25 x[8] + 1, -x[3] + 2.12132034 x[4] - 0.28284271 x[5] - 2.25 x[6] + 0.42426407 x[7] - 0.04 x[8] + 1]  Nonnegatives()

julia> @constraint(model, b[8:end] - A[8:end, :] * x in MOI.LogDetConeTriangle(3))
[x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]]  MathOptInterface.LogDetConeTriangle(3)

julia> optimize!(model)
------------------------------------------------------------------
	       SCS v3.2.9 - Splitting Conic Solver
	(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem:  variables n: 8, constraints m: 15
cones: 	  z: primal zero / dual free vars: 1
	  l: linear vars: 6
	  d: logdet vars: 8, dsize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
	  alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
	  max_iters: 100000, normalize: 1, rho_x: 1.00e-06
	  acceleration_lookback: 10, acceleration_interval: 10
	  compiled with openmp parallelization enabled
lin-sys:  sparse-direct-amd-qdldl
	  nnz(A): 45, nnz(P): 0
------------------------------------------------------------------
 iter | pri res | dua res |   gap   |   obj   |  scale  | time (s)
------------------------------------------------------------------
     0| 1.06e+01  1.44e+00  1.46e+01 -8.15e+00  1.00e-01  1.22e-04 
   250| 1.82e-04  3.01e-06  1.87e-04  3.03e+00  3.41e-01  7.72e-04 
------------------------------------------------------------------
status:  solved
timings: total: 7.74e-04s = setup: 3.64e-05s + solve: 7.37e-04s
	 lin-sys: 4.86e-05s, cones: 2.37e-04s, accel: 1.10e-05s
------------------------------------------------------------------
objective = 3.033227
------------------------------------------------------------------

julia> opt = 3.0333290743428574
3.0333290743428574

But it fails a bunch of unit tests.

Here's one that it fails. The answer should be t = log(5):

julia> using JuMP, SCS

julia> model = Model(SCS.Optimizer)
A JuMP Model
├ solver: SCS
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> @variable(model, t)
t

julia> @objective(model, Max, t)
t

julia> @constraint(model, [t, 1, 3, 2, 2, 1, 1, 3] in MOI.LogDetConeTriangle(3))
[t, 1, 3, 2, 2, 1, 1, 3]  MathOptInterface.LogDetConeTriangle(3)

julia> optimize!(model)
------------------------------------------------------------------
	       SCS v3.2.9 - Splitting Conic Solver
	(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem:  variables n: 1, constraints m: 8
cones: 	  d: logdet vars: 8, dsize: 1
settings: eps_abs: 1.0e-04, eps_rel: 1.0e-04, eps_infeas: 1.0e-07
	  alpha: 1.50, scale: 1.00e-01, adaptive_scale: 1
	  max_iters: 100000, normalize: 1, rho_x: 1.00e-06
	  acceleration_lookback: 10, acceleration_interval: 10
	  compiled with openmp parallelization enabled
lin-sys:  sparse-direct-amd-qdldl
	  nnz(A): 1, nnz(P): 0
------------------------------------------------------------------
 iter | pri res | dua res |   gap   |   obj   |  scale  | time (s)
------------------------------------------------------------------
     0| 3.00e+00  1.00e+00  2.50e+00 -1.25e+00  1.00e-01  2.38e-04 
    25| 1.90e+06  0.00e+00  2.51e+19 -1.32e+19  1.00e-01  3.58e-04 
------------------------------------------------------------------
status:  unbounded
timings: total: 3.62e-04s = setup: 6.72e-05s + solve: 2.95e-04s
	 lin-sys: 4.54e-06s, cones: 8.57e-05s, accel: 7.04e-07s
------------------------------------------------------------------
objective = -inf
------------------------------------------------------------------

@araujoms
Copy link
Contributor

I would expect the vectorization to be the same as the one in the PSD cone, which is not the same as the MOI one, so you need a bridge.

@odow
Copy link
Member Author

odow commented Oct 19, 2025

I thought so too, but the experiment design example worked

@araujoms
Copy link
Contributor

araujoms commented Oct 19, 2025

Just tried the example, and it works if you minimise instead of maximising, and the answer is correct if you use the SCS vectorization instead of the MOI one. No idea why the example worked, perhaps the matrix is by coincidence the same in both vectorizations?

Looks like they implemented the cone t >= - u logdet(X/u) instead of the more standard t <= u logdet(X/u).

julia> scsmat(v) = Hermitian([v[1] v[2]/sqrt(2) v[3]/sqrt(2); 0 v[4] v[5]/sqrt(2);0 0 v[6]])
scsmat (generic function with 1 method)

julia> v = [3, 2, 2, 1, 1, 3];

julia> logdet(scsmat(v))
0.8451929858692304

@odow
Copy link
Member Author

odow commented Oct 19, 2025

A bridge it is then

@dance858
Copy link

You're right, we use a different convention for the log-determinant cone. Sorry for that; I should have told you earlier.

I wrote down the cone definitions we used in #316.

@odow
Copy link
Member Author

odow commented Oct 19, 2025

It's times like this when I really appreciate the bridge system in MOI. A set that's scaled, pemuated, and the first element negated? Not a problem.

@odow
Copy link
Member Author

odow commented Oct 20, 2025

Going to merge this as-is.

Adding NormNuclearCone is going to require a change to MathOptInterface.

(Not for any technical cone/SCS reason, just so that we can keep using the existing utility functions for manipulating the constraint matrix; we assume that all cones have a unique representation for a given dimension, which isn't the case for the NormNuclearCone and NormSpectralCone.)

@odow odow merged commit 7aa5bb9 into master Oct 20, 2025
12 checks passed
@odow odow deleted the od/spectral-cones branch October 20, 2025 01:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants