问题配置更改后,如何配置OptaPlanner仅重新启动某些求解器阶段?

时间:2018-11-06 21:40:19

标签: optaplanner

在基于OptaPlanner的应用程序中,我想使用ProblemFactChange,根据https://docs.optaplanner.org/7.13.0.Final/optaplanner-docs/html_single/index.html#problemFactChange,它将重新启动所有求解器阶段。

问题是我不想重新启动分区搜索阶段-求解器应进入下一个阶段(CH),然后进入本地搜索阶段。

是否可以使其以某种方式工作?

2 个答案:

答案 0 :(得分:0)

有趣的用例。我首先想到的是使用自定义Termination,但这仍然会导致阶段启动开销-而且Termination接口不是公共api。

这真的是一个RFE-我们需要能够在阶段配置中插入一个条件。

答案 1 :(得分:0)

我认为我设法解决了这个问题。我创建了两个求解器行为:第一个行为完成后从求解器中删除分区搜索阶段,第二个行为恢复阶段开始时间以保留终止结束。

行为的基类,请注意,我使用非公共API。 我已将所有类放在原始OptaPlanner的程序包中,以便可以在无需反思的情况下访问受保护和受程序包保护的类。

package org.optaplanner.core.impl.phase.scope

import org.optaplanner.core.impl.solver.DefaultSolver


/**
 * Base interface for all behaviors
 */
abstract class SolverBehavior<T>(protected val solver: DefaultSolver<T>) {

    abstract fun apply()

    abstract fun unapply()
}

以下行为在完成后从求解器阶段中删除了分区搜索阶段

package org.optaplanner.core.impl.solver

import org.optaplanner.core.impl.partitionedsearch.PartitionedSearchPhase
import org.optaplanner.core.impl.phase.event.PhaseLifecycleListener
import org.optaplanner.core.impl.phase.scope.AbstractPhaseScope
import org.optaplanner.core.impl.phase.scope.AbstractStepScope
import org.optaplanner.core.impl.phase.scope.SolverBehavior
import org.optaplanner.core.impl.solver.scope.DefaultSolverScope


/**
 * Ensures that [org.optaplanner.core.impl.partitionedsearch.PartitionedSearchPhase] run only once
 */
class RunPartitionedSearchOnceBehavior<T>(solver: DefaultSolver<T>) : SolverBehavior<T>(solver) {

    private var solverStartCount: Int = 0

    private val isFirstSolverCycle: Boolean get() = solverStartCount <= 1

    override fun apply() {
        registerForSolverEvents()
    }

    override fun unapply() {
        unregisterFromSolverEvents()
    }

    private fun registerForSolverEvents() {
        solver.addPhaseLifecycleListener(phaseLifecycleListener)
    }

    private fun unregisterFromSolverEvents() {
        solver.removePhaseLifecycleListener(phaseLifecycleListener)
    }

    private fun handleSolverStart() {
        incrementSolverStartCount()
        removePartitionedSearchPhasesIfNecessary()
    }

    private fun removePartitionedSearchPhasesIfNecessary() {
        if (!isFirstSolverCycle) {
            removePartitionedSearchPhases()
        }
    }

    private fun removePartitionedSearchPhases() {
        val phaseListIterator = solver.phaseList.iterator()
        for (phase in phaseListIterator) {
            if (phase is PartitionedSearchPhase) {
                phaseListIterator.remove()
            }
        }
    }

    private fun incrementSolverStartCount() {
        solverStartCount++
    }


    private val phaseLifecycleListener = object : PhaseLifecycleListener<T> {
        override fun solvingStarted(solverScope: DefaultSolverScope<T>) = handleSolverStart()

        override fun phaseStarted(phaseScope: AbstractPhaseScope<T>) {}

        override fun stepStarted(stepScope: AbstractStepScope<T>) {}

        override fun solvingEnded(solverScope: DefaultSolverScope<T>) {}

        override fun phaseEnded(phaseScope: AbstractPhaseScope<T>) {}

        override fun stepEnded(stepScope: AbstractStepScope<T>) {}
    }
}

以下行为(包含在Kotlin文件中)恢复所有阶段的开始时间,以保留其终止时间。

package org.optaplanner.core.impl.phase.scope

import org.optaplanner.core.impl.constructionheuristic.scope.ConstructionHeuristicPhaseScope
import org.optaplanner.core.impl.localsearch.scope.LocalSearchPhaseScope
import org.optaplanner.core.impl.phase.event.PhaseLifecycleListener
import org.optaplanner.core.impl.solver.DefaultSolver
import org.optaplanner.core.impl.solver.scope.DefaultSolverScope
import java.lang.IllegalStateException
import java.util.*
import java.util.concurrent.atomic.AtomicInteger


