OptaPlanner ShadowVariable

初学OptaPlanner,在测试@ShadowVariable注解时,总是报错:
The entityClass (class com.example.optatest.timeTabling.entity.Room) has an @InverseRelationShadowVariable annotated property (lessons) with a sourceClass (class com.example.optatest.timeTabling.entity.Lesson) which is not a valid planning entity.

仅在案例TimeTable的基础上,为Room类增加了一个阴影变量lessons,其余不变。

烦请各位帮忙看看如何解决。不胜感激。或者,烦请指示,在TimeTable案例的基础上,如何在Room类里增加一个lessonCount阴影变量,以便其可以跟随Lesson类里room变量变化而变化。

  1. Lesson.java
@PlanningEntity
public class Lesson {

    @PlanningId
    private Long id;

    private String subject;
    private String teacher;
    private String studentGroup;

    @PlanningVariable
    private Timeslot timeslot;
    @PlanningVariable
    private Room room;

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

    public Lesson(long id, String subject, String teacher, String studentGroup) {
        this.id = id;
        this.subject = subject;
        this.teacher = teacher;
        this.studentGroup = studentGroup;
    }

    public Lesson(long id, String subject, String teacher, String studentGroup, Timeslot timeslot, Room room) {
        this(id, subject, teacher, studentGroup);
        this.timeslot = timeslot;
        this.room = room;
    }

    @Override
    public String toString() {
        return subject + "(" + id + ")";
    }

    // ************************************************************************
    // Getters and setters
    // ************************************************************************

    public Long getId() {
        return id;
    }

    public String getSubject() {
        return subject;
    }

    public String getTeacher() {
        return teacher;
    }

    public String getStudentGroup() {
        return studentGroup;
    }

    public Timeslot getTimeslot() {
        return timeslot;
    }

    public void setTimeslot(Timeslot timeslot) {
        this.timeslot = timeslot;
    }

    public Room getRoom() {
        return room;
    }

    public void setRoom(Room room) {
        this.room = room;
    }

}
  1. Room.java
@Slf4j
@PlanningEntity
public class Room {

    @PlanningId
    private Long id;

    private String name;

    // *********** 测试01 *************************

    // @InverseRelationShadowVariable(sourceVariableName = "room")
    // public List<Lesson> lessons = new ArrayList<>();
    //
    // public Integer getLessonCount() {
    //     log.info("size: {}", lessons.size());
    //     return this.lessons.size();
    // }

    // *********** 测试02 *************************

    @ShadowVariable(variableListenerClass = LessonCountUpdatingVariableListener.class, sourceEntityClass = Lesson.class, sourceVariableName = "room")
    private Integer lessonCount = 0;

    public Integer getLessonCount() {
        return lessonCount;
    }

    public void setLessonCount(Integer lessonCount) {
        this.lessonCount = lessonCount;
    }

    public Room() {
    }

    public Room(String name) {
        this.name = name;
    }

    public Room(long id, String name) {
        this(name);
        this.id = id;
    }

    @Override
    public String toString() {
        return name;
    }

    // ************************************************************************
    // Getters and setters
    // ************************************************************************

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
  1. TimeTable.java
@PlanningSolution
public class TimeTable {

    @ProblemFactCollectionProperty
    @ValueRangeProvider
    private List<Timeslot> timeslotList;
    @ProblemFactCollectionProperty
    @ValueRangeProvider
    private List<Room> roomList;
    @PlanningEntityCollectionProperty
    private List<Lesson> lessonList;

    @PlanningScore
    private HardSoftScore score;

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

    public TimeTable(List<Timeslot> timeslotList, List<Room> roomList, List<Lesson> lessonList) {
        this.timeslotList = timeslotList;
        this.roomList = roomList;
        this.lessonList = lessonList;
    }

    // ************************************************************************
    // Getters and setters
    // ************************************************************************

    public List<Timeslot> getTimeslotList() {
        return timeslotList;
    }

    public List<Room> getRoomList() {
        return roomList;
    }

    public List<Lesson> getLessonList() {
        return lessonList;
    }

    public HardSoftScore getScore() {
        return score;
    }

}

  1. LessonCountUpdatingVariableListener.java
@Slf4j
public class LessonCountUpdatingVariableListener implements VariableListener<TimeTable, Lesson> {

    @Override
    public void beforeVariableChanged(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>beforeVariableChanged");
        Room room = lesson.getRoom();
        if (room != null) {
            scoreDirector.beforeVariableChanged(room, "lessonCount");
            room.setLessonCount(room.getLessonCount() - 1);
            scoreDirector.afterVariableChanged(room, "lessonCount");
        }
    }

    @Override
    public void afterVariableChanged(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>afterVariableChanged");
        Room room = lesson.getRoom();
        if (room != null) {
            scoreDirector.beforeVariableChanged(room, "lessonCount");
            room.setLessonCount(room.getLessonCount() + 1);
            scoreDirector.afterVariableChanged(room, "lessonCount");
        }
    }

