Um guia para o OptaPlanner
1. Introdução ao OptaPlanner
Neste tutorial, examinamos um solucionador de satisfação de restrição Java chamadoOptaPlanner.
O OptaPlanner resolve problemas de planejamento usando um conjunto de algoritmos com configuração mínima.
Embora a compreensão dos algoritmos possa fornecer detalhes úteis, com a estrutura realizando o trabalho duro para nós.
2. Dependência do Maven
Primeiro, vamos adicionar uma dependência Maven para OptaPlanner:
org.optaplanner
optaplanner-core
7.9.0.Final
Localizamos a versão mais recente do OptaPlanner emMaven Central repository.
3. Problem/Solution Class
Para resolver um problema, certamente precisamos de um específico como exemplo.
O cronograma de palestras é um exemplo adequado devido à dificuldade de equilibrar recursos, como salas, horário e professores.
3.1. CourseSchedule
CourseSchedule contém uma combinação de nossas variáveis de problema e entidades de planejamento, portanto, é a classe de solução. Como resultado, usamos várias anotações para configurá-lo.
Vamos dar uma olhada em cada um separadamente:
@PlanningSolution
public class CourseSchedule {
private List roomList;
private List periodList;
private List lectureList;
private HardSoftScore score;
A nota sanitáriaPlanningSolution diz ao OptaPlanner que esta classe contém os dados para abranger uma solução.
O OptaPlanner espera esses componentes mínimos: a entidade de planejamento, fatos do problema e uma pontuação.
3.2. Lecture
Lecture, um POJO, semelhante a:
@PlanningEntity
public class Lecture {
public Integer roomNumber;
public Integer period;
public String teacher;
@PlanningVariable(
valueRangeProviderRefs = {"availablePeriods"})
public Integer getPeriod() {
return period;
}
@PlanningVariable(
valueRangeProviderRefs = {"availableRooms"})
public Integer getRoomNumber() {
return roomNumber;
}
}
Usamos a classeLecture como entidade de planejamento, então adicionamos outra anotação no getter emCourseSchedule:
@PlanningEntityCollectionProperty
public List getLectureList() {
return lectureList;
}
Nossa entidade de planejamento contém as restrições que estão sendo definidas.
A anotaçãoPlanningVariable e as anotaçõesvalueRangeProviderRef vinculam as restrições aos fatos do problema.
Esses valores de restrição serão pontuados posteriormente em todas as entidades de planejamento.
3.3. Fatos do problema
As variáveisroomNumber andperiod atuam como restrições de maneira semelhante.
O OptaPlanner pontua as soluções como resultado da lógica usando essas variáveis. Adicionamos anotações a ambos os métodosgetter:
@ValueRangeProvider(id = "availableRooms")
@ProblemFactCollectionProperty
public List getRoomList() {
return roomList;
}
@ValueRangeProvider(id = "availablePeriods")
@ProblemFactCollectionProperty
public List getPeriodList() {
return periodList;
}
Essas listas são todos os valores possíveis usados nos camposLecture .
O OptaPlanner os preenche em todas as soluções no espaço de pesquisa.
Por fim, ele define uma pontuação para cada uma das soluções; portanto, precisamos de um campo para armazenar a pontuação:
@PlanningScore
public HardSoftScore getScore() {
return score;
}
Sem uma pontuação, o OptaPlanner não consegue encontrar a solução ótima, daí a importância enfatizada anteriormente.
4. Pontuação
Em contraste com o que vimos até agora, a classe de pontuação requer mais código personalizado.
Isso ocorre porque a calculadora de pontuação é específica para o problema e o modelo de domínio.
4.1. Java Customizado
Usamos um cálculo simples de pontuação para resolver esse problema (embora possa não parecer):
public class ScoreCalculator
implements EasyScoreCalculator {
@Override
public Score calculateScore(CourseSchedule courseSchedule) {
int hardScore = 0;
int softScore = 0;
Set occupiedRooms = new HashSet<>();
for(Lecture lecture : courseSchedule.getLectureList()) {
String roomInUse = lecture.getPeriod()
.toString() + ":" + lecture.getRoomNumber().toString();
if(occupiedRooms.contains(roomInUse)){
hardScore += -1;
} else {
occupiedRooms.add(roomInUse);
}
}
return HardSoftScore.valueOf(hardScore, softScore);
}
}
Se olharmos mais de perto o código acima, as partes importantes se tornarão mais claras. We calculate a score in the loop because the List<Lecture> contains specific non-unique combinations of rooms and periods.
OHashSet é usado para salvar uma chave única (string) para que possamos penalizar palestras duplicadas na mesma sala e período.
Como resultado, recebemos conjuntos exclusivos de salas e períodos.
4.2. Drools
Os arquivos Drools nos fornecem uma maneira rápida de alterar as regras de aplicação a arquivos. Embora a sintaxe às vezes possa ser confusa, o arquivo Drools pode ser uma maneira de gerenciar a lógica fora das classes compiladas.
Nossa regra para impedir entradas nulas se parece com esta:
global HardSoftScoreHolder scoreHolder;
rule "noNullRoomPeriod"
when
Lecture( roomNumber == null );
Lecture( period == null );
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
5. Configuração do Solver
Outro arquivo de configuração necessário, precisamos de um arquivo XML para configurar o solucionador.
5.1. Arquivo de Configuração XML
org.example.optaplanner.ScoreCalculator
10
Devido às nossas anotações na classeCourseSchedule, usamos o elementoscanAnnotatedClasses aqui para fazer a varredura de arquivos no caminho de classe.
O conteúdo do elementoscoreDirectorFactory define nossa classeScoreCalculator para conter nossa lógica de pontuação.
Quando queremos usar um arquivo Drools, substituímos o conteúdo do elemento por:
courseScheduleScoreRules.drl
Nossa configuração final é o elemento de terminação. Em vez de procurar infinitamente por uma solução otimizada que talvez nunca exista, essa configuração interromperá a pesquisa após um limite de tempo.
Dez segundos é mais que suficiente para a maioria dos problemas.
6. Teste
Configuramos nossa solução, solucionador e classes de problemas. Vamos testar!
6.1. Configurando nosso teste
Primeiro, fazemos algumas configurações:
SolverFactory solverFactory = SolverFactory
.createFromXmlResource("courseScheduleSolverConfiguration.xml");
solver = solverFactory.buildSolver();
unsolvedCourseSchedule = new CourseSchedule();
Em segundo lugar, populamos os dados na coleção de entidades de planejamento e nos objetos de fatoList do problema.
6.2. Execução e verificação de teste
Finalmente, testamos chamandosolve.
CourseSchedule solvedCourseSchedule = solver.solve(unsolvedCourseSchedule);
assertNotNull(solvedCourseSchedule.getScore());
assertEquals(-4, solvedCourseSchedule.getScore().getHardScore());
Verificamos sesolvedCourseSchedule tem uma pontuação que nos diz que temos a solução “ótima”.
Para um bônus, criamos um método de impressão que exibirá nossa solução otimizada:
public void printCourseSchedule() {
lectureList.stream()
.map(c -> "Lecture in Room "
+ c.getRoomNumber().toString()
+ " during Period " + c.getPeriod().toString())
.forEach(k -> logger.info(k));
}
Este método exibe:
Lecture in Room 1 during Period 1
Lecture in Room 2 during Period 1
Lecture in Room 1 during Period 2
Lecture in Room 2 during Period 2
Lecture in Room 1 during Period 3
Lecture in Room 2 during Period 3
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
Lecture in Room 1 during Period 1
Observe como as três últimas entradas estão se repetindo. Isso acontece porque não há solução ideal para o nosso problema. Escolhemos três períodos, duas salas de aula e dez palestras.
Existem apenas seis palestras possíveis devido a esses recursos fixos. No mínimo, essa resposta mostra ao usuário que não há salas ou períodos suficientes para conter todas as palestras.
7. Recursos extras
Nosso exemplo para o OptaPlanner que criamos foi simples, no entanto, a estrutura adicionou recursos para casos de uso mais diversos. Podemos querer implementar ou alterar nosso algoritmo para otimização e depois especificar a estrutura para usá-lo.
Devido às melhorias recentes nos recursos de multi-threading do Java, o OptaPlanner também oferece aos desenvolvedores a capacidade de usar várias implementações de multi-threading, como fork e join, resolução incremental e multilocação.
Consultedocumentation para obter mais informações.
8. Conclusão
A estrutura do OptaPlanner fornece aos desenvolvedores uma ferramenta poderosa para resolver problemas de satisfação de restrições, como agendamento e alocação de recursos.
O OptaPlanner oferece um uso mínimo de recursos da JVM, além de integrar-se ao Java EE. O autor continua a apoiar a estrutura e a Red Hat a adicionou como parte de seu Business Rules Management Suite.
Como sempre, o código pode ser encontradoover on Github.