Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f61f012
Add BurerMonteiro formulation
blegat May 28, 2025
ec17b21
Rename
blegat May 29, 2025
be132e9
Fixes
blegat May 30, 2025
69353e1
Fixes
blegat Jun 1, 2025
6d80835
Define jprod
blegat Jun 1, 2025
0957df9
Add tests
blegat Jun 1, 2025
a0eea2b
Fixes
blegat Jun 2, 2025
ce58ccf
Fixes for Loraine
blegat Jun 2, 2025
d7c9296
Remove objective constant
blegat Jun 2, 2025
bb93263
Flip sign of A and C
blegat Jun 2, 2025
c14e6e9
Reverse cons
blegat Jun 2, 2025
fdc0798
Swap jtprod
blegat Jun 2, 2025
42e217c
Swap C
blegat Jun 2, 2025
1382360
Add errors
blegat Jun 2, 2025
8615b95
Fix diff check tests
blegat Jun 2, 2025
ee9099a
Fix
blegat Jun 2, 2025
24e4828
Remove full_check
blegat Jun 3, 2025
cea54ba
Fixes
blegat Jun 3, 2025
95b5877
Fixes
blegat Jun 3, 2025
c081bef
SA v0.10
blegat Jun 3, 2025
0a2bbcc
Fixes
blegat Jun 3, 2025
7a48bbc
Test with Dualizaton
blegat Jun 3, 2025
bf1f5dd
Add ref to Kojima paper
blegat Jun 4, 2025
9651db2
Fix format
blegat Jun 4, 2025
5501063
Add types to arguments
blegat Jun 4, 2025
022e85a
fix format
blegat Jun 4, 2025
dc329c7
Fix
blegat Jun 4, 2025
c7cac62
Alloc 20 iterations
blegat Jun 4, 2025
e0cdf66
Add tests
blegat Jun 4, 2025
17349ec
fix
blegat Jun 4, 2025
bd43c06
Improve coverage of MOI wrapper
blegat Jun 5, 2025
af6d598
Fix format
blegat Jun 5, 2025
d8d8c6f
Add coverage
blegat Jun 5, 2025
a37de47
Complete coverage of src/BurerMonteiro
blegat Jun 5, 2025
ce10892
Complete coverage of factorization
blegat Jun 5, 2025
a8e15e4
Fix format
blegat Jun 5, 2025
b72d43d
Add tests for errors
blegat Jun 5, 2025
dac7279
Relax tol
blegat Jun 5, 2025
849225e
Add tests
blegat Jun 5, 2025
e2b2172
Add more tests
blegat Jun 5, 2025
f61638e
Fix format
blegat Jun 5, 2025
d680192
Inplace schur complement
blegat Jun 6, 2025
0781cf9
Fix
blegat Jun 6, 2025
e56f9e7
Fixes
blegat Jun 6, 2025
7ad4403
Add tests
blegat Jun 6, 2025
c7dc384
Cover all
blegat Jun 6, 2025
f32596e
Fix format
blegat Jun 6, 2025
3f2cbf7
Add SparseArrays
blegat Jun 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/burer_monteiro.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import NLPModels

struct BurerMonteiro{T} <: NLPModels.AbstractNLPModel{T,Vector{T}}
model::Model{T}
meta::NLPModels.NLPModelMeta{T,Vector{T}}
counters::NLPModels.Counters
function BurerMonteiro(model::Model{T}) where {T}
n = num_scalars(model) + sum(side_dimension(model, i) for i in matrix_indices(model); init = 0)
ncon = num_constraints(model)
return new(
ad,
NLPModels.NLPModelMeta(
n, #nvar
ncon = ncon,
nnzj = 0,
nnzh = 0,
x0 = rand(n),
y0 = rand(ncon),
lvar = fill(-Inf, n),
uvar = fill(Inf, n),
lcon = cons_constant(model),
ucon = cons_constant(model),
minimize = true,
),
NLPModels.Counters(),
)
end
end

function NLPModels.obj(model::BurerMonteiro, x::AbstractVector)
return obj(model.model, x)
end

function NLPModels.grad!(model::BurerMonteiro, x::AbstractVector, g::AbstractVector)
grad!(model.model, x, g)
return g
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I will have the same comment as usual: do you think it would be worth providing the hprod! as well? I have the feeling that Newton-CG can be faster than LBFGS, if done properly.

If you think that's more relevant, I can open a separate issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Yes, it will be useful but too early in the development stage at the moment. The code is too unstable for now. I prefer stabilizing first :)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

hprod! is done now, so Percival is now an SDP solver ! We should now try MadNLP ;)

201 changes: 201 additions & 0 deletions src/model.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Adapted from Loraine.jl

import SparseArrays
import LinearAlgebra
import MutableArithmetics as MA
import MathOptInterface as MOI

"""
MyModel
Comment thread
blegat marked this conversation as resolved.
Outdated

Model representing the problem:
```math
\\begin{aligned}
\\max {} & b^\\top y - b_\\text{const}
\\\\
& \\sum_{j=1}^n y_j A_{i,j} \\preceq C_i
\\qquad
\\forall i \\in \\{1,\\ldots,\\text{nlmi}\\}
\\\\
& C_\\text{lin}^\\top y \\le d_\\text{lin}
\\end{aligned}
```
The fields of the `struct` as related to the arrays of the above formulation as follows:

* The ``i``th PSD constraint is of size `msize[i] × msisze[i]`
* The matrix ``C_i`` is given by `C[i]`.
* The matrix ``A_{i,j}`` is given by `-A[i,j]`.
"""
mutable struct MyModel{T,A<:AbstractMatrix{T}}
C::Vector{SparseArrays.SparseMatrixCSC{T,Int}}
A::Matrix{A}
b::Vector{T}
b_const::T
d_lin::SparseArrays.SparseVector{T, Int64}
C_lin::SparseArrays.SparseMatrixCSC{T, Int64}
msizes::Vector{Int64}

