Skip to content

Commit

Permalink
Implement random key genetic algorithm for permutation based genetic …
Browse files Browse the repository at this point in the history
…algorithm optimizations. In first level, the job-shop scheduling problem that can not be solved by Johnson's rule are now solved by this GA.
  • Loading branch information
jbytecode committed Aug 1, 2024
1 parent 399a496 commit 486ac8b
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 6 deletions.
9 changes: 7 additions & 2 deletions src/OperationsResearchModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ include("cpm.jl")
include("simplex.jl")
include("knapsack.jl")
include("latex.jl")
include("randomkeyga.jl")
include("johnsons.jl")

import .Network
Expand All @@ -34,8 +35,10 @@ import .Simplex
import .Knapsack
import .Latex
import .Utility
import .RandomKeyGA
import .Johnsons


import .Transportation:
TransportationProblem, TransportationResult, balance, isbalanced, northwestcorner
import .ShortestPath: ShortestPathResult, ShortestPathProblem
Expand All @@ -50,7 +53,8 @@ import .CPM: CpmActivity, earliestfinishtime, longestactivity, CpmProblem, CpmRe
import .CPM: PertActivity, PertProblem, PertResult
import .Knapsack: KnapsackResult, KnapsackProblem
import .Latex: latex
import .Johnsons: JohnsonResult, johnsons, JohnsonException, makespan
import .Johnsons: JohnsonResult, johnsons, JohnsonException, makespan, johnsons_ga
import .RandomKeyGA: Chromosome, run_ga

export TransportationProblem, TransportationResult, balance, isbalanced, northwestcorner
export Connection, ShortestPathResult, MaximumFlowResult, nodes
Expand All @@ -65,7 +69,8 @@ export KnapsackResult, KnapsackProblem
export Simplex
export Utility
export latex
export JohnsonResult, johnsons, JohnsonException, makespan
export Chromosome, run_ga
export JohnsonResult, johnsons, JohnsonException, makespan, johnsons_ga

export JuMP, HiGHS

Expand Down
19 changes: 17 additions & 2 deletions src/johnsons.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
module Johnsons


import ..RandomKeyGA: run_ga, Chromosome, Population, generation, run_ga

export JohnsonResult
export JohnsonException
export johnsons
export makespan
export johnsons_ga


# TODO: Add makespan calculation

struct JohnsonException <: Exception
message::String
end
Expand All @@ -25,6 +27,19 @@ struct Process
end


function johnsons_ga(times::Matrix; popsize = 100, ngen = 1000, pcross = 0.8, pmutate = 0.01, nelites = 1)::JohnsonResult

n, m = size(times)

function costfn(perm::Vector{Int})
return makespan(times, perm)
end

finalpop = run_ga(popsize, n, costfn, ngen, pcross, pmutate, nelites)

return JohnsonResult(sortperm(finalpop.chromosomes[1].values))
end



