Respostas – maximizar afinidades

Numa escola, antes da definição das turmas, cada aluno preenche um formulário informando com quais colegas tem afinidade, resultando na tabela abaixo (onde 1 indica afinidade).

Matriz de afinidades (é simétrica, se um aluno tem afinidade com outro, o inverso também é verdadeiro):

Aluno 1Aluno 2Aluno 3Aluno 4Aluno 5Aluno 6Aluno 7Aluno 8Aluno 9Aluno 10Aluno 11Aluno 12Aluno 13Aluno 14Aluno 15
Aluno 110111100101101
Aluno 210010001010000
Aluno 300000101001001
Aluno 410010000010011
Aluno 511010010011101
Aluno 610000100001000
Aluno 710100111010100
Aluno 800001011100111
Aluno 901100011010001
Aluno 1010000001011101
Aluno 1101011010111100
Aluno 1210101100011000
Aluno 1310001011011011
Aluno 1400010001000010
Aluno 1510111001110010

A escola deve dividir os 15 alunos em 3 turmas de 5 alunos cada.

Dois alunos com afinidade gostam de estar na mesma turma, e dois alunos sem afinidade são indiferentes.

O score de afinidade total é calculado assim: para cada turma, cada par de alunos com afinidade acrescenta 1 ao score.

Desafio proposto em https://ideiasesquecidas.com/2023/05/20/desafio-maximizar-afinidades/. Seguem algumas resoluções.

Solução 1) Algoritmo aleatório

É possível resolver utilizando algoritmo aleatório puro. Fiz uma versão em VBA, e guardei no Github (https://github.com/asgunzi/max_afinidades).

Basicamente o algoritmo vai testar soluções aleatórias que atendam às restrições, e ir salvando a melhor. Funciona bem, para o caso.

É necessário ativar macros para rodar.

Solução 2) Programação linear inteira

Outra alternativa é modelando via programação linear inteira. Para tal, é necessário utilizar variáveis auxiliares binárias. A ideia seria criar uma variável indicador, que indique se dois alunos estão na mesma turma.

Fiz a resolução em Pyomo (pacote do Python) e resolução no CBC (é um solver free). Formulação no Github https://github.com/asgunzi/max_afinidades,

É bem mais complexo, porém é muito mais poderoso. Essa formulação vai garantir o ótimo global.

Um trecho do código para dar uma ideia.

# Objective function

def somaFO(model):

    return sum(model.indicador[aluno1,aluno2, turma]*int(afinidades[aluno1][aluno2]) for aluno1 in alunos for aluno2 in alunosaux  for turma in turmas) – 1000*sum(model.indicador[aluno1,aluno2, turma] for aluno1 in alunos for aluno2 in alunosaux  for turma in turmas)

model.objective = Objective(rule = somaFO, sense=maximize)

# Constraint: Capacidade turmas

def capacidadeturmas_rule(model, turma):

    return sum(model.aloc[aluno,turma] for aluno in alunos) == 5

model.capacidade= Constraint(turmas, rule=capacidadeturmas_rule)

# Constraint: alocar todo mundo

def alocar_rule(model, aluno):

    return sum(model.aloc[aluno,turma] for turma in turmas) <= 1

model.alocartodos= Constraint(alunos, rule=alocar_rule)

# Constraint: indicador de dois alunos na mesma turma

def indicador_rule(model, aluno1, aluno2, turma):

   return model.aloc[aluno1,turma] + model.aloc[aluno2,turma] – 1 <= model.indicador[aluno1,aluno2,turma]

model.indicadorafim= Constraint(alunos, alunosaux, turmas, rule=indicador_rule)


A resposta ótima é 22, e uma das soluções possíveis é a seguinte:

 AlocaçãoTurma 1Turma 2Turma 3
Aluno 11
Aluno 21
Aluno 31
Aluno 41
Aluno 51
Aluno 61
Aluno 71
Aluno 81
Aluno 91
Aluno 101
Aluno 111
Aluno 121
Aluno 131
Aluno 141
Aluno 151

Este é um problema um pouquinho mais difícil de formular.

Algumas pessoas enviaram resposta utilizando o solver, numa função não linear que necessita da opção Evolutionary do mesmo. Também é possível chegar à resposta assim, porém, o cuidado é que utilizando o solver assim não garante o ótimo global.

Veja também. https://ideiasesquecidas.com/laboratorio-de-matematica/

Deixe um comentário