From d3ff835f9240f490ffb893280f02044db6aa329f Mon Sep 17 00:00:00 2001 From: erwanlecarpentier Date: Fri, 18 Jun 2021 16:35:03 +0200 Subject: [PATCH 1/3] Implemented custom individual constructor + tests for regular (float-handling) CGP --- src/individual.jl | 97 +++++++++++++++++++++++++++++++++++++++++++++- test/individual.jl | 44 +++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/individual.jl b/src/individual.jl index 00c4a19..2699009 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,101 @@ 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; kwargs...) + # 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) + cfg = ( + 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 + ) + cfg +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...) + #= + if img_proc + CartesianGeneticProgramming.CGPInd(cfg, chromosome; buffer=buffer) + else + CGPInd(cfg, chromosome; kwargs...) + end + =# +end + function CGPInd(cfg::NamedTuple, chromosome::Array{Float64}, genes::Array{Float64}, outputs::Array{Int16}; kwargs...)::CGPInd R = cfg.rows diff --git a/test/individual.jl b/test/individual.jl index de6af97..f376c17 100644 --- a/test/individual.jl +++ b/test/individual.jl @@ -203,3 +203,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) == 6 + for i in 1:3 + @test ind.nodes[i+3].f == my_nodes[i].f + @test ind.nodes[i+3].p == my_nodes[i].p + @test ind.nodes[i+3].x == my_nodes[i].x + @test ind.nodes[i+3].y == my_nodes[i].y + @test ind.nodes[i+3].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 From 1620a5bd0feb1a33faf52bf60cfe153683d08cb4 Mon Sep 17 00:00:00 2001 From: erwanlecarpentier Date: Fri, 18 Jun 2021 17:11:48 +0200 Subject: [PATCH 2/3] clean-up --- src/individual.jl | 12 ++---------- test/individual.jl | 14 +++++++------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/individual.jl b/src/individual.jl index 2699009..d92d561 100644 --- a/src/individual.jl +++ b/src/individual.jl @@ -63,7 +63,7 @@ cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, Deduce config from given information. """ function cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, - function_module::Module, d_fitness::Int64; kwargs...) + function_module::Module, d_fitness::Int64) # Create the appropriate cfg functions = Function[] two_arity = BitVector() @@ -78,7 +78,7 @@ function cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, n_out = length(outputs) R = 1 C = length(nodes) - cfg = ( + ( two_arity=two_arity, n_in=n_in, #m_rate=0, # Not used in handcrafter CGP @@ -90,7 +90,6 @@ function cfg_from_info(nodes::Array{Node}, n_in::Int64, outputs::Array{Int16}, rows=R, columns=C ) - cfg end """ @@ -142,13 +141,6 @@ function CGPInd(nodes::Array{Node}, cfg::NamedTuple, outputs::Array{Int16}; chromosome = vcat(xs, ys, fs, ps, outs) # Create individual CGPInd(cfg, chromosome; kwargs...) - #= - if img_proc - CartesianGeneticProgramming.CGPInd(cfg, chromosome; buffer=buffer) - else - CGPInd(cfg, chromosome; kwargs...) - end - =# end function CGPInd(cfg::NamedTuple, chromosome::Array{Float64}, diff --git a/test/individual.jl b/test/individual.jl index f376c17..d60ef07 100644 --- a/test/individual.jl +++ b/test/individual.jl @@ -232,13 +232,13 @@ end @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) == 6 - for i in 1:3 - @test ind.nodes[i+3].f == my_nodes[i].f - @test ind.nodes[i+3].p == my_nodes[i].p - @test ind.nodes[i+3].x == my_nodes[i].x - @test ind.nodes[i+3].y == my_nodes[i].y - @test ind.nodes[i+3].active == [true, false, false][i] + @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)) From 3a9c2517302c16c8fc649cb388e7481b467f5992 Mon Sep 17 00:00:00 2001 From: erwanlecarpentier Date: Tue, 24 Aug 2021 11:51:06 +0200 Subject: [PATCH 3/3] Fixed copying routine: included function parameters copy --- src/individual.jl | 6 +++--- test/individual.jl | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/individual.jl b/src/individual.jl index d92d561..4b399ce 100644 --- a/src/individual.jl +++ b/src/individual.jl @@ -207,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) @@ -216,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 d60ef07..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