    @Override
    public void beforeEntityAdded(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>beforeEntityAdded");
        Room room = lesson.getRoom();
        if (room != null) {
            scoreDirector.beforeVariableChanged(room, "lessonCount");
            room.setLessonCount(room.getLessonCount() + 1);
            scoreDirector.afterVariableChanged(room, "lessonCount");
        }
    }

    @Override
    public void afterEntityAdded(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>afterEntityAdded");
    }

    @Override
    public void beforeEntityRemoved(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>beforeEntityRemoved");
        Room room = lesson.getRoom();
        if (room != null) {
            scoreDirector.beforeVariableChanged(room, "lessonCount");
            room.setLessonCount(room.getLessonCount() - 1);
            scoreDirector.afterVariableChanged(room, "lessonCount");
        }
    }

    @Override
    public void afterEntityRemoved(ScoreDirector<TimeTable> scoreDirector, Lesson lesson) {
        log.info("==>afterEntityRemoved");
    }
}
  1. Test
        SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
                .withSolutionClass(TimeTable.class)
                .withEntityClasses(Lesson.class)
                .withEntityClasses(Room.class)
                .withConstraintProviderClass(TimeTableConstraintProvider.class)
                // The solver runs only for 5 seconds on this small dataset.
                // It's recommended to run for at least 5 minutes ("5m") otherwise.
                .withTerminationSpentLimit(Duration.ofSeconds(1)));

        // Load the problem
        TimeTable problem = generateDemoData();

        // Solve the problem
        Solver<TimeTable> solver = solverFactory.buildSolver();
        TimeTable solution = solver.solve(problem);
  1. 报错:
2023-08-07 09:54:48,331 ERROR [http-nio-8090-exec-5] o.a.c.c.C.[.[.[.[dispatcherServlet].log(175): Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: The entityClass (class com.example.optatest.timeTabling.entity.Room) has an @InverseRelationShadowVariable annotated property (lessonList) with a sourceClass (class com.example.optatest.timeTabling.entity.Lesson) which is not a valid planning entity.
Maybe check the annotations of the class (class com.example.optatest.timeTabling.entity.Lesson).
Maybe add the class (class com.example.optatest.timeTabling.entity.Lesson) among planning entities in the solver configuration.] with root cause
java.lang.IllegalArgumentException: The entityClass (class com.example.optatest.timeTabling.entity.Room) has an @InverseRelationShadowVariable annotated property (lessonList) with a sourceClass (class com.example.optatest.timeTabling.entity.Lesson) which is not a valid planning entity.
Maybe check the annotations of the class (class com.example.optatest.timeTabling.entity.Lesson).
Maybe add the class (class com.example.optatest.timeTabling.entity.Lesson) among planning entities in the solver configuration.
    at org.optaplanner.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor.linkShadowSources(InverseRelationShadowVariableDescriptor.java:77) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor.linkVariableDescriptors(InverseRelationShadowVariableDescriptor.java:47) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor.linkVariableDescriptors(EntityDescriptor.java:413) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.afterAnnotationsProcessed(SolutionDescriptor.java:482) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.buildSolutionDescriptor(SolutionDescriptor.java:108) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildSolutionDescriptor(DefaultSolverFactory.java:147) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:69) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at org.optaplanner.core.api.solver.SolverFactory.create(SolverFactory.java:106) ~[optaplanner-core-impl-9.41.0.Final.jar:9.41.0.Final]
    at com.example.optatest.controller.Test04Controller.test04(Test04Controller.java:27) ~[classes/:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.16.jar:5.3.16]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.58.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.16.jar:5.3.16]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.58.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.16.jar:5.3.16]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.16.jar:5.3.16]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.16.jar:5.3.16]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.16.jar:5.3.16]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.58.jar:9.0.58]
    at java.lang.Thread.run(Thread.java:829) [?:?]

写法上可能存在问题
Lesson 包含了Room 属性,Room 中又包含List,循环依赖了吧

@PlanningEntity
public class Lesson {
   
    @PlanningVariable
    private Room room;
}

你给的视频截图好像不是这么应用的。

OptaPlanner里的阴影变量必须要跟有效的规划实体相关联,你的代码里Room类不是,所以才一直报错

保证两端的类都是规划实体,然后建立关系绑定

OptaPlanner 是一个开源的基于约束编程的规划器,可以有效地解决优化问题。在 OptaPlanner 中,ShadowVariable 是一种特殊的变量,用于提高解决方案的效率。本文将详细介绍 OptaPlanner 的 ShadowVariable。

ShadowVariable 是一种不直接依赖于领域模型的变量。它们的值是根据规划算法或决策引擎确定的,并不是由领域模型状态决定的。它们的作用在于增强规划器的搜索效率,以实现更快的解决方案。

