最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Timefold: Unable to serialize unknown type: <java class 'ai.timefold.solver.core.impl.score.stream.colle

programmeradmin2浏览0评论

Context: I am trying to solve the tournament scheduling problem.

I want to analyze the solution that was generated by Timefold's algorithm but I am facing a serialization problem:

INFO:     @ timefold_solver : Validating score: type=<class 'str'>, value=0hard/-1.195230medium/-2.563480soft
INFO:     @ timefold_solver : Analysis result: Explanation of score (0hard/-1.19523medium/-2.56348soft):
    Constraint matches:
        -1.19523medium: constraint (fairAssignmentCountPerTeam) has 1 matches:
            -1.19523medium: justified with ([ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@34afdb84])
        -2.56348soft: constraint (evenlyConfrontationCount) has 1 matches:
            -2.56348soft: justified with ([ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@3192c24f])
        0: constraint (oneAssignmentPerDatePerTeam) has no matches.
        0: constraint (unavailabilityPenalty) has no matches.

INFO:     @ timefold_solver : Constraint Analysis: evenlyConfrontationCount, Weight: 0hard/0medium/-1soft, Score: 0hard/0medium/-2.56348soft
INFO:     @ timefold_solver :   Match Analysis: evenlyConfrontationCount, Score: 0hard/0medium/-2.56348soft, Justification: DefaultConstraintJustification(facts=(<java object 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>,), impact=HardMediumSoftDecimalScore(init_score=0, hard_score=Decimal('0'), medium_score=Decimal('0'), soft_score=Decimal('-2.56348')))
INFO:     @ timefold_solver : Constraint Analysis: fairAssignmentCountPerTeam, Weight: 0hard/-1medium/0soft, Score: 0hard/-1.19523medium/0soft
INFO:     @ timefold_solver :   Match Analysis: fairAssignmentCountPerTeam, Score: 0hard/-1.19523medium/0soft, Justification: DefaultConstraintJustification(facts=(<java object 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>,), impact=HardMediumSoftDecimalScore(init_score=0, hard_score=Decimal('0'), medium_score=Decimal('-1.19523'), soft_score=Decimal('0')))
INFO:     @ timefold_solver : Constraint Analysis: oneAssignmentPerDatePerTeam, Weight: -1hard/0medium/0soft, Score: 0hard/0medium/0soft
INFO:     @ timefold_solver : Constraint Analysis: unavailabilityPenalty, Weight: -1hard/0medium/0soft, Score: 0hard/0medium/0soft
INFO:     @ uvicorn.access : 127.0.0.1:59834 - "PUT /schedules/analyze HTTP/1.1" 500
ERROR:    @ uvicorn.error : Exception in ASGI application
Traceback (most recent call last):
  [...]
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/starlette/routing.py", line 72, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 296, in app
    content = await serialize_response(
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 160, in serialize_response
    return field.serialize(
           ^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/_compat.py", line 149, in serialize
    return self._type_adapter.dump_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/pydantic/type_adapter.py", line 339, in dump_python
    return self.serializer.to_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <java class 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>

Below is my code (code fragment relevant to this issue):

rest_api.py

async def setup_context(request: Request) -> TournamentSchedule:
    json = await request.json()
    return TournamentSchedule.model_validate(json,
                                         context={
                                             'teams': {
                                                 team['id']: Team.model_validate(team) for
                                                 team in json.get('teams', [])
                                             },
                                             'days': {
                                                 day['dateIndex']: Day.model_validate(day) for
                                                 day in json.get('days', [])
                                             }
                                         })

@app.put("/schedules/analyze")
async def analyze_timetable(tournament_schedule: Annotated[TournamentSchedule, Depends(setup_context)]) -> dict:
    # Call the analyze method and log its raw output
    analysis_result = solution_manager.analyze(tournament_schedule)
    logger.info(f"Analysis result: {analysis_result}")

    # Log detailed constraint analyses
    for constraint in analysis_result.constraint_analyses:
        logger.info(
            f"Constraint Analysis: {constraint.constraint_name}, Weight: {constraint.weight}, Score: {constraint.score}")
        for match in constraint.matches:
            logger.info(
                f"  Match Analysis: {match.constraint_ref.constraint_name}, Score: {match.score}, Justification: {match.justification}")


    return {'constraints': [ConstraintAnalysisDTO(
        name=constraint.constraint_name,
        weight=constraint.weight,
        score=constraint.score,
        matches=[
            MatchAnalysisDTO(
                name=match.constraint_ref.constraint_name,
                score=match.score,
                justification=match.justification
            )
            for match in constraint.matches
        ]
    ) for constraint in solution_manager.analyze(tournament_schedule).constraint_analyses]}

json_serialization.py

ScoreSerializer = PlainSerializer(lambda score: str(score) if score is not None else None,
                                  return_type=str | None)


def validate_score(v: Any, info: ValidationInfo) -> Any:
    logger.info(f"Validating score: type={type(v)}, value={v}")
    if isinstance(v, HardMediumSoftDecimalScore) or v is None:
        return v
    if isinstance(v, str):
        return HardMediumSoftDecimalScore.parse(v)
    raise ValueError('"score" should be a string')


ScoreValidator = BeforeValidator(validate_score)

class JsonDomainBase(BaseModel):
    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
        from_attributes=True,
    )

domain.py

class Day(JsonDomainBase):
    date_index: int


class Team(JsonDomainBase):
    id: Annotated[int, PlanningId]
    name: Annotated[str, Field(default=None)]


class UnavailabilityPenalty(JsonDomainBase):
    team: Annotated[Team | None,
                   IdSerializer,
                   TeamDeserializer,
                   Field(default=None)]
    day: Annotated[Day | None,
                  IdSerializer,
                  DayDeserializer,
                  Field(default=None)]


@planning_entity
class TeamAssignment(JsonDomainBase):
    id: Annotated[int, PlanningId]
    day: Annotated[Day | None,
                  IdSerializer,
                  DayDeserializer,
                  Field(default=None)]
    index_in_day: int
    pinned: Annotated[bool, PlanningPin]
    team: Annotated[Team | None,
                   PlanningVariable,
                   IdSerializer,
                   TeamDeserializer,
                   Field(default=None)]


@planning_solution
class TournamentSchedule(JsonDomainBase):
    teams: Annotated[list[Team],
                    ProblemFactCollectionProperty,
                    ValueRangeProvider]
    days: Annotated[list[Day],
                   ProblemFactCollectionProperty]
    unavailability_penalties: Annotated[list[UnavailabilityPenalty],
                                      ProblemFactCollectionProperty]
    team_assignments: Annotated[list[TeamAssignment],
                              PlanningEntityCollectionProperty]
    score: Annotated[HardMediumSoftDecimalScore | None,
                    PlanningScore,
                    ScoreSerializer,
                    ScoreValidator,
                    Field(default=None)]
    solver_status: Annotated[SolverStatus | None, Field(default=SolverStatus.NOT_SOLVING)]

constraints.py

def unavailability_penalty(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .group_by(ConstraintCollectors.load_balance(lambda team_assignment: team_assignment.team))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_MEDIUM, lambda balance: balance.unfairness())
            .as_constraint("unavailability_penalty"))


