Skip to content

Commit 4cab7b6

Browse files
authored
Merge pull request #86 from JuliaConstraints/dev
Complete refactor, tag and release
2 parents 949e7e0 + 045b1b0 commit 4cab7b6

28 files changed

+1408
-907
lines changed

.JuliaFormatter.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
style = "sciml"

Project.toml

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,46 @@
11
name = "CompositionalNetworks"
22
uuid = "4b67e4b5-442d-4ef5-b760-3f5df3a57537"
33
authors = ["Jean-François Baffier"]
4-
version = "0.5.9"
4+
version = "0.6.0"
55

66
[deps]
77
ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954"
88
ConstraintDomains = "5800fd60-8556-4464-8d61-84ebf7a0bedb"
99
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
1010
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
11+
ExproniconLite = "55351af7-c7e9-48d6-89ff-24e801d99491"
1112
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
1213
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
1314
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1415
TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe"
1516
Unrolled = "9602ed7d-8fef-5bc8-8597-8f21381861e8"
1617

18+
[weakdeps]
19+
Evolutionary = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6"
20+
# LocalSearchSolvers = "2b10edaa-728d-4283-ac71-07e312d6ccf3"
21+
# JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
22+
# Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84"
23+
# Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
24+
# Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
25+
26+
[extensions]
27+
GeneticExt = "Evolutionary"
28+
# LocalSearchSolversExt = "LocalSearchSolvers"
29+
# JuMPExt = ["JuMP", "Juniper", "Ipopt", "Gurobi"]
30+
1731
[compat]
18-
ConstraintCommons = "0.2"
19-
ConstraintDomains = "0.3"
32+
LocalSearchSolvers = "0.4"
33+
Evolutionary = "0.11"
34+
# JuMP = "1"
35+
# Juniper = "0.9"
36+
# Ipopt = "1"
37+
# Gurobi = "1.7"
38+
ConstraintCommons = "0.2, 0.3"
39+
ConstraintDomains = "0.3, 0.4"
2040
Dictionaries = "0.4"
2141
Distances = "0.10"
22-
JuliaFormatter = "1"
42+
ExproniconLite = "0.10.13"
43+
JuliaFormatter = "1, 2"
2344
OrderedCollections = "1"
2445
Random = "1"
2546
TestItems = "1"
@@ -29,6 +50,11 @@ julia = "1.10"
2950
[extras]
3051
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
3152
Evolutionary = "86b6b26d-c046-49b6-aa0b-5f0f74682bd6"
53+
LocalSearchSolvers = "2b10edaa-728d-4283-ac71-07e312d6ccf3"
54+
# JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
55+
# Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84"
56+
# Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
57+
# Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
3258
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
3359
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
3460
Memoization = "6fafb56a-5788-4b4e-91ca-c0cea6611c73"
@@ -37,4 +63,14 @@ TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
3763
ThreadPools = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431"
3864

3965
[targets]
40-
test = ["Aqua", "ExplicitImports", "JET", "Evolutionary", "Memoization", "Test", "TestItemRunner", "ThreadPools"]
66+
test = [
67+
"Aqua",
68+
"ExplicitImports",
69+
"JET",
70+
"Evolutionary",
71+
# "LocalSearchSolvers",
72+
"Memoization",
73+
"Test",
74+
"TestItemRunner",
75+
"ThreadPools",
76+
]

ext/GeneticExt.jl

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
module GeneticExt
2+
3+
import CompositionalNetworks:
4+
CompositionalNetworks, AbstractICN, Configurations, manhattan,
5+
hamming
6+
import CompositionalNetworks: GeneticOptimizer, apply!, weights_bias, regularization
7+
import CompositionalNetworks: evaluate, solutions
8+
import Evolutionary: Evolutionary, tournament, SPX, flip, GA
9+
10+
function CompositionalNetworks.GeneticOptimizer(;
11+
global_iter = Threads.nthreads(),
12+
# local_iter=64,
13+
local_iter = 400,
14+
memoize = false,
15+
#pop_size=64,
16+
pop_size = 100,
17+
sampler = nothing
18+
)
19+
return GeneticOptimizer(global_iter, local_iter, memoize, pop_size, sampler)
20+
end
21+
22+
function generate_population(icn, pop_size; vect = [])
23+
population = Vector{BitVector}()
24+
if isempty(vect)
25+
foreach(_ -> push!(population, falses(length(icn.weights))), 1:pop_size)
26+
else
27+
foreach(_ -> push!(population, vect), 1:pop_size)
28+
end
29+
return population
30+
end
31+
32+
function CompositionalNetworks.optimize!(
33+
icn::T,
34+
configurations::Configurations,
35+
# dom_size,
36+
metric_function::Union{Function, Vector{Function}},
37+
optimizer_config::GeneticOptimizer;
38+
samples = nothing,
39+
memoize = false,
40+
parameters...
41+
) where {T <: AbstractICN}
42+
43+
# @info icn.weights
44+
45+
# inplace = zeros(dom_size, 18)
46+
solution_iter = solutions(configurations)
47+
non_solutions = solutions(configurations; non_solutions = true)
48+
solution_vector = [i.x for i in solution_iter]
49+
50+
function fitness(w)
51+
weights_validity = apply!(icn, w)
52+
53+
a = if metric_function isa Function
54+
metric_function(
55+
icn,
56+
configurations,
57+
solution_vector;
58+
weights_validity = weights_validity,
59+
parameters...
60+
)
61+
else
62+
minimum(
63+
met -> met(
64+
icn,
65+
configurations,
66+
solution_vector;
67+
weights_validity = weights_validity,
68+
parameters...
69+
),
70+
metric_function
71+
)
72+
end
73+
74+
b = weights_bias(w)
75+
c = regularization(icn)
76+
77+
function new_regularization(icn::AbstractICN)
78+
start = 1
79+
count = 0
80+
total = 0
81+
for (i, layer) in enumerate(icn.layers)
82+
if !layer.mutex
83+
ran = start:(start + icn.weightlen[i] - 1)
84+
op = findall(icn.weights[ran])
85+
max_op = ran .- (start - 1)
86+
total += (sum(op) / sum(max_op))
87+
count += 1
88+
end
89+
start += icn.weightlen[i]
90+
end
91+
return total / count
92+
end
93+
94+
d = sum(findall(icn.weights)) /
95+
(length(icn.weights) * (length(icn.weights) + 1) / 2)
96+
97+
e = new_regularization(icn)
98+
99+
# @info "Lot of things" a b c d e
100+
#=
101+
println("""
102+
sum: $a
103+
weights bias: $b
104+
regularization: $c
105+
new reg: $e
106+
thread: $(Threads.threadid())
107+
""") =#
108+
109+
return a + b + c
110+
end
111+
112+
_icn_ga = GA(;
113+
populationSize = optimizer_config.pop_size,
114+
crossoverRate = 0.8,
115+
epsilon = 0.05,
116+
selection = tournament(4),
117+
crossover = SPX,
118+
mutation = flip,
119+
mutationRate = 1.0
120+
)
121+
122+
pop = generate_population(icn, optimizer_config.pop_size)
123+
r = Evolutionary.optimize(
124+
fitness,
125+
pop,
126+
_icn_ga,
127+
Evolutionary.Options(; iterations = optimizer_config.local_iter)
128+
)
129+
validity = apply!(icn, Evolutionary.minimizer(r))
130+
return icn => validity
131+
end
132+
133+
end