在 OptaPlanner 中,ShadowVariable 分为以下两种:

  1. PlanningVariableReference 属性:这是一种特殊变量,用于在解决方案中引用其他变量。在解决方案中,决策引擎会寻找为每个 PlanningVariableReference 属性设置的变量值。这意味着 PlanningVariableReference 不提供任何直接信息,它只是给直接关联的 PlanningVariable 提供了一个引用。

  2. ShadowVariable 属性:这是一种不直接映射到领域模型的变量,而是在规划过程中计算的变量。这些变量可以用于补充约束或者提高搜索效率。例如,在车辆路径规划问题中,我们可以定义一个 ShadowVariable,表示每个车辆行驶的距离,这会增加搜索空间,但可以通过合理地设置这个变量来提高搜索效率。

在 OptaPlanner 中,ShadowVariable 的值是根据当前解决方案中变量的值计算的,而不是从领域模型中获取。这意味着每次解决方案变化时,ShadowVariable 的值也会随之改变。这使得 OptaPlanner 可以利用 ShadowVariable 的值来加速搜索,同时还使得 ShadowVariable 的值成为解决方案的一部分。

使用 ShadowVariable 的好处在于它可以使规划器更加高效,并允许规划算法在搜索空间中探索更广泛的范围。此外,ShadowVariable 还可以用于引入新的限制条件或目标功能,从而更好地定制解决方案。在实际应用中,ShadowVariable 的使用范围非常广泛,包括车辆路线规划、员工排班、资源调度等领域。

综上所述,ShadowVariable 是 OptaPlanner 中的一种特殊变量,用于提高解决方案的效率。它们的值不直接映射到领域模型,而是根据解决方案中其他变量的值计算得出的。ShadowVariable 可以用于引入新的限制条件或目标功能,并允许规划算法在搜索空间中探索更广泛的范围。这种技术在实际应用中十分有效,可用于解决各种优化问题。

已找到答案:
需在SolverConfig中将Lesson.class和Room.class并列作为参数,如下:

        SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
                .withSolutionClass(TimeTable.class)
                .withEntityClasses(Lesson.class, Room.class)
                .withConstraintProviderClass(TimeTableConstraintProvider.class)
                .withTerminationSpentLimit(Duration.ofSeconds(1)));

您的问题似乎是由于@ShadowVariable注解的使用不正确导致的。根据OptaPlanner的文档,@ShadowVariable注解用于标记一个阴影变量(Shadow Variable),它是一个计算属性,用于优化规划问题的解决方案。

根据您提供的错误信息,问题出现在Room类的lessonList属性上,该属性被标记为@InverseRelationShadowVariable。根据错误信息,lessonList属性的源类(sourceClass)应该是一个有效的规划实体(planning entity),但是它被识别为无效的规划实体。

要解决这个问题,您可以检查以下几点:

  1. 确保Lesson类被正确地标记为规划实体(使用@PlanningEntity注解)。
  2. 确保Room类被正确地标记为规划实体(使用@PlanningEntity注解)。
  3. 确保lessonList属性在Room类中被正确地标记为阴影变量(使用@ShadowVariable注解)。

【以下回答由 GPT 生成】

OptaPlanner ShadowVariable问题解决方案:

问题内容:初始OptaPlanner,在测试@ShadowVariable注解时,总是报错。实例TimeTable的基础上,为Room类增加了一个阴影变量lessons,但是报错 "The entityClass (class com.example.optatest.timeTabling.entity.Room) has an @InverseRelationShadowVariable annotated property (lessons) with a sourceClass (class com.example.optatest.timeTabling.entity.Lesson) which is not a valid planning entity."

解决方案:

在OptaPlanner中,@ShadowVariable注解用于描述一个解的属性(阴影变量),这个属性值是根据其他规划变量计算得到的,而不是通过规划器生成的。

根据问题描述,Room类似乎不是一个有效的规划实体,因此无法在Room类中添加阴影变量。为了解决这个问题,需要将Room类标记为一个有效的规划实体,并在Room类中添加一个与Lesson类的room变量相关的阴影变量。

以下是可能的解决方案之一:

  1. 将Room类标记为一个有效的规划实体(添加@PlanningEntity注解)。
  2. 在Room类中添加一个与Lesson类的room变量相关的阴影变量lessonCount。

修改后的Room类可以如下所示:

@PlanningEntity
public class Room {

    @PlanningId
    private Long id;

    private String name;

    private int capacity;

    @InverseRelationShadowVariable(sourceVariableName = "room")
    private int lessonCount;

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

    public Room(Long id, String name, int capacity) {
        this.id = id;
        this.name = name;
        this.capacity = capacity;
    }

    // Getters and setters

    @Override
    public String toString() {
        return name + " (" + id + ")";
    }
}

在修改后的Room类中,lessonCount被标记为阴影变量,并且使用@InverseRelationShadowVariable注解引用了与Lesson类的room变量。这样,当Lesson类中的room变量发生变化时,该阴影变量也会相应地更新。

希望这个解决方案能够帮助您解决问题。如有其他疑问,请随时提问。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

OptaPlanner里的阴影变量必须要跟有效的规划实体相关联,你的代码里Room类不是,所以才一直报错