Skip to content

Commit dc1ce41

Browse files
authored
Add support for multi-indexed constraints (#69)
* MOI fix * multi_con * Double index constraints * first attempt -- doesn't look promising * new idx introduced * Working. Need to add a test * added test * dead end? * collect approach * test fix * test fix * typo fix * fixes * works except for roc and oneapi * fixed oneapi and amdgpu issues * rollback changes in ext packages --------- Co-authored-by: Sungho Shin <[email protected]>
1 parent 8580c50 commit dc1ce41

File tree

7 files changed

+94
-54
lines changed

7 files changed

+94
-54
lines changed

ext/ExaModelsCUDA.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module ExaModelsCUDA
33
import ExaModels: ExaModels, NLPModels
44
import CUDA: CUDA, CUDABackend, CuArray
55

6-
ExaModels.ExaCore(backend::CUDABackend) = ExaModels.ExaCore(Float64, backend)
76
ExaModels.convert_array(v, backend::CUDABackend) = CuArray(v)
87

98
end

ext/ExaModelsMOI.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,16 @@ function _exafy(e::MOI.ScalarNonlinearFunction, p = ())
332332
end
333333

334334
function _exafy(e::MOI.ScalarAffineFunction{T}, p = ()) where {T}
335-
return sum(begin
336-
c1, p = _exafy(term, p)
337-
c1
338-
end for term in e.terms) + ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1),
339-
(p..., e.constant)
335+
ec = if !isempty(e.terms)
336+
sum(begin
337+
c1, p = _exafy(term, p)
338+
c1
339+
end for term in e.terms) + ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1)
340+
else
341+
ExaModels.ParIndexed(ExaModels.ParSource(), length(p) + 1)
342+
end
343+
344+
return ec, (p..., e.constant)
340345
end
341346

342347
function _exafy(e::MOI.ScalarAffineTerm{T}, p = ()) where {T}

ext/ExaModelsOneAPI.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ function ExaModels.append!(
77
a::A,
88
b::Base.Generator{UnitRange{I}},
99
lb,
10-
) where {I,A<:oneAPI.oneVector}
10+
) where {I,A<:oneAPI.oneArray}
1111
la = length(a)
1212
aa = similar(a, la + lb)
1313
copyto!(view(aa, 1:la), a)
1414
map!(b.f, view(aa, (la+1):(la+lb)), b.iter)
1515
return aa
1616
end
1717

