using Pkg: @pkg_str pkg"activate ." pkg"status" using Convex using GLPKMathProgInterface using Cbc using Random Convex.MathProgBase.ConicModel |> methods num_rooms = 4 timeslots = 1:8*3 program_items = 1:(4*8*3) num_voters = 250 max_votes = 24 people_votes = [rand(program_items, rand(1:max_votes)) for _ in 1:num_voters]; A = Variable((length(timeslots), length(program_items)), :Int) size(A) function count_clashes(A, people_votes) num_clashes = 0 for vote_slip in people_votes for timeslot in timeslots items_in_slot = sum(A[timeslot, vote_slip]) num_clashes += max(items_in_slot-1, 0) # Having 1 item is same as having none. end end; return num_clashes end m = minimize(count_clashes(A, people_votes)) for x in A # Negative items would break the model m.constraints += 0 <= x # The below constraint to only use a 1 to make it occuring is redundant, since it will be minimized down to that # But adding it makes things faster m.constraints += x <=1 end for program_item in program_items # Every program item must occur exactly once m.constraints += sum(A[:,program_item]) == 1 end for timeslot in timeslots # In any given time slot at most there are a number of items equal to the number of rooms. m.constraints += sum(A[timeslot, :]) <= num_rooms end solver = CbcSolver(; threads=8, logLevel=1, seconds=2*60*60) @time solve!(m, solver) @show m.optval allocations_raw = A.value allocations_list = collect.(Tuple.(findall(allocations_raw .> 0))) allocation_matrix = reduce(hcat, sort(allocations_list))' people_votes function random_solution(num_rooms, num_timeslots, num_program_items) A = zeros(Int, num_timeslots, num_program_items) program_items = shuffle(1:num_program_items) for (ii,item) in enumerate(program_items) timeslot = ceil(Int, ii/num_rooms) A[timeslot, item] = 1 end @assert all(isequal(1), sum(randA, dims=1)) # Everything occurs once @assert all(sum(randA, dims=2) .<= num_rooms) # Don't double book rooms return A end maximum( Base.Generator(1:100_000) do ii sol = random_solution(num_rooms, length(timeslots), length(program_items)) score = count_clashes(sol, people_votes) return (score, rand(), sol) # the rand is to break ties so it never compares last arg end )