I’m considering a problem which involves constraints on a solution to a linear system whose coefficients depend on the optimization variables. So essentially, the constraints are functions of a matrix inverse involving the optimization variables. I’m trying to figure out a good way of modeling this problem. As an example, consider the following model:
where C(x) = A(x)^{-1} \cdot B with constant data matrix B \in \mathbb{R}^{n \times n}, C_i is the i-th row of C, and A(x)_{ij} = (x_i + x_j)^2 with i,j \in \{1, \ldots, n\}.
I’m considering two approaches:
- Enforce C(x)_{ij} = [A(x)^{-1} \cdot B]_{ij} entrywise via n^2
@NLconstraint
s and letJuMP
s AD handle the derivatives - Register a user-defined multivariate function \mathcal{C}: \mathbb{R}^{n} \to \mathbb{R}^{n \times n} and provide only the first derivative \frac{{\rm d}}{{\rm d}x} \mathcal{C}.
The issue with (1) is that for larger systems, the approach introduces n^2 nonconvex constraints. The issue with (2) is that each constraint i only relies on the i-th row of C, and I think I’d have to compute \frac{{\rm d}}{{\rm d}x} \mathcal{C} redundantly in each constraint. Currently working on (2) and would appreciate any thoughts / guidance!
approach 1
using JuMP, Ipopt, LinearAlgebra
## data
n = 3
B = Diagonal(ones(n))
bounds = 1500ones(n)
## model
## min_x 0
## s.t. C(x) = -A(x)^{-1} * B
## ||C(x)_i * x||_2^2 <= rhs_i for i ∈ {1, ... , n}
m = Model(solver=IpoptSolver())
@variable(m, x[1:n])
@variable(m, C[1:n, 1:n])
setvalue(getindex(m, :x), collect(1:n))
# @NLobjective(m, Min, sum(x[i]^2 for i in 1:n))
## A(x)
A = Array{JuMP.NonlinearExpression,2}(undef, n, n)
for i in 1:n
for j in 1:n
A[i, j] = @NLexpression(m, (x[i] + x[j])^2)
end
end
JuMP.registerobject(m, :A, A, "A")
setvalue(getindex(m, :C), getvalue(m[:A]) \ -B)
## C(x) = A^{-1}(x) * -B
@constraintref C_constraint[1:n, 1:n]
for i in 1:n
for j in 1:n
C_constraint[i, j] = @NLconstraint(m, sum(A[i,k] * C[k,j] for k in 1:n) + B[i,j] == 0)
println(sum(sum(getvalue(A[i,k]) * getvalue(C[k,j]) for k in 1:n) + B[i,j] for k in 1:n))
end
end
JuMP.registercon(m, :C_constraint, C_constraint)
## primary constraint
@constraintref primary_constraint[1:n]
for i in 1:n
primary_constraint[i] = @NLconstraint(m, sum((C[i,k] * x[k])^2 for k in 1:n) <= bounds[i])
println(sum((getvalue(C[i,k]) * getvalue(x[k]))^2 for k in 1:n))
end
solve(m)
xstar = getvalue(m[:x])
Cstar = getvalue(m[:C])
Astar = getvalue(m[:A])
norm(Astar * Cstar + B)