18-
function ExaModels.append!(backend, a::A, b::Base.Generator, lb) where {A<:oneAPI.oneVector}
18+
function ExaModels.append!(backend, a::A, b::Base.Generator, lb) where {A<:oneAPI.oneArray}
1919
la = length(a)
2020
aa = similar(a, la + lb)
2121
copyto!(view(aa, 1:la), a)
@@ -29,7 +29,7 @@ function ExaModels.append!(
2929
a::A,
3030
b::V,
3131
lb,
32-
) where {A<:oneAPI.oneVector,V<:AbstractVector}
32+
) where {A<:oneAPI.oneArray,V<:AbstractArray}
3333
la = length(a)
3434
aa = similar(a, la + lb)
3535
copyto!(view(aa, 1:la), a)
@@ -38,7 +38,7 @@ function ExaModels.append!(
3838
end
3939

4040

41-
function ExaModels.append!(backend, a::A, b::Number, lb) where {A<:oneAPI.oneVector}
41+
function ExaModels.append!(backend, a::A, b::Number, lb) where {A<:oneAPI.oneArray}
4242
la = length(a)
4343
aa = similar(a, la + lb)
4444
copyto!(view(aa, 1:la), a)
@@ -48,16 +48,16 @@ end
4848

4949
ExaModels.convert_array(v, backend::oneAPI.oneAPIBackend) = oneAPI.oneArray(v)
5050

51-
ExaModels.sort!(array::A; lt = isless) where {A<:oneAPI.oneVector} =
51+
ExaModels.sort!(array::A; lt = isless) where {A<:oneAPI.oneArray} =
5252
copyto!(array, sort!(Array(array); lt = lt))
5353

5454
# below is type piracy
55-
function Base.findall(f::F, bitarray::A) where {F<:Function,A<:oneAPI.oneVector}
55+
function Base.findall(f::F, bitarray::A) where {F<:Function,A<:oneAPI.oneArray}
5656
a = Array(bitarray)
5757
b = findall(f, a)
5858
c = similar(bitarray, eltype(b), length(b))
5959
return copyto!(c, b)
6060
end
61-
Base.findall(bitarray::A) where {A<:oneAPI.oneVector} = Base.findall(identity, bitarray)
61+
Base.findall(bitarray::A) where {A<:oneAPI.oneArray} = Base.findall(identity, bitarray)
6262

6363
end # module

src/nlp.jl

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ julia> result = ipopt(m; print_level=0) # solve the problem
223223
224224
```
225225
"""
226-
function ExaModel(c::C; kwargs...) where {C<:ExaCore}
226+
function ExaModel(c::C; prod = nothing) where {C<:ExaCore}
227227
return ExaModel(
228228
c.obj,
229229
c.con,
@@ -254,15 +254,16 @@ end
254254
)
255255

256256
function append!(backend, a, b::Base.Generator, lb)
257-
257+
b = _adapt_gen(b)
258+
258259
la = length(a)
259260
resize!(a, la + lb)
260261
map!(b.f, view(a, (la+1):(la+lb)), convert_array(b.iter, backend))
261262
return a
262263
end
263264

264265
function append!(backend, a, b::Base.Generator{UnitRange{I}}, lb) where {I}
265-
266+
266267
la = length(a)
267268
resize!(a, la + lb)
268269
map!(b.f, view(a, (la+1):(la+lb)), b.iter)
@@ -367,6 +368,7 @@ Objective
367368
```
368369
"""
369370
function objective(c::C, gen) where {C<:ExaCore}
371+
gen = _adapt_gen(gen)
370372
f = SIMDFunction(gen, c.nobj, c.nnzg, c.nnzh)
371373
pars = gen.iter
372374

@@ -426,8 +428,9 @@ function constraint(
426428
start = zero(T),
427429
lcon = zero(T),
428430
ucon = zero(T),
429-
) where {T,C<:ExaCore{T}}
430-
431+
) where {T,C<:ExaCore{T}}
432+
433+
gen = _adapt_gen(gen)
431434
f = SIMDFunction(gen, c.ncon, c.nnzj, c.nnzh)
432435
pars = gen.iter
433436

@@ -487,6 +490,8 @@ function _constraint(c, f, pars, start, lcon, ucon)
487490
end
488491

489492
function constraint!(c::C, c1, gen::Base.Generator) where {C<:ExaCore}
493+
494+
gen = _adapt_gen(gen)
490495
f = SIMDFunction(gen, offset0(c1, 0), c.nnzj, c.nnzh)
491496
pars = gen.iter
492497

@@ -621,7 +626,7 @@ function hess_coord!(
621626
x::AbstractVector,
622627
y::AbstractVector,
623628
hess::AbstractVector;
624-
obj_weight = one(eltype(x)),
629+
obj_weight = one(eltype(x)),
625630
)
626631

627632
fill!(hess, zero(eltype(hess)))
@@ -680,6 +685,22 @@ end
680685
@inbounds @inline offset0(a::C, i) where {C<:ConstraintAug} = offset0(a.f, a.itr, i)
681686
@inbounds @inline offset0(f::F, itr, i) where {P<:Pair,F<:SIMDFunction{P}} =
682687
f.o0 + f.f.first(itr[i], nothing)
688+
@inbounds @inline offset0(f::F, itr, i) where {T<:Tuple,P<:Pair{T},F<:SIMDFunction{P}} = f.o0 + idxx(coord(itr, i, f.f.first), Base.size(itr))
689+
690+
@inline idx(itr, I) = @inbounds itr[I]
691+
@inline idx(itr::Base.Iterators.ProductIterator{V}, I) where V = _idx(I-1, itr.iterators, Base.size(itr))
692+
@inline function _idx(n, (vec1, vec...), (si1, si...))
693+
d, r = divrem(n, si1)
694+
return (vec1[r + 1], _idx(d, vec, si)...)
695+
end
696+
@inline _idx(n, (vec,), ::Tuple{Int}) = @inbounds vec[n + 1]
697+
698+
@inline idxx(coord, si) = _idxx(coord, si, 1) + 1
699+
@inline _idxx(coord, si, a) = a * (coord[1] - 1) + _idxx(coord[2:end], si[2:end], a*si[1])
700+
@inline _idxx(::Tuple{}, ::Tuple{}, a) = 0
701+
702+
@inline coord(itr, i, (f,fs...)) = (f(idx(itr,i), nothing), coord(itr, i, fs)...)
703+
@inline coord(itr, i, ::Tuple{}) = ()
683704

684705
for (thing, val) in [(:solution, 1), (:multipliers_L, 0), (:multipliers_U, 2)]
685706
@eval begin
@@ -755,3 +776,7 @@ function multipliers(result::SolverCore.AbstractExecutionStats, y::Constraint)
755776
len = length(y.itr)
756777
return view(result.multipliers, o+1:o+len)
757778
end
779+
780+
781+
_adapt_gen(gen) = Base.Generator(gen.f, collect(gen.iter))
782+
_adapt_gen(gen::Base.Generator{P}) where P <: Union{AbstractArray,AbstractRange} = gen

src/simdfunction.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@inline (a::Pair{P,S} where {P<:AbstractNode,S<:AbstractNode})(i, x) = a.second(i, x)
1+
@inline (a::Pair{P,S} where {P,S<:AbstractNode})(i, x) = a.second(i, x)
22

33
"""
44
Compressor{I}

test/NLPTest/NLPTest.jl

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,24 +123,24 @@ end
123123

124124
function runtests()
125125
@testset "NLP test" begin
126-
for (name, args) in NLP_TEST_ARGUMENTS
127-
@testset "$name $args" begin
126+
for backend in BACKENDS
127+
@testset "$backend" begin
128+
for (name, args) in NLP_TEST_ARGUMENTS
129+
@testset "$name $args" begin
128130

129-
exa_model = getfield(@__MODULE__, Symbol("_exa_$(name)_model"))
130-
jump_model = getfield(@__MODULE__, Symbol("_jump_$(name)_model"))
131+
exa_model = getfield(@__MODULE__, Symbol("_exa_$(name)_model"))
132+
jump_model = getfield(@__MODULE__, Symbol("_jump_$(name)_model"))
131133

132-
m, vars0, cons0 = exa_model(nothing, args)
133-
m0 = WrapperNLPModel(m)
134+
m, vars0, cons0 = exa_model(nothing, args)
135+
m0 = WrapperNLPModel(m)
134136

135-
m, vars2, cons2 = jump_model(nothing, args)
136-
m2 = MathOptNLPModel(m)
137+
m, vars2, cons2 = jump_model(nothing, args)
138+
m2 = MathOptNLPModel(m)
139+
140+
set_optimizer(m, MadNLP.Optimizer)
141+
set_optimizer_attribute(m, "print_level", MadNLP.ERROR)
142+
optimize!(m)
137143

138-
set_optimizer(m, MadNLP.Optimizer)
139-
set_optimizer_attribute(m, "print_level", MadNLP.ERROR)
140-
optimize!(m)
141-
142-
for backend in BACKENDS
143-
@testset "$backend" begin
144144

145145
m, vars1, cons1 = exa_model(backend, args)
146146
m1 = WrapperNLPModel(m)
@@ -164,11 +164,18 @@ function runtests()
164164
end
165165
end
166166
end
167-
168167
result1 = madnlp(m1; print_level = MadNLP.ERROR)
169168
test_api(result1, vars1, cons1, vars2, cons2)
170169
end
171170
end
171+
172+
m3 = WrapperNLPModel(exa_luksan_vlcek_model(nothing, 3; M = 2))
173+
m4 = jump_luksan_vlcek_model(nothing, 3; M = 2)
174+
175+
@testset "Multi-column constraints" begin
176+
test_nlp(m3, m4; full = false)
177+
end
178+
172179
end
173180
end
174181
end

test/NLPTest/luksan.jl

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,51 @@
1-
function luksan_vlcek_obj(x, i)
2-
return 100 * (x[i-1]^2 - x[i])^2 + (x[i-1] - 1)^2
1+
function luksan_vlcek_obj(x, i, j)
2+
return 100 * (x[i-1, j]^2 - x[i, j])^2 + (x[i-1, j] - 1)^2
33
end
44

5-
function luksan_vlcek_con(x, i)
6-
return 3x[i+1]^3 + 2 * x[i+2] - 5 + sin(x[i+1] - x[i+2])sin(x[i+1] + x[i+2]) + 4x[i+1] -
7-
x[i]exp(x[i] - x[i+1]) - 3
5+
function luksan_vlcek_con1(x, i, j)
6+
return 3x[i+1, j]^3 + 2 * x[i+2, j] - 5
7+
end
8+
function luksan_vlcek_con2(x, i, j)
9+
return sin(x[i+1, j] - x[i+2, j])sin(x[i+1, j] + x[i+2, j]) + 4x[i+1, j] -
10+
x[i, j]exp(x[i, j] - x[i+1, j]) - 3
811
end
912

1013
function luksan_vlcek_x0(i)
1114
return mod(i, 2) == 1 ? -1.2 : 1.0
1215
end
1316

14-
function _exa_luksan_vlcek_model(backend, N)
17+
function _exa_luksan_vlcek_model(backend, N; M = 1)
1518

1619
c = ExaCore(backend = backend)
17-
x = variable(c, N; start = (luksan_vlcek_x0(i) for i = 1:N))
18-
s = constraint(c, luksan_vlcek_con(x, i) for i = 1:N-2)
19-
objective(c, luksan_vlcek_obj(x, i) for i = 2:N)
20+
x = variable(c, N, M; start = [luksan_vlcek_x0(i) for i = 1:N, j=1:M])
21+
s = constraint(c, luksan_vlcek_con1(x, i, j) for i = 1:N-2, j=1:M)
22+
constraint!(c, s, (i,j) => luksan_vlcek_con2(x, i, j) for i = 1:N-2, j=1:M)
23+
objective(c, luksan_vlcek_obj(x, i, j) for i = 2:N, j=1:M)
2024

2125
return ExaModel(c; prod = true), (x,), (s,)
2226
end
2327

24-
function exa_luksan_vlcek_model(backend, N)
25-
m, vars, cons = _exa_luksan_vlcek_model(backend, N)
28+
function exa_luksan_vlcek_model(backend, N; M = 1)
29+
m, vars, cons = _exa_luksan_vlcek_model(backend, N;M = M)
2630
return m
2731
end
2832

29-
function _jump_luksan_vlcek_model(backend, N)
33+
function _jump_luksan_vlcek_model(backend, N; M = 1)
3034
jm = JuMP.Model()
3135

32-
JuMP.@variable(jm, x[i = 1:N], start = mod(i, 2) == 1 ? -1.2 : 1.0)
36+
JuMP.@variable(jm, x[i = 1:N, j=1:M], start = mod(i, 2) == 1 ? -1.2 : 1.0)
3337
JuMP.@NLconstraint(
3438
jm,
35-
s[i = 1:N-2],
36-
3x[i+1]^3 + 2x[i+2] - 5 + sin(x[i+1] - x[i+2])sin(x[i+1] + x[i+2]) + 4x[i+1] -
37-
x[i]exp(x[i] - x[i+1]) - 3 == 0.0
39+
s[i = 1:N-2, j=1:M],
40+
3x[i+1,j]^3 + 2x[i+2,j] - 5 + sin(x[i+1,j] - x[i+2,j])sin(x[i+1,j] + x[i+2,j]) + 4x[i+1,j] -
41+
x[i,j]exp(x[i,j] - x[i+1,j]) - 3 == 0.0
3842
)
39-
JuMP.@NLobjective(jm, Min, sum(100(x[i-1]^2 - x[i])^2 + (x[i-1] - 1)^2 for i = 2:N))
43+
JuMP.@NLobjective(jm, Min, sum(100(x[i-1,j]^2 - x[i,j])^2 + (x[i-1,j] - 1)^2 for i = 2:N, j=1:M))
4044

4145
return jm, (x,), (s,)
4246
end
4347

44-
function jump_luksan_vlcek_model(backend, N)
45-
jm, vars, cons = _jump_luksan_vlcek_model(backend, N)
48+
function jump_luksan_vlcek_model(backend, N; M = 1)
49+
jm, vars, cons = _jump_luksan_vlcek_model(backend, N; M = M)
4650
return MathOptNLPModel(jm)
4751
end

0 commit comments

Comments
 (0)