"""
Expand Down
112 changes: 112 additions & 0 deletions src/randomkeyga.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
module RandomKeyGA

export Chromosome
export Population
export create_population
export onepoint_crossover
export random_mutation
export tournament_selection
export generation
export run_ga

mutable struct Chromosome
values::Vector{Float64}
cost::Float64
end

mutable struct Population{F<:Function}
chromosomes::Vector{Chromosome}
costfn::F
end

function Chromosome(len::Int)
return Chromosome(rand(len), Inf64)
end

function Chromosome(vals::Vector{Float64})
return Chromosome(vals, Inf64)
end

function create_population(popsize::Int, chsize::Int, costfn::F) where {F<:Function}
chs = Chromosome[Chromosome(chsize) for i in 1:popsize]
return Population(chs, costfn)
end

function onepoint_crossover(ch::Chromosome, ch2::Chromosome)::Chromosome
L = length(ch.values)
cutpoint = rand(2:(L-1))
vals1 = vcat(ch.values[1:cutpoint], ch2.values[(cutpoint+1):end])
return Chromosome(vals1)
end

function random_mutation(ch::Chromosome)::Chromosome
luckyindex = rand(1:length(ch.values))
newch = Chromosome(copy(ch.values))
newch.values[luckyindex] = rand()
return newch
end

function tournament_selection(pop::Population, tournament_size::Int)::Chromosome
indices = rand(1:length(pop.chromosomes), tournament_size)
chromosomes = pop.chromosomes[indices]
sorted_chromosomes = sort!(chromosomes, by=ch -> ch.cost)
return sorted_chromosomes[1]
end

function generation(initialpop::Population, pcross::Float64, pmutate::Float64, nelites::Int)::Population

n = length(initialpop.chromosomes)

for i in 1:n
initialpop.chromosomes[i].cost = initialpop.costfn(sortperm(initialpop.chromosomes[i].values))
end

sort!(initialpop.chromosomes, by=ch -> ch.cost)


chsize = length(initialpop.chromosomes[1].values)

newpop = create_population(n, chsize, initialpop.costfn)

for i in 1:nelites
newpop.chromosomes[i] = Chromosome(initialpop.chromosomes[i].values, initialpop.chromosomes[i].cost)
end

for i in (nelites+1):n
ch1 = tournament_selection(initialpop, 2)

if rand() < pcross
ch2 = tournament_selection(initialpop, 2)
ch1 = onepoint_crossover(ch1, ch2)
end

if rand() < pmutate
ch1 = random_mutation(ch1)
end

newpop.chromosomes[i] = ch1
end

return newpop
end

function run_ga(popsize::Int, chsize::Int, costfn::F, ngen::Int, pcross::Float64, pmutate::Float64, nelites::Int) where {F<:Function}

pop = create_population(popsize, chsize, costfn)

for _ in 1:ngen
pop = generation(pop, pcross, pmutate, nelites)
end

# calculate costs
for i in 1:popsize
pop.chromosomes[i].cost = pop.costfn(sortperm(pop.chromosomes[i].values))
end

sort!(pop.chromosomes, by=ch -> ch.cost)

return pop
end


end # end of module
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ using Test
using OperationsResearchModels
using OperationsResearchModels.Simplex

include("testrandomkeyga.jl")
include("testjohnsons.jl")
include("testutility.jl")
include("testjump.jl")
Expand Down
100 changes: 98 additions & 2 deletions test/testjohnsons.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
2 5 6
]


ms = Float64[
makespan(times, [1, 2, 3, 4, 5]),
makespan(times, [1, 2, 3, 5, 4]),
Expand All @@ -160,4 +160,100 @@



end
@testset "GA" verbose = true begin

@testset "Two machines - with ga" verbose = true begin

@testset "Example 1 with 2-machines with ga" begin
times = Float64[
3.2 4.2;
4.7 1.5;
2.2 5.0;
5.8 4.0;
3.1 2.8
]

result = johnsons_ga(times)

@test makespan(times, result.permutation) == 20.5
end


@testset "Example 2 with 2-machines with ga" begin
times = Float64[
4 7;
8 3;
5 8;
6 4;
8 5;
7 4
]

result = johnsons_ga(times)

time_elapsed = makespan(times, result.permutation)

@test time_elapsed == 41
end
end


@testset "Three machines - with ga" verbose = true begin

@testset "Example 1 for 3-machines with ga" begin

times = Float64[
3 3 5;
8 4 8;
7 2 10;
5 1 7;
2 5 6
]

result = johnsons_ga(times)

time_elapsed = makespan(times, result.permutation)

@test time_elapsed == 42
end
end

@testset "Five machines - with ga" verbose = true begin

@testset "Example 1 for 5-machines - with ga " begin

times = Float64[
7 5 2 3 9;
6 6 4 5 10;
5 4 5 6 8;
8 3 3 2 6
]

result = johnsons_ga(times)

time_elapsed = makespan(times, result.permutation)

@test time_elapsed == 51
end
end

@testset "Cannot reduce to 2-machine - but solvable with GA" begin

times = Float64[
3 3 5 2;
8 4 8 3;
7 2 10 4;
5 1 7 5;
2 5 6 6
]

result = johnsons_ga(times)

time_elapsed = makespan(times, result.permutation)

@test time_elapsed == 44
end

end # end of GA testset

end # end of Johnson's algorithm testset
57 changes: 57 additions & 0 deletions test/testrandomkeyga.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@testset "Random Key Genetic Algorithm" verbose = true begin

@testset "Example with chsize = 5" begin

# Correct order is 1 2 3 4 5
function costfn(permutation::Vector{Int})::Float64
sum = 0.0
for i in 1:5
sum += abs(permutation[i] - i)
end
return sum
end

popsize = 100
chsize = 5
maxiter = 5000
crossoverprob = 0.8
mutationprob = 0.1
elitism = 1

# Random key genetic algorithm
# popsize::Int, chsize::Int, costfn::F, ngen::Int, pcross::Float64, pmutate::Float64, nelites::Int
result = run_ga(popsize, chsize, costfn, maxiter, crossoverprob, mutationprob, elitism)

@test sortperm(result.chromosomes[1].values) == [1, 2, 3, 4, 5]
@test costfn(sortperm(result.chromosomes[1].values)) == 0.0

end


@testset "Example with chsize = 10" begin

# Correct order is 1 2 3 4 5 6 7 8 9 10
function costfn(permutation::Vector{Int})::Float64
sum = 0.0
for i in 1:10
sum += abs(permutation[i] - i)
end
return sum
end

popsize = 100
chsize = 10
maxiter = 5000
crossoverprob = 0.8
mutationprob = 0.1
elitism = 1

# Random key genetic algorithm
# popsize::Int, chsize::Int, costfn::F, ngen::Int, pcross::Float64, pmutate::Float64, nelites::Int
result = run_ga(popsize, chsize, costfn, maxiter, crossoverprob, mutationprob, elitism)

@test sortperm(result.chromosomes[1].values) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
@test costfn(sortperm(result.chromosomes[1].values)) == 0.0

end
end

0 comments on commit 486ac8b

Please sign in to comment.