Search code examples
quarkusoptaplanner

OptaPlanner domainAccessType Gizmo


I'm trying to use <domainAccessType>GIZMO</domainAccessType> in my solver config.

It seems to get fast access with most of my variables, but it throws an exception for variables in my @PlanningSolution:

17:30:17.863 [main        ] TRACE     Model annotations parsed for solution VehicleRoutingSolution:
17:30:17.866 [main        ] TRACE         Entity Standstill:
17:30:17.866 [main        ] TRACE             Shadow variable nextVisit (Fast access with generated bytecode)
17:30:17.866 [main        ] TRACE         Entity Visit:
17:30:17.866 [main        ] TRACE             Genuine variable previousStandstill (Fast access with generated bytecode)
17:30:17.866 [main        ] TRACE             Shadow variable arrivalTime (Fast access with generated bytecode)
17:30:17.866 [main        ] TRACE             Shadow variable subShift (Fast access with generated bytecode)
Exception in thread "main" java.lang.IllegalStateException: Member (solverStatus) of class (org.acme.domain.VehicleRoutingSolution) is not public and domainAccessType is GIZMO.
Maybe put the annotations onto the public getter of the field.
Maybe use domainAccessType REFLECTION instead of GIZMO.
    at org.optaplanner.core.impl.domain.common.accessor.gizmo.GizmoMemberDescriptor.<init>(GizmoMemberDescriptor.java:79)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionOrEntityDescriptor.getFieldsToSolutionFieldToMemberDescriptorMap(GizmoSolutionOrEntityDescriptor.java:63)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionOrEntityDescriptor.<init>(GizmoSolutionOrEntityDescriptor.java:39)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerImplementor.lambda$createCloneSolutionRun$6(GizmoSolutionClonerImplementor.java:294)
    at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1220)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerImplementor.createCloneSolutionRun(GizmoSolutionClonerImplementor.java:293)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerImplementor.defineClonerFor(GizmoSolutionClonerImplementor.java:157)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerImplementor.createClonerFor(GizmoSolutionClonerImplementor.java:200)
    at org.optaplanner.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerFactory.build(GizmoSolutionClonerFactory.java:31)
    at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.initSolutionCloner(SolutionDescriptor.java:601)
    at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.afterAnnotationsProcessed(SolutionDescriptor.java:545)
    at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.buildSolutionDescriptor(SolutionDescriptor.java:126)
    at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildSolutionDescriptor(DefaultSolverFactory.java:160)
    at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildScoreDirectorFactory(DefaultSolverFactory.java:133)
    at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildSolver(DefaultSolverFactory.java:87)
    at org.optaplanner.core.impl.solver.DefaultSolverManager.validateSolverFactory(DefaultSolverManager.java:69)
    at org.optaplanner.core.impl.solver.DefaultSolverManager.<init>(DefaultSolverManager.java:58)
    at org.optaplanner.core.api.solver.SolverManager.create(SolverManager.java:111)
    at org.optaplanner.core.api.solver.SolverManager.create(SolverManager.java:84)

Here is the Planning Solution I'm using:

@PlanningSolution
class VehicleRoutingSolution {

    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "visitRange")
    lateinit var visitList: List<Visit>

    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "subShiftRange")
    lateinit var subShiftList: List<SubShift>


    private var solverStatus: SolverStatus? = null
    fun getSolverStatus(): SolverStatus? {
        return solverStatus
    }
    fun setSolverStatus(solverStatus: SolverStatus?) {
        this.solverStatus = solverStatus
    }

    private var score: SimpleLongScore? = null
    @PlanningScore
    fun getScore(): SimpleLongScore? {
        return score
    }
    fun setScore(score: SimpleLongScore?) {
        this.score = score
    }

    // No-arg constructor required for OptaPlanner
    constructor() {}

    constructor(subShiftList: List<SubShift>, visitList: List<Visit>) {
        this.subShiftList = subShiftList
        this.visitList = visitList
    }

}

The solverStatus doesn't even have an annotation, so I don't understand why it's complaining about that.

And oddly, if I put my score variable section above my solverStatus section, it complains about score not being public when the getter clearly is.

Any idea what's going on here?


Solution

  • Unable to reproduce in Quarkus. See https://github.com/kiegroup/optaplanner-quickstarts/tree/stable/technology/kotlin-quarkus for an example of using OptaPlanner with Kotlin in Quarkus. I tested changing the the fields of TimeTable to private, and it works as expected.

    Outside of Quarkus, this behaviour is expected. When domain access type GIZMO is used, member accessors and a custom solution cloner is generated for the domain. The custom solution cloner requires all fields to be public; public getters/setters are not used since they are not guaranteed to be simple (they could do validation checks that throw an exception, or modify other fields).