Optaplanner 多线程异常:"The externalObject ... has no known workingObject"
Optaplanner multithreading exception: "The externalObject ... has no known workingObject"
TLDR: 在Optaplanner中启用多线程应该是一行,但会抛出异常
我正在尝试使用电子游戏中的可配置负载来优化伤害计算。就上下文而言,玩家可以为他们拥有的每件物品配置“重铸”,这会增加力量或暴击伤害的统计数据。最终的伤害计算必须最大化为强度和暴击伤害的组合。出于这个原因,我正在使用 Optaplanner 来为物品分配重铸。
但是,在 XML 求解器配置中通过 <moveThreadCount>AUTO</moveThreadCount>
启用多线程会引发异常(在单线程执行中不会发生):
Caused by: java.lang.IllegalStateException: The externalObject (ReforgeProblemFact(id=897f4bab-80e0-4eb9-a1d7-974f7cddfd9e, name=Fierce, rarity=COMMON, strength=4, critDamage=0)) with planningId ((class net.javaman.optaplanner_reproducible.ReforgeProblemFact,897f4bab-80e0-4eb9-a1d7-974f7cddfd9e)) has no known workingObject (null).
Maybe the workingObject was never added because the planning solution doesn't have a @ProblemFactCollectionProperty annotation on a member with instances of the externalObject's class (class net.javaman.optaplanner_reproducible.ReforgeProblemFact).
This SO question 类似,但其答案并未修复此示例中的异常。
我在下面的代码中删除了包和导入。 Full GitHub repository link
项目结构:
src/main/
kotlin/
net/javaman/optaplanner_reproducible/
Rarity.kt
ReforgeProblemFact.kt
ItemPlanningEntity.kt
ReforgePlanningSolution.kt
MaximizeDamageConstraintProvider.kt
Main.kt
resources/
reforgeSolverConfig.xml
Rarity.kt:
enum class Rarity {
COMMON,
RARE,
LEGENDARY
}
ReforgeProblemFact.kt:
data class ReforgeProblemFact(
@PlanningId
val id: UUID,
val name: String,
val rarity: Rarity,
val strength: Int,
val critDamage: Int
)
ItemPlanningEntity.kt:
@PlanningEntity
data class ItemPlanningEntity @JvmOverloads constructor(
@PlanningId
val id: UUID? = null,
val rarity: Rarity? = null,
@PlanningVariable(valueRangeProviderRefs = ["reforgeRange"])
var reforge: ReforgeProblemFact? = null,
@ValueRangeProvider(id = "reforgeRange")
@ProblemFactCollectionProperty
val availableReforges: List<ReforgeProblemFact>? = null
)
ReforgePlanningSolution.kt:
@PlanningSolution
class ReforgePlanningSolution @JvmOverloads constructor(
@PlanningEntityCollectionProperty
val availableItems: List<ItemPlanningEntity>? = null,
@PlanningScore
val score: HardSoftScore? = null,
)
MaximizeDamageConstraintProvider.kt:
class MaximizeDamageConstraintProvider : ConstraintProvider {
override fun defineConstraints(factory: ConstraintFactory): Array<Constraint> = arrayOf(maximizeDamage(factory))
// This approach does not take full advantage of incremental solving,
// but it is necessary to compute strength and critDamage together in the same equation
private fun maximizeDamage(factory: ConstraintFactory) = factory.from(ItemPlanningEntity::class.java)
.map(ItemPlanningEntity::reforge) // Get each item's reforge
.groupBy({ 0 }, toList { reforge: ReforgeProblemFact? -> reforge }) // Compile into one List<ReforgeProblemFact>
.reward("damage", HardSoftScore.ONE_SOFT) { _, reforges: List<ReforgeProblemFact?> ->
val strengthSum = reforges.stream().collect(Collectors.summingInt { reforge -> reforge?.strength ?: 0 })
val critDamageSum = reforges.stream().collect(Collectors.summingInt { reforge -> reforge?.critDamage ?: 0 })
(100 + strengthSum) * (100 + critDamageSum)
}
}
Main.kt:
class Main {
companion object {
private val allReforges = listOf(
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.COMMON, 0, 3),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.COMMON, 4, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.COMMON, 2, 1),
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.RARE, 1, 3),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.RARE, 5, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.RARE, 3, 2),
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.LEGENDARY, 1, 4),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.LEGENDARY, 6, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.LEGENDARY, 4, 2),
)
private val solverManager: SolverManager<ReforgePlanningSolution, UUID> = SolverManager.create(
SolverConfig.createFromXmlResource("reforgeSolverConfig.xml")
)
@JvmStatic
fun main(args: Array<String>) {
val availableItems = generateAvailableItems(
mapOf(
Rarity.COMMON to 4,
Rarity.RARE to 3,
Rarity.LEGENDARY to 1
)
)
val solverJob = solverManager.solve(UUID.randomUUID(), ReforgePlanningSolution(availableItems))
val solution = solverJob.finalBestSolution
solution.availableItems!!
.map { it.reforge!! }
.forEach { println(it.rarity.name + " " + it.name) }
}
private fun generateAvailableItems(itemCounts: Map<Rarity, Int>): MutableList<ItemPlanningEntity> {
val availableItems = mutableListOf<ItemPlanningEntity>()
for (itemCount in itemCounts) {
for (count in 0 until itemCount.value) {
val rarity = itemCount.key
availableItems.add(
ItemPlanningEntity(
UUID.randomUUID(),
rarity,
null,
allReforges.filter { it.rarity == rarity }
)
)
}
}
return availableItems
}
}
}
The externalObject (ReforgeProblemFact(id=897f...)) with planningId ((class ReforgeProblemFact,897f...)) has no known workingObject (null).
planningId ((class ReforgeProblemFact
没有意义,因为 planningId class 是模型中的 UUID。
查看PlanningIdLookUpStrategy
第71行的代码,报错信息正确。在该行上放置一个断点,然后查看 planningId 变量的类型 class。应该是UUID。
我重新访问了 the similar SO question。在尝试了几个不同版本的答案后,它终于奏效了。这里有一个比另一个更详细的解释post:
每个 PlanningEntity
的 ProblemFactCollectionProperty
必须是 PlanningSolution
中主要 ProblemFactCollectionProperty
的一部分。这意味着实体和解决方案都应该定义它们的问题事实。以下是为我修复的问题:
保持 ItemPlanningEntity.kt 不变。
在 ReforgePlanningSolution.kt 中包含全局 ProblemFactCollectionProperty
:
@PlanningSolution
class ReforgePlanningSolution @JvmOverloads constructor(
@PlanningEntityCollectionProperty
val availableItems: List<ItemPlanningEntity>? = null,
@ProblemFactCollectionProperty
val allReforges: List<ReforgeProblemFact>? = null,
@PlanningScore
val score: HardSoftScore? = null
)
实例化Main.kt中的解决方案时定义全局集合:
val solverJob = solverManager.solve(UUID.randomUUID(), ReforgePlanningSolution(availableItems, allReforges))
Upgrade OptaPlanner,例如最近发布的 8.12.0.Final,以获得像这样的有用错误消息:
Caused by: java.lang.IllegalStateException: The externalObject (2018-10-01T10:15-12:15) with planningId ((class org.optaplanner.examples.conferencescheduling.domain.Timeslot, 0)) has no known workingObject (null).
Maybe the workingObject was never added because the planning solution doesn't have a @ProblemFactCollectionProperty annotation on a member with instances of the externalObject's class (class org.optaplanner.examples.conferencescheduling.domain.Timeslot).
at org.optaplanner.core.impl.domain.lookup.PlanningIdLookUpStrategy.lookUpWorkingObject(PlanningIdLookUpStrategy.java:76)
有一条错误消息,其中有一条“可能”行直接指向另一个答案中显示的解决方案。
TLDR: 在Optaplanner中启用多线程应该是一行,但会抛出异常
我正在尝试使用电子游戏中的可配置负载来优化伤害计算。就上下文而言,玩家可以为他们拥有的每件物品配置“重铸”,这会增加力量或暴击伤害的统计数据。最终的伤害计算必须最大化为强度和暴击伤害的组合。出于这个原因,我正在使用 Optaplanner 来为物品分配重铸。
但是,在 XML 求解器配置中通过 <moveThreadCount>AUTO</moveThreadCount>
启用多线程会引发异常(在单线程执行中不会发生):
Caused by: java.lang.IllegalStateException: The externalObject (ReforgeProblemFact(id=897f4bab-80e0-4eb9-a1d7-974f7cddfd9e, name=Fierce, rarity=COMMON, strength=4, critDamage=0)) with planningId ((class net.javaman.optaplanner_reproducible.ReforgeProblemFact,897f4bab-80e0-4eb9-a1d7-974f7cddfd9e)) has no known workingObject (null).
Maybe the workingObject was never added because the planning solution doesn't have a @ProblemFactCollectionProperty annotation on a member with instances of the externalObject's class (class net.javaman.optaplanner_reproducible.ReforgeProblemFact).
This SO question 类似,但其答案并未修复此示例中的异常。
我在下面的代码中删除了包和导入。 Full GitHub repository link
项目结构:
src/main/
kotlin/
net/javaman/optaplanner_reproducible/
Rarity.kt
ReforgeProblemFact.kt
ItemPlanningEntity.kt
ReforgePlanningSolution.kt
MaximizeDamageConstraintProvider.kt
Main.kt
resources/
reforgeSolverConfig.xml
Rarity.kt:
enum class Rarity {
COMMON,
RARE,
LEGENDARY
}
ReforgeProblemFact.kt:
data class ReforgeProblemFact(
@PlanningId
val id: UUID,
val name: String,
val rarity: Rarity,
val strength: Int,
val critDamage: Int
)
ItemPlanningEntity.kt:
@PlanningEntity
data class ItemPlanningEntity @JvmOverloads constructor(
@PlanningId
val id: UUID? = null,
val rarity: Rarity? = null,
@PlanningVariable(valueRangeProviderRefs = ["reforgeRange"])
var reforge: ReforgeProblemFact? = null,
@ValueRangeProvider(id = "reforgeRange")
@ProblemFactCollectionProperty
val availableReforges: List<ReforgeProblemFact>? = null
)
ReforgePlanningSolution.kt:
@PlanningSolution
class ReforgePlanningSolution @JvmOverloads constructor(
@PlanningEntityCollectionProperty
val availableItems: List<ItemPlanningEntity>? = null,
@PlanningScore
val score: HardSoftScore? = null,
)
MaximizeDamageConstraintProvider.kt:
class MaximizeDamageConstraintProvider : ConstraintProvider {
override fun defineConstraints(factory: ConstraintFactory): Array<Constraint> = arrayOf(maximizeDamage(factory))
// This approach does not take full advantage of incremental solving,
// but it is necessary to compute strength and critDamage together in the same equation
private fun maximizeDamage(factory: ConstraintFactory) = factory.from(ItemPlanningEntity::class.java)
.map(ItemPlanningEntity::reforge) // Get each item's reforge
.groupBy({ 0 }, toList { reforge: ReforgeProblemFact? -> reforge }) // Compile into one List<ReforgeProblemFact>
.reward("damage", HardSoftScore.ONE_SOFT) { _, reforges: List<ReforgeProblemFact?> ->
val strengthSum = reforges.stream().collect(Collectors.summingInt { reforge -> reforge?.strength ?: 0 })
val critDamageSum = reforges.stream().collect(Collectors.summingInt { reforge -> reforge?.critDamage ?: 0 })
(100 + strengthSum) * (100 + critDamageSum)
}
}
Main.kt:
class Main {
companion object {
private val allReforges = listOf(
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.COMMON, 0, 3),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.COMMON, 4, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.COMMON, 2, 1),
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.RARE, 1, 3),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.RARE, 5, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.RARE, 3, 2),
ReforgeProblemFact(UUID.randomUUID(), "Clean", Rarity.LEGENDARY, 1, 4),
ReforgeProblemFact(UUID.randomUUID(), "Fierce", Rarity.LEGENDARY, 6, 0),
ReforgeProblemFact(UUID.randomUUID(), "Shiny", Rarity.LEGENDARY, 4, 2),
)
private val solverManager: SolverManager<ReforgePlanningSolution, UUID> = SolverManager.create(
SolverConfig.createFromXmlResource("reforgeSolverConfig.xml")
)
@JvmStatic
fun main(args: Array<String>) {
val availableItems = generateAvailableItems(
mapOf(
Rarity.COMMON to 4,
Rarity.RARE to 3,
Rarity.LEGENDARY to 1
)
)
val solverJob = solverManager.solve(UUID.randomUUID(), ReforgePlanningSolution(availableItems))
val solution = solverJob.finalBestSolution
solution.availableItems!!
.map { it.reforge!! }
.forEach { println(it.rarity.name + " " + it.name) }
}
private fun generateAvailableItems(itemCounts: Map<Rarity, Int>): MutableList<ItemPlanningEntity> {
val availableItems = mutableListOf<ItemPlanningEntity>()
for (itemCount in itemCounts) {
for (count in 0 until itemCount.value) {
val rarity = itemCount.key
availableItems.add(
ItemPlanningEntity(
UUID.randomUUID(),
rarity,
null,
allReforges.filter { it.rarity == rarity }
)
)
}
}
return availableItems
}
}
}
The externalObject (ReforgeProblemFact(id=897f...)) with planningId ((class ReforgeProblemFact,897f...)) has no known workingObject (null).
planningId ((class ReforgeProblemFact
没有意义,因为 planningId class 是模型中的 UUID。
查看PlanningIdLookUpStrategy
第71行的代码,报错信息正确。在该行上放置一个断点,然后查看 planningId 变量的类型 class。应该是UUID。
我重新访问了 the similar SO question。在尝试了几个不同版本的答案后,它终于奏效了。这里有一个比另一个更详细的解释post:
每个 PlanningEntity
的 ProblemFactCollectionProperty
必须是 PlanningSolution
中主要 ProblemFactCollectionProperty
的一部分。这意味着实体和解决方案都应该定义它们的问题事实。以下是为我修复的问题:
保持 ItemPlanningEntity.kt 不变。
在 ReforgePlanningSolution.kt 中包含全局 ProblemFactCollectionProperty
:
@PlanningSolution
class ReforgePlanningSolution @JvmOverloads constructor(
@PlanningEntityCollectionProperty
val availableItems: List<ItemPlanningEntity>? = null,
@ProblemFactCollectionProperty
val allReforges: List<ReforgeProblemFact>? = null,
@PlanningScore
val score: HardSoftScore? = null
)
实例化Main.kt中的解决方案时定义全局集合:
val solverJob = solverManager.solve(UUID.randomUUID(), ReforgePlanningSolution(availableItems, allReforges))
Upgrade OptaPlanner,例如最近发布的 8.12.0.Final,以获得像这样的有用错误消息:
Caused by: java.lang.IllegalStateException: The externalObject (2018-10-01T10:15-12:15) with planningId ((class org.optaplanner.examples.conferencescheduling.domain.Timeslot, 0)) has no known workingObject (null).
Maybe the workingObject was never added because the planning solution doesn't have a @ProblemFactCollectionProperty annotation on a member with instances of the externalObject's class (class org.optaplanner.examples.conferencescheduling.domain.Timeslot).
at org.optaplanner.core.impl.domain.lookup.PlanningIdLookUpStrategy.lookUpWorkingObject(PlanningIdLookUpStrategy.java:76)
有一条错误消息,其中有一条“可能”行直接指向另一个答案中显示的解决方案。