def evenly_confrontation_count(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .join(TeamAssignment,
                  Joiners.equal(lambda team_assignment: team_assignment.day),
                          Joiners.less_than(lambda assignment: assignment.team.id))
            .group_by(ConstraintCollectors.load_balance(lambda assignment, other_assignment: (assignment.team, other_assignment.team)))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_SOFT, lambda balance: balance.unfairness())
            .as_constraint("evenlyConfrontationCount"))

score_analysis.py

class MatchAnalysisDTO(JsonDomainBase):
    name: str
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    justification: object


class ConstraintAnalysisDTO(JsonDomainBase):
    name: str
    weight: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    matches: list[MatchAnalysisDTO]
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]

How can I solve this?

Update:

I tried to implement a CustomConstraintJustification to override the DefaultConstraintJustification that is called by default in the following way:

constraint.py:

def fair_assignment_count_per_team(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .group_by(ConstraintCollectors.load_balance(lambda team_assignment: team_assignment.team))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_MEDIUM, lambda balance: balance.unfairness())
            .justify_with(lambda balance, solution_score: CustomConstraintJustification(facts=("unfairness",), impact=solution_score))
            .as_constraint("fairAssignmentCountPerTeam"))

score_analysis.py:

from _jpyinterpreter import add_java_interface
from timefold.solver.score import ConstraintJustification


@add_java_interface('ai.timefold.solver.core.api.score.stream.ConstraintJustification')
class CustomConstraintJustification(ConstraintJustification):
    facts: tuple[Any, ...]
    impact: HardMediumSoftDecimalScore


class MatchAnalysisDTO(JsonDomainBase):
    name: str
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    justification: object


