Try this:
module SimplePDHG
import MathOptInterface as MOI
import SparseArrays
"""
solve_pdhg(
A::SparseArrays.SparseMatrixCSC{Float64,Int},
b::Vector{Float64},
c::Vector{Float64},
)::Tuple{MOI.TerminationStatusCode,Vector{Float64}}
"""
function solve_pdhg(
A::SparseArrays.SparseMatrixCSC{Float64,Int},
b::Vector{Float64},
c::Vector{Float64},
)::Tuple{MOI.TerminationStatusCode,Vector{Float64}}
x = fill(NaN, size(A, 2))
return MOI.OTHER_ERROR, x
end
MOI.Utilities.@product_of_sets(RHS, MOI.Zeros)
const OptimizerCache = MOI.Utilities.GenericModel{
Float64,
MOI.Utilities.ObjectiveContainer{Float64},
MOI.Utilities.VariablesContainer{Float64},
MOI.Utilities.MatrixOfConstraints{
Float64,
MOI.Utilities.MutableSparseMatrixCSC{
Float64,
Int,
MOI.Utilities.OneBasedIndexing,
},
Vector{Float64},
RHS{Float64},
},
}
function MOI.add_constrained_variables(
model::OptimizerCache,
set::MOI.Nonnegatives,
)
x = MOI.add_variables(model, MOI.dimension(set))
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
ci = MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives}(x[1].value)
return x, ci
end
mutable struct Optimizer <: MOI.AbstractOptimizer
x_primal::Dict{MOI.VariableIndex,Float64}
termination_status::MOI.TerminationStatusCode
function Optimizer()
return new(Dict{MOI.VariableIndex,Float64}(), MOI.OPTIMIZE_NOT_CALLED)
end
end
function MOI.is_empty(model::Optimizer)
return isempty(model.x_primal) &&
model.termination_status == MOI.OPTIMIZE_NOT_CALLED
end
function MOI.empty!(model::Optimizer)
empty!(model.x_primal)
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
return
end
function MOI.supports_constraint(
::Optimizer,
::Type{MOI.VectorAffineFunction{Float64}},
::Type{MOI.Zeros},
)
return true
end
MOI.supports_add_constrained_variables(::Optimizer, ::Type{MOI.Reals}) = false
function MOI.supports_add_constrained_variables(
::Optimizer,
::Type{MOI.Nonnegatives},
)
return true
end
function MOI.supports(
::Optimizer,
::MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}},
)
return true
end
function MOI.optimize!(dest::Optimizer, src::MOI.ModelLike)
cache = OptimizerCache()
index_map = MOI.copy_to(cache, src)
@assert all(iszero, cache.variables.lower)
@assert all(==(Inf), cache.variables.upper)
A = convert(
SparseArrays.SparseMatrixCSC{Float64,Int},
cache.constraints.coefficients,
)
b = cache.constraints.constants
c = zeros(size(A, 2))
offset = cache.objective.scalar_affine.constant
for term in cache.objective.scalar_affine.terms
c[term.variable.value] += term.coefficient
end
if cache.objective.sense == MOI.MAX_SENSE
c *= -1
end
dest.termination_status, x_primal = solve_pdhg(A, b, c)
for x in MOI.get(src, MOI.ListOfVariableIndices())
dest.x_primal[x] = x_primal[index_map[x].value]
end
return index_map, false
end
function MOI.get(model::Optimizer, ::MOI.VariablePrimal, x::MOI.VariableIndex)
return model.x_primal[x]
end
function MOI.get(model::Optimizer, ::MOI.ResultCount)
return model.termination_status == MOI.OPTIMAL ? 1 : 0
end
function MOI.get(model::Optimizer, ::MOI.RawStatusString)
return "$(model.termination_status)"
end
MOI.get(model::Optimizer, ::MOI.TerminationStatus) = model.termination_status
function MOI.get(model::Optimizer, ::MOI.PrimalStatus)
if model.termination_status == MOI.OPTIMAL
return MOI.FEASIBLE_POINT
else
return MOI.NO_SOLUTION
end
end
MOI.get(model::Optimizer, ::MOI.DualStatus) = MOI.NO_SOLUTION
MOI.get(::Optimizer, ::MOI.SolverName) = "PDHG"
end
using JuMP
model = Model(SimplePDHG.Optimizer)
@variable(model, x >= 1)
@constraint(model, x <= 2)
@objective(model, Max, x + 1)
optimize!(model)
solution_summary(model; verbose = true)