optapy / optapy

OptaPy is an AI constraint solver for Python to optimize planning and scheduling problems.

Home Page:https://www.optapy.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Quickstarts School Timetabling throws error when running

mmmvvvppp opened this issue · comments

I recently built a wheel from the latest commit of optapy and installed it into the virtual environment in optapy-quickstarts. However, when I try to run the school-timetabling example, I get the following error:

16:55:13.418 [main        ] INFO  Solving started: time spent (113), best score (-38init/0hard/0soft), environment mode (REPRODUCIBLE), move thread count (NONE), random (JDK with seed 0).
Traceback (most recent call last):
  File "DefaultSolver.java", line 193, in org.optaplanner.core.impl.solver.DefaultSolver.solve
java.lang.java.lang.ClassCastException: java.lang.ClassCastException: class org.optaplanner.jpyinterpreter.types.PythonNone cannot be cast to class org.jpyinterpreter.user.domain.Timeslot$$2 (org.optaplanner.jpyinterpreter.types.PythonNone is in unnamed module of loader 'app'; org.jpyinterpreter.user.domain.Timeslot$$2 is in unnamed module of loader 'OptaPlanner Gizmo Python Bytecode ClassLoader' @3632be31)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "DefaultSolver.java", line 193, in org.optaplanner.core.impl.solver.DefaultSolver.solve
Exception: Java Exception

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/user/workspaces/optapy-quickstarts/.venv/lib/python3.10/site-packages/optapy/optaplanner_api_wrappers.py", line 419, in solve
    return _unwrap_java_object(self._java_solve(wrapped_problem))
java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: The property (timeslot) setterMethod (public void org.jpyinterpreter.user.domain.Lesson$$2.setTimeslot(org.jpyinterpreter.user.domain.Timeslot$$2)) on bean of class (class org.jpyinterpreter.user.domain.Lesson$$2) throws an exception for value (null).

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/user/workspaces/optapy-quickstarts/school-timetabling/main.py", line 80, in <module>
    solution = solver.solve(generate_problem())
  File "/Users/user/workspaces/optapy-quickstarts/.venv/lib/python3.10/site-packages/optapy/optaplanner_api_wrappers.py", line 426, in solve
    raise RuntimeError(error_message) from e
RuntimeError: An error occurred during solving. This can occur when functions take the wrong number of parameters (ex: a setter that does not take exactly one parameter) or by a function returning an incompatible return type (ex: returning a str in a filter, which expects a bool). This can also occur when an exception is raised when evaluating constraints/getters/setters.

I'll enumerate my procedure, as I may be making a mistake with building the wheel and installing into optapy-quickstarts which produces this error.

  1. Clone optapy. From the optapy root directory, ./mvnw clean install and python -m build
  2. Copy optapy-8.31.1b0-py3-none-any.whl to optapy-quickstarts/school-timetabling directory
  3. From optapy-quickstarts/school-timetabling, run pip install optapy-8.31.1b0-py3-none-any.whl then python main.py

This is related to #152 , although it appears it passing None although NoneType is not assignable to Timeslot; changing the type annotation on Lesson to:

    timeslot: Optional[Timeslot]
    room: Optional[Room]

avoids the issue; my guess it is because the Timeslot and Room are set to None in the constructor (despite not being assignable to None from type annotations)

So the Optional[] typing is required because (1) this is a non-nullable planning variable and (2) it's initialized to None? It seems like that's a very normal initialization for most under-constrained planning problems.
Would you mind clarifying how typing is used and the assumptions that jpyinterpreter has regarding None? I couldn't find anything in the documentation about it and it appears to be essential to correct function even for non-decorated funtions/classes. Are all typing declarations used literally by jpyinterpreter?

Typing declarations are assumed to be correct by jpyinterpreter, and are used to change generic method calls (which have to go through __getattribute__ lookups) into specific method calls (this significantly increase speed, for school timetabling, without the Optional, it three times faster because of the specific method calls). That being said, planning variables should be allowed to take the value None even if they are not annotated Optional (this assumes simple getters and setters, which changes it from setting it to None to deleting the attribute).

This is due to how Python None works, it cannot be treated as null (i.e. isinstance(None, x) is false for all x except NoneType = type(None); whereas isinstance(null, x) is true for all x in Java; this means we cannot assign None to attributes that are not Optional).

Fixed by #156 ; ultimately, the issue was caused because the attributes of the class were typed, and the setters were not. This made optapy think None was assignable to the field, when really it was not.

Thank you once again. I appreciate the responsiveness and clarity.