class ConstraintAnalysisDTO(JsonDomainBase):
    name: str
    weight: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    matches: list[MatchAnalysisDTO]
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]

But I received this error now:

INFO:     @ uvicorn.access : 127.0.0.1:64450 - "PUT /schedules/analyze HTTP/1.1" 500
ERROR:    @ uvicorn.error : Exception in ASGI application
Traceback (most recent call last):
  File "SolutionManager.java", line 114, in ai.timefold.solver.core.api.solver.SolutionManager.analyze
ai.timefold.jpyinterpreter.types.errors.ai.timefold.jpyinterpreter.types.errors.AttributeError: ai.timefold.jpyinterpreter.types.errors.AttributeError: object '0' does not have attribute 'toPlainString'

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

Traceback (most recent call last):
  File "SolutionManager.java", line 114, in ai.timefold.solver.core.api.solver.SolutionManager.analyze
Exception: Java Exception

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

Traceback (most recent call last):
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 399, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[...]
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/src/tournament_scheduling/rest_api.py", line 95, in analyze_timetable
    analysis_result = solution_manager.analyze(tournament_schedule)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/timefold/solver/_solution_manager.py", line 89, in analyze
    return ScoreAnalysis(self._delegate.analyze(convert_to_java_python_like_object(solution)))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: Consequence of a constraint (org.jpyinterpreter.user.tournament_scheduling.domain/evenlyConfrontationCount) threw an exception creating constraint justification from a tuple ({ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@ebbd6d8}).

Did I implemented/applied the custom ConstraintJustification in a wrong way? Or is there something that I am missing?

Context: I am trying to solve the tournament scheduling problem.

I want to analyze the solution that was generated by Timefold's algorithm but I am facing a serialization problem:

INFO:     @ timefold_solver : Validating score: type=<class 'str'>, value=0hard/-1.195230medium/-2.563480soft
INFO:     @ timefold_solver : Analysis result: Explanation of score (0hard/-1.19523medium/-2.56348soft):
    Constraint matches:
        -1.19523medium: constraint (fairAssignmentCountPerTeam) has 1 matches:
            -1.19523medium: justified with ([ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@34afdb84])
        -2.56348soft: constraint (evenlyConfrontationCount) has 1 matches:
            -2.56348soft: justified with ([ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@3192c24f])
        0: constraint (oneAssignmentPerDatePerTeam) has no matches.
        0: constraint (unavailabilityPenalty) has no matches.

