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

TypeError: Ambiguous overloads ... when using ConstraintCollectors.compose #85

Closed
MrGreenTea opened this issue Jun 2, 2022 · 8 comments

Comments

@MrGreenTea
Copy link

I am currently trying to optimize some shifts. As these shifts start every 8 hours I want to make sure that no person does more than 2 shifts in a row and has to work 24h at once.

My idea was to group all shift assignments by person and then use a composed collector to get the earliest and latest shift and then make sure these shifts do not fill the entire 24 hours (because another constraint is that every person gets exactly 3 shifts).

ONE_DAY_IN_SECONDS = 24 * 60 * 60
def diff_in_seconds(earliest, latest):
    # I tried timedelta before but that did not work either
    return (latest - earliest).total_seconds()

def no_24h_marathon(constraint_factory):
    return constraint_factory.forEach(assignment_class).groupBy(
        lambda a: a.person.name,
        ConstraintCollectors.compose(
            ConstraintCollectors.min(lambda a: a.shift.start),
            ConstraintCollectors.max(lambda a: a.shift.end),
            more_than_16h,
        )
    ).penalize("No 24h marathon", HardSoftScore.ONE_HARD, lambda _, c: c > ONE_DAY_IN_SECONDS )

When I do this I get this TypError about overloads when trying to solve:

TypeError: Ambiguous overloads found for org.optaplanner.core.api.score.stream.ConstraintCollectors.min(function) between:
	public static org.optaplanner.core.api.score.stream.uni.UniConstraintCollector org.optaplanner.core.api.score.stream.ConstraintCollectors.min(java.util.function.Function)
	public static org.optaplanner.core.api.score.stream.tri.TriConstraintCollector org.optaplanner.core.api.score.stream.ConstraintCollectors.min(org.optaplanner.core.api.function.TriFunction)
	public static org.optaplanner.core.api.score.stream.quad.QuadConstraintCollector org.optaplanner.core.api.score.stream.ConstraintCollectors.min(org.optaplanner.core.api.function.QuadFunction)
	public static org.optaplanner.core.api.score.stream.bi.BiConstraintCollector org.optaplanner.core.api.score.stream.ConstraintCollectors.min(java.util.function.BiFunction)

Should I use a different approach? Any and every help would be greatly appreciated.
Thanks for this python wrapper, it helps a lot!

@Christopher-Chianelli
Copy link
Contributor

This is caused by jpype-project/jpype#1016 , which is fixed in the latest version of JPype (which main uses, but not in the current latest release on PyPI). However, it probably still not work on main, since I need to modify the function return value so Java will understand it; this is done automatically for Joiners and constraint stream methods, but not yet for ConstraintCollectors. As a workaround, you can cast the lambda used in min/max to java.util.function.Function as shown in this SO answer: https://stackoverflow.com/a/70714798/9698517 .

@MrGreenTea
Copy link
Author

Thanks for the quick reply!
Sadly I now get another TypeError:

TypeError: No matching overloads found for *static* org.optaplanner.core.api.score.stream.ConstraintCollectors.min(java.util.function.ToIntFunction), options are:
...

My function looks like this now:

def no_24h_marathon(constraint_factory):
    def more_than_16h(earliest, latest):
        return latest - earliest
    
    def shift_start(assign):
        return assign.shift.start.timestamp()
    
    return (
        constraint_factory.forEach(assignment_class)
        .groupBy(
            lambda a: a.person.name,
            ConstraintCollectors.compose(
                # https://stackoverflow.com/questions/70507127/optapy-having-problems-with-constraints-groupby-and-sum/70714798#70714798
                ConstraintCollectors.min(
                    ToIntFunction @ shift_start
                ),
                ConstraintCollectors.max(
                    ToIntFunction @ shift_start
                ),
                more_than_16h,
            ),
        )
        .penalize(
            "No 24h marathon",
            HardSoftScore.ONE_HARD,
            lambda _, c: c <= _DIFF_IN_SECONDS,
        )
    )

@MrGreenTea
Copy link
Author

My bad, I overlooked that I have to use Function not ToIntFunction 😓

I still haven't got it to work, as I can't really calculate anything with the PythonComparable that this creates.

The code now looks like this:

_DIFF_IN_SECONDS = 16 * 60 * 60


def no_24h_marathon(constraint_factory):
    def time_difference(earliest, latest):
        return (latest - earliest).total_seconds()
    
    return (
        constraint_factory.forEach(assignment_class)
        .groupBy(
            lambda a: a.person.name,
            ConstraintCollectors.compose(
                # https://stackoverflow.com/questions/70507127/optapy-having-problems-with-constraints-groupby-and-sum/70714798#70714798
                ConstraintCollectors.min(
                    Function @ (lambda assign: assign.shift.start.timestamp())
                ),
                ConstraintCollectors.max(
                    Function @ (lambda assign: assign.shift.start.timestamp())
                ),
                time_difference,
            ),
        )
        .penalize(
            "No 24h marathon",
            HardSoftScore.ONE_HARD,
            lambda _, c: c <= _DIFF_IN_SECONDS,
        )
    )

With this I get:

TypeError: unsupported operand type(s) for -: 'org.optaplanner.optapy.PythonComparable' and 'org.optaplanner.optapy.PythonComparable'

I've tried doing this too:

def time_difference(earliest, latest):
        return int(latest) - int(earliest)

but get this error:

TypeError: int() argument must be a string, a bytes-like object or a number, not 'org.optaplanner.optapy.PythonComparable'

Sorry for bugging you like this and I am immensly grateful for your help!

@Christopher-Chianelli
Copy link
Contributor

That should be fixed by 7c8c1b0, which is in the latest release (8.19.0a1). I recommend upgrading your optapy version.

@MrGreenTea
Copy link
Author

I did that and tried around a lot. I still get the TypeError that PythonComparable is an unsupported operand type(s) for -.

It seems to work fine when using int but when I try to use datetime the error happens. I can't get around it by for example using datetime.timestamp() or trying to convert to int somewhere.

I have attached a minimal example of the error happening (GitHub does not allow adding .py files, so I had to change the extension to txt)
test_groupby.txt

When you change value_codes to for example [1, 2, 3] it works without issue though.

@Christopher-Chianelli
Copy link
Contributor

This is what I get running your code using optapy-8.19.0a1:

1009861200 - 946702800 = 63158400
11:22:55.777 [main        ] INFO  Solving started: time spent (44), best score (1), environment mode (REPRODUCIBLE), move thread count (NONE), random (JDK with seed 0).
11:22:55.782 [main        ] INFO  Solving ended: time spent (47), best score (1), score calculation speed (21/sec), phase total (2), environment mode (REPRODUCIBLE), move thread count (NONE).
<jpype._jproxy.proxy.Solution object at 0x7ff6424263e0>

I was able to reproduce it by changing the group by lambda to:

@ (lambda e: e.value.code)

The issue seems to be a typecheck that happens inside datetime.__sub__ which cause it to return NotImplemented. I probably need to update the wrapper methods to also unwrap the arguments to binary dunder methods.

@MrGreenTea
Copy link
Author

awesome, I now got it to work with converting the datetime to a int timestamp as I don't need float precision anyways :)

@Christopher-Chianelli
Copy link
Contributor

Look like the error was caused by this code: lambda _, c: c > 1, which make sense, as a timedelta is not comparable to an integer. Since ConstraintCollectors are now automatically cast, I'll close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants