|
| 1 | +struct ExploreSettings |
| 2 | + complete_search_limit::Int |
| 3 | + max_samplings::Int |
| 4 | + search::Symbol |
| 5 | + solutions_limit::Int |
| 6 | +end |
| 7 | + |
| 8 | +""" |
| 9 | + ExploreSettings( |
| 10 | + domains; |
| 11 | + complete_search_limit = 10^6, |
| 12 | + max_samplings = sum(domain_size, domains), |
| 13 | + search = :flexible, |
| 14 | + solutions_limit = floor(Int, sqrt(max_samplings)), |
| 15 | + ) |
| 16 | +
|
| 17 | +Settings for the exploration of a search space composed by a collection of domains. |
| 18 | +""" |
| 19 | +function ExploreSettings( |
| 20 | + domains; |
| 21 | + complete_search_limit=10^6, |
| 22 | + max_samplings=sum(domain_size, domains), |
| 23 | + search=:flexible, |
| 24 | + solutions_limit=floor(Int, sqrt(max_samplings)), |
| 25 | +) |
| 26 | + return ExploreSettings(complete_search_limit, max_samplings, search, solutions_limit) |
| 27 | +end |
| 28 | + |
| 29 | +""" |
| 30 | + _explore(args...) |
| 31 | +
|
| 32 | +Internals of the `explore` function. Behavior is automatically adjusted on the kind of exploration: `:flexible`, `:complete`, `:partial`. |
| 33 | +""" |
| 34 | +function _explore(domains, f, s, ::Val{:partial}) |
| 35 | + solutions = Set{Vector{Int}}() |
| 36 | + non_sltns = Set{Vector{Int}}() |
| 37 | + |
| 38 | + sl = s.solutions_limit |
| 39 | + |
| 40 | + for _ = 1:s.max_samplings |
| 41 | + length(solutions) ≥ sl && length(non_sltns) ≥ sl && break |
| 42 | + config = map(rand, domains) |
| 43 | + c = f(config) ? solutions : non_sltns |
| 44 | + length(c) < sl && push!(c, config) |
| 45 | + end |
| 46 | + return solutions, non_sltns |
| 47 | +end |
| 48 | + |
| 49 | +function _explore(domains, f, ::ExploreSettings, ::Val{:complete}) |
| 50 | + solutions = Set{Vector{Int}}() |
| 51 | + non_sltns = Set{Vector{Int}}() |
| 52 | + |
| 53 | + configurations = Base.Iterators.product(map(d -> get_domain(d), domains)...) |
| 54 | + foreach( |
| 55 | + c -> (cv = collect(c); push!(f(cv) ? solutions : non_sltns, cv)), |
| 56 | + configurations, |
| 57 | + ) |
| 58 | + return solutions, non_sltns |
| 59 | +end |
| 60 | + |
| 61 | +function _explore(domains, f, s, ::Val{:flexible}) |
| 62 | + search = s.max_samplings < s.complete_search_limit ? :complete : :partial |
| 63 | + return _explore(domains, f, s, Val(search)) |
| 64 | +end |
| 65 | + |
| 66 | +""" |
| 67 | + explore(domains, concept, param = nothing; search_limit = 1000, solutions_limit = 100) |
| 68 | +
|
| 69 | +Search (a part of) a search space and returns a pair of vector of configurations: `(solutions, non_solutions)`. If the search space size is over `search_limit`, then both `solutions` and `non_solutions` are limited to `solutions_limit`. |
| 70 | +
|
| 71 | +Beware that if the density of the solutions in the search space is low, `solutions_limit` needs to be reduced. This process will be automatic in the future (simple reinforcement learning). |
| 72 | +
|
| 73 | +# Arguments: |
| 74 | +- `domains`: a collection of domains |
| 75 | +- `concept`: the concept of the targeted constraint |
| 76 | +- `param`: an optional parameter of the constraint |
| 77 | +- `sol_number`: the required number of solutions (half of the number of configurations), default to `100` |
| 78 | +""" |
| 79 | +function explore(domains, concept; settings=ExploreSettings(domains), parameters...) |
| 80 | + f = x -> concept(x; parameters...) |
| 81 | + return _explore(domains, f, settings, Val(settings.search)) |
| 82 | +end |
| 83 | + |
| 84 | +## SECTION - Test Items |
| 85 | +@testitem "Exploration" tags = [:exploration] begin |
| 86 | + domains = [domain([1, 2, 3, 4]) for i = 1:4] |
| 87 | + X, X̅ = explore(domains, allunique) |
| 88 | + @test length(X) == factorial(4) |
| 89 | + @test length(X̅) == 4^4 - factorial(4) |
| 90 | +end |
0 commit comments