INFO:     @ timefold_solver : Constraint Analysis: evenlyConfrontationCount, Weight: 0hard/0medium/-1soft, Score: 0hard/0medium/-2.56348soft
INFO:     @ timefold_solver :   Match Analysis: evenlyConfrontationCount, Score: 0hard/0medium/-2.56348soft, Justification: DefaultConstraintJustification(facts=(<java object 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>,), impact=HardMediumSoftDecimalScore(init_score=0, hard_score=Decimal('0'), medium_score=Decimal('0'), soft_score=Decimal('-2.56348')))
INFO:     @ timefold_solver : Constraint Analysis: fairAssignmentCountPerTeam, Weight: 0hard/-1medium/0soft, Score: 0hard/-1.19523medium/0soft
INFO:     @ timefold_solver :   Match Analysis: fairAssignmentCountPerTeam, Score: 0hard/-1.19523medium/0soft, Justification: DefaultConstraintJustification(facts=(<java object 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>,), impact=HardMediumSoftDecimalScore(init_score=0, hard_score=Decimal('0'), medium_score=Decimal('-1.19523'), soft_score=Decimal('0')))
INFO:     @ timefold_solver : Constraint Analysis: oneAssignmentPerDatePerTeam, Weight: -1hard/0medium/0soft, Score: 0hard/0medium/0soft
INFO:     @ timefold_solver : Constraint Analysis: unavailabilityPenalty, Weight: -1hard/0medium/0soft, Score: 0hard/0medium/0soft
INFO:     @ uvicorn.access : 127.0.0.1:59834 - "PUT /schedules/analyze HTTP/1.1" 500
ERROR:    @ uvicorn.error : Exception in ASGI application
Traceback (most recent call last):
  [...]
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/starlette/routing.py", line 72, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 296, in app
    content = await serialize_response(
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 160, in serialize_response
    return field.serialize(
           ^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/_compat.py", line 149, in serialize
    return self._type_adapter.dump_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/pydantic/type_adapter.py", line 339, in dump_python
    return self.serializer.to_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <java class 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>

Below is my code (code fragment relevant to this issue):

rest_api.py

async def setup_context(request: Request) -> TournamentSchedule:
    json = await request.json()
    return TournamentSchedule.model_validate(json,
                                         context={
                                             'teams': {
                                                 team['id']: Team.model_validate(team) for
                                                 team in json.get('teams', [])
                                             },
                                             'days': {
                                                 day['dateIndex']: Day.model_validate(day) for
                                                 day in json.get('days', [])
                                             }
                                         })

@app.put("/schedules/analyze")
async def analyze_timetable(tournament_schedule: Annotated[TournamentSchedule, Depends(setup_context)]) -> dict:
    # Call the analyze method and log its raw output
    analysis_result = solution_manager.analyze(tournament_schedule)
    logger.info(f"Analysis result: {analysis_result}")

    # Log detailed constraint analyses
    for constraint in analysis_result.constraint_analyses:
        logger.info(
            f"Constraint Analysis: {constraint.constraint_name}, Weight: {constraint.weight}, Score: {constraint.score}")
        for match in constraint.matches:
            logger.info(
                f"  Match Analysis: {match.constraint_ref.constraint_name}, Score: {match.score}, Justification: {match.justification}")


    return {'constraints': [ConstraintAnalysisDTO(
        name=constraint.constraint_name,
        weight=constraint.weight,
        score=constraint.score,
        matches=[
            MatchAnalysisDTO(
                name=match.constraint_ref.constraint_name,
                score=match.score,
                justification=match.justification
            )
            for match in constraint.matches
        ]
    ) for constraint in solution_manager.analyze(tournament_schedule).constraint_analyses]}

json_serialization.py

ScoreSerializer = PlainSerializer(lambda score: str(score) if score is not None else None,
                                  return_type=str | None)


def validate_score(v: Any, info: ValidationInfo) -> Any:
    logger.info(f"Validating score: type={type(v)}, value={v}")
    if isinstance(v, HardMediumSoftDecimalScore) or v is None:
        return v
    if isinstance(v, str):
        return HardMediumSoftDecimalScore.parse(v)
    raise ValueError('"score" should be a string')


ScoreValidator = BeforeValidator(validate_score)

class JsonDomainBase(BaseModel):
    model_config = ConfigDict(
        alias_generator=to_camel,
        populate_by_name=True,
        from_attributes=True,
    )

domain.py

class Day(JsonDomainBase):
    date_index: int


class Team(JsonDomainBase):
    id: Annotated[int, PlanningId]
    name: Annotated[str, Field(default=None)]


class UnavailabilityPenalty(JsonDomainBase):
    team: Annotated[Team | None,
                   IdSerializer,
                   TeamDeserializer,
                   Field(default=None)]
    day: Annotated[Day | None,
                  IdSerializer,
                  DayDeserializer,
                  Field(default=None)]


@planning_entity
class TeamAssignment(JsonDomainBase):
    id: Annotated[int, PlanningId]
    day: Annotated[Day | None,
                  IdSerializer,
                  DayDeserializer,
                  Field(default=None)]
    index_in_day: int
    pinned: Annotated[bool, PlanningPin]
    team: Annotated[Team | None,
                   PlanningVariable,
                   IdSerializer,
                   TeamDeserializer,
                   Field(default=None)]


@planning_solution
class TournamentSchedule(JsonDomainBase):
    teams: Annotated[list[Team],
                    ProblemFactCollectionProperty,
                    ValueRangeProvider]
    days: Annotated[list[Day],
                   ProblemFactCollectionProperty]
    unavailability_penalties: Annotated[list[UnavailabilityPenalty],
                                      ProblemFactCollectionProperty]
    team_assignments: Annotated[list[TeamAssignment],
                              PlanningEntityCollectionProperty]
    score: Annotated[HardMediumSoftDecimalScore | None,
                    PlanningScore,
                    ScoreSerializer,
                    ScoreValidator,
                    Field(default=None)]
    solver_status: Annotated[SolverStatus | None, Field(default=SolverStatus.NOT_SOLVING)]

constraints.py

def unavailability_penalty(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .group_by(ConstraintCollectors.load_balance(lambda team_assignment: team_assignment.team))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_MEDIUM, lambda balance: balance.unfairness())
            .as_constraint("unavailability_penalty"))


def evenly_confrontation_count(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .join(TeamAssignment,
                  Joiners.equal(lambda team_assignment: team_assignment.day),
                          Joiners.less_than(lambda assignment: assignment.team.id))
            .group_by(ConstraintCollectors.load_balance(lambda assignment, other_assignment: (assignment.team, other_assignment.team)))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_SOFT, lambda balance: balance.unfairness())
            .as_constraint("evenlyConfrontationCount"))

