diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 463810b..8ab778e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - os: windows-latest arch: x86 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} @@ -63,7 +63,7 @@ jobs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: "1" diff --git a/.gitignore b/.gitignore index fcf3cb2..45c2497 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,4 @@ _git2_* .gitignore *Manifest.toml -test_dummy.jl .vscode/* diff --git a/Project.toml b/Project.toml index f900fef..bb08543 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CompositionalNetworks" uuid = "4b67e4b5-442d-4ef5-b760-3f5df3a57537" authors = ["Jean-François Baffier"] -version = "0.5.3" +version = "0.5.4" [deps] ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954" @@ -11,6 +11,8 @@ Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" ThreadSafeDicts = "4239201d-c60e-5e0a-9702-85d713665ba7" Unrolled = "9602ed7d-8fef-5bc8-8597-8f21381861e8" @@ -21,6 +23,8 @@ Dictionaries = "0.3" Distances = "0.10" JuliaFormatter = "0.22, 1" OrderedCollections = "1" +TestItemRunner = "0.2" +TestItems = "0.1" ThreadSafeDicts = "0.1" Unrolled = "0.1" julia = "1.6" diff --git a/src/CompositionalNetworks.jl b/src/CompositionalNetworks.jl index 3a91cc2..869b65c 100644 --- a/src/CompositionalNetworks.jl +++ b/src/CompositionalNetworks.jl @@ -8,6 +8,8 @@ import Distances using JuliaFormatter using OrderedCollections using Random +using TestItemRunner +using TestItems using ThreadSafeDicts using Unrolled @@ -47,10 +49,10 @@ include("metrics.jl") # Includes layers include("layer.jl") -include("transformation.jl") -include("arithmetic.jl") -include("aggregation.jl") -include("comparison.jl") +include("layers/transformation.jl") +include("layers/arithmetic.jl") +include("layers/aggregation.jl") +include("layers/comparison.jl") # Include ICN include("icn.jl") diff --git a/src/composition.jl b/src/composition.jl index 11fb44d..b1bac08 100644 --- a/src/composition.jl +++ b/src/composition.jl @@ -1,18 +1,45 @@ +""" + struct Composition{F<:Function} + +Store the all the information of a composition learned by an ICN. +""" struct Composition{F<:Function} code::Dict{Symbol,String} f::F symbols::Vector{Vector{Symbol}} end +""" + Composition(f::F, symbols) where {F<:Function} + +Construct a `Composition`. +""" function Composition(f::F, symbols) where {F<:Function} code = Dict{Symbol,String}() return Composition{F}(code, f, symbols) end +""" + code(c::Composition, lang=:maths; name="composition") + +Access the code of a composition `c` in a given language `lang`. The name of the generated method is optional. +""" function code(c::Composition, lang=:maths; name="composition") return get!(c.code, lang, generate(c, name, Val(lang))) end + +""" + composition(c::Composition) + +Access the actual method of an ICN composition `c`. +""" composition(c::Composition) = c.f + +""" + symbols(c::Composition) + +Output the composition as a layered collection of `Symbol`s. +""" symbols(c::Composition) = c.symbols """ @@ -25,6 +52,11 @@ function compose(icn::ICN, weigths::BitVector=BitVector()) return Composition(composition, symbols) end +""" + generate(c::Composition, name, lang) + +Generates the code of `c` in a specific language `lang`. +""" function generate(c::Composition, name, ::Val{:maths}) aux = map(s -> reduce_symbols(s, ", ", length(s) > 1), symbols(c)) def = reduce_symbols(aux, " ∘ ", false) @@ -62,6 +94,11 @@ function generate(c::Composition, name, ::Val{:Julia}) return documentation * format_text(output, BlueStyle(); pipe_to_function_call=false) end +""" + composition_to_file!(c::Composition, path, name, language=:Julia) + +Write the composition code in a given `language` into a file at `path`. +""" function composition_to_file!(c::Composition, path, name, language=:Julia) output = code(c, language; name) write(path, output) diff --git a/src/aggregation.jl b/src/layers/aggregation.jl similarity index 57% rename from src/aggregation.jl rename to src/layers/aggregation.jl index 01856cd..9f4887a 100644 --- a/src/aggregation.jl +++ b/src/layers/aggregation.jl @@ -22,3 +22,20 @@ function aggregation_layer() return Layer(true, aggregations, Vector{Symbol}()) end + +## SECTION - Test Items +@testitem "Aggregation Layer" tags = [:aggregation, :layer] begin + CN = CompositionalNetworks + + data = [ + [1, 5, 2, 4, 3] => 2, + [1, 2, 3, 2, 1] => 2, + ] + + @test CN.ag_sum(data[1].first) == 15 + @test CN.ag_sum(data[2].first) == 9 + + @test CN.ag_count_positive(data[1].first) == 5 + @test CN.ag_count_positive(data[2].first) == 5 + @test CN.ag_count_positive([1, 0, 1, 0, 1]) == 3 +end diff --git a/src/arithmetic.jl b/src/layers/arithmetic.jl similarity index 63% rename from src/arithmetic.jl rename to src/layers/arithmetic.jl index 6d76eac..3991cd2 100644 --- a/src/arithmetic.jl +++ b/src/layers/arithmetic.jl @@ -22,3 +22,17 @@ function arithmetic_layer() return Layer(true, arithmetics, Vector{Symbol}()) end + +## SECTION - Test Items +@testitem "Arithmetic Layer" tags = [:arithmetic, :layer] begin + CN = CompositionalNetworks + + data = [ + [1, 5, 2, 4, 3] => 2, + [1, 2, 3, 2, 1] => 2, + ] + + @test CN.ar_sum(map(p -> p.first, data)) == [2, 7, 5, 6, 4] + @test CN.ar_prod(map(p -> p.first, data)) == [1, 10, 6, 8, 3] + +end diff --git a/src/comparison.jl b/src/layers/comparison.jl similarity index 67% rename from src/comparison.jl rename to src/layers/comparison.jl index 18ced9e..2f30b13 100644 --- a/src/comparison.jl +++ b/src/layers/comparison.jl @@ -94,3 +94,66 @@ function comparison_layer(parameters = Vector{Symbol}()) return Layer(true, comparisons, parameters) end + +## SECTION - Test Items +@testitem "Comparison Layer" tags = [:comparison, :layer] begin + CN = CompositionalNetworks + + data = [3 => (1, 5), 5 => (10, 5)] + + funcs = [ + CN.co_identity => [3, 5], + ] + + # test no param/vars + for (f, results) in funcs + for (key, vals) in enumerate(data) + @test f(vals.first) == results[key] + end + end + + funcs_param = [ + CN.co_abs_diff_val_param => [2, 5], + CN.co_val_minus_param => [2, 0], + CN.co_param_minus_val => [0, 5], + ] + + for (f, results) in funcs_param + for (key, vals) in enumerate(data) + @test f(vals.first; param=vals.second[1]) == results[key] + end + end + + funcs_vars = [ + CN.co_abs_diff_val_vars => [2, 0], + CN.co_val_minus_vars => [0, 0], + CN.co_vars_minus_val => [2, 0], + ] + + for (f, results) in funcs_vars + for (key, vals) in enumerate(data) + @test f(vals.first, nvars=vals.second[2]) == results[key] + end + end + + funcs_param_dom = [ + CN.co_euclidian_param => [1.4, 2.0], + ] + + for (f, results) in funcs_param_dom + for (key, vals) in enumerate(data) + @test f(vals.first, param=vals.second[1], dom_size=vals.second[2]) ≈ results[key] + end + end + + funcs_dom = [ + CN.co_euclidian => [1.6, 2.0], + ] + + for (f, results) in funcs_dom + for (key, vals) in enumerate(data) + @test f(vals.first, dom_size=vals.second[2]) ≈ results[key] + end + end + +end diff --git a/src/transformation.jl b/src/layers/transformation.jl similarity index 77% rename from src/transformation.jl rename to src/layers/transformation.jl index 5ecabe6..2d4e837 100644 --- a/src/transformation.jl +++ b/src/layers/transformation.jl @@ -262,3 +262,110 @@ function transformation_layer(parameters = Vector{Symbol}()) return Layer(false, transformations, parameters) end + +## SECTION - Test Items +@testitem "Arithmetic Layer" tags = [:arithmetic, :layer] begin + CN = CompositionalNetworks + + data = [ + [1, 5, 2, 4, 3] => 2, + [1, 2, 3, 2, 1] => 2, + ] + + # Test transformations without parameters + funcs = Dict( + CN.tr_identity => [ + data[1].first, + data[2].first, + ], + CN.tr_count_eq => [ + [0, 0, 0, 0, 0], + [1, 1, 0, 1, 1], + ], + CN.tr_count_eq_right => [ + [0, 0, 0, 0, 0], + [1, 1, 0, 0, 0], + ], + CN.tr_count_eq_left => [ + [0, 0, 0, 0, 0], + [0, 0, 0, 1, 1], + ], + CN.tr_count_greater => [ + [4, 0, 3, 1, 2], + [3, 1, 0, 1, 3], + ], + CN.tr_count_lesser => [ + [0, 4, 1, 3, 2], + [0, 2, 4, 2, 0], + ], + CN.tr_count_g_left => [ + [0, 0, 1, 1, 2], + [0, 0, 0, 1, 3], + ], + CN.tr_count_l_left => [ + [0, 1, 1, 2, 2], + [0, 1, 2, 1, 0], + ], + CN.tr_count_g_right => [ + [4, 0, 2, 0, 0], + [3, 1, 0, 0, 0], + ], + CN.tr_count_l_right => [ + [0, 3, 0, 1, 0], + [0, 1, 2, 1, 0], + ], + CN.tr_contiguous_vals_minus => [ + [0, 3, 0, 1, 0], + [0, 0, 1, 1, 0], + ], + CN.tr_contiguous_vals_minus_rev => [ + [4, 0, 2, 0, 0], + [1, 1, 0, 0, 0], + ], + ) + + for (f, results) in funcs + @info f + for (key, vals) in enumerate(data) + @test f(vals.first) == results[key] + foreach(i -> f(i, vals.first), vals.first) + end + end + + # Test transformations with parameter + funcs_param = Dict( + CN.tr_count_eq_param => [ + [1, 0, 1, 0, 1], + [1, 0, 0, 0, 1], + ], + CN.tr_count_l_param => [ + [2, 5, 3, 5, 4], + [4, 5, 5, 5, 4], + ], + CN.tr_count_g_param => [ + [2, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + ], + CN.tr_count_bounding_param => [ + [3, 1, 3, 2, 3], + [5, 3, 1, 3, 5], + ], + CN.tr_val_minus_param => [ + [0, 3, 0, 2, 1], + [0, 0, 1, 0, 0], + ], + CN.tr_param_minus_val => [ + [1, 0, 0, 0, 0], + [1, 0, 0, 0, 1], + ], + ) + + for (f, results) in funcs_param + @info f + for (key, vals) in enumerate(data) + @test f(vals.first; param=vals.second) == results[key] + foreach(i -> f(i, vals.first; param=vals.second), vals.first) + end + end + +end diff --git a/src/utils.jl b/src/utils.jl index cc4299b..60f9688 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -77,6 +77,11 @@ function reduce_symbols(symbols, sep, parenthesis=true; prefix="") return parenthesis ? "[$str]" : str end +""" + tr_in(tr, X, x, param) + +Application of an operation from the transformation layer. Used to generate more efficient code for all compositions. +""" @unroll function tr_in(tr, X, x, param) @unroll for i in 1:length(tr) tr[i](x, @view(X[:, i]); param) diff --git a/test/Project.toml b/test/Project.toml index b822739..1600931 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -5,4 +5,6 @@ Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4" Evolutionary = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6" Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" diff --git a/test/icn.jl b/test/icn.jl index fdd7b66..a66dfac 100644 --- a/test/icn.jl +++ b/test/icn.jl @@ -24,3 +24,4 @@ compo, _ = explore_learn_compose(domains, allunique; optimizer = GeneticOptimize ## Test export to file composition_to_file!(compo, "test_dummy.jl", "all_different") +rm("test_dummy.jl"; force = true) diff --git a/test/layers.jl b/test/layers.jl deleted file mode 100644 index c939ad6..0000000 --- a/test/layers.jl +++ /dev/null @@ -1,182 +0,0 @@ -# Transformation layer -data = [ - [1, 5, 2, 4, 3] => 2, - [1, 2, 3, 2, 1] => 2, -] - -# Test transformations without parameters -funcs = Dict( - CN.tr_identity => [ - data[1].first, - data[2].first, - ], - CN.tr_count_eq => [ - [0, 0, 0, 0, 0], - [1, 1, 0, 1, 1], - ], - CN.tr_count_eq_right => [ - [0, 0, 0, 0, 0], - [1, 1, 0, 0, 0], - ], - CN.tr_count_eq_left => [ - [0, 0, 0, 0, 0], - [0, 0, 0, 1, 1], - ], - CN.tr_count_greater => [ - [4, 0, 3, 1, 2], - [3, 1, 0, 1, 3], - ], - CN.tr_count_lesser => [ - [0, 4, 1, 3, 2], - [0, 2, 4, 2, 0], - ], - CN.tr_count_g_left => [ - [0, 0, 1, 1, 2], - [0, 0, 0, 1, 3], - ], - CN.tr_count_l_left => [ - [0, 1, 1, 2, 2], - [0, 1, 2, 1, 0], - ], - CN.tr_count_g_right => [ - [4, 0, 2, 0, 0], - [3, 1, 0, 0, 0], - ], - CN.tr_count_l_right => [ - [0, 3, 0, 1, 0], - [0, 1, 2, 1, 0], - ], - CN.tr_contiguous_vals_minus => [ - [0, 3, 0, 1, 0], - [0, 0, 1, 1, 0], - ], - CN.tr_contiguous_vals_minus_rev => [ - [4, 0, 2, 0, 0], - [1, 1, 0, 0, 0], - ], -) - -for (f, results) in funcs - @info f - for (key, vals) in enumerate(data) - @test f(vals.first) == results[key] - foreach(i -> f(i, vals.first), vals.first) - end -end - -# Test transformations with parameter -funcs_param = Dict( - CN.tr_count_eq_param => [ - [1, 0, 1, 0, 1], - [1, 0, 0, 0, 1], - ], - CN.tr_count_l_param => [ - [2, 5, 3, 5, 4], - [4, 5, 5, 5, 4], - ], - CN.tr_count_g_param => [ - [2, 0, 1, 0, 0], - [0, 0, 0, 0, 0], - ], - CN.tr_count_bounding_param => [ - [3, 1, 3, 2, 3], - [5, 3, 1, 3, 5], - ], - CN.tr_val_minus_param => [ - [0, 3, 0, 2, 1], - [0, 0, 1, 0, 0], - ], - CN.tr_param_minus_val => [ - [1, 0, 0, 0, 0], - [1, 0, 0, 0, 1], - ], -) - -for (f, results) in funcs_param - @info f - for (key, vals) in enumerate(data) - @test f(vals.first; param=vals.second) == results[key] - foreach(i -> f(i, vals.first; param=vals.second), vals.first) - end -end - -# arithmetic layer -@info CN.ar_sum -@test CN.ar_sum(map(p -> p.first, data)) == [2, 7, 5, 6, 4] -@info CN.ar_prod -@test CN.ar_prod(map(p -> p.first, data)) == [1, 10, 6, 8, 3] - -# aggregation layer -@info CN.ag_sum -@test CN.ag_sum(data[1].first) == 15 -@test CN.ag_sum(data[2].first) == 9 - -@info CN.ag_count_positive -@test CN.ag_count_positive(data[1].first) == 5 -@test CN.ag_count_positive(data[2].first) == 5 -@test CN.ag_count_positive([1, 0, 1, 0, 1]) == 3 - -# Comparison layer -data = [3 => (1, 5), 5 => (10, 5)] - -funcs = [ - CN.co_identity => [3, 5], -] - -# test no param/vars -for (f, results) in funcs - @info f - for (key, vals) in enumerate(data) - @test f(vals.first) == results[key] - end -end - -funcs_param = [ - CN.co_abs_diff_val_param => [2, 5], - CN.co_val_minus_param => [2, 0], - CN.co_param_minus_val => [0, 5], -] - -for (f, results) in funcs_param - @info f - for (key, vals) in enumerate(data) - @test f(vals.first; param=vals.second[1]) == results[key] - end -end - -funcs_vars = [ - CN.co_abs_diff_val_vars => [2, 0], - CN.co_val_minus_vars => [0, 0], - CN.co_vars_minus_val => [2, 0], -] - -for (f, results) in funcs_vars - @info f - for (key, vals) in enumerate(data) - @test f(vals.first, nvars=vals.second[2]) == results[key] - end -end - -funcs_param_dom = [ - CN.co_euclidian_param => [1.4, 2.0], -] - -for (f, results) in funcs_param_dom - @info f - for (key, vals) in enumerate(data) - # @info "Updated" f(vals.first, param=vals.second[1], dom_size=vals.second[2]) results key - @test f(vals.first, param=vals.second[1], dom_size=vals.second[2]) ≈ results[key] - end -end - -funcs_dom = [ - CN.co_euclidian => [1.6, 2.0], -] - -for (f, results) in funcs_dom - @info f - for (key, vals) in enumerate(data) - # @info "Updated" f(vals.first, dom_size=vals.second[2]) results key - @test f(vals.first, dom_size=vals.second[2]) ≈ results[key] - end -end diff --git a/test/runtests.jl b/test/runtests.jl index 5f78136..ff7f373 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,18 +1,21 @@ -using CompositionalNetworks -using ConstraintDomains -using Dictionaries -using Evolutionary -using Memoization -using Test -using ThreadPools +using TestItemRunner +using TestItems -CN = CompositionalNetworks +@run_package_tests -import CompositionalNetworks: AbstractOptimizer +@testitem "ICN: genetic algo" tags = [:icn, :genetic] default_imports=false begin + using CompositionalNetworks + using ConstraintDomains + using Dictionaries + using Evolutionary + using Memoization + using Test + using ThreadPools -include("genetic.jl") + CN = CompositionalNetworks -@testset "CompositionalNetworks.jl" begin - include("layers.jl") + import CompositionalNetworks: AbstractOptimizer + + include("genetic.jl") include("icn.jl") end