Open dysonance opened 6 years ago
What your are looking for looks like a CSP solver
With Python, there is
I don't know if there is project like this written in Julia.
I also wonder if https://github.com/JuliaOpt/JuMP.jl have such feature (or more generally https://github.com/JuliaOpt )
See http://nbviewer.jupyter.org/github/JuliaOpt/juliaopt-notebooks/tree/master/notebooks/ for some sample notebooks
python-constraint is "only" 1500 lines of code
A simpler approach with Channel
and for
loops
with Tuple
for parameters
parameter_generator() = Channel(ctype=Tuple{Int,Int}) do c
for short in 1:10
for long in 1:10
if short < long
push!(c, (short, long))
end
end
end
end
for (i, param) in enumerate(parameter_generator())
short, long = param
println("i=$i short=$short long=$long")
end
or with Dict
for parameters
parameter_generator() = Channel(ctype=Dict{String,Int}) do c
for short in 1:10
for long in 1:10
if short < long
push!(c, Dict("short"=>short, "long"=>long))
end
end
end
end
for (i, param) in enumerate(parameter_generator())
short = param["short"]
long = param["long"]
println("i=$i short=$short long=$long")
end
it displays:
i=1 short=1 long=2
i=2 short=1 long=3
i=3 short=1 long=4
i=4 short=1 long=5
i=5 short=1 long=6
i=6 short=1 long=7
i=7 short=1 long=8
i=8 short=1 long=9
i=9 short=1 long=10
i=10 short=2 long=3
i=11 short=2 long=4
i=12 short=2 long=5
i=13 short=2 long=6
i=14 short=2 long=7
i=15 short=2 long=8
i=16 short=2 long=9
i=17 short=2 long=10
i=18 short=3 long=4
i=19 short=3 long=5
i=20 short=3 long=6
i=21 short=3 long=7
i=22 short=3 long=8
i=23 short=3 long=9
i=24 short=3 long=10
i=25 short=4 long=5
i=26 short=4 long=6
i=27 short=4 long=7
i=28 short=4 long=8
i=29 short=4 long=9
i=30 short=4 long=10
i=31 short=5 long=6
i=32 short=5 long=7
i=33 short=5 long=8
i=34 short=5 long=9
i=35 short=5 long=10
i=36 short=6 long=7
i=37 short=6 long=8
i=38 short=6 long=9
i=39 short=6 long=10
i=40 short=7 long=8
i=41 short=7 long=9
i=42 short=7 long=10
i=43 short=8 long=9
i=44 short=8 long=10
i=45 short=9 long=10
Using NamedTuples could also be considered
Code updated with NamedTuples (from https://github.com/JuliaData/NamedTuples.jl )
mutable struct ParameterSet
arg_names::Vector{Symbol}
arg_defaults::Vector
arg_ranges::Vector
arg_types::Vector{<:Type}
n_args::Int
constraints::Vector{Function}
function ParameterSet(arg_names::Vector{Symbol},
arg_defaults::Vector,
arg_ranges::Vector=[x:x for x in arg_defaults],
constraints=Vector{Function}())
@assert length(arg_names) == length(arg_defaults) == length(arg_ranges)
@assert eltype.(arg_defaults) == eltype.(arg_ranges)
arg_types::Vector{<:Type} = eltype.(arg_defaults)
return new(arg_names, arg_defaults, arg_ranges, arg_types, length(arg_names), constraints)
end
end
import Base: length
length(ps::ParameterSet) = mapreduce(length, *, 1, ps.arg_ranges) # inspired by IterTools.jl length(p::Product)
abstract type ParameterIteration end
struct CartesianProductIteration <: ParameterIteration
end
using IterTools
using NamedTuples: make_tuple
function get_iterator(ps::ParameterSet, method::CartesianProductIteration)
itr = IterTools.product(ps.arg_ranges...)
itr = map(t->make_tuple(ps.arg_names)(t...), itr)
#itr = map(t->NamedTuple{Tuple(ps.arg_names)}(t...), itr) # see https://github.com/JuliaData/NamedTuples.jl/issues/53
for constraint in ps.constraints
itr = Iterators.filter(constraint, itr)
end
itr
end
using Base.Test
# write your own tests here
@testset "Main tests" begin
arg_names = [:real1, :real2]
arg_defaults = [0.5, 0.05]
arg_ranges = [0.01:0.01:0.99, 0.01:0.01:0.99]
#constraints = [
# p -> p[1] + p[2] < 1.0,
# p -> p[1] + p[2] > 0.5
#]
constraints = [
p -> p.real1 + p.real2 < 1.0,
p -> p.real1 + p.real2 > 0.5
]
ps = ParameterSet(arg_names, arg_defaults, arg_ranges, constraints)
@test length(ps) == 9801 # 99*99
itr_method = CartesianProductIteration()
#itr_method = RandomIteration()
itr = get_iterator(ps, itr_method)
#for p in itr
# println(p)
#end
v = collect(itr)
#println(v)
@test length(v) == 3626
end
@testset "Other test" begin
arg_names = [:short, :long]
arg_defaults = [1, 1]
arg_ranges = [1:10, 1:10]
#constraints = [
# p -> p[1] < p[2]
#]
constraints = [
p -> p.short < p.long
]
ps = ParameterSet(arg_names, arg_defaults, arg_ranges, constraints)
@test length(ps) == 100 # 10*10
itr_method = CartesianProductIteration()
#itr_method = RandomIteration()
itr = get_iterator(ps, itr_method)
v = collect(itr)
@test length(v) == 45
end
When defining a
ParameterSet
object with ranges corresponding to each of its variables (i.e. thearg_ranges
member), currently there isn't any way to constrain the parameter space using the relationships between the variables.There should be logic to support these kinds of inter-variable relationship constraints. For example, take a simple strategy using two moving averages, one with a short lookback period and one with a long lookback period. Suppose our rule is to go long when the short MA crosses over the long MA, and we want to simulate this strategy over a bunch of pairs of lookback windows. There should be logic to constrain the simulation such that the short MA's lookback must be less than the long MA's lookback.