Skip to content

Commit

Permalink
Merge pull request #23 from manuelbb-upb/concurrent_optional
Browse files Browse the repository at this point in the history
Concurrent optional
  • Loading branch information
manuelbb-upb authored Apr 5, 2024
2 parents b7c568e + 8f724b4 commit b33fa32
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 90 deletions.
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
name = "Compromise"
uuid = "254bc946-86ae-484f-a9da-8147cb79ba93"
authors = ["Manuel Berkemeier <[email protected]> and contributors"]
version = "0.0.3"
version = "0.1.0"

[deps]
ConcurrentUtils = "3df5f688-6c4c-4767-8685-17f5ad261477"
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
ElasticArrays = "fdbdab4c-e67f-52f5-8c3f-e7b388dad3d4"
Expand All @@ -23,20 +22,23 @@ StridedViews = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143"
StructHelpers = "4093c41a-2008-41fd-82b8-e3f9d02b504f"

[weakdeps]
ConcurrentUtils = "3df5f688-6c4c-4767-8685-17f5ad261477"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"

[extensions]
ForwardDiffBackendExt = "ForwardDiff"
ConcurrentRWLockExt = "ConcurrentUtils"

[compat]
julia = "1"

[extras]
ConcurrentUtils = "3df5f688-6c4c-4767-8685-17f5ad261477"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["SafeTestsets", "Test", "ForwardDiff", "Random", "LinearAlgebra"]
test = ["SafeTestsets", "Test", "ConcurrentUtils", "ForwardDiff", "Random", "LinearAlgebra"]
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ constraints are violated.
I don't really keep up a consistent versioning scheme.
But the changes in this section have been significant enough to warrant some comments.

#### Version 0.1.0
This release is breaking, because the the RBF database is no longer thread-safe by default.
Instead, `ConcurrentUtils` is a weak dependency and no longer mandatory.
To use a thread-safe RBF database, either configure your problem functions
with `:rbfLocked`, use an `RBFConfig` with
`database_rwlock = ConcurrentRWLock()`
or pre-initialize a thread-safe database by setting the field `rwlock`.

#### Version 0.0.3
Internally, there have been major changes regarding the caching of MOP and surrogate result values.
Previously, separate preallocation functions were required (e.g., `prealloc_fx` …).
Expand Down Expand Up @@ -274,6 +282,11 @@ multiple optimization runs are done concurrently.
There even is an “algorithm” for this:

