Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Oct 4, 2024
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
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
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 @@ -19,48 +19,137 @@ Settings for the exploration of a search space composed by a collection of domai
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 @@ -78,7 +167,9 @@ Beware that if the density of the solutions in the search space is low, `solutio
"""
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 @@ -87,4 +178,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
Loading