diff --git a/src/individual.jl b/src/individual.jl index 00c4a19..4b399ce 100644 --- a/src/individual.jl +++ b/src/individual.jl @@ -1,4 +1,4 @@ -export Node, CGPInd, get_genes, set_genes!, reset!, forward_connections, get_output_trace +export Node, CGPInd, get_genes, set_genes!, reset!, forward_connections, get_output_trace, cfg_from_info import Base.copy, Base.String, Base.show, Base.summary import Cambrian.print @@ -56,6 +56,93 @@ function find_active(cfg::NamedTuple, genes::Array{Float64}, active end +""" +cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, + function_module::Module, d_fitness::Int64; kwargs...) + +Deduce config from given information. +""" +function cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, + function_module::Module, d_fitness::Int64) + # Create the appropriate cfg + functions = Function[] + two_arity = BitVector() + for i in eachindex(nodes) + fi = getfield(function_module, Symbol(nodes[i].f)) + if fi ∉ functions + push!(functions, fi) + push!(two_arity, function_module.arity[String(Symbol(nodes[i].f))] == 2) + end + end + P = length(nodes[1].p) + n_out = length(outputs) + R = 1 + C = length(nodes) + ( + two_arity=two_arity, + n_in=n_in, + #m_rate=0, # Not used in handcrafter CGP + n_parameters=P, + functions=functions, + recur=0.0, # Not used in handcrafter CGP + d_fitness=d_fitness, + n_out=n_out, + rows=R, + columns=C + ) +end + +""" + CGPInd(nodes::Array{Node}, cfg::NamedTuple, outputs::Array{Int16}; kwargs...)::CGPInd + +Constructor for CGP individuals based on given nodes and corresponding config +file. Recommended use in conjonction with `cfg_from_info` function. + +Example: + + my_nodes = [ + Node(1, 2, CGPFunctions.f_abs, [], false), + Node(1, 2, CGPFunctions.f_add, [], false), + Node(3, 3, CGPFunctions.f_cos, [], false) + ] + my_n_in = 3 + my_outputs = Int16[3, 4, 5] + my_module = CGPFunctions + cfg = cfg_from_info(my_nodes, my_n_in, my_outputs, my_module, 1) + cont = CGPInd(my_nodes, cfg) +""" +function CGPInd(nodes::Array{Node}, cfg::NamedTuple, outputs::Array{Int16}; + kwargs...)::CGPInd + R = cfg.rows + C = cfg.columns + P = cfg.n_parameters + functions = cfg.functions + # Create the appropriate chromosome + maxs = collect(1:R:R*C) + maxs = round.((R*C .- maxs) .* cfg.recur .+ maxs) + maxs = min.(R*C + cfg.n_in, maxs .+ cfg.n_in) + maxs = repeat(maxs, 1, R)' + xs = Float64[] + ys = Float64[] + fs = Float64[] + ps = rand(P, length(nodes)) + for i in eachindex(nodes) + push!(xs, nodes[i].x) + push!(ys, nodes[i].y) + push!(fs, findall(functions -> functions == nodes[i].f, functions)[1] / length(functions)) + for j in eachindex(nodes[i].p) + ps[j, i] = nodes[i].p[j] + end + end + xs = (reshape(xs, (1, length(xs))) ./ maxs)[1,:] + ys = (reshape(ys, (1, length(ys))) ./ maxs)[1,:] + ps = [ps[i, j] for i in eachindex(ps[:,1]) for j in eachindex(ps[1,:])] + outs = outputs ./ (R * C + cfg.n_in) + chromosome = vcat(xs, ys, fs, ps, outs) + # Create individual + CGPInd(cfg, chromosome; kwargs...) +end + function CGPInd(cfg::NamedTuple, chromosome::Array{Float64}, genes::Array{Float64}, outputs::Array{Int16}; kwargs...)::CGPInd R = cfg.rows @@ -120,7 +207,7 @@ function CGPInd(cfg::NamedTuple, ind::String)::CGPInd end function copy(n::Node) - Node(n.x, n.y, n.f, n.active) + Node(n.x, n.y, n.f, n.p, n.active) end function copy(ind::CGPInd) @@ -129,8 +216,8 @@ function copy(ind::CGPInd) for i in eachindex(ind.nodes) nodes[i] = copy(ind.nodes[i]) end - CGPInd(ind.n_in, ind.n_out, copy(ind.chromosome), copy(ind.genes), - copy(ind.outputs), nodes, buffer, copy(ind.fitness)) + CGPInd(ind.n_in, ind.n_out, ind.n_parameters, copy(ind.chromosome), + copy(ind.genes), copy(ind.outputs), nodes, buffer, copy(ind.fitness)) end function String(n::Node) diff --git a/test/individual.jl b/test/individual.jl index de6af97..c2c6665 100644 --- a/test/individual.jl +++ b/test/individual.jl @@ -40,6 +40,26 @@ end test_ind(ind, cfg) end +@testset "CGPInd copy" begin + cfg = get_config(test_filename) + ind = CGPInd(cfg) + ind_cp = copy(ind) + test_ind(ind, cfg) + @test ind.buffer == ind_cp.buffer + @test ind.chromosome == ind_cp.chromosome + @test ind.fitness == ind_cp.fitness + @test ind.genes == ind_cp.genes + @test ind.n_in == ind_cp.n_in + @test ind.n_out == ind_cp.n_out + @test ind.n_parameters == ind_cp.n_parameters + @test ind.outputs == ind_cp.outputs + for i in eachindex(ind.nodes) + @test ind.nodes[i] == ind_cp.nodes[i] + end + ind.fitness[1] = 1.0 + @test ind.fitness != ind_cp.fitness +end + """ A minimal function module example. Note that one can provide any function names, these are just to keep consistency @@ -203,3 +223,47 @@ end all_traces = get_output_trace(ind) @test issubset(ot, all_traces) end + +@testset "Custom CGP individual" begin + my_nodes = [ + Node(1, 2, CGPFunctions.f_subtract, [0.5], false), + Node(1, 2, CGPFunctions.f_add, [0.5], false), + Node(3, 3, CGPFunctions.f_cos, [0.6], false) + ] + n_in = 3 + outputs = Int16[1, 4] + d_fitness = 1 + + cfg = cfg_from_info(my_nodes, n_in, outputs, CGPFunctions, d_fitness) + @test cfg.n_in == n_in + @test cfg.rows == 1 # default + @test cfg.recur == 0.0 # default + @test cfg.columns == length(my_nodes) + @test cfg.two_arity == Bool[1, 1, 0] + @test cfg.functions == Function[ + CartesianGeneticProgramming.CGPFunctions.f_subtract, + CartesianGeneticProgramming.CGPFunctions.f_add, + CartesianGeneticProgramming.CGPFunctions.f_cos + ] + @test cfg.d_fitness == d_fitness + @test cfg.n_parameters == length(my_nodes[1].p) + + ind = CGPInd(my_nodes, cfg, outputs) + @test ind.n_in == n_in + @test ind.n_out == length(outputs) + @test ind.n_parameters == length(my_nodes[1].p) + @test length(ind.nodes) == n_in + length(my_nodes) + for i in eachindex(my_nodes) + @test ind.nodes[i+n_in].f == my_nodes[i].f + @test ind.nodes[i+n_in].p == my_nodes[i].p + @test ind.nodes[i+n_in].x == my_nodes[i].x + @test ind.nodes[i+n_in].y == my_nodes[i].y + @test ind.nodes[i+n_in].active == [true, false, false][i] + end + @test length(ind.fitness) == d_fitness + @test length(ind.chromosome) == (1 * length(my_nodes) * (3 + length(my_nodes[1].p)) + length(outputs)) + @test size(ind.genes) == (1, length(my_nodes), 3 + length(my_nodes[1].p)) + @test length(ind.buffer) == n_in + length(my_nodes) + @test typeof(ind.buffer) == Array{Float64,1} + @test ind.outputs == outputs +end