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

Parameter Set Constraints #8

Open
dysonance opened this issue Oct 27, 2017 · 3 comments
Open

Parameter Set Constraints #8

dysonance opened this issue Oct 27, 2017 · 3 comments

Comments

@dysonance
Copy link
Owner

When defining a ParameterSet object with ranges corresponding to each of its variables (i.e. the arg_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.

@femtotrader
Copy link
Collaborator

femtotrader commented Oct 27, 2017

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

@femtotrader
Copy link
Collaborator

femtotrader commented Oct 27, 2017

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

@femtotrader
Copy link
Collaborator

femtotrader commented Apr 24, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants