javaconstraintsoptaplanner

Pulling Info From More Than One Class in a Constraint Stream


So I am creating an application that will schedule assistants for preset weekly services. I am using Optaplanner 9.44.0.Final to do so. Currently, I am having some issues creating one of my constraints, and wondered if anyone knew of a fix.

I have a list of assistants who are willing to volunteer during services. I also have a list of services that need assistants scheduled for them. Assistants are assigned to a service via a ServiceAssignment, which tracks the service, assistant, and position of the assistant for that particular assignment. I have a number of constraints that help dictate what positions people are assigned, when they are assigned, and all that jazz. Unfortunately, what seems to be occurring is that, provided there are no constraints saying not to, an assistant will be scheduled over and over, without pulling new assistants from the list. I want to instead create a soft punishment for each assistant left out of the schedule, hoping that it will vary the assignments a bit (unfortunately I can't just say, don't schedule anyone more than x number of times, cause sometimes we don't have enough volunteers for that).

So far, this is the mocked-up constraint I have written:

private Constraint utilizedAssistantsConstraint(ConstraintFactory constraintFactory) {
            return constraintFactory.forEach(Assistant.class)
                    .filter(assistant -> constraintFactory.forEach(ServiceAssignment.class).map(ServiceAssignment::getAssistant).collect(Collector.toList()).contains(assistant))
                    .penalize(HardMediumSoftScore.ONE_SOFT)
                    .asConstraint("Utilized All Assistants Constraint");
        }

Now, this obviously doesn't work, since the uni constraint stream cannot be collected into a list like this, and casting does not work in constraint providers. Basically, what I need to do is something like this:

private Constraint utilizedAssistantsConstraint(ConstraintFactory constraintFactory) {
            return constraintFactory.forEach(Assistant.class)
                            .filter(assistant -> !isAssistantInServiceAssignments)
                    .penalize(HardMediumSoftScore.ONE_SOFT)
                    .asConstraint("Utilized All Assistants Constraint");
        }

Where isAssistantInServiceAssignments is pseudo code for some way to check if the assistant appears in a ServiceAssignment somewhere. I am struggling to come up with a way to access the ServiceAssignment objects that the app has generated, since I need the Assistant class as well.


Solution

  • I think you're looking for conditional propagation.

    private Constraint utilizedAssistantsConstraint(ConstraintFactory constraintFactory) {
            return constraintFactory.forEach(Assistant.class)
                    .ifNotExists(ServiceAssignment.class,
                            Joiners.equal(Functions.identity(), ServiceAssignment::getAssistant))
                    .penalize(HardMediumSoftScore.ONE_SOFT)
                    .asConstraint("Unused assistant");
        }