Skip to content

Commit 1e5d175

Browse files
authored
Autodiff support for 2nd order constraints (#116)
* ForwardDiff support for 2nd order constraints * is_finitediff and is_forwarddiff in constraints * added fd_type to finitediff oncediff * added FiniteDiff autodiff on 2nd order constraints * added tests * fix a test error * fix more test errors * more error fixes on tests * fixing test errors number 4 * fix test errors number 5 * fixing test errors number 6 * everything for that sweet coverage
1 parent 5e615dd commit 1e5d175

File tree

2 files changed

+225
-26
lines changed

2 files changed

+225
-26
lines changed

src/objective_types/constraints.jl

Lines changed: 171 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
### Constraints
2-
#
2+
#
33
# Constraints are specified by the user as
44
# lx_i ≤ x[i] ≤ ux_i # variable (box) constraints
55
# lc_i ≤ c(x)[i] ≤ uc_i # linear/nonlinear constraints
66
# and become equality constraints with l_i = u_i. ±∞ are allowed for l
77
# and u, in which case the relevant side(s) are unbounded.
8-
#
8+
#
99
# The user supplies functions to calculate c(x) and its derivatives.
10-
#
10+
#
1111
# Of course we could unify the box-constraints into the
1212
# linear/nonlinear constraints, but that would force the user to
1313
# provide the variable-derivatives manually, which would be silly.
14-
#
14+
#
1515
# This parametrization of the constraints gets "parsed" into a form
1616
# that speeds and simplifies the IPNewton algorithm, at the cost of many
1717
# additional variables. See `parse_constraints` for details.
@@ -35,7 +35,7 @@ function ConstraintBounds(lx, ux, lc, uc)
3535
_cb(symmetrize(lx, ux)..., symmetrize(lc, uc)...)
3636
end
3737
function _cb(lx::AbstractArray{Tx}, ux::AbstractArray{Tx}, lc::AbstractVector{Tc}, uc::AbstractVector{Tc}) where {Tx,Tc}
38-
T = promote_type(Tx,Tc)
38+
T = promote_type(Tx, Tc)
3939
ConstraintBounds{T}(length(lc), parse_constraints(T, lx, ux)..., parse_constraints(T, lc, uc)...)
4040
end
4141

@@ -114,8 +114,8 @@ function OnceDifferentiableConstraints(lx::AbstractArray, ux::AbstractArray)
114114
end
115115

116116
function OnceDifferentiableConstraints(bounds::ConstraintBounds)
117-
c! = (c,x)->nothing
118-
J! = (J,x)->nothing
117+
c! = (c, x)->nothing
118+
J! = (J, x)->nothing
119119
OnceDifferentiableConstraints(c!, J!, bounds)
120120
end
121121

@@ -136,19 +136,19 @@ function OnceDifferentiableConstraints(c!, lx::AbstractVector, ux::AbstractVecto
136136
sizex = size(lx)
137137
sizec = size(lc)
138138

139-
xcache = zeros(T,sizex)
140-
ccache = zeros(T,sizec)
139+
xcache = zeros(T, sizex)
140+
ccache = zeros(T, sizec)
141141

142-
if any(autodiff .== (:finite, :central))
142+
if is_finitediff(autodiff)
143143
ccache2 = similar(ccache)
144-
central_cache = FiniteDiff.JacobianCache(xcache, ccache,
145-
ccache2)
144+
fdtype = finitediff_fdtype(autodiff)
145+
jacobian_cache = FiniteDiff.JacobianCache(xcache, ccache,ccache2,fdtype)
146146
function jfinite!(J, x)
147-
FiniteDiff.finite_difference_jacobian!(J, c!, x, central_cache)
147+
FiniteDiff.finite_difference_jacobian!(J, c!, x, jacobian_cache)
148148
J
149149
end
150150
return OnceDifferentiableConstraints(c!, jfinite!, bounds)
151-
elseif autodiff == :forward
151+
elseif is_forwarddiff(autodiff)
152152
jac_cfg = ForwardDiff.JacobianConfig(c!, ccache, xcache, chunk)
153153
ForwardDiff.checktag(jac_cfg, c!, xcache)
154154

@@ -169,20 +169,167 @@ struct TwiceDifferentiableConstraints{F,J,H,T} <: AbstractConstraints
169169
h!::H # h!(storage, x) stores the hessian of the constraint functions
170170
bounds::ConstraintBounds{T}
171171
end
172+
172173
function TwiceDifferentiableConstraints(c!, jacobian!, h!, lx, ux, lc, uc)
173174
b = ConstraintBounds(lx, ux, lc, uc)
174175
TwiceDifferentiableConstraints(c!, jacobian!, h!, b)
175176
end
176177

178+
function TwiceDifferentiableConstraints(c!, lx::AbstractVector, ux::AbstractVector,
179+
lc::AbstractVector, uc::AbstractVector,
180+
autodiff::Symbol = :central,
181+
chunk::ForwardDiff.Chunk = checked_chunk(lx))
182+
if is_finitediff(autodiff)
183+
fdtype = finitediff_fdtype(autodiff)
184+
return twicediff_constraints_finite(c!,lx,ux,lc,uc,fdtype,nothing)
185+
elseif is_forwarddiff(autodiff)
186+
return twicediff_constraints_forward(c!,lx,ux,lc,uc,chunk,nothing)
187+
else
188+
error("The autodiff value $autodiff is not support. Use :finite or :forward.")
189+
end
190+
end
191+
192+
function TwiceDifferentiableConstraints(c!, con_jac!,lx::AbstractVector, ux::AbstractVector,
193+
lc::AbstractVector, uc::AbstractVector,
194+
autodiff::Symbol = :central,
195+
chunk::ForwardDiff.Chunk = checked_chunk(lx))
196+
if is_finitediff(autodiff)
197+
fdtype = finitediff_fdtype(autodiff)
198+
return twicediff_constraints_finite(c!,lx,ux,lc,uc,fdtype,con_jac!)
199+
elseif is_forwarddiff(autodiff)
200+
return twicediff_constraints_forward(c!,lx,ux,lc,uc,chunk,con_jac!)
201+
else
202+
error("The autodiff value $autodiff is not support. Use :finite or :forward.")
203+
end
204+
end
205+
206+
207+
177208
function TwiceDifferentiableConstraints(lx::AbstractArray, ux::AbstractArray)
178209
bounds = ConstraintBounds(lx, ux, [], [])
179210
TwiceDifferentiableConstraints(bounds)
180211
end
181212

213+
214+
function twicediff_constraints_forward(c!, lx, ux, lc, uc,chunk,con_jac! = nothing)
215+
bounds = ConstraintBounds(lx, ux, lc, uc)
216+
T = eltype(bounds)
217+
nc = length(lc)
218+
nx = length(lx)
219+
ccache = zeros(T, nc)
220+
xcache = zeros(T, nx)
221+
cache_check = Ref{DataType}(Missing) #the datatype Missing, not the singleton
222+
ref_f= Ref{Any}() #cache for intermediate jacobian used in the hessian
223+
cxxcache = zeros(T, nx * nc, nx) #output cache for hessian
224+
h = reshape(cxxcache, (nc, nx, nx)) #reshaped output
225+
hi = [@view h[i,:,:] for i in 1:nc]
226+
#ref_f caches the closure function with its caches. other aproaches include using a Dict, but the
227+
#cost of switching happens just once per optimize call.
228+
229+
if isnothing(con_jac!) #if the jacobian is not provided, generate one
230+
jac_cfg = ForwardDiff.JacobianConfig(c!, ccache, xcache, chunk)
231+
ForwardDiff.checktag(jac_cfg, c!, xcache)
232+
233+
jac! = (J, x) -> begin
234+
ForwardDiff.jacobian!(J, c!, ccache, x, jac_cfg, Val{false}())
235+
J
236+
end
237+
238+
con_jac_cached = x -> begin
239+
exists_cache = (cache_check[] == eltype(x))
240+
if exists_cache
241+
f = ref_f[]
242+
return f(x)
243+
else
244+
jcache = zeros(eltype(x), nc)
245+
out_cache = zeros(eltype(x), nc, nx)
246+
cfg_cache = ForwardDiff.JacobianConfig(c!,jcache,x)
247+
f = z->ForwardDiff.jacobian!(out_cache, c!, jcache, z,cfg_cache,Val{false}())
248+
ref_f[] = f
249+
cache_check[]= eltype(x)
250+
return f(x)
251+
end
252+
end
253+
254+
else
255+
jac! = (J,x) -> con_jac!(J,x)
256+
257+
#here, the cache should also include a JacobianConfig
258+
con_jac_cached = x -> begin
259+
exists_cache = (cache_check[] == eltype(x))
260+
if exists_cache
261+
f = ref_f[]
262+
return f(x)
263+
else
264+
out_cache = zeros(eltype(x), nc, nx)
265+
f = z->jac!(out_cache,x)
266+
ref_f[] = f
267+
cache_check[]= eltype(x)
268+
return f(x)
269+
end
270+
end
271+
end
272+
273+
hess_config_cache = ForwardDiff.JacobianConfig(typeof(con_jac_cached),lx)
274+
function con_hess!(hess, x, λ)
275+
ForwardDiff.jacobian!(cxxcache, con_jac_cached, x,hess_config_cache,Val{false}())
276+
for i = 1:nc #hot hessian loop
277+
hess+=λ[i].*hi[i]
278+
end
279+
return hess
280+
end
281+
282+
return TwiceDifferentiableConstraints(c!, jac!, con_hess!, bounds)
283+
end
284+
285+
286+
function twicediff_constraints_finite(c!,lx,ux,lc,uc,fdtype,con_jac! = nothing)
287+
bounds = ConstraintBounds(lx, ux, lc, uc)
288+
T = eltype(bounds)
289+
nx = length(lx)
290+
nc = length(lc)
291+
xcache = zeros(T, nx)
292+
ccache = zeros(T, nc)
293+
294+
if isnothing(con_jac!)
295+
jac_ccache = similar(ccache)
296+
jacobian_cache = FiniteDiff.JacobianCache(xcache, ccache,jac_ccache,fdtype)
297+
function jac!(J, x)
298+
FiniteDiff.finite_difference_jacobian!(J, c!, x, jacobian_cache)
299+
J
300+
end
301+
else
302+
jac! = (J,x) -> con_jac!(J,x)
303+
end
304+
cxxcache = zeros(T,nc*nx,nx) # to create cached jacobian
305+
h = reshape(cxxcache, (nc, nx, nx)) #reshaped output
306+
hi = [@view h[i,:,:] for i in 1:nc]
307+
308+
function jac_vec!(J,x) #to evaluate the jacobian of a jacobian, FiniteDiff needs a vector version of that
309+
j_mat = reshape(J,nc,nx)
310+
return jac!(j_mat,x)
311+
return J
312+
end
313+
hess_xcache =similar(xcache)
314+
hess_cxcache =zeros(T,nc*nx) #output of jacobian, as a vector
315+
hess_cxxcache =similar(hess_cxcache)
316+
hess_config_cache = FiniteDiff.JacobianCache(hess_xcache,hess_cxcache,hess_cxxcache,fdtype)
317+
function con_hess!(hess, x, λ)
318+
FiniteDiff.finite_difference_jacobian!(cxxcache, jac_vec!, x,hess_config_cache)
319+
for i = 1:nc
320+
hi = @view h[i,:,:]
321+
hess+=λ[i].*hi
322+
end
323+
return hess
324+
end
325+
return TwiceDifferentiableConstraints(c!, jac!, con_hess!, bounds)
326+
end
327+
328+
182329
function TwiceDifferentiableConstraints(bounds::ConstraintBounds)
183-
c! = (x,c)->nothing
184-
J! = (x,J)->nothing
185-
h! = (x,λ,h)->nothing
330+
c! = (x, c)->nothing
331+
J! = (x, J)->nothing
332+
h! = (x, λ, h)->nothing
186333
TwiceDifferentiableConstraints(c!, J!, h!, bounds)
187334
end
188335

@@ -278,11 +425,11 @@ function showeq(io, indent, eq, val, chr, style)
278425
if !isempty(eq)
279426
print(io, '\n', indent)
280427
if style == :bracket
281-
eqstrs = map((i,v) -> "$chr[$i]=$v", eq, val)
428+
eqstrs = map((i, v)->"$chr[$i]=$v", eq, val)
282429
else
283-
eqstrs = map((i,v) -> "$(chr)_$i=$v", eq, val)
430+
eqstrs = map((i, v)->"$(chr)_$i=$v", eq, val)
284431
end
285-
foreach(s->print(io, s*", "), eqstrs[1:end-1])
432+
foreach(s->print(io, s * ", "), eqstrs[1:end - 1])
286433
print(io, eqstrs[end])
287434
end
288435
end
@@ -291,12 +438,12 @@ function showineq(io, indent, ineqs, σs, bs, chr, style)
291438
if !isempty(ineqs)
292439
print(io, '\n', indent)
293440
if style == :bracket
294-
ineqstrs = map((i,σ,b) -> "$chr[$i]"*ineqstr(σ,b), ineqs, σs, bs)
441+
ineqstrs = map((i, σ, b)->"$chr[$i]" * ineqstr(σ, b), ineqs, σs, bs)
295442
else
296-
ineqstrs = map((i,σ,b) -> "$(chr)_$i"*ineqstr(σ,b), ineqs, σs, bs)
443+
ineqstrs = map((i, σ, b)->"$(chr)_$i" * ineqstr(σ, b), ineqs, σs, bs)
297444
end
298-
foreach(s->print(io, s*", "), ineqstrs[1:end-1])
445+
foreach(s->print(io, s * ", "), ineqstrs[1:end - 1])
299446
print(io, ineqstrs[end])
300447
end
301448
end
302-
ineqstr(σ,b) = σ>0 ? "$b" : "$b"
449+
ineqstr(σ, b) = σ > 0 ? "$b" : "$b"

test/constraints.jl

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@test NLSolversBase.nconstraints_x(cb) == 0
66
@test cb.valc == [0.0]
77
@test eltype(cb) == Float64
8-
8+
@test eltype(cb) == eltype(typeof(cb))
99
cb = ConstraintBounds([0], [0], [], [])
1010
@test NLSolversBase.nconstraints(cb) == 0
1111
@test NLSolversBase.nconstraints_x(cb) == 1
@@ -69,7 +69,8 @@
6969
lx = fill(-Inf, nx)
7070
ux = fill(Inf, nx)
7171
end
72-
72+
@test_throws ErrorException OnceDifferentiableConstraints(cbd.c!, lx, ux,
73+
lc, uc, :wuoah)
7374
for autodiff in (:finite, :forward)
7475
odca = OnceDifferentiableConstraints(cbd.c!, lx, ux,
7576
lc, uc, autodiff)
@@ -111,5 +112,56 @@
111112
@test isempty(odc.bounds.bc)
112113
@test odc.bounds.valc == [0.0]
113114

115+
@testset "second order autodiff" begin
116+
117+
prob = MVP.ConstrainedProblems.examples["HS9"]
118+
cbd = prob.constraintdata
119+
nc = length(cbd.lc)
120+
nx = length(prob.initial_x)
121+
lx = fill(-Inf, nx)
122+
ux = fill(Inf, nx)
123+
cb = ConstraintBounds(lx, ux, cbd.lc, cbd.uc)
124+
odc = TwiceDifferentiableConstraints(cbd.c!, cbd.jacobian!, cbd.h!,lx, ux, cbd.lc, cbd.uc)
125+
126+
T = eltype(odc.bounds)
127+
jac_result = zeros(T, nc,nx)
128+
jac_result_autodiff = zeros(T, nc,nx)
129+
130+
hess_result = zeros(T, nx,nx)
131+
hess_result_autodiff = zeros(T, nx,nx)
132+
λ = rand(T,nc)
133+
λ0 = zeros(T,nc)
134+
@test_throws ErrorException TwiceDifferentiableConstraints(cbd.c!,lx, ux, cbd.lc, cbd.uc,:campanario)
135+
for autodiff in (:finite,:forward) #testing double differentiation
136+
odca2 = TwiceDifferentiableConstraints(cbd.c!,lx, ux, cbd.lc, cbd.uc,autodiff)
137+
138+
odca2.jacobian!(jac_result_autodiff,prob.initial_x)
139+
odc.jacobian!(jac_result,prob.initial_x)
140+
odca2.h!(hess_result_autodiff,prob.initial_x,λ0) #warmup,λ0 means no modification
141+
odca2.h!(hess_result_autodiff,prob.initial_x,λ)
142+
143+
odc.h!(hess_result,prob.initial_x,λ)
144+
145+
146+
@test isapprox(jac_result, jac_result_autodiff, atol=1e-10)
147+
@test isapprox(hess_result, hess_result_autodiff, atol=1e-10)
148+
149+
fill!(hess_result_autodiff,zero(T))
150+
fill!(hess_result,zero(T))
151+
fill!(jac_result_autodiff,zero(T))
152+
fill!(jac_result,zero(T))
153+
end
154+
@test_throws ErrorException TwiceDifferentiableConstraints(cbd.c!, cbd.jacobian!,lx, ux, cbd.lc, cbd.uc,:qloctm)
155+
for autodiff in (:finite,:forward) #testing autodiff hessian from constraint jacobian
156+
odca2 = TwiceDifferentiableConstraints(cbd.c!, cbd.jacobian!,lx, ux, cbd.lc, cbd.uc,autodiff)
157+
odca2.h!(hess_result_autodiff,prob.initial_x,λ0) #warmup,λ0 means no modification
158+
odca2.h!(hess_result_autodiff,prob.initial_x,λ)
159+
odc.h!(hess_result,prob.initial_x,λ)
160+
@test isapprox(hess_result, hess_result_autodiff, atol=1e-10)
161+
fill!(hess_result_autodiff,zero(T))
162+
fill!(hess_result,zero(T))
163+
end
164+
end
114165
end
115166
end
167+

0 commit comments

Comments
 (0)