ext/JuMPExt.jl

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
module JuMPExt
2+
3+
using JuMP
4+
using Juniper
5+
using Ipopt
6+
using Gurobi
7+
8+
# Original imports
9+
import CompositionalNetworks: CompositionalNetworks, AbstractICN, Configurations
10+
import CompositionalNetworks: JuMPOptimizer, apply!, weights_bias, regularization
11+
import CompositionalNetworks: evaluate, solutions
12+
13+
function CompositionalNetworks.optimize!(
14+
icn::T,
15+
configurations::Configurations,
16+
metric_function::Union{Function, Vector{Function}},
17+
optimizer_config::JuMPOptimizer;
18+
parameters...
19+
) where {T <: AbstractICN}
20+
# Create model
21+
m = Model()
22+
23+
# Set up MINLP solver
24+
nl_solver = optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)
25+
mip_solver = optimizer_with_attributes(Gurobi.Optimizer, "OutputFlag" => 0)
26+
27+
set_optimizer(
28+
m,
29+
optimizer_with_attributes(
30+
Juniper.Optimizer,
31+
"nl_solver" => nl_solver,
32+
"mip_solver" => mip_solver,
33+
"log_levels" => []
34+
)
35+
)
36+
37+
n = length(icn.weights)
38+
39+
# All variables are binary
40+
@variable(m, w[1:n], Bin)
41+
42+
# Add constraints
43+
start = 1
44+
for (i, layer) in enumerate(icn.layers)
45+
stop = start + icn.weightlen[i] - 1
46+
idx_range = start:stop
47+
48+
if layer.mutex
49+
# Mutually exclusive constraint - at most one variable can be true
50+
# Equivalent to: max(0.0, sum(w[idx_range]) - 1) = 0
51+
@constraint(m, sum(w[j] for j in idx_range) <= 1)
52+
else
53+
# No empty layer constraint - at least one variable must be true
54+
# Equivalent to: max(0, 1 - sum(w[idx_range])) = 0
55+
@constraint(m, sum(w[j] for j in idx_range) >= 1)
56+
end
57+
58+
start = stop + 1
59+
end
60+
61+
# Define fitness function - keeping the original structure
62+
function fitness(w_values)
63+
# Convert JuMP variables to BitVector
64+
w_bits = BitVector([value(w_values[i]) > 0.5 for i in 1:length(w_values)])
65+
66+
weights_validity = apply!(icn, w_bits)
67+
68+
s = if metric_function isa Function
69+
metric_function(
70+
icn,
71+
configurations,
72+
solution_vector;
73+
weights_validity = weights_validity,
74+
parameters...
75+
)
76+
else
77+
minimum(
78+
met -> met(
79+
icn,
80+
configurations,
81+
solution_vector;
82+
weights_validity = weights_validity,
83+
parameters...
84+
),
85+
metric_function
86+
)
87+
end
88+
return s + weights_bias(w_bits) + regularization(icn)
89+
end
90+
91+
# Define objective using the fitness function
92+
@NLobjective(m, Min, fitness(w))
93+
94+
# Solve model
95+
optimize!(m)
96+
97+
# Return solution
98+
if termination_status(m) in [MOI.OPTIMAL, MOI.LOCALLY_SOLVED]
99+
w_sol = value.(w) .> 0.5 # Convert to BitVector
100+
weights_validity = apply!(icn, BitVector(w_sol))
101+
return icn => weights_validity
102+
else
103+
# No solution found, generate new valid weights
104+
CompositionalNetworks.generate_new_valid_weights!(icn)
105+
return icn => true
106+
end
107+
end
108+
109+
end

0 commit comments

Comments
 (0)