````julia
using ConcurrentUtils
mop =MutableMOP(; num_vars=2)
add_objectives!(
mop, counted_objf, :rbfLocked; dim_out=2, func_iip=false,
)
X0 = [
-2.0 -2.0 0.0
0.5 0.0 0.0
Expand Down
13 changes: 13 additions & 0 deletions docs/literate_src/README.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ constraints are violated.
I don't really keep up a consistent versioning scheme.
But the changes in this section have been significant enough to warrant some comments.
#### Version 0.1.0
This release is breaking, because the the RBF database is no longer thread-safe by default.
Instead, `ConcurrentUtils` is a weak dependency and no longer mandatory.
To use a thread-safe RBF database, either configure your problem functions
with `:rbfLocked`, use an `RBFConfig` with
`database_rwlock = ConcurrentRWLock()`
or pre-initialize a thread-safe database by setting the field `rwlock`.
#### Version 0.0.3
Internally, there have been major changes regarding the caching of MOP and surrogate result values.
Previously, separate preallocation functions were required (e.g., `prealloc_fx` …).
Expand Down Expand Up @@ -219,6 +227,11 @@ objf_counter[]
# The RBF update algorithm has a lock to access the database in a safe way (?) when
# multiple optimization runs are done concurrently.
# There even is an “algorithm” for this:
using ConcurrentUtils
mop =MutableMOP(; num_vars=2)
add_objectives!(
mop, counted_objf, :rbfLocked; dim_out=2, func_iip=false,
)
X0 = [
-2.0 -2.0 0.0
0.5 0.0 0.0
Expand Down
13 changes: 13 additions & 0 deletions docs/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ constraints are violated.
I don't really keep up a consistent versioning scheme.
But the changes in this section have been significant enough to warrant some comments.

#### Version 0.1.0
This release is breaking, because the the RBF database is no longer thread-safe by default.
Instead, `ConcurrentUtils` is a weak dependency and no longer mandatory.
To use a thread-safe RBF database, either configure your problem functions
with `:rbfLocked`, use an `RBFConfig` with
`database_rwlock = ConcurrentRWLock()`
or pre-initialize a thread-safe database by setting the field `rwlock`.

#### Version 0.0.3
Internally, there have been major changes regarding the caching of MOP and surrogate result values.
Previously, separate preallocation functions were required (e.g., `prealloc_fx` …).
Expand Down Expand Up @@ -278,6 +286,11 @@ multiple optimization runs are done concurrently.
There even is an “algorithm” for this:

````@example README
using ConcurrentUtils
mop = MutableMOP(; num_vars=2)
add_objectives!(
mop, counted_objf, :rbfLocked; dim_out=2, func_iip=false,
)
X0 = [
-2.0 -2.0 0.0
0.5 0.0 0.0
Expand Down
30 changes: 30 additions & 0 deletions ext/ConcurrentRWLockExt/ConcurrentRWLockExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module ConcurrentRWLockExt

import Compromise as C
import Compromise: AbstractReadWriteLock, lock_write, unlock_write, lock_read, unlock_read, init_rw_lock

if isdefined(Base, :get_extension)
import ConcurrentUtils as CU
import ConcurrentUtils: ReadWriteLock
else
import ..ConcurrentUtils as CU
import ..ConcurrentUtils: ReadWriteLock
end

struct ConcurrentRWLock <: AbstractReadWriteLock
wrapped :: ReadWriteLock
end
ConcurrentRWLock() =ConcurrentRWLock(ReadWriteLock())

lock_read(l::ConcurrentRWLock)=CU.lock_read(l.wrapped)
lock_read(@nospecialize(f), l::ConcurrentRWLock)=CU.lock_read(f, l.wrapped)
unlock_read(l::ConcurrentRWLock)=CU.unlock_read(l.wrapped)

lock_write(l::ConcurrentRWLock)=lock(l.wrapped)
lock_write(@nospecialize(f), l::ConcurrentRWLock)=lock(f, l.wrapped)
unlock_write(l::ConcurrentRWLock)=unlock(l.wrapped)

init_rw_lock(::Type{ConcurrentRWLock})=ConcurrentRWLock(ReadWriteLock())
init_rw_lock(::Type{ReadWriteLock})=ConcurrentRWLock(ReadWriteLock())

end
18 changes: 17 additions & 1 deletion src/Compromise.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import StructHelpers: @batteries
include("value_caches.jl")
include("types.jl")
include("utils.jl")
include("concurrent_locks.jl")

# #### Optimization Packages
# At some point, the choice of solver is meant to be configurable, with different
Expand Down Expand Up @@ -78,6 +79,10 @@ using Requires
include("../ext/ForwardDiffBackendExt/ForwardDiffBackendExt.jl")
import .ForwardDiffBackendExt
end
@require ConcurrentUtils = "3df5f688-6c4c-4767-8685-17f5ad261477" begin
include("../ext/ConcurrentRWLockExt/ConcurrentRWLockExt.jl")
import .ConcurrentRWLockExt
end
end
end

Expand All @@ -94,7 +99,18 @@ function ForwardDiffBackend()
return isnothing(m) ? m : m.ForwardDiffBackend()
end
end
export ForwardDiffBackend
function ConcurrentRWLock()
if !isdefined(Base, :get_extension)
if isdefined(@__MODULE__, :ConcurrentRWLockExt)
return ConcurrentRWLockExt.ConcurrentRWLock()
end
return nothing
else
m = Base.get_extension(@__MODULE__, :ConcurrentRWLockExt)
return isnothing(m) ? m : m.ConcurrentRWLock()
end
end
export ForwardDiffBackend, ConcurrentRWLock

# Import Radial Basis Function surrogates:
include("evaluators/RBFModels/RBFModels.jl")
Expand Down
22 changes: 22 additions & 0 deletions src/concurrent_locks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
abstract type AbstractReadWriteLock end

lock_write(::AbstractReadWriteLock)=nothing
lock_write(@nospecialize(func), ::AbstractReadWriteLock)=func()
unlock_write(::AbstractReadWriteLock)=nothing
lock_read(::AbstractReadWriteLock)=nothing
lock_read(@nospecialize(func), ::AbstractReadWriteLock)=func()
unlock_read(::AbstractReadWriteLock)=nothing

function init_rw_lock(rwtype)
error("'init_rw_lock` not defined for $(rwtype)")
end

struct PseudoRWLock <: AbstractReadWriteLock end

function init_rw_lock(rwtype::Type{PseudoRWLock})
return PseudoRWLock()
end

function default_rw_lock()
return init_rw_lock(PseudoRWLock)
end
6 changes: 3 additions & 3 deletions src/evaluators/RBFModels/RBFModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module RBFModels
using ..Compromise.CompromiseEvaluators
const CE = CompromiseEvaluators
import ..Compromise: @ignoraise, DEFAULT_FLOAT_TYPE, project_into_box!
import ..Compromise: AbstractReadWriteLock, default_rw_lock, lock_read, unlock_read, lock_write
import ..Compromise: subscript, supscript, pretty_row_vec, RVec
import ..Compromise: trust_region_bounds!, intersect_box, AbstractStoppingCriterion, stop_message
import Printf: @sprintf
Expand All @@ -12,7 +13,6 @@ using ElasticArrays: resize! # explicit import to avoid false linter hints
import LinearAlgebra as LA
using Parameters: @with_kw, @unpack
using StructHelpers: @batteries
import ConcurrentUtils: ReadWriteLock, lock_read, unlock_read
import Logging: @logmsg, Info

struct RBFConstructionImpossible <: AbstractStoppingCriterion end
Expand Down Expand Up @@ -99,15 +99,15 @@ function CE.init_surrogate(
)
@unpack (
kernel, search_factor, max_search_factor, th_qr, th_cholesky, max_points,
database, database_size, database_chunk_size, enforce_fully_linear, poly_deg,
database, database_rwlock, database_size, database_chunk_size, enforce_fully_linear, poly_deg,
shape_parameter_function, sampling_factor, max_sampling_factor,
) = cfg
if require_fully_linear
enforce_fully_linear = require_fully_linear
end
return rbf_init_model(
dim_in, dim_out, poly_deg, delta_max, kernel, shape_parameter_function,
database, database_size, database_chunk_size, max_points, enforce_fully_linear,
database, database_rwlock, database_size, database_chunk_size, max_points, enforce_fully_linear,
search_factor, max_search_factor, sampling_factor, max_sampling_factor,
th_qr, th_cholesky, T
)
Expand Down
15 changes: 10 additions & 5 deletions src/evaluators/RBFModels/database.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ### Sample Database
Base.@kwdef struct RBFDatabase{T<:Number}
Base.@kwdef struct RBFDatabase{T<:Number, RWLType<:AbstractReadWriteLock}
dim_x :: Int
dim_y :: Int

Expand All @@ -13,7 +13,7 @@ Base.@kwdef struct RBFDatabase{T<:Number}
current_size :: Base.RefValue{Int}
state :: MutableNumber{UInt64}

rwlock :: ReadWriteLock
rwlock :: RWLType = default_rw_lock()
end

function db_current_size(db)
Expand Down Expand Up @@ -55,7 +55,8 @@ function filtered_view_y(db::RBFDatabase, flags)
end

function init_rbf_database(
rbf_cfg, dim_x, dim_y, T=DEFAULT_FLOAT_TYPE
rbf_cfg, dim_x, dim_y, T=DEFAULT_FLOAT_TYPE,
rwlock::AbstractReadWriteLock=default_rw_lock()
)
@unpack database_size, database_chunk_size = rbf_cfg
return init_rbf_database(dim_x, dim_y, database_size, database_chunk_size, T)
Expand All @@ -65,7 +66,8 @@ function init_rbf_database(
dim_x::Integer, dim_y::Integer,
database_size::Union{Nothing,Integer},
database_chunk_size::Union{Nothing, Integer},
::Type{T}=DEFAULT_FLOAT_TYPE
::Type{T}=DEFAULT_FLOAT_TYPE,
rwlock::Union{Nothing,AbstractReadWriteLock}=nothing
) where {T<:Number}
min_points = dim_x + 1 :: Integer

Expand Down Expand Up @@ -102,8 +104,11 @@ function init_rbf_database(
flags_y = zeros(Int, chunk_size)

state = MutableNumber(zero(UInt64))
rwlock =ReadWriteLock()
if isnothing(rwlock)
rwlock =default_rw_lock()
end
current_size = Ref(chunk_size)

return RBFDatabase(;
dim_x, dim_y, max_size, chunk_size, x, y,
flags_x, flags_y, state, rwlock, current_size
Expand Down
5 changes: 4 additions & 1 deletion src/evaluators/RBFModels/frontend_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
max_points :: Union{Int, Nothing} = nothing

database :: Union{Nothing, RBFDatabase} =nothing
database_rwlock :: Union{Nothing, AbstractReadWriteLock}=nothing
database_size :: Union{Int, Nothing} = nothing
database_chunk_size :: Union{Int, Nothing} = nothing

Expand Down Expand Up @@ -388,6 +389,7 @@ function rbf_init_model(
kernel :: AbstractRBFKernel,
shape_parameter_function :: Union{Nothing, Number, Function},
database :: Union{Nothing, RBFDatabase},
database_rwlock :: Union{Nothing, AbstractReadWriteLock},
database_size :: Union{Nothing, Integer},
database_chunk_size :: Union{Nothing, Integer},
max_points :: Union{Nothing, Integer},
Expand All @@ -413,7 +415,8 @@ function rbf_init_model(
surrogate = RBFSurrogate(; dim_x, dim_y, kernel, poly_deg, dim_φ=-1)

if isnothing(database) || database.dim_x != dim_x || database.dim_y != dim_y
database = init_rbf_database(dim_x, dim_y, database_size, database_chunk_size, T)
database = init_rbf_database(
dim_x, dim_y, database_size, database_chunk_size, T, database_rwlock)
end

@unpack poly_deg, dim_π = surrogate
Expand Down
6 changes: 3 additions & 3 deletions src/evaluators/RBFModels/update.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function CE.process_trial_point!(
if !is_next
@unpack params, database = rbf
if params.xtrial != xtrial
lock(database.rwlock) do
lock_write(database.rwlock) do
add_to_database!(database, xtrial, fxtrial)
end
params.xtrial .= xtrial
Expand Down Expand Up @@ -71,7 +71,7 @@ function update_rbf_model!(
n_X_affine_sampling, n_X
end

new_db_state =lock(rwlock) do
new_db_state =lock_write(rwlock) do
put_new_evals_into_db!(rbf, x0, n_X_affine_sampling, buffers.xZ)
db_state(database)
end
Expand All @@ -94,7 +94,7 @@ function update_rbf_model!(

val!(params.n_X_ref, n_X)
val!(buffers.x0_db_index_ref, buffers.db_index[1])
val!(params.database_state_ref, db_state(database))
val!(params.database_state_ref, new_db_state)

return nothing
end
Expand Down
8 changes: 8 additions & 0 deletions src/simple_mop.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ parse_mcfg(::Val{:exact})=ExactModelConfig()
parse_mcfg(::Val{:rbf})=RBFConfig()
parse_mcfg(::Val{:taylor1})=TaylorPolynomialConfig(;degree=1)
parse_mcfg(::Val{:taylor2})=TaylorPolynomialConfig(;degree=2)
function parse_mcfg(::Val{:rbfLocked})
database_rwlock = Compromise.ConcurrentRWLock()
if isnothing(database_rwlock)
error("Cannot add RBF using read-write-lock without `ConcurrentUtils`.")
end
cfg = RBFConfig(; database_rwlock)
return cfg
end
# The default value `nothing` redirects to an `ExactModelConfig`:
parse_mcfg(::Nothing)=parse_mcfg(:exact)

Expand Down
6 changes: 3 additions & 3 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,10 @@ in the main loop.
=#

struct ReturnObject{X, V, S, M}
ξ0 ::X
vals ::V
ξ0 :: X
vals :: V
stop_code :: S
mod ::M
mod :: M
end

opt_surrogate(r::ReturnObject) = r.mod
Expand Down
Loading

0 comments on commit b33fa32

Please sign in to comment.