function MyModel(
C::Vector{SparseArrays.SparseMatrixCSC{T,Int}},
A::Matrix{AT},
b::Vector{T},
b_const::T,
d_lin::SparseArrays.SparseVector{T, Int64},
C_lin::SparseArrays.SparseMatrixCSC{T, Int64},
msizes::Vector{Int64},
) where {T,AT<:AbstractMatrix{T}}

model = new{T,AT}()
model.C = C
model.A = A
model.b = b
model.b_const = b_const
model.d_lin = d_lin
model.C_lin = C_lin
model.msizes = msizes
return model
end
end

struct ScalarIndex
value::Int64
end

num_scalars(model::MyModel) = length(model.d_lin)

function scalar_indices(model::MyModel)
return MOI.Utilities.LazyMap{ScalarIndex}(ScalarIndex, Base.OneTo(num_scalars(model)))
end

struct MatrixIndex
value::Int64
end

num_matrices(model::MyModel) = length(model.C)

function matrix_indices(model::MyModel)
return MOI.Utilities.LazyMap{MatrixIndex}(MatrixIndex, Base.OneTo(num_matrices(model)))
end

side_dimension(model::MyModel, i::MatrixIndex) = model.msizes[i.value]

struct ConstraintIndex
value::Int64
end
num_constraints(model::MyModel) = length(model.b)
function constraint_indices(model::MyModel)
return MOI.Utilities.LazyMap{ConstraintIndex}(ConstraintIndex, Base.OneTo(num_constraints(model)))
end

# Should be only used with `norm`
jac(model::MyModel, i::ConstraintIndex, ::Type{ScalarIndex}) = model.C_lin[i.value,:]
function norm_jac(model::MyModel{T}, i::MatrixIndex) where {T}
if isempty(model.A)
return zero(T)
end
return norm(model.A[i.value, :])
end

function obj(model::MyModel, X, i::MatrixIndex)
Comment thread
blegat marked this conversation as resolved.
Outdated
return -dot(model.C[i.value], X)
end

function obj(model::MyModel, X, ::Type{MatrixIndex})
Comment thread
blegat marked this conversation as resolved.
Outdated
result = zero(eltype(eltype(X)))
for mat_idx in matrix_indices(model)
result += obj(model, X[mat_idx.value], mat_idx)
end
return result
end

function obj(model::MyModel, X_lin, ::Type{ScalarIndex})
return -dot(model.d_lin, X_lin)
end

function obj(model::MyModel, X_lin, X)
return model.b_const + obj(model, X, MatrixIndex) - dot(model.d_lin, X_lin)
end

dual_obj(model::MyModel, y) = -dot(model.b, y) + model.b_const

function jtprod(model::MyModel, ::Type{ScalarIndex}, y)
return -model.C_lin' * y
end

function dual_cons(model::MyModel, ::Type{ScalarIndex}, y, S)
return model.d_lin - S + jtprod(model, ScalarIndex, y)
end

function buffer_for_jtprod(model::MyModel)
if iszero(num_matrices(model))
return
end
return map(Base.Fix1(buffer_for_jtprod, model), matrix_indices(model))
end

function buffer_for_jtprod(model::MyModel, mat_idx::MatrixIndex)
if iszero(num_constraints(model))
return
end
# FIXME: at some point, switch to dense
return sum(
abs.(model.A[mat_idx.value, j])
for j in 1:num_constraints(model)
)
end

function _add_mul!(A::SparseMatrixCSC, B::SparseMatrixCSC, α)
Comment thread
blegat marked this conversation as resolved.
Outdated
for col in axes(A, 2)
range_A = SparseArrays.nzrange(A, col)
it_A = iterate(range_A)
for k in SparseArrays.nzrange(B, col)
row_B = SparseArrays.rowvals(B)[k]
while SparseArrays.rowvals(A)[it_A[1]] < row_B
it_A = iterate(range_A, it_A[2])
end
@assert row_B == SparseArrays.rowvals(A)[it_A[1]]
SparseArrays.nonzeros(A)[it_A[1]] += SparseArrays.nonzeros(B)[k] * α
end
end
end

_zero!(A::SparseMatrixCSC) = fill!(SparseArrays.nonzeros(A), 0.0)

function jtprod!(buffer, model::MyModel, mat_idx::MatrixIndex, y)
if iszero(num_constraints(model))
return MA.Zero()
end
_zero!(buffer)
for j in eachindex(y)
_add_mul!(buffer, model.A[mat_idx.value, j], y[j])
end
return buffer
end

function dual_cons!(buffer, model::MyModel, mat_idx::MatrixIndex, y, S)
i = mat_idx.value
return jtprod!(buffer[i], model, mat_idx, y) + model.C[i] - S[i]
end

objgrad(model::MyModel, ::Type{ScalarIndex}) = model.d_lin
objgrad(model::MyModel, i::MatrixIndex) = model.C[i.value]

cons_constant(model::MyModel) = model.b

function cons(model::MyModel, x, X)
return model.b - jprod(model, x, X)
end

function jprod(model::MyModel, i::MatrixIndex, W)
return eltype(W)[
-dot(model.A[i.value, j], W) for j in 1:num_constraints(model)
]
end

function jprod(model::MyModel, w, W)
h = model.C_lin * w
for i in matrix_indices(model)
h += jprod(model, i, W[i.value])
end
return h
end
Loading