score_analysis.py

class MatchAnalysisDTO(JsonDomainBase):
    name: str
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    justification: object


class ConstraintAnalysisDTO(JsonDomainBase):
    name: str
    weight: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    matches: list[MatchAnalysisDTO]
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]

How can I solve this?

Update:

I tried to implement a CustomConstraintJustification to override the DefaultConstraintJustification that is called by default in the following way:

constraint.py:

def fair_assignment_count_per_team(constraint_factory: ConstraintFactory) -> Constraint:
    return (constraint_factory
            .for_each(TeamAssignment)
            .group_by(ConstraintCollectors.load_balance(lambda team_assignment: team_assignment.team))
            .penalize_decimal(HardMediumSoftDecimalScore.ONE_MEDIUM, lambda balance: balance.unfairness())
            .justify_with(lambda balance, solution_score: CustomConstraintJustification(facts=("unfairness",), impact=solution_score))
            .as_constraint("fairAssignmentCountPerTeam"))

score_analysis.py:

from _jpyinterpreter import add_java_interface
from timefold.solver.score import ConstraintJustification


@add_java_interface('ai.timefold.solver.core.api.score.stream.ConstraintJustification')
class CustomConstraintJustification(ConstraintJustification):
    facts: tuple[Any, ...]
    impact: HardMediumSoftDecimalScore


class MatchAnalysisDTO(JsonDomainBase):
    name: str
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    justification: object


class ConstraintAnalysisDTO(JsonDomainBase):
    name: str
    weight: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]
    matches: list[MatchAnalysisDTO]
    score: Annotated[HardMediumSoftDecimalScore, ScoreSerializer]

But I received this error now:

INFO:     @ uvicorn.access : 127.0.0.1:64450 - "PUT /schedules/analyze HTTP/1.1" 500
ERROR:    @ uvicorn.error : Exception in ASGI application
Traceback (most recent call last):
  File "SolutionManager.java", line 114, in ai.timefold.solver.core.api.solver.SolutionManager.analyze
ai.timefold.jpyinterpreter.types.errors.ai.timefold.jpyinterpreter.types.errors.AttributeError: ai.timefold.jpyinterpreter.types.errors.AttributeError: object '0' does not have attribute 'toPlainString'

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

Traceback (most recent call last):
  File "SolutionManager.java", line 114, in ai.timefold.solver.core.api.solver.SolutionManager.analyze
Exception: Java Exception

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

Traceback (most recent call last):
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 399, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 70, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[...]
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/fastapi/routing.py", line 191, in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/src/tournament_scheduling/rest_api.py", line 95, in analyze_timetable
    analysis_result = solution_manager.analyze(tournament_schedule)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/Desktop/workspace/timefold-quickstarts/python/tournament-scheduling/.venv/lib/python3.11/site-packages/timefold/solver/_solution_manager.py", line 89, in analyze
    return ScoreAnalysis(self._delegate.analyze(convert_to_java_python_like_object(solution)))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
java.lang.java.lang.IllegalStateException: java.lang.IllegalStateException: Consequence of a constraint (org.jpyinterpreter.user.tournament_scheduling.domain/evenlyConfrontationCount) threw an exception creating constraint justification from a tuple ({ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl@ebbd6d8}).

Did I implemented/applied the custom ConstraintJustification in a wrong way? Or is there something that I am missing?

Share Improve this question edited Jan 20 at 15:23 Diallo Francis Patrick asked Jan 19 at 5:07 Diallo Francis PatrickDiallo Francis Patrick 17713 bronze badges 2
  • Could you try to add a custom justification? i am not entirely sure why it's trying to serialize the ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl and since that is only used in the justifications, maybe overriding it might help. – Tom Cools Commented Jan 20 at 9:49
  • I have updated the question with an example of custom justification implementation, but it seems (for some reason) that this custom justification cannot override the DefaultConstraintJustification. Maybe I did something wrong. – Diallo Francis Patrick Commented Jan 20 at 15:24
Add a comment  | 

1 Answer 1

Reset to default 2

This was reported a while back as a bug in the Timefold Solver. There has been no progress yet.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论