Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 0 additions & 6 deletions .github/workflows/Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ jobs:
- 'lts'
- '1'
os: [ubuntu-latest, windows-latest, macOS-latest]
exclude:
- os: macOS-latest # Apple Silicon
version: 'min'
include:
- os: macOS-13 # Intel
version: 'min'
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
Expand Down
7 changes: 4 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
name = "NLSolversBase"
uuid = "d41bc354-129a-5804-8e4c-c37616107c6c"
version = "7.10.0"
version = "7.11.0"

[deps]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[compat]
ADTypes = "1.11.0"
DifferentiationInterface = "0.6.43, 0.7"
FiniteDiff = "2.0"
ForwardDiff = "0.10, 1.0"
LinearAlgebra = "<0.0.1, 1"
julia = "1.10"

[extras]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
OptimTestProblems = "cec144fc-5a64-5bc6-99fb-dde8f63e154c"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["ADTypes", "ComponentArrays", "LinearAlgebra", "OptimTestProblems", "Random", "RecursiveArrayTools", "SparseArrays", "Test"]
test = ["ADTypes", "ComponentArrays", "OptimTestProblems", "Random", "RecursiveArrayTools", "SparseArrays", "Test"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ interface as shown below.
```julia
function fg!(F, G, x)
common_calc(...)
if !(G == nothing)
if G !== nothing
# mutating calculations specific to g!
end
if !(F == nothing)
if F !== nothing
# calculations specific to f
return f
end
Expand Down
9 changes: 4 additions & 5 deletions src/NLSolversBase.jl
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
__precompile__(true)

module NLSolversBase

using ADTypes: AbstractADType, AutoForwardDiff, AutoFiniteDiff
import DifferentiationInterface as DI
using FiniteDiff: FiniteDiff
using ForwardDiff: ForwardDiff
using LinearAlgebra: LinearAlgebra
import Distributed: clear!
export AbstractObjective,
NonDifferentiable,
Expand Down Expand Up @@ -51,7 +50,7 @@ function finitediff_fdtype(autodiff)
fdtype = Val{:forward}
elseif autodiff == :finitecomplex
fdtype = Val{:complex}
elseif any(autodiff .== (:finite, :central, :finitecentral))
elseif autodiff == :finite || autodiff == :central || autodiff == :finitecentral
fdtype = Val{:central}
end
fdtype
Expand All @@ -71,11 +70,11 @@ function get_adtype(autodiff::Union{Symbol,Bool}, chunk=nothing)
elseif is_forwarddiff(autodiff)
return AutoForwardDiff(; chunksize=forwarddiff_chunksize(chunk))
else
error("The autodiff value $autodiff is not supported. Use :finite or :forward.")
throw(ArgumentError(LazyString("The autodiff value `", repr(autodiff), "` is not supported. Use `:finite` or `:forward`.")))
end
end

x_of_nans(x, Tf=eltype(x)) = fill!(Tf.(x), Tf(NaN))
x_of_nans(x::AbstractArray, ::Type{Tf}=float(eltype(x))) where {Tf} = fill!(similar(x, Tf), NaN)

include("objective_types/inplace_factory.jl")
include("objective_types/abstract.jl")
Expand Down
62 changes: 31 additions & 31 deletions src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Force (re-)evaluation of the objective value at `x`.
Returns `f(x)` and stores the value in `obj.F`
"""
function value!!(obj::AbstractObjective, x)
obj.f_calls .+= 1
obj.f_calls += 1
copyto!(obj.x_f, x)
obj.F = obj.f(x)
value(obj)
Expand All @@ -15,7 +15,7 @@ Evaluates the objective value at `x`.
Returns `f(x)`, but does *not* store the value in `obj.F`
"""
function value(obj::AbstractObjective, x)
obj.f_calls .+= 1
obj.f_calls += 1
return obj.f(x)
end
"""
Expand All @@ -38,7 +38,7 @@ This does *not* update `obj.DF` or `obj.x_df`.
function gradient(obj::AbstractObjective, x)
newdf = copy(obj.DF)
obj.df(newdf, x)
obj.df_calls .+= 1
obj.df_calls += 1
return newdf
end
"""
Expand All @@ -58,7 +58,7 @@ Force (re-)evaluation of the gradient value at `x`.
Stores the value in `obj.DF`.
"""
function gradient!!(obj::AbstractObjective, x)
obj.df_calls .+= 1
obj.df_calls += 1
copyto!(obj.x_df, x)
obj.df(obj.DF, x)
gradient(obj)
Expand All @@ -75,8 +75,8 @@ function value_gradient!(obj::AbstractObjective, x)
value(obj), gradient(obj)
end
function value_gradient!!(obj::AbstractObjective, x)
obj.f_calls .+= 1
obj.df_calls .+= 1
obj.f_calls += 1
obj.df_calls += 1
copyto!(obj.x_f, x)
copyto!(obj.x_df, x)
obj.F = obj.fdf(gradient(obj), x)
Expand All @@ -90,7 +90,7 @@ function hessian!(obj::AbstractObjective, x)
hessian(obj)
end
function hessian!!(obj::AbstractObjective, x)
obj.h_calls .+= 1
obj.h_calls += 1
copyto!(obj.x_h, x)
obj.h(obj.H, x)
hessian(obj)
Expand Down Expand Up @@ -124,8 +124,8 @@ function value_jacobian!!(obj, F, J, x)
obj.fdf(F, J, x)
copyto!(obj.x_f, x)
copyto!(obj.x_df, x)
obj.f_calls .+= 1
obj.df_calls .+= 1
obj.f_calls += 1
obj.df_calls += 1
obj.df_calls
F, J
end
Expand All @@ -141,7 +141,7 @@ jacobian!!(obj, x) = jacobian!!(obj, obj.DF, x)
function jacobian!!(obj, J, x)
obj.df(J, x)
copyto!(obj.x_df, x)
obj.df_calls .+= 1
obj.df_calls += 1
obj.df_calls
J
end
Expand All @@ -156,7 +156,7 @@ end
value(obj::NonDifferentiable{TF, TX}, x) where {TF<:AbstractArray, TX} = value(obj, copy(obj.F), x)
value(obj::OnceDifferentiable{TF, TDF, TX}, x) where {TF<:AbstractArray, TDF, TX} = value(obj, copy(obj.F), x)
function value(obj::AbstractObjective, F, x)
obj.f_calls .+= 1
obj.f_calls += 1
return obj.f(F, x)
end

Expand All @@ -165,41 +165,41 @@ value!!(obj::OnceDifferentiable{TF, TDF, TX}, x) where {TF<:AbstractArray, TDF,
function value!!(obj::AbstractObjective, F, x)
obj.f(F, x)
copyto!(obj.x_f, x)
obj.f_calls .+= 1
obj.f_calls += 1
obj.f_calls
F
end

function _clear_f!(d::NLSolversBase.AbstractObjective)
d.f_calls .= 0
if typeof(d.F) <: AbstractArray
d.F .= eltype(d.F)(NaN)
d.f_calls = 0
if d.F isa AbstractArray
fill!(d.F, NaN)
else
d.F = typeof(d.F)(NaN)
d.F = NaN
end
d.x_f .= eltype(d.x_f)(NaN)
fill!(d.x_f, NaN)
nothing
end

function _clear_df!(d::NLSolversBase.AbstractObjective)
d.df_calls .= 0
d.DF .= eltype(d.DF)(NaN)
d.x_df .= eltype(d.x_df)(NaN)
d.df_calls = 0
fill!(d.DF, NaN)
fill!(d.x_df, NaN)
nothing
end

function _clear_h!(d::NLSolversBase.AbstractObjective)
d.h_calls .= 0
d.H .= eltype(d.H)(NaN)
d.x_h .= eltype(d.x_h)(NaN)
d.h_calls = 0
fill!(d.H, NaN)
fill!(d.x_h, NaN)
nothing
end

function _clear_hv!(d::NLSolversBase.AbstractObjective)
d.hv_calls .= 0
d.Hv .= eltype(d.Hv)(NaN)
d.x_hv .= eltype(d.x_hv)(NaN)
d.v_hv .= eltype(d.v_hv)(NaN)
d.hv_calls = 0
fill!(d.Hv, NaN)
fill!(d.x_hv, NaN)
fill!(d.v_hv, NaN)
nothing
end

Expand Down Expand Up @@ -227,9 +227,9 @@ end

g_calls(d::NonDifferentiable) = 0
h_calls(d::Union{NonDifferentiable, OnceDifferentiable}) = 0
f_calls(d) = first(d.f_calls)
g_calls(d) = first(d.df_calls)
h_calls(d) = first(d.h_calls)
f_calls(d) = d.f_calls
g_calls(d) = d.df_calls
h_calls(d) = d.h_calls
hv_calls(d) = 0
h_calls(d::TwiceDifferentiableHV) = 0
hv_calls(d::TwiceDifferentiableHV) = first(d.hv_calls)
hv_calls(d::TwiceDifferentiableHV) = d.hv_calls
73 changes: 46 additions & 27 deletions src/objective_types/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ function _cb(lx::AbstractArray{Tx}, ux::AbstractArray{Tx}, lc::AbstractVector{Tc
end

Base.eltype(::Type{ConstraintBounds{T}}) where T = T
Base.eltype(cb::ConstraintBounds) = eltype(typeof(cb))

Base.convert(::Type{ConstraintBounds{T}}, cb::ConstraintBounds{T}) where {T} = cb
Base.convert(::Type{ConstraintBounds{T}}, cb::ConstraintBounds{S}) where {T,S} =
ConstraintBounds(cb.nc, cb.eqx, convert(Vector{T}, cb.valx),
ConstraintBounds{T}(cb.nc, cb.eqx, convert(Vector{T}, cb.valx),
cb.ineqx, cb.σx, convert(Vector{T}, cb.bx),
cb.eqc, convert(Vector{T}, cb.valc), cb.ineqc,
cb.σc, convert(Vector{T}, cb.bc))
Expand Down Expand Up @@ -80,11 +80,11 @@ function Base.show(io::IO, cb::ConstraintBounds)
indent = " "
print(io, "ConstraintBounds:")
print(io, "\n Variables:")
showeq(io, indent, cb.eqx, cb.valx, 'x', :bracket)
showineq(io, indent, cb.ineqx, cb.σx, cb.bx, 'x', :bracket)
showeq(io, indent, cb.eqx, cb.valx, 'x', true) # bracket
showineq(io, indent, cb.ineqx, cb.σx, cb.bx, 'x', true) # bracket
print(io, "\n Linear/nonlinear constraints:")
showeq(io, indent, cb.eqc, cb.valc, 'c', :subscript)
showineq(io, indent, cb.ineqc, cb.σc, cb.bc, 'c', :subscript)
showeq(io, indent, cb.eqc, cb.valc, 'c', false) # subscript
showineq(io, indent, cb.ineqc, cb.σc, cb.bc, 'c', false) # subscript
nothing
end

Expand Down Expand Up @@ -177,7 +177,7 @@ function TwiceDifferentiableConstraints(c!, lx::AbstractVector, ux::AbstractVect
# TODO: get rid of this allocation with DI.Cache
ccache_righttype = zeros(promote_type(T, eltype(_x)), nc)
c!(ccache_righttype, _x)
return sum(_λ[i] * ccache[i] for i in eachindex(_λ, ccache))
return LinearAlgebra.dot(_λ, ccache_righttype)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pkofod this seems to be a bug that could explain spurious IPNewton test failures.

Copy link
Member

Choose a reason for hiding this comment

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

good catch

end

backend = get_adtype(autodiff, chunk)
Expand Down Expand Up @@ -281,17 +281,17 @@ corresponding entry in `ineq`/`σ`/`b`.
T is the element-type of the non-Int outputs
"""
function parse_constraints(::Type{T}, l, u) where T
size(l) == size(u) || throw(DimensionMismatch("l and u must be the same size, got $(size(l)) and $(size(u))"))
if size(l) != size(u)
throw(DimensionMismatch(LazyString("Size of lower bounds (", size(l), ") and number of upper bounds (", size(u), ") must be equal.")))
Copy link
Member

Choose a reason for hiding this comment

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

I see you typically use lazy but LazyString here. Any particular reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked at the implememtation of the macro in base Julia 😅 And noticed all the parsing and string indexing going on there. This caused me to reconsider my (and our) use of the macro - it seems a bit unnecessary to hide all this information by using the macro if you can directly provide it with the constructor, even though it only affects the compilation times.

Copy link
Member

Choose a reason for hiding this comment

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

I mean, it's meant as an optimization so why settle :)

end
eq, ineq = Int[], Int[]
σ = Int8[]
val, b = T[], T[]
σ = Array{Int8}(undef, 0)
for i = 1:length(l)
li, ui = l[i], u[i]
li <= ui || throw(ArgumentError("l must be smaller than u, got $li, $ui"))
for (i, (li, ui)) in enumerate(zip(l, u))
if li == ui
push!(eq, i)
push!(val, ui)
else
elseif li < ui
if isfinite(li)
push!(ineq, i)
push!(σ, 1)
Expand All @@ -302,36 +302,55 @@ function parse_constraints(::Type{T}, l, u) where T
push!(σ, -1)
push!(b, ui)
end
else
throw(ArgumentError(LazyString("Lower bound (", li, ") must not be greater than upper bound (", ui, ").")))
end
end
eq, val, ineq, σ, b
end

### Compact printing of constraints

function showeq(io, indent, eq, val, chr, style)
if !isempty(eq)
function showeq(io::IO, indent::String, eqs::Vector{Int}, vals::Vector, chr::Char, bracket::Bool)
if !isempty(eqs)
print(io, '\n', indent)
if style == :bracket
eqstrs = map((i, v)->"$chr[$i]=$v", eq, val)
if bracket
for (i, (eq, val)) in enumerate(zip(eqs, vals))
if i != 1
print(io, ", ")
end
print(io, chr, "[", eq, "]=", val)
end
else
eqstrs = map((i, v)->"$(chr)_$i=$v", eq, val)
for (i, (eq, val)) in enumerate(zip(eqs, vals))
if i != 1
print(io, ", ")
end
print(io, chr, "_", eq, "=", val)
end
end
foreach(s->print(io, s * ", "), eqstrs[1:end - 1])
print(io, eqstrs[end])
end
return nothing
end

function showineq(io, indent, ineqs, σs, bs, chr, style)
function showineq(io::IO, indent::String, ineqs::Vector{Int}, σs::Vector{Int8}, bs::Vector, chr::Char, bracket::Bool)
if !isempty(ineqs)
print(io, '\n', indent)
if style == :bracket
ineqstrs = map((i, σ, b)->"$chr[$i]" * ineqstr(σ, b), ineqs, σs, bs)
if bracket
for (i, (ineq, σ, b)) in enumerate(zip(ineqs, σs, bs))
if i != 1
print(io, ", ")
end
print(io, chr, "[", ineq, "]", σ > 0 ? '≥' : '≤', b)
end
else
ineqstrs = map((i, σ, b)->"$(chr)_$i" * ineqstr(σ, b), ineqs, σs, bs)
for (i, (ineq, σ, b)) in enumerate(zip(ineqs, σs, bs))
if i != 1
print(io, ", ")
end
print(io, chr, "_", ineq, σ > 0 ? '≥' : '≤', b)
end
end
foreach(s->print(io, s * ", "), ineqstrs[1:end - 1])
print(io, ineqstrs[end])
end
return nothing
end
ineqstr(σ, b) = σ > 0 ? "≥$b" : "≤$b"
Loading