private var AbstractPhaseScope<*>.startDate
    get() = Date(startingSystemTimeMillis)
    set(value) {
        startingSystemTimeMillis = value.time
    }

private data class PhaseScopeId constructor(private val phaseClass: Class<*>, private val phaseIndex: Int)

/**
 * When solver restarts it restores phase's start time information in order to preserve termination times.
 * This helps smooth [org.optaplanner.core.impl.solver.ProblemFactChange] execution
 */
class PreservePhaseStartTimeBehavior<T>(solver: DefaultSolver<T>) : SolverBehavior<T>(solver) {

    private var solverStartCount: Int = 0

    private var startedPhasesCounters: MutableMap<Class<out AbstractPhaseScope<T>>, AtomicInteger> = mutableMapOf()

    private val isFirstSolverCycle: Boolean get() = solverStartCount <= 1

    private val phasesStartDates: MutableMap<PhaseScopeId, Date> = mutableMapOf()

    private val startTimePreservationPhaseIds: MutableSet<PhaseScopeId> = mutableSetOf()

    fun addLocalSearchPhaseStartTimePreservation(solverPhaseIndex: Int) {
        startTimePreservationPhaseIds.add(PhaseScopeId(LocalSearchPhaseScope::class.java, solverPhaseIndex))
    }

    fun addConstructionHeuristicPhaseStartTimePreservation(solverPhaseIndex: Int) {
        startTimePreservationPhaseIds.add(PhaseScopeId(ConstructionHeuristicPhaseScope::class.java, solverPhaseIndex))
    }

    override fun apply() {
        registerForSolverEvents()
    }

    override fun unapply() {
        unregisterFromSolverEvents()
    }

    private fun registerForSolverEvents() {
        solver.addPhaseLifecycleListener(phaseLifecycleListener)
    }

    private fun unregisterFromSolverEvents() {
        solver.removePhaseLifecycleListener(phaseLifecycleListener)
    }

    private fun handleSolverStart() {
        incrementSolverStartCount()
        resetPhaseStartCounters()
    }

    private fun handlePhaseStarted(phaseScope: AbstractPhaseScope<T>) {
        incrementPhaseStartCount(phaseScope)
        savePhaseStartTimeIfNecessary(phaseScope)
        restorePhaseStartTimeIfNecessary(phaseScope)
    }

    private fun savePhaseStartTimeIfNecessary(phaseScope: AbstractPhaseScope<T>) {
        if (isFirstSolverCycle) {
            phasesStartDates[getPhaseIdForStartedPhase(phaseScope)] = phaseScope.startDate
        }
    }

    private fun restorePhaseStartTimeIfNecessary(phaseScope: AbstractPhaseScope<T>) {
        if (!isFirstSolverCycle) {
            getPhaseIdForStartedPhase(phaseScope).let { phaseScopeId ->
                if (phaseScopeId in startTimePreservationPhaseIds) {
                    restorePhaseStartTime(phaseScope, phaseScopeId)
                }
            }
        }
    }

    private fun restorePhaseStartTime(phaseScope: AbstractPhaseScope<T>, phaseScopeId: PhaseScopeId) {
        phaseScope.startDate = phasesStartDates[phaseScopeId]
                ?: throw IllegalStateException("No preserved start date for phase scope: $phaseScopeId")
    }

    private fun incrementSolverStartCount() {
        solverStartCount++
    }

    private fun resetPhaseStartCounters() = startedPhasesCounters.clear()

    private fun incrementPhaseStartCount(phaseScope: AbstractPhaseScope<T>) {
        getStartedPhaseCounterForPhase(phaseScope).incrementAndGet()
    }

    private fun getStartedPhaseCounterForPhase(phaseScope: AbstractPhaseScope<T>): AtomicInteger {
        return startedPhasesCounters.getOrPut(phaseScope::class.java) { AtomicInteger(-1) }
    }

    private fun getPhaseIdForStartedPhase(phaseScope: AbstractPhaseScope<T>): PhaseScopeId {
        return PhaseScopeId(phaseScope::class.java, getStartedPhaseCounterForPhase(phaseScope).get())
    }

    private val phaseLifecycleListener = object : PhaseLifecycleListener<T> {
        override fun solvingStarted(solverScope: DefaultSolverScope<T>) = handleSolverStart()

        override fun phaseStarted(phaseScope: AbstractPhaseScope<T>) = handlePhaseStarted(phaseScope)

        override fun stepStarted(stepScope: AbstractStepScope<T>) {}

        override fun solvingEnded(solverScope: DefaultSolverScope<T>) {}

        override fun phaseEnded(phaseScope: AbstractPhaseScope<T>) {}

        override fun stepEnded(stepScope: AbstractStepScope<T>) {}
    }
}