Hi @MLL, welcome to JuMP.
First, here’s how you can solve your model with JuMP. (Note that I made up B, because it wasn’t in your example code.)
julia> using JuMP
julia> import Random
julia> import SCS
julia> begin
rng = Random.MersenneTwister(1234)
n, r = 20, 2
Y = randn(rng, n, r) * randn(rng, r, n)
X = Y .* (rand(rng, n, n) .< 1 / 2)
mask = X .!= 0
B = rand(rng, n, n)
model = Model(SCS.Optimizer)
@variable(model, X[1:n, 1:n])
@constraint(model, X[mask] .== B[mask])
@variable(model, t)
@constraint(model, [t; vec(X)] in MOI.NormNuclearCone(n, n))
@objective(model, Min, t)
optimize!(model)
end
------------------------------------------------------------------
SCS v3.2.6 - Splitting Conic Solver
(c) Brendan O'Donoghue, Stanford University, 2012
------------------------------------------------------------------
problem: variables n: 821, constraints m: 1006
cones: z: primal zero / dual free vars: 185
l: linear vars: 1
s: psd vars: 820, ssize: 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): 1046, nnz(P): 0
------------------------------------------------------------------
iter | pri res | dua res | gap | obj | scale | time (s)
------------------------------------------------------------------
0| 7.78e+01 1.00e+00 3.78e+02 -1.72e+02 1.00e-01 1.76e-03
125| 3.73e-05 1.47e-05 3.48e-07 2.03e+01 6.29e-01 2.06e-02
------------------------------------------------------------------
status: solved
timings: total: 2.06e-02s = setup: 4.78e-04s + solve: 2.02e-02s
lin-sys: 1.85e-03s, cones: 1.63e-02s, accel: 8.10e-05s
------------------------------------------------------------------
objective = 20.349979
------------------------------------------------------------------
I note that we don’t have this example in our documentation, so it’s not surprising that you didn’t find it! I’ll fix by adding a new section to Modeling with cones · JuMP
Your other attempts didn’t work for a number of reasons:
You need to use op_nuclearnorm in @objective, not the function nuclearnorm.
But operators take splatted scalar arguments, so you cannot pass a matrix X.
The result would be a nonlinear program, so you couldn’t use ProxSDP, you’d need a solver like Ipopt.
All together, it’d be something like this:
using JuMP, Ipopt
model = Model(Ipopt.Optimizer)
@variable(model, X[1:n, 1:n])
nuclearnorm(x...) = sum(LinearAlgebra.svdvals(reshape(collect(x), n, n)))
@operator(model, op_nuclearnorm, n^2, nuclearnorm)
@objective(model, Min, op_nuclearnorm(X...))
except this would still be broken, because we use ForwardDiff.jl to automatically compute the Jacobian of nuclearnorm, and ForwardDiff.jl does not support differentiating through svdvals.
So all in all, it’s just a lot easier to use MOI.NormNuclearCone!