Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python solver: support JSON of LoadBalance justification info #1264

Open
ge0ffrey opened this issue Dec 9, 2024 · 3 comments
Open

Python solver: support JSON of LoadBalance justification info #1264

ge0ffrey opened this issue Dec 9, 2024 · 3 comments
Labels
bug Something isn't working process/needs triage Requires initial assessment of validity, priority etc. python Is related to Python code.

Comments

@ge0ffrey
Copy link
Contributor

ge0ffrey commented Dec 9, 2024

To reproduce:

  • run score analysis on a load balancing use case and serialize to JSON (through a rest endpoint)
  • with python

Error:

Unable to serialize unknown type: <java class 'ai.timefold.solver.core.impl.score.stream.collector.LoadBalanceImpl'>
@ge0ffrey ge0ffrey added bug Something isn't working process/needs triage Requires initial assessment of validity, priority etc. labels Dec 9, 2024
@triceo triceo added the python Is related to Python code. label Dec 10, 2024
@PatrickDiallo23
Copy link

Hello @ge0ffrey,

I have reproduced this issue here where I tried to port the tournament example from Java to Python. It can be tested by pressing the "analyze" button after the Solver finishes its' job.

For a larger context, check the StackOverflow question that I submitted.

Also, I saw in the Java example that the expected JSON response should look like this:

{
    "score": "0hard/-1.19523medium/-2.56348soft",
    "constraints": [
        {
            "package": "org.acme.tournamentschedule.domain",
            "name": "evenlyConfrontationCount",
            "weight": "0hard/0medium/-1soft",
            "score": "0hard/0medium/-2.56348soft",
            "matches": [
                {
                    "score": "0hard/0medium/-2.56348soft",
                    "justification": [
                        {
                            "unfairness": 2.56348
                        }
                    ]
                }
            ],
            "matchCount": 1
        },
        {
            "package": "org.acme.tournamentschedule.domain",
            "name": "fairAssignmentCountPerTeam",
            "weight": "0hard/-1medium/0soft",
            "score": "0hard/-1.19523medium/0soft",
            "matches": [
                {
                    "score": "0hard/-1.19523medium/0soft",
                    "justification": [
                        {
                            "unfairness": 1.19523
                        }
                    ]
                }
            ],
            "matchCount": 1
        },
        {
            "package": "org.acme.tournamentschedule.domain",
            "name": "oneAssignmentPerDatePerTeam",
            "weight": "-1hard/0medium/0soft",
            "score": "0hard/0medium/0soft",
            "matches": [],
            "matchCount": 0
        },
        {
            "package": "org.acme.tournamentschedule.domain",
            "name": "unavailabilityPenalty",
            "weight": "-1hard/0medium/0soft",
            "score": "0hard/0medium/0soft",
            "matches": [],
            "matchCount": 0
        }
    ]
}

I hope these details are useful to you.

Thank you,
Patrick Diallo

@Christopher-Chianelli
Copy link
Contributor

@PatrickDiallo23 there is a workaround; use a custom justification function.

@dataclass
class LoadBalanceJustification(ConstraintJustification):
    unfairness: Decimal

 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, score: LoadBalanceJustification(balance.unfairness()))
             .as_constraint("fairAssignmentCountPerTeam"))

This causes (the Java LoadBalance class) to not be in the justification list; instead it is replaced by a double.
Unfortunately, there is a bug currently when custom justifications are used with Decimal scores, which I created a fix for: #1344 .

You can try it out locally by checking out the branch and do pip install path/to/timefold-solver when your virtual environment is activated. Alternatively, you can install it directly with pip install git+https://github.com/Christopher-Chianelli/timefold-solver.git@fix/python-justifications.

@PatrickDiallo23
Copy link

Hello @Christopher-Chianelli,

It works. Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working process/needs triage Requires initial assessment of validity, priority etc. python Is related to Python code.
Projects
None yet
Development

No branches or pull requests

4 participants