Skip to content

Commit

Permalink
Tag new release for ConstraintExplorer.jl (#61)
Browse files Browse the repository at this point in the history
* Fixes for tests

* Fix format

* Update Project.toml

* Make an Explorer to prepare a JuMP interface and broader search space exploration (#60)

* New Explorer structure

* Makes the explorer adaptive

* Updates for ConstraintExplorer

* Fix in explore

* Format

* Update ci.yml

* Update Project.toml
  • Loading branch information
Azzaare authored Oct 4, 2024
1 parent 7e8f095 commit cf4fe62
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 31 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ jobs:
fail-fast: false
matrix:
version:
- "1.8"
- "1" # automatically expands to the latest stable 1.x release of Julia
- "pre"
os:
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ConstraintDomains"
uuid = "5800fd60-8556-4464-8d61-84ebf7a0bedb"
authors = ["Jean-François Baffier"]
version = "0.3.13"
version = "0.3.14"

[deps]
ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954"
Expand All @@ -16,7 +16,7 @@ Intervals = "1"
PatternFolds = "0.2"
StatsBase = "0.34"
TestItems = "0.1, 1"
julia = "1.8"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
5 changes: 2 additions & 3 deletions src/ConstraintDomains.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module ConstraintDomains

# SECTION - Imports
import ConstraintCommons: ConstraintCommons, δ_extrema
# import Dictionaries
import PatternFolds: PatternFolds, Interval, Closed
import StatsBase: sample
import TestItems: @testitem
Expand All @@ -11,15 +10,15 @@ import TestItems: @testitem
export AbstractDomain
export ContinuousDomain
export DiscreteDomain
export ExploreSettings
export Explorer, ExploreSettings
export RangeDomain
export SetDomain

export add!
export delete!
export domain
export domain_size
export explore
export explore, explore!
export generate_parameters
export get_domain
export intersect_domains
Expand Down
3 changes: 3 additions & 0 deletions src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ function Base.string(D::Vector{<:AbstractDomain})
end
Base.string(d::AbstractDomain) = replace(string(d.domain), " " => "")

merge_domains(::EmptyDomain, d::D) where {D<:AbstractDomain} = d
merge_domains(d::D, ::EmptyDomain) where {D<:AbstractDomain} = d

## SECTION - Test Items
@testitem "EmptyDomain" tags = [:domains, :empty] begin
ed = domain()
Expand Down
1 change: 0 additions & 1 deletion src/continuous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ function intersect_domains(
return Intervals(new_itvls)
end


"""
Base.size(i::I) where {I <: Interval}
Expand Down
139 changes: 116 additions & 23 deletions src/explore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,137 @@ Create an `ExploreSettings` object to configure the exploration of a search spac
function ExploreSettings(
domains;
complete_search_limit = 10^6,
max_samplings = sum(domain_size, domains),
max_samplings = sum(domain_size, domains; init = 0),
search = :flexible,
solutions_limit = floor(Int, sqrt(max_samplings)),
)
return ExploreSettings(complete_search_limit, max_samplings, search, solutions_limit)
end

struct ExplorerState{T}
best::Vector{T}
solutions::Set{Vector{T}}
non_solutions::Set{Vector{T}}

ExplorerState{T}() where {T} = new{T}([], Set{Vector{T}}(), Set{Vector{T}}())
end

ExplorerState(domains) = ExplorerState{Union{map(eltype, domains)...}}()

mutable struct Explorer{F1<:Function,D<:AbstractDomain,F2<:Union{Function,Nothing},T}
concepts::Dict{Int,Tuple{F1,Vector{Int}}}
domains::Dict{Int,D}
objective::F2
settings::ExploreSettings
state::ExplorerState{T}

function Explorer(
concepts,
domains,
objective = nothing;
settings = ExploreSettings(domains),
)
F1 = isempty(concepts) ? Function : Union{map(c -> typeof(c[1]), concepts)...}
D = isempty(domains) ? AbstractDomain : Union{map(typeof, domains)...}
F2 = typeof(objective)
T = isempty(domains) ? Real : Union{map(eltype, domains)...}
d_c = Dict(enumerate(concepts))
d_d = Dict(enumerate(domains))
return new{F1,D,F2,T}(d_c, d_d, objective, settings, ExplorerState{T}())
end
end

function Explorer()
concepts = Vector{Tuple{Function,Vector{Int}}}()
domains = Vector{AbstractDomain}()
objective = nothing
settings = ExploreSettings(domains)
return Explorer(concepts, domains, objective; settings)
end

function Base.push!(explorer::Explorer, concept::Tuple{Function,Vector{Int}})
max_key = maximum(keys(explorer.concepts); init = 0)
explorer.concepts[max_key+1] = concept
return max_key + 1
end

function delete_concept!(explorer::Explorer, key::Int)
delete!(explorer.concepts, key)
return nothing
end

function Base.push!(explorer::Explorer, domain::AbstractDomain)
max_key = maximum(keys(explorer.domains); init = 0)
explorer.domains[max_key+1] = domain
return max_key + 1
end

function delete_domain!(explorer::Explorer, key::Int)
delete!(explorer.domains, key)
return nothing
end

set!(explorer::Explorer, objective::Function) = explorer.objective = objective

function update_exploration!(explorer, f, c, search = explorer.settings.search)
solutions = explorer.state.solutions
non_sltns = explorer.state.non_solutions
obj = explorer.objective
sl = search == :complete ? Inf : explorer.settings.solutions_limit

cv = collect(c)
if f(cv)
if length(solutions) < sl
push!(solutions, cv)
obj !== nothing && (explorer.state.best = argmin(obj, solutions))
end
else
if length(non_sltns) < sl
push!(non_sltns, cv)
end
end
return nothing
end

"""
_explore(args...)
Internals of the `explore` function. Behavior is automatically adjusted on the kind of exploration: `:flexible`, `:complete`, `:partial`.
"""
function _explore(domains, f, s, ::Val{:partial})
solutions = Set{Vector{Int}}()
non_sltns = Set{Vector{Int}}()
function _explore!(explorer, f, ::Val{:partial})
sl = explorer.settings.solutions_limit
ms = explorer.settings.max_samplings

sl = s.solutions_limit
solutions = explorer.state.solutions
non_sltns = explorer.state.non_solutions
domains = explorer.domains |> values

for _ = 1:s.max_samplings
for _ = 1:ms
length(solutions) sl && length(non_sltns) sl && break
config = map(rand, domains)
c = f(config) ? solutions : non_sltns
length(c) < sl && push!(c, config)
update_exploration!(explorer, f, config)
end
return solutions, non_sltns
return nothing
end

function _explore(domains, f, ::ExploreSettings, ::Val{:complete})
solutions = Set{Vector{Int}}()
non_sltns = Set{Vector{Int}}()

configurations = Base.Iterators.product(map(d -> get_domain(d), domains)...)
foreach(
c -> (cv = collect(c); push!(f(cv) ? solutions : non_sltns, cv)),
configurations,
)
return solutions, non_sltns
function _explore!(explorer, f, ::Val{:complete})
C = Base.Iterators.product(map(d -> get_domain(d), explorer.domains |> values)...)
foreach(c -> update_exploration!(explorer, f, c, :complete), C)
return nothing
end

function _explore(domains, f, s, ::Val{:flexible})
search = s.max_samplings < s.complete_search_limit ? :complete : :partial
return _explore(domains, f, s, Val(search))
function explore!(explorer::Explorer)
c =
x -> all([
f(isempty(vars) ? x : @view x[vars]) for
(f, vars) in explorer.concepts |> values
])
s = explorer.settings
search = s.search
if search == :flexible
search = s.max_samplings < s.complete_search_limit ? :complete : :partial
end
return _explore!(explorer, c, Val(search))
end

"""
Expand All @@ -87,7 +176,9 @@ Search (a part of) a search space and return a pair of vectors of configurations
"""
function explore(domains, concept; settings = ExploreSettings(domains), parameters...)
f = x -> concept(x; parameters...)
return _explore(domains, f, settings, Val(settings.search))
explorer = Explorer([(f, Vector{Int}())], domains; settings)
explore!(explorer)
return explorer.state.solutions, explorer.state.non_solutions
end

## SECTION - Test Items
Expand All @@ -96,4 +187,6 @@ end
X, X̅ = explore(domains, allunique)
@test length(X) == factorial(4)
@test length(X̅) == 4^4 - factorial(4)

explorer = ConstraintDomains.Explorer()
end
2 changes: 1 addition & 1 deletion src/general.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function Base.convert(::Type{Intervals}, d::RangeDomain{T}) where {T<:Real}
return domain(Interval{T,Closed,Closed}(a, b))
end

function Base.convert(::Type{RangeDomain}, d::Intervals{T}) where {T<:Real}
function Base.convert(::Type{RangeDomain}, d::Intervals)
i = get_domain(d)[1]
a = Int(i.first)
b = Int(i.last)
Expand Down

0 comments on commit cf4fe62

Please sign in to comment.