Skip to content

Commit d749bef

Browse files
committed
Use task_local_storage
This replaces the storage indexed by `Threads.threadid()` which was bogus. See issue #47.
1 parent 8502a75 commit d749bef

File tree

1 file changed

+79
-125
lines changed

1 file changed

+79
-125
lines changed

src/PRIMA.jl

Lines changed: 79 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -399,19 +399,16 @@ function bobyqa!(f, x::DenseVector{Cdouble};
399399
fx = Ref{Cdouble}(NaN) # to store f(x) on return
400400
nf = Ref{Cint}(0) # to store number of function calls
401401

402-
# Create wrapper to objective function and push it on top of per-thread
403-
# stack before calling optimizer.
404-
fw = ObjFun(f, n, scl) # wrapper to objective function
405-
fp = _push_objfun(bobyqa, fw) # pointer to C-callable function
406-
try
407-
# Call low-level optimizer on the (scaled) variables.
408-
isempty(scl) || _scale!(x, /, scl)
409-
status = prima_bobyqa(fp, n, x, fx, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
410-
isempty(scl) || _scale!(x, *, scl)
411-
return Info(; fx = fx[], nf = nf[], status)
412-
finally
413-
_pop_objfun(fw)
402+
# Call low-level optimizer on the scaled variables and with a wrapper to the objective
403+
# function stored in the task local storage.
404+
isempty(scl) || _scale!(x, /, scl)
405+
fw = ObjFun(f, n, scl) # wrapper to objective function
406+
fp = _objfun_ptr(bobyqa) # pointer to C-callable function
407+
status = task_local_storage(:objfun, fw) do
408+
prima_bobyqa(fp, n, x, fx, xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
414409
end
410+
isempty(scl) || _scale!(x, *, scl)
411+
return Info(; fx = fx[], nf = nf[], status)
415412
end
416413

417414
"""
@@ -440,19 +437,16 @@ function newuoa!(f, x::DenseVector{Cdouble};
440437
fx = Ref{Cdouble}(NaN) # to store f(x) on return
441438
nf = Ref{Cint}(0) # to store number of function calls
442439

443-
# Create wrapper to objective function and push it on top of per-thread
444-
# stack before calling optimizer.
445-
fw = ObjFun(f, n, scl) # wrapper to objective function
446-
fp = _push_objfun(newuoa, fw) # pointer to C-callable function
447-
try
448-
# Call low-level optimizer on the (scaled) variables.
449-
isempty(scl) || _scale!(x, /, scl)
450-
status = prima_newuoa(fp, n, x, fx, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
451-
isempty(scl) || _scale!(x, *, scl)
452-
return Info(; fx = fx[], nf = nf[], status)
453-
finally
454-
_pop_objfun(fw)
440+
# Call low-level optimizer on the scaled variables and with a wrapper to the objective
441+
# function stored in the task local storage.
442+
isempty(scl) || _scale!(x, /, scl)
443+
fw = ObjFun(f, n, scl) # wrapper to objective function
444+
fp = _objfun_ptr(newuoa) # pointer to C-callable function
445+
status = task_local_storage(:objfun, fw) do
446+
prima_newuoa(fp, n, x, fx, nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
455447
end
448+
isempty(scl) || _scale!(x, *, scl)
449+
return Info(; fx = fx[], nf = nf[], status)
456450
end
457451

458452
"""
@@ -479,19 +473,16 @@ function uobyqa!(f, x::DenseVector{Cdouble};
479473
fx = Ref{Cdouble}(NaN) # to store f(x) on return
480474
nf = Ref{Cint}(0) # to store number of function calls
481475

482-
# Create wrapper to objective function and push it on top of per-thread
483-
# stack before calling optimizer.
484-
fw = ObjFun(f, n, scl) # wrapper to objective function
485-
fp = _push_objfun(uobyqa, fw) # pointer to C-callable function
486-
try
487-
# Call low-level optimizer on the (scaled) variables.
488-
isempty(scl) || _scale!(x, /, scl)
489-
status = prima_uobyqa(fp, n, x, fx, nf, rhobeg, rhoend, ftarget, maxfun, iprint)
490-
isempty(scl) || _scale!(x, *, scl)
491-
return Info(; fx = fx[], nf = nf[], status)
492-
finally
493-
_pop_objfun(fw)
476+
# Call low-level optimizer on the scaled variables and with a wrapper to the objective
477+
# function stored in the task local storage.
478+
isempty(scl) || _scale!(x, /, scl)
479+
fw = ObjFun(f, n, scl) # wrapper to objective function
480+
fp = _objfun_ptr(uobyqa) # pointer to C-callable function
481+
status = task_local_storage(:objfun, fw) do
482+
prima_uobyqa(fp, n, x, fx, nf, rhobeg, rhoend, ftarget, maxfun, iprint)
494483
end
484+
isempty(scl) || _scale!(x, *, scl)
485+
return Info(; fx = fx[], nf = nf[], status)
495486
end
496487

497488
"""
@@ -535,37 +526,35 @@ function cobyla!(f, x::DenseVector{Cdouble};
535526
fx = Ref{Cdouble}(NaN) # to store f(x) on return
536527
nf = Ref{Cint}(0) # to store number of function calls
537528

538-
# Create wrapper to objective function and push it on top of per-thread
539-
# stack before calling optimizer.
529+
# Call low-level optimizer on the scaled variables and with a wrapper to the objective
530+
# function stored in the task local storage.
531+
isempty(scl) || _scale!(x, /, scl)
540532
fw = ObjFun(f, n, scl, c_nl_eq, length(nl_eq), c_nl_ineq, length(nl_ineq))
541-
fp = _push_objfun(cobyla, fw) # pointer to C-callable function
542-
try
543-
# Call low-level optimizer on the (scaled) variables.
544-
isempty(scl) || _scale!(x, /, scl)
545-
status = prima_cobyla(length(nl_all), fp, n, x, fx, cstrv, nl_all,
546-
length(b_ineq), A_ineq, b_ineq,
547-
length(b_eq), A_eq, b_eq,
548-
xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, iprint)
549-
isempty(scl) || _scale!(x, *, scl)
550-
# Unpack constraints.
551-
if length(nl_eq) > 0
552-
i = firstindex(nl_all)
553-
for j in eachindex(nl_eq)
554-
nl_eq[j] = nl_all[i]
555-
i += 2
556-
end
533+
fp = _objfun_ptr(cobyla) # pointer to C-callable function
534+
status = task_local_storage(:objfun, fw) do
535+
prima_cobyla(length(nl_all), fp, n, x, fx, cstrv, nl_all,
536+
length(b_ineq), A_ineq, b_ineq,
537+
length(b_eq), A_eq, b_eq,
538+
xl, xu, nf, rhobeg, rhoend, ftarget, maxfun, iprint)
539+
end
540+
isempty(scl) || _scale!(x, *, scl)
541+
542+
# Unpack constraints.
543+
if length(nl_eq) > 0
544+
i = firstindex(nl_all)
545+
for j in eachindex(nl_eq)
546+
nl_eq[j] = nl_all[i]
547+
i += 2
557548
end
558-
if length(nl_ineq) > 0
559-
i = firstindex(nl_all) + 2*length(nl_eq)
560-
for j in eachindex(nl_ineq)
561-
nl_ineq[j] = nl_all[i]
562-
i += 1
563-
end
549+
end
550+
if length(nl_ineq) > 0
551+
i = firstindex(nl_all) + 2*length(nl_eq)
552+
for j in eachindex(nl_ineq)
553+
nl_ineq[j] = nl_all[i]
554+
i += 1
564555
end
565-
return Info(; fx = fx[], nf = nf[], status, cstrv = cstrv[], nl_eq, nl_ineq)
566-
finally
567-
_pop_objfun(fw)
568556
end
557+
return Info(; fx = fx[], nf = nf[], status, cstrv = cstrv[], nl_eq, nl_ineq)
569558
end
570559

571560
"""
@@ -603,23 +592,20 @@ function lincoa!(f, x::DenseVector{Cdouble};
603592
fx = Ref{Cdouble}(NaN) # to store f(x) on return
604593
nf = Ref{Cint}(0) # to store number of function calls
605594

606-
# Create wrapper to objective function and push it on top of per-thread
607-
# stack before calling optimizer.
608-
fw = ObjFun(f, n, scl) # wrapper to objective function
609-
fp = _push_objfun(lincoa, fw) # pointer to C-callable function
610-
try
611-
# Call low-level optimizer on the (scaled) variables.
612-
isempty(scl) || _scale!(x, /, scl)
613-
status = prima_lincoa(fp, n, x, fx, cstrv,
614-
length(b_ineq), A_ineq, b_ineq,
615-
length(b_eq), A_eq, b_eq,
616-
xl, xu,
617-
nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
618-
isempty(scl) || _scale!(x, *, scl)
619-
return Info(; fx = fx[], nf = nf[], status, cstrv = cstrv[])
620-
finally
621-
_pop_objfun(fw)
595+
# Call low-level optimizer on the scaled variables and with a wrapper to the objective
596+
# function stored in the task local storage.
597+
isempty(scl) || _scale!(x, /, scl)
598+
fw = ObjFun(f, n, scl) # wrapper to objective function
599+
fp = _objfun_ptr(lincoa) # pointer to C-callable function
600+
status = task_local_storage(:objfun, fw) do
601+
prima_lincoa(fp, n, x, fx, cstrv,
602+
length(b_ineq), A_ineq, b_ineq,
603+
length(b_eq), A_eq, b_eq,
604+
xl, xu,
605+
nf, rhobeg, rhoend, ftarget, maxfun, npt, iprint)
622606
end
607+
isempty(scl) || _scale!(x, *, scl)
608+
return Info(; fx = fx[], nf = nf[], status, cstrv = cstrv[])
623609
end
624610

625611
#------------------------------------------------------------------------------
@@ -751,25 +737,24 @@ end
751737
# the type of the user-defined Julia functions and (ii) to make possible to
752738
# extend `call` or `call!` at the last stage .
753739

754-
# C-callable objective function for problems with no non-linear constraints
755-
# (for other algorithms than COBYLA).
740+
# C-callable objective function for problems with no non-linear constraints (for other
741+
# algorithms than COBYLA).
756742
function _objfun(x_ptr::Ptr{Cdouble}, # (input) variables
757743
f_ptr::Ptr{Cdouble}) # (output) function value
758-
# Retrieve objective function object and dispatch on its type to compute
759-
# f(x).
760-
f = last(_objfun_stack[Threads.threadid()])
744+
# Retrieve objective function object and dispatch on its type to compute f(x).
745+
f = task_local_storage(:objfun)
761746
unsafe_store!(f_ptr, unsafe_call(f, x_ptr))
762747
return nothing
763748
end
764749

765-
# C-callable objective function for problems with non-linear constraints (for
766-
# COBYLA algorithm).
750+
# C-callable objective function for problems with non-linear constraints (for COBYLA
751+
# algorithm).
767752
function _objfun(x_ptr::Ptr{Cdouble}, # (input) variables
768753
f_ptr::Ptr{Cdouble}, # (output) function value
769754
c_ptr::Ptr{Cdouble}) # (output) constraints
770-
# Retrieve objective function object and dispatch on its type to compute
771-
# f(x) and the non-linear constraints.
772-
f = last(_objfun_stack[Threads.threadid()])
755+
# Retrieve objective function object and dispatch on its type to compute f(x) and the
756+
# non-linear constraints.
757+
f = task_local_storage(:objfun)
773758
unsafe_store!(f_ptr, unsafe_call(f, x_ptr, c_ptr))
774759
return nothing
775760
end
@@ -807,42 +792,11 @@ end
807792
@noinline corrupted_structure(msg::AbstractString) =
808793
throw(AssertionError("corrupted structure ($msg)"))
809794

810-
# Global variable storing the per-thread stacks of objective functions indexed
811-
# by thread identifier and then by execution order. On start of an
812-
# optimization, an object linked to the user-defined objective function is
813-
# pushed. This object is popped out of the stack on return of the optimization
814-
# call, whatever happens. It is therefore necessary to wrap the call to the
815-
# optimization method in a `try-finally` clause.
816-
const _objfun_stack = Vector{Vector{ObjFun}}(undef, 0)
817-
818-
# Private function `_get_objfun_stack` yields the stack of objective functions
819-
# for the caller thread.
820-
function _get_objfun_stack()
821-
i = Threads.threadid()
822-
while length(_objfun_stack) < i
823-
push!(_objfun_stack, Vector{ObjFun}(undef, 0))
824-
end
825-
return _objfun_stack[i]
826-
end
827-
828-
# Private functions `_push_objfun` and `_pop_objfun` are to be used in a
829-
# `try-finally` clause as explained above.
830-
function _push_objfun(algorithm, fw::ObjFun)
831-
_objfun_ptr(::Any) =
832-
@cfunction(_objfun, Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}))
833-
_objfun_ptr(::typeof(cobyla)) =
834-
@cfunction(_objfun, Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}))
835-
push!(_get_objfun_stack(), fw)
836-
return _objfun_ptr(algorithm)
837-
end
838-
839-
function _pop_objfun(fw::ObjFun)
840-
stack = _get_objfun_stack()
841-
last(stack) === fw || error(
842-
"objective function is not the last one in the caller thread stask")
843-
resize!(stack, length(stack) - 1)
844-
return nothing
845-
end
795+
# Pointer to C callable function.
796+
_objfun_ptr(::Any) =
797+
@cfunction(_objfun, Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}))
798+
_objfun_ptr(::typeof(cobyla)) =
799+
@cfunction(_objfun, Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}))
846800

847801
function _check_rho(rhobeg, rhoend)
848802
isfinite(rhobeg) && rhobeg > zero(rhobeg) || throw(ArgumentError(

0 commit comments

Comments
 (0)