From 15d26cfd7ce2a3c709f0e9e77287e83938300c55 Mon Sep 17 00:00:00 2001 From: Azzaare Date: Thu, 30 May 2024 18:03:31 +0900 Subject: [PATCH 1/2] Cleaning and CI updates --- .JuliaFormatter.toml | 1 + .github/workflows/CI.yml | 122 ++++++++++++++++++++--------- .github/workflows/CompatHelper.yml | 37 +++++++-- .github/workflows/SpellCheck.yml | 13 +++ .github/workflows/TagBot.yml | 16 ++++ .github/workflows/register.yml | 2 + Project.toml | 4 +- docs/Manifest.toml | 103 ------------------------ docs/Project.toml | 3 - docs/make.jl | 24 ------ docs/src/index.md | 14 ---- src/common.jl | 6 +- src/icn.jl | 52 ++++++------ src/icn/base.jl | 2 +- src/icn/cbls.jl | 16 ++-- src/icn/genetic.jl | 99 ++++++++++++----------- src/qubo.jl | 31 ++++---- src/qubo/gradient.jl | 98 +++++++++++------------ test/Aqua.jl | 32 ++++++++ test/TestItemRunner.jl | 3 + test/runtests.jl | 7 +- 21 files changed, 349 insertions(+), 336 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 .github/workflows/SpellCheck.yml delete mode 100644 docs/Manifest.toml delete mode 100644 docs/Project.toml delete mode 100644 docs/make.jl delete mode 100644 docs/src/index.md create mode 100644 test/Aqua.jl create mode 100644 test/TestItemRunner.jl diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..453925c --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1 @@ +style = "sciml" \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d94ad98..74f28b5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,16 +1,48 @@ name: CI on: + pull_request: + branches: + - main + - dev + paths-ignore: + - "docs/**" push: branches: - main - tags: '*' - pull_request: -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + paths-ignore: + - "docs/**" jobs: + formatter: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: [1] + julia-arch: [x86] + os: [ubuntu-latest] + steps: + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia-version }} + + - uses: actions/checkout@v4 + - name: Install JuliaFormatter and format + # This will use the latest version by default but you can set the version like so: + # + # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' + run: | + julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="1.0.50"))' + julia -e 'using JuliaFormatter; format(".", verbose=true)' + - name: Format check + run: | + julia -e ' + out = Cmd(`git diff`) |> read |> String + if out == "" + exit(0) + else + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) + end' test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} @@ -18,49 +50,67 @@ jobs: fail-fast: false matrix: version: - - "1.10" - - 'nightly' + - "1.8" + - "1" # automatically expands to the latest stable 1.x release of Julia + - nightly os: - ubuntu-latest - - macOS-latest - - windows-latest arch: - x64 - x86 - exclude: + include: + # test macOS and Windows with latest Julia only - os: macOS-latest - arch: x86 + arch: x64 + version: 1 + - os: windows-latest + arch: x64 + version: 1 - os: windows-latest arch: x86 + version: 1 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: julia-actions/cache@v1 + - uses: actions/cache@v4 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v4 with: - files: lcov.info - docs: - name: Documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: julia-actions/setup-julia@v1 - with: - version: '1' - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-docdeploy@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} - - run: | - julia --project=docs -e ' - using Documenter: DocMeta, doctest - using ConstraintLearning - DocMeta.setdocmeta!(ConstraintLearning, :DocTestSetup, :(using ConstraintLearning); recursive=true) - doctest(ConstraintLearning)' + file: lcov.info + # docs: + # name: Documentation + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - uses: julia-actions/setup-julia@v1 + # with: + # version: "1" + # - run: | + # julia --project=docs -e ' + # using Pkg + # Pkg.develop(PackageSpec(path=pwd())) + # Pkg.instantiate()' + # - run: | + # julia --project=docs -e ' + # using Documenter: DocMeta, doctest + # using Constraints + # DocMeta.setdocmeta!(Constraints, :DocTestSetup, :(using Constraints); recursive=true) + # doctest(Constraints)' + # - run: julia --project=docs docs/make.jl + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index cba9134..5577817 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -1,16 +1,43 @@ -name: CompatHelper on: schedule: - cron: 0 0 * * * workflow_dispatch: +permissions: + contents: write + pull-requests: write jobs: CompatHelper: runs-on: ubuntu-latest steps: - - name: Pkg.add("CompatHelper") - run: julia -e 'using Pkg; Pkg.add("CompatHelper")' - - name: CompatHelper.main() + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v1 + with: + version: "1" + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} - run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 0000000..ed4fe17 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +name: Spell Check + +on: [pull_request] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Check spelling + uses: crate-ci/typos@v1.18.0 diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index f49313b..0cd3114 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' diff --git a/.github/workflows/register.yml b/.github/workflows/register.yml index 6e71f2f..5b7cd3b 100644 --- a/.github/workflows/register.yml +++ b/.github/workflows/register.yml @@ -8,6 +8,8 @@ on: jobs: register: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: julia-actions/RegisterAction@latest with: diff --git a/Project.toml b/Project.toml index 2335d97..f2a26f6 100644 --- a/Project.toml +++ b/Project.toml @@ -27,6 +27,7 @@ DataFrames = "1" Dictionaries = "0.4" Evolutionary = "0.11" Flux = "0.14" +LocalSearchSolvers = "0.4" Memoization = "0.2" PrettyTables = "2" QUBOConstraints = "0.2" @@ -36,7 +37,8 @@ ThreadPools = "2" julia = "1.8" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Aqua", "Test"] \ No newline at end of file diff --git a/docs/Manifest.toml b/docs/Manifest.toml deleted file mode 100644 index 3ce8313..0000000 --- a/docs/Manifest.toml +++ /dev/null @@ -1,103 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.8.0-beta3" -manifest_format = "2.0" -project_hash = "c289fb75c6c218b8293b35c4990a18ff04e2157f" - -[[deps.ANSIColoredPrinters]] -git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" -uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" -version = "0.0.1" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.ConstraintLearning]] -path = ".." -uuid = "4bd09668-9077-4be7-adc9-6307a490e6df" -version = "0.1.0" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DocStringExtensions]] -deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" -uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" - -[[deps.Documenter]] -deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "7d9a46421aef53cbd6b8ecc40c3dcbacbceaf40e" -uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.15" - -[[deps.IOCapture]] -deps = ["Logging", "Random"] -git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" -uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.2" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.3" - -[[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] -uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -version = "1.2.0" - -[[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "621f4f3b4977325b9128d5fae7a8b4829a0c2222" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.2.4" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" - -[[deps.Random]] -deps = ["SHA", "Serialization"] -uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" -version = "0.7.0" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.Unicode]] -uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" diff --git a/docs/Project.toml b/docs/Project.toml deleted file mode 100644 index 971aed5..0000000 --- a/docs/Project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[deps] -ConstraintLearning = "4bd09668-9077-4be7-adc9-6307a490e6df" -Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" diff --git a/docs/make.jl b/docs/make.jl deleted file mode 100644 index 5a9a1f7..0000000 --- a/docs/make.jl +++ /dev/null @@ -1,24 +0,0 @@ -using ConstraintLearning -using Documenter - -DocMeta.setdocmeta!(ConstraintLearning, :DocTestSetup, :(using ConstraintLearning); recursive=true) - -makedocs(; - modules=[ConstraintLearning], - authors="azzaare and contributors", - repo="https://github.com/JuliaConstraints/ConstraintLearning.jl/blob/{commit}{path}#{line}", - sitename="ConstraintLearning.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://JuliaConstraints.github.io/ConstraintLearning.jl", - assets=String[], - ), - pages=[ - "Home" => "index.md", - ], -) - -deploydocs(; - repo="github.com/JuliaConstraints/ConstraintLearning.jl", - devbranch="main", -) diff --git a/docs/src/index.md b/docs/src/index.md deleted file mode 100644 index 2fa9b99..0000000 --- a/docs/src/index.md +++ /dev/null @@ -1,14 +0,0 @@ -```@meta -CurrentModule = ConstraintLearning -``` - -# ConstraintLearning - -Documentation for [ConstraintLearning](https://github.com/JuliaConstraints/ConstraintLearning.jl). - -```@index -``` - -```@autodocs -Modules = [ConstraintLearning] -``` diff --git a/src/common.jl b/src/common.jl index d9ad4ad..6c6aa3e 100644 --- a/src/common.jl +++ b/src/common.jl @@ -41,7 +41,7 @@ function make_training_sets(X, penalty, p, ds) foreach( c -> (cv = collect(c); push!(f(cv; param = p) ? solutions : non_sltns, cv)), - X, + X ) return solutions, non_sltns @@ -54,7 +54,7 @@ function make_training_sets(X, penalty::Vector{T}, _) where {T <: Real} foreach( (c, p) -> (cv = collect(c); push!(p ? non_sltns : solutions, cv)), - Iterators.zip(X, penalty), + Iterators.zip(X, penalty) ) return solutions, non_sltns @@ -66,7 +66,7 @@ end Return a penalty function when the training set is already split into a pair of solutions `X` and non solutions `X̅`. """ function make_set_penalty(X, X̅) - penalty = x -> x ∈ X ? 1. : x ∈ X̅ ? 0. : 0.5 + penalty = x -> x ∈ X ? 1.0 : x ∈ X̅ ? 0.0 : 0.5 X_train = union(X, X̅) return X_train, penalty end diff --git a/src/icn.jl b/src/icn.jl index 7a31646..ab699f1 100644 --- a/src/icn.jl +++ b/src/icn.jl @@ -4,14 +4,14 @@ TBW """ function icn( - X, - X̅; - discrete = true, - dom_size = δ(Iterators.flatten(X), Iterators.flatten(X̅); discrete), - metric = :hamming, - optimizer = ICNGeneticOptimizer(), - X_test = nothing, - parameters... + X, + X̅; + discrete = true, + dom_size = δ(Iterators.flatten(X), Iterators.flatten(X̅); discrete), + metric = :hamming, + optimizer = ICNGeneticOptimizer(), + X_test = nothing, + parameters... ) lc = learn_compose( X, @@ -26,15 +26,15 @@ function icn( end function icn( - domains::Vector{D}, - penalty::F; - configurations = nothing, - discrete = true, - dom_size = nothing, - metric=:hamming, - optimizer = ICNGeneticOptimizer(), - X_test = nothing, - parameters... + domains::Vector{D}, + penalty::F; + configurations = nothing, + discrete = true, + dom_size = nothing, + metric = :hamming, + optimizer = ICNGeneticOptimizer(), + X_test = nothing, + parameters... ) where {D <: AbstractDomain, F <: Function} if isnothing(configurations) configurations = explore(domains, penalty; parameters...) @@ -44,7 +44,7 @@ function icn( dom_size = δ( Iterators.flatten(configurations[1]), Iterators.flatten(configurations[2]); - discrete, + discrete ) end @@ -61,14 +61,14 @@ function icn( end function icn( - X, - penalty::F; - discrete = true, - dom_size = δ(Iterators.flatten(X); discrete), - metric = :hamming, - optimizer = ICNGeneticOptimizer(), - X_test = nothing, - parameters... + X, + penalty::F; + discrete = true, + dom_size = δ(Iterators.flatten(X); discrete), + metric = :hamming, + optimizer = ICNGeneticOptimizer(), + X_test = nothing, + parameters... ) where {F <: Function} solutions, non_sltns = make_training_sets(X, penalty, dom_size; parameters...) return icn( diff --git a/src/icn/base.jl b/src/icn/base.jl index 715ff07..88f848e 100644 --- a/src/icn/base.jl +++ b/src/icn/base.jl @@ -8,7 +8,7 @@ const ICNOptimizer = CompositionalNetworks.AbstractOptimizer """ struct ICNConfig{O <: ICNOptimizer} -A structure to hold the metric and optimizer configurations used in learning the weigths of an ICN. +A structure to hold the metric and optimizer configurations used in learning the weights of an ICN. """ struct ICNConfig{O <: ICNOptimizer} metric::Symbol diff --git a/src/icn/cbls.jl b/src/icn/cbls.jl index 3d9fbd1..abb07e5 100644 --- a/src/icn/cbls.jl +++ b/src/icn/cbls.jl @@ -40,7 +40,8 @@ parameter_specific_operations(x; X = nothing) = 0.0 Extends the `optimize!` method to `ICNLocalSearchOptimizer`. """ function CompositionalNetworks.optimize!( - icn, solutions, non_sltns, dom_size, metric, optimizer::ICNLocalSearchOptimizer; parameters... + icn, solutions, non_sltns, dom_size, metric, + optimizer::ICNLocalSearchOptimizer; parameters... ) @debug "starting debug opt" m = model(; kind = :icn) @@ -75,8 +76,11 @@ function CompositionalNetworks.optimize!( f = composition(compo) S = Iterators.flatten((solutions, non_sltns)) @debug _w compo f S metric - σ = sum(x -> abs(f(x; X=inplace, dom_size, parameters...) - eval(metric)(x, solutions)), S) - return σ + regularization(icn) + weigths_bias(_w) + σ = sum( + x -> abs(f(x; X = inplace, dom_size, parameters...) - + eval(metric)(x, solutions)), + S) + return σ + regularization(icn) + weights_bias(_w) end objective!(m, fitness) @@ -88,16 +92,16 @@ function CompositionalNetworks.optimize!( # Return best values best = BitVector(collect(best_values(s))) - weigths!(icn, best) + weights!(icn, best) return best, Dictionary{BitVector, Int}([best], [1]) end -@testitem "ICN: CBLS" tags = [:icn, :cbls] default_imports=false begin +@testitem "ICN: CBLS" tags=[:icn, :cbls] default_imports=false begin using ConstraintDomains using ConstraintLearning - domains = [domain([1,2,3,4]) for i in 1:4] + domains = [domain([1, 2, 3, 4]) for i in 1:4] compo = icn(domains, allunique; optimizer = ICNLocalSearchOptimizer()) # @test compo([1,2,3,3], dom_size = 4) > 0.0 end diff --git a/src/icn/genetic.jl b/src/icn/genetic.jl index f7fce4f..e71fdda 100644 --- a/src/icn/genetic.jl +++ b/src/icn/genetic.jl @@ -1,6 +1,6 @@ """ generate_population(icn, pop_size -Generate a pôpulation of weigths (individuals) for the genetic algorithm weigthing `icn`. +Generate a pôpulation of weights (individuals) for the genetic algorithm weighting `icn`. """ function generate_population(icn, pop_size) population = Vector{BitVector}() @@ -10,19 +10,19 @@ end """ _optimize!(icn, X, X_sols; metric = hamming, pop_size = 200) -Optimize and set the weigths of an ICN with a given set of configuration `X` and solutions `X_sols`. +Optimize and set the weights of an ICN with a given set of configuration `X` and solutions `X_sols`. """ function _optimize!( - icn, - solutions, - non_sltns, - dom_size, - metric, - pop_size, - iterations; - samples=nothing, - memoize=false, - parameters... + icn, + solutions, + non_sltns, + dom_size, + metric, + pop_size, + iterations; + samples = nothing, + memoize = false, + parameters... ) inplace = zeros(dom_size, max_icn_length()) _non_sltns = isnothing(samples) ? non_sltns : rand(non_sltns, samples) @@ -32,50 +32,50 @@ function _optimize!( f = composition(compo) S = Iterators.flatten((solutions, _non_sltns)) σ = sum( - x -> abs(f(x; X=inplace, dom_size, parameters...) - metric(x, solutions)), S + x -> abs(f(x; X = inplace, dom_size, parameters...) - metric(x, solutions)), S ) - return σ + regularization(icn) + weigths_bias(w) + return σ + regularization(icn) + weights_bias(w) end - _fitness = memoize ? (@memoize Dict memoize_fitness(w) = fitness(w)) : fitness + _fitness = memoize ? (@memoize Dict memoize_fitness(w)=fitness(w)) : fitness _icn_ga = GA(; - populationSize=pop_size, - crossoverRate=0.8, - epsilon=0.05, - selection=tournament(2), - crossover=SPX, - mutation=flip, - mutationRate=1.0 + populationSize = pop_size, + crossoverRate = 0.8, + epsilon = 0.05, + selection = tournament(2), + crossover = SPX, + mutation = flip, + mutationRate = 1.0 ) pop = generate_population(icn, pop_size) r = Evolutionary.optimize(_fitness, pop, _icn_ga, Evolutionary.Options(; iterations)) - return weigths!(icn, Evolutionary.minimizer(r)) + return weights!(icn, Evolutionary.minimizer(r)) end """ optimize!(icn, X, X_sols, global_iter, local_iter; metric=hamming, popSize=100) -Optimize and set the weigths of an ICN with a given set of configuration `X` and solutions `X_sols`. The best weigths among `global_iter` will be set. +Optimize and set the weights of an ICN with a given set of configuration `X` and solutions `X_sols`. The best weights among `global_iter` will be set. """ function optimize!( - icn, - solutions, - non_sltns, - global_iter, - iter, - dom_size, - metric, - pop_size; - sampler=nothing, - memoize=false, - parameters... + icn, + solutions, + non_sltns, + global_iter, + iter, + dom_size, + metric, + pop_size; + sampler = nothing, + memoize = false, + parameters... ) - results = Dictionary{BitVector,Int}() + results = Dictionary{BitVector, Int}() aux_results = Vector{BitVector}(undef, global_iter) nt = Base.Threads.nthreads() - @info """Starting optimization of weigths$(nt > 1 ? " (multithreaded)" : "")""" + @info """Starting optimization of weights$(nt > 1 ? " (multithreaded)" : "")""" samples = isnothing(sampler) ? nothing : sampler(length(solutions) + length(non_sltns)) @qthreads for i in 1:global_iter @info "Iteration $i" @@ -92,11 +92,11 @@ function optimize!( memoize, parameters... ) - aux_results[i] = weigths(aux_icn) + aux_results[i] = weights(aux_icn) end foreach(bv -> incsert!(results, bv), aux_results) best = rand(findall(x -> x == maximum(results), results)) - weigths!(icn, best) + weights!(icn, best) return best, results end @@ -114,11 +114,11 @@ end Default constructor to learn an ICN through a Genetic Algorithm. Default `kargs` TBW. """ function ICNGeneticOptimizer(; - global_iter=Threads.nthreads(), - local_iter=64, - memoize=false, - pop_size=64, - sampler=nothing, + global_iter = Threads.nthreads(), + local_iter = 64, + memoize = false, + pop_size = 64, + sampler = nothing ) return ICNGeneticOptimizer(global_iter, local_iter, memoize, pop_size, sampler) end @@ -129,8 +129,8 @@ end Extends the `optimize!` method to `ICNGeneticOptimizer`. """ function CompositionalNetworks.optimize!( - icn, solutions, non_sltns, dom_size, metric, optimizer::ICNGeneticOptimizer; - parameters... + icn, solutions, non_sltns, dom_size, metric, optimizer::ICNGeneticOptimizer; + parameters... ) return optimize!( icn, @@ -156,13 +156,12 @@ function ICNConfig(; metric = :hamming, optimizer = ICNGeneticOptimizer()) return ICNConfig(metric, optimizer) end - -@testitem "ICN: Genetic" tags = [:icn, :genetic] default_imports=false begin +@testitem "ICN: Genetic" tags=[:icn, :genetic] default_imports=false begin using ConstraintDomains using ConstraintLearning using Test - domains = [domain([1,2,3,4]) for i in 1:4] + domains = [domain([1, 2, 3, 4]) for i in 1:4] compo = icn(domains, allunique) - @test compo([1,2,3,3], dom_size = 4) > 0.0 + @test compo([1, 2, 3, 3], dom_size = 4) > 0.0 end diff --git a/src/qubo.jl b/src/qubo.jl index f3b26a8..fa04cb6 100644 --- a/src/qubo.jl +++ b/src/qubo.jl @@ -4,28 +4,29 @@ TBW """ function qubo( - X, - penalty::Function, - dom_stuff = nothing; - param = nothing, - icn_conf = nothing, - optimizer = GradientDescentOptimizer(), - X_test = X, + X, + penalty::Function, + dom_stuff = nothing; + param = nothing, + icn_conf = nothing, + optimizer = GradientDescentOptimizer(), + X_test = X ) if icn_conf !== nothing - penalty = icn(X, penalty; param, metric = icn_conf.metric, optimizer = icn_conf.optimizer) + penalty = icn( + X, penalty; param, metric = icn_conf.metric, optimizer = icn_conf.optimizer) end return train(X, penalty, dom_stuff; optimizer, X_test) end function qubo( - X, - X̅, - dom_stuff = nothing; - icn_conf = nothing, - optimizer = GradientDescentOptimizer(), - param = nothing, - X_test = union(X, X̅), + X, + X̅, + dom_stuff = nothing; + icn_conf = nothing, + optimizer = GradientDescentOptimizer(), + param = nothing, + X_test = union(X, X̅) ) X_train, penalty = make_set_penalty(X, X̅, param, icn_conf) return qubo(X_train, penalty, dom_stuff; icn_conf, optimizer, param, X_test) diff --git a/src/qubo/gradient.jl b/src/qubo/gradient.jl index 9834564..ae99281 100644 --- a/src/qubo/gradient.jl +++ b/src/qubo/gradient.jl @@ -11,10 +11,10 @@ end A QUBO optimizer based on gradient descent. Defaults TBW """ function QUBOGradientOptimizer(; - binarization = :one_hot, - η = .001, - precision = 5, - oversampling = false, + binarization = :one_hot, + η = 0.001, + precision = 5, + oversampling = false ) return QUBOGradientOptimizer(binarization, η, precision, oversampling) end @@ -31,7 +31,7 @@ predict(x, Q) = transpose(x) * Q * x Loss of the prediction given by `Q`, a training set `y`, and a given configuration `x`. """ -loss(x, y, Q) = (predict(x, Q) .- y).^2 +loss(x, y, Q) = (predict(x, Q) .- y) .^ 2 """ make_df(X, Q, penalty, binarization, domains) @@ -39,39 +39,39 @@ loss(x, y, Q) = (predict(x, Q) .- y).^2 DataFrame arrangement to ouput some basic evaluation of a matrix `Q`. """ function make_df(X, Q, penalty, binarization, domains) - df = DataFrame() - for (i,x) in enumerate(X) - if i == 1 - df = DataFrame(transpose(x), :auto) - else - push!(df, transpose(x)) - end - end + df = DataFrame() + for (i, x) in enumerate(X) + if i == 1 + df = DataFrame(transpose(x), :auto) + else + push!(df, transpose(x)) + end + end - dim = length(df[1,:]) + dim = length(df[1, :]) if binarization == :none - df[!,:penalty] = map(r -> penalty(Vector(r)), eachrow(df)) - df[!,:predict] = map(r -> predict(Vector(r), Q), eachrow(df[:, 1:dim])) + df[!, :penalty] = map(r -> penalty(Vector(r)), eachrow(df)) + df[!, :predict] = map(r -> predict(Vector(r), Q), eachrow(df[:, 1:dim])) else - df[!,:penalty] = map( + df[!, :penalty] = map( r -> penalty(binarize(Vector(r), domains; binarization)), eachrow(df) ) - df[!,:predict] = map( + df[!, :predict] = map( r -> predict(binarize(Vector(r), domains; binarization), Q), eachrow(df[:, 1:dim]) ) end - min_false = minimum( - filter(:penalty => >(minimum(df[:,:penalty])), df)[:,:predict]; + min_false = minimum( + filter(:penalty => >(minimum(df[:, :penalty])), df)[:, :predict]; init = typemax(Int) ) - df[!,:shifted] = df[:,:predict] .- min_false - df[!,:accurate] = df[:, :penalty] .* df[:,:shifted] .≥ 0. + df[!, :shifted] = df[:, :predict] .- min_false + df[!, :accurate] = df[:, :penalty] .* df[:, :shifted] .≥ 0.0 - return df + return df end """ @@ -80,19 +80,19 @@ end Preliminaries to the training process in a `QUBOGradientOptimizer` run. """ function preliminaries(X, domains, binarization) - if binarization==:none + if binarization == :none n = length(first(X)) - return X, zeros(n,n) + return X, zeros(n, n) else Y = map(x -> collect(binarize(x, domains; binarization)), X) n = length(first(Y)) - return Y, zeros(n,n) + return Y, zeros(n, n) end end function preliminaries(X, _) n = length(first(X)) - return X, zeros(n,n) + return X, zeros(n, n) end """ @@ -106,7 +106,8 @@ function train!(Q, X, penalty, η, precision, X_test, oversampling, binarization penalty(first(X)) catch e if isa(e, UndefKeywordError) - penalty = (x; dom_size = δ_extrema(Iterators.flatten(X)))-> penalty(x; dom_size) + penalty = (x; dom_size = δ_extrema(Iterators.flatten(X))) -> penalty( + x; dom_size) else throw(e) end @@ -116,10 +117,11 @@ function train!(Q, X, penalty, η, precision, X_test, oversampling, binarization Q .-= η * grads[Q] end - Q[:,:] = round.(precision*Q) + Q[:, :] = round.(precision * Q) df = make_df(X_test, Q, penalty, binarization, domains) - return pretty_table(DataFrames.describe(df[!, [:penalty, :predict, :shifted, :accurate]])) + return pretty_table(DataFrames.describe(df[ + !, [:penalty, :predict, :shifted, :accurate]])) end """ @@ -128,11 +130,11 @@ end Learn a QUBO matrix on training set `X` for a constraint defined by `penalty` with optional domain information `d`. By default, it uses a `QUBOGradientOptimizer` and `X` as a testing set. """ function train( - X, - penalty, - domains::Vector{D}; - optimizer = QUBOGradientOptimizer(), - X_test = X, + X, + penalty, + domains::Vector{D}; + optimizer = QUBOGradientOptimizer(), + X_test = X ) where {D <: DiscreteDomain} Y, Q = preliminaries(X, domains, optimizer.binarization) train!( @@ -143,17 +145,17 @@ function train( end function train( - X, - penalty, - dom_stuff = nothing; - optimizer = QUBOGradientOptimizer(), - X_test = X, + X, + penalty, + dom_stuff = nothing; + optimizer = QUBOGradientOptimizer(), + X_test = X ) return train(X, penalty, to_domains(X, dom_stuff); optimizer, X_test) end ## SECTION - Test Items -@testitem "QUBOConstraints" tags =[:qubo, :gradient] default_imports=false begin +@testitem "QUBOConstraints" tags=[:qubo, :gradient] default_imports=false begin using ConstraintLearning using QUBOConstraints @@ -169,34 +171,34 @@ end :train => X₃₃, :test => X₃₃_test, :encoding => :none, - :binarization => :none, + :binarization => :none ), Dict( :info => "Domain Wall binarization on ⟦0,2⟧³", :train => X₃₃, :test => X₃₃_test, :encoding => :none, - :binarization => :domain_wall, + :binarization => :domain_wall ), Dict( :info => "One-Hot pre-encoded on ⟦0,2⟧³", :train => B₉, :test => B₉_test, :encoding => :one_hot, - :binarization => :none, - ), + :binarization => :none + ) ] function all_different(x, encoding) encoding == :none && (return allunique(x)) isv = if encoding == :one_hot - mapreduce(i -> is_valid(x[i:i+2], Val(encoding)), *, 1:3:9) + mapreduce(i -> is_valid(x[i:(i + 2)], Val(encoding)), *, 1:3:9) else - mapreduce(i -> is_valid(x[i:i+1], Val(encoding)), *, 1:2:6) + mapreduce(i -> is_valid(x[i:(i + 1)], Val(encoding)), *, 1:2:6) end if isv b = all_different(debinarize(x; binarization = encoding), :none) - return b ? -1. : 1. + return b ? -1.0 : 1.0 else return length(x) end diff --git a/test/Aqua.jl b/test/Aqua.jl new file mode 100644 index 0000000..06b2b2f --- /dev/null +++ b/test/Aqua.jl @@ -0,0 +1,32 @@ +@testset "Aqua.jl" begin + import Aqua + import ConstraintLearning + + # TODO: Fix the broken tests and remove the `broken = true` flag + Aqua.test_all( + ConstraintLearning; + ambiguities = (broken = true,), + deps_compat = false, + piracies = (broken = false,), + unbound_args = (broken = false) + ) + + @testset "Ambiguities: ConstraintLearning" begin + # Aqua.test_ambiguities(ConstraintLearning;) + end + + @testset "Piracies: ConstraintLearning" begin + # Aqua.test_piracies(ConstraintLearning;) + end + + @testset "Dependencies compatibility (no extras)" begin + Aqua.test_deps_compat( + ConstraintLearning; + check_extras = false # ignore = [:Random] + ) + end + + @testset "Unbound type parameters" begin + # Aqua.test_unbound_args(ConstraintLearning;) + end +end diff --git a/test/TestItemRunner.jl b/test/TestItemRunner.jl new file mode 100644 index 0000000..cf86c5a --- /dev/null +++ b/test/TestItemRunner.jl @@ -0,0 +1,3 @@ +@testset "TestItemRunner" begin + @run_package_tests +end diff --git a/test/runtests.jl b/test/runtests.jl index b9e874d..198dcd2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,8 @@ +using Test using TestItemRunner +using TestItems -@run_package_tests +@testset "Package tests: ConstraintLearning" begin + include("Aqua.jl") + include("TestItemRunner.jl") +end From da1a1c258a5cc6aa89471fe1a1e22dd601b2bf60 Mon Sep 17 00:00:00 2001 From: Azzaare Date: Thu, 30 May 2024 19:34:00 +0900 Subject: [PATCH 2/2] Fix CI for Julia 1.8 --- Project.toml | 4 ++-- src/qubo/gradient.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index f2a26f6..c3ab028 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ Constraints = "0.5" DataFrames = "1" Dictionaries = "0.4" Evolutionary = "0.11" -Flux = "0.14" +Flux = "0.13, 0.14" LocalSearchSolvers = "0.4" Memoization = "0.2" PrettyTables = "2" @@ -41,4 +41,4 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "Test"] \ No newline at end of file +test = ["Aqua", "Test"] diff --git a/src/qubo/gradient.jl b/src/qubo/gradient.jl index ae99281..272f3a1 100644 --- a/src/qubo/gradient.jl +++ b/src/qubo/gradient.jl @@ -36,7 +36,7 @@ loss(x, y, Q) = (predict(x, Q) .- y) .^ 2 """ make_df(X, Q, penalty, binarization, domains) -DataFrame arrangement to ouput some basic evaluation of a matrix `Q`. +DataFrame arrangement to output some basic evaluation of a matrix `Q`. """ function make_df(X, Q, penalty, binarization, domains) df = DataFrame()