diff --git a/tests/test_constraint_streams.py b/tests/test_constraint_streams.py index 488de865..4a86996e 100644 --- a/tests/test_constraint_streams.py +++ b/tests/test_constraint_streams.py @@ -558,6 +558,31 @@ def define_constraints(constraint_factory: ConstraintFactory): assert len(justifications) == 0 +def test_long_scores(): + @constraint_provider + def define_constraints(constraint_factory: ConstraintFactory): + return [ + constraint_factory.for_each(Entity) + .reward(SimpleScore.ONE, lambda e: e.value.number) + .as_constraint('Maximize value') + ] + + score_manager = create_score_manager(define_constraints) + entity_a: Entity = Entity('A') + entity_b: Entity = Entity('B') + + # Overflow an int + value_1 = Value(3_000_000_000) + value_2 = Value(6_000_000_000) + + entity_a.value = value_1 + entity_b.value = value_2 + + problem = Solution([entity_a, entity_b], [value_1, value_2]) + + assert score_manager.explain(problem).score == SimpleScore.of(9_000_000_000) + + ignored_python_functions = { '_call_comparison_java_joiner', '__init__', diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java index 82fe8218..7a9e193b 100644 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java +++ b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMapping.java @@ -9,9 +9,9 @@ import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.collections.PythonLikeTuple; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendable.BendableScore; +import ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore; -public final class BendableScorePythonJavaTypeMapping implements PythonJavaTypeMapping { +public final class BendableScorePythonJavaTypeMapping implements PythonJavaTypeMapping { private final PythonLikeType type; private final Constructor constructor; private final Field initScoreField; @@ -34,20 +34,20 @@ public PythonLikeType getPythonType() { } @Override - public Class getJavaType() { - return BendableScore.class; + public Class getJavaType() { + return BendableLongScore.class; } - private static PythonLikeTuple toPythonList(int[] scores) { + private static PythonLikeTuple toPythonList(long[] scores) { PythonLikeTuple out = new PythonLikeTuple<>(); - for (int score : scores) { + for (long score : scores) { out.add(PythonInteger.valueOf(score)); } return out; } @Override - public PythonLikeObject toPythonObject(BendableScore javaObject) { + public PythonLikeObject toPythonObject(BendableLongScore javaObject) { try { var instance = constructor.newInstance(); initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); @@ -60,23 +60,23 @@ public PythonLikeObject toPythonObject(BendableScore javaObject) { } @Override - public BendableScore toJavaObject(PythonLikeObject pythonObject) { + public BendableLongScore toJavaObject(PythonLikeObject pythonObject) { try { var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); var hardScoreTuple = ((PythonLikeTuple) hardScoresField.get(pythonObject)); var softScoreTuple = ((PythonLikeTuple) softScoresField.get(pythonObject)); - int[] hardScores = new int[hardScoreTuple.size()]; - int[] softScores = new int[softScoreTuple.size()]; + long[] hardScores = new long[hardScoreTuple.size()]; + long[] softScores = new long[softScoreTuple.size()]; for (int i = 0; i < hardScores.length; i++) { - hardScores[i] = ((PythonInteger) hardScoreTuple.get(i)).value.intValue(); + hardScores[i] = ((PythonInteger) hardScoreTuple.get(i)).value.longValue(); } for (int i = 0; i < softScores.length; i++) { - softScores[i] = ((PythonInteger) softScoreTuple.get(i)).value.intValue(); + softScores[i] = ((PythonInteger) softScoreTuple.get(i)).value.longValue(); } if (initScore == 0) { - return BendableScore.of(hardScores, softScores); + return BendableLongScore.of(hardScores, softScores); } else { - return BendableScore.ofUninitialized(initScore, hardScores, softScores); + return BendableLongScore.ofUninitialized(initScore, hardScores, softScores); } } catch (IllegalAccessException e) { throw new RuntimeException(e); diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java index 98ebe894..f98cf322 100644 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java +++ b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMapping.java @@ -8,10 +8,10 @@ import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore; +import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore; public final class HardMediumSoftScorePythonJavaTypeMapping - implements PythonJavaTypeMapping { + implements PythonJavaTypeMapping { private final PythonLikeType type; private final Constructor constructor; private final Field initScoreField; @@ -36,12 +36,12 @@ public PythonLikeType getPythonType() { } @Override - public Class getJavaType() { - return HardMediumSoftScore.class; + public Class getJavaType() { + return HardMediumSoftLongScore.class; } @Override - public PythonLikeObject toPythonObject(HardMediumSoftScore javaObject) { + public PythonLikeObject toPythonObject(HardMediumSoftLongScore javaObject) { try { var instance = constructor.newInstance(); initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); @@ -55,16 +55,16 @@ public PythonLikeObject toPythonObject(HardMediumSoftScore javaObject) { } @Override - public HardMediumSoftScore toJavaObject(PythonLikeObject pythonObject) { + public HardMediumSoftLongScore toJavaObject(PythonLikeObject pythonObject) { try { var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.intValue(); - var mediumScore = ((PythonInteger) mediumScoreField.get(pythonObject)).value.intValue(); - var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.intValue(); + var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.longValue(); + var mediumScore = ((PythonInteger) mediumScoreField.get(pythonObject)).value.longValue(); + var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.longValue(); if (initScore == 0) { - return HardMediumSoftScore.of(hardScore, mediumScore, softScore); + return HardMediumSoftLongScore.of(hardScore, mediumScore, softScore); } else { - return HardMediumSoftScore.ofUninitialized(initScore, hardScore, mediumScore, softScore); + return HardMediumSoftLongScore.ofUninitialized(initScore, hardScore, mediumScore, softScore); } } catch (IllegalAccessException e) { throw new RuntimeException(e); diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java index 4ef2efb7..20b94866 100644 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java +++ b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMapping.java @@ -8,9 +8,9 @@ import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore; -public final class HardSoftScorePythonJavaTypeMapping implements PythonJavaTypeMapping { +public final class HardSoftScorePythonJavaTypeMapping implements PythonJavaTypeMapping { private final PythonLikeType type; private final Constructor constructor; private final Field initScoreField; @@ -33,12 +33,12 @@ public PythonLikeType getPythonType() { } @Override - public Class getJavaType() { - return HardSoftScore.class; + public Class getJavaType() { + return HardSoftLongScore.class; } @Override - public PythonLikeObject toPythonObject(HardSoftScore javaObject) { + public PythonLikeObject toPythonObject(HardSoftLongScore javaObject) { try { var instance = constructor.newInstance(); initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); @@ -51,15 +51,15 @@ public PythonLikeObject toPythonObject(HardSoftScore javaObject) { } @Override - public HardSoftScore toJavaObject(PythonLikeObject pythonObject) { + public HardSoftLongScore toJavaObject(PythonLikeObject pythonObject) { try { var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.intValue(); - var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.intValue(); + var hardScore = ((PythonInteger) hardScoreField.get(pythonObject)).value.longValue(); + var softScore = ((PythonInteger) softScoreField.get(pythonObject)).value.longValue(); if (initScore == 0) { - return HardSoftScore.of(hardScore, softScore); + return HardSoftLongScore.of(hardScore, softScore); } else { - return HardSoftScore.ofUninitialized(initScore, hardScore, softScore); + return HardSoftLongScore.ofUninitialized(initScore, hardScore, softScore); } } catch (IllegalAccessException e) { throw new RuntimeException(e); diff --git a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java index 40f3fcdb..749ec0f6 100644 --- a/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java +++ b/timefold-solver-python-core/src/main/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMapping.java @@ -8,9 +8,9 @@ import ai.timefold.jpyinterpreter.types.PythonJavaTypeMapping; import ai.timefold.jpyinterpreter.types.PythonLikeType; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore; -public final class SimpleScorePythonJavaTypeMapping implements PythonJavaTypeMapping { +public final class SimpleScorePythonJavaTypeMapping implements PythonJavaTypeMapping { private final PythonLikeType type; private final Constructor constructor; private final Field initScoreField; @@ -31,12 +31,12 @@ public PythonLikeType getPythonType() { } @Override - public Class getJavaType() { - return SimpleScore.class; + public Class getJavaType() { + return SimpleLongScore.class; } @Override - public PythonLikeObject toPythonObject(SimpleScore javaObject) { + public PythonLikeObject toPythonObject(SimpleLongScore javaObject) { try { var instance = constructor.newInstance(); initScoreField.set(instance, PythonInteger.valueOf(javaObject.initScore())); @@ -48,14 +48,14 @@ public PythonLikeObject toPythonObject(SimpleScore javaObject) { } @Override - public SimpleScore toJavaObject(PythonLikeObject pythonObject) { + public SimpleLongScore toJavaObject(PythonLikeObject pythonObject) { try { var initScore = ((PythonInteger) initScoreField.get(pythonObject)).value.intValue(); - var score = ((PythonInteger) scoreField.get(pythonObject)).value.intValue(); + var score = ((PythonInteger) scoreField.get(pythonObject)).value.longValue(); if (initScore == 0) { - return SimpleScore.of(score); + return SimpleLongScore.of(score); } else { - return SimpleScore.ofUninitialized(initScore, score); + return SimpleLongScore.ofUninitialized(initScore, score); } } catch (IllegalAccessException e) { throw new RuntimeException(e); diff --git a/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py b/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py index c78c8f58..0ac85d1f 100644 --- a/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py +++ b/timefold-solver-python-core/src/main/python/_jpype_type_conversions.py @@ -138,6 +138,56 @@ def applyAsInt(self, argument1, argument2, argument3, argument4, argument5): return JInt(self.delegate(argument1, argument2, argument3, argument4, argument5)) +@JImplements('java.util.function.ToLongFunction', deferred=True) +class PythonToLongFunction: + def __init__(self, delegate): + self.delegate = delegate + + @JOverride + def applyAsLong(self, argument): + return JLong(self.delegate(argument)) + + +@JImplements('java.util.function.ToLongBiFunction', deferred=True) +class PythonToLongBiFunction: + def __init__(self, delegate): + self.delegate = delegate + + @JOverride + def applyAsLong(self, argument1, argument2): + return JLong(self.delegate(argument1, argument2)) + + +@JImplements('ai.timefold.solver.core.api.function.ToLongTriFunction', deferred=True) +class PythonToLongTriFunction: + def __init__(self, delegate): + self.delegate = delegate + + @JOverride + def applyAsLong(self, argument1, argument2, argument3): + return JLong(self.delegate(argument1, argument2, argument3)) + + +@JImplements('ai.timefold.solver.core.api.function.ToLongQuadFunction', deferred=True) +class PythonToLongQuadFunction: + def __init__(self, delegate): + self.delegate = delegate + + @JOverride + def applyAsLong(self, argument1, argument2, argument3, argument4): + return JLong(self.delegate(argument1, argument2, argument3, argument4)) + + +@JImplements('ai.timefold.solver.core.api.function.ToLongPentaFunction', deferred=True) +class PythonToLongPentaFunction: + def __init__(self, delegate): + self.delegate = delegate + + @JOverride + def applyAsLong(self, argument1, argument2, argument3, argument4, argument5): + return JLong(self.delegate(argument1, argument2, argument3, argument4, argument5)) + + @JImplements('java.util.function.Predicate', deferred=True) class PythonPredicate: diff --git a/timefold-solver-python-core/src/main/python/_timefold_java_interop.py b/timefold-solver-python-core/src/main/python/_timefold_java_interop.py index 311caa44..05a7f833 100644 --- a/timefold-solver-python-core/src/main/python/_timefold_java_interop.py +++ b/timefold-solver-python-core/src/main/python/_timefold_java_interop.py @@ -106,10 +106,10 @@ def register_score_python_java_type_mappings(): _scores_registered = True from .score._score import SimpleScore, HardSoftScore, HardMediumSoftScore, BendableScore - from ai.timefold.solver.core.api.score.buildin.simple import SimpleScore as _SimpleScore - from ai.timefold.solver.core.api.score.buildin.hardsoft import HardSoftScore as _HardSoftScore - from ai.timefold.solver.core.api.score.buildin.hardmediumsoft import HardMediumSoftScore as _HardMediumSoftScore - from ai.timefold.solver.core.api.score.buildin.bendable import BendableScore as _BendableScore + from ai.timefold.solver.core.api.score.buildin.simplelong import SimpleLongScore as _SimpleScore + from ai.timefold.solver.core.api.score.buildin.hardsoftlong import HardSoftLongScore as _HardSoftScore + from ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong import HardMediumSoftLongScore as _HardMediumSoftScore + from ai.timefold.solver.core.api.score.buildin.bendablelong import BendableLongScore as _BendableScore from ai.timefold.solver.python.score import (SimpleScorePythonJavaTypeMapping, HardSoftScorePythonJavaTypeMapping, diff --git a/timefold-solver-python-core/src/main/python/score/_constraint_stream.py b/timefold-solver-python-core/src/main/python/score/_constraint_stream.py index d6395ebb..285ebf3f 100644 --- a/timefold-solver-python-core/src/main/python/score/_constraint_stream.py +++ b/timefold-solver-python-core/src/main/python/score/_constraint_stream.py @@ -477,8 +477,8 @@ def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A], in if match_weigher is None: return UniConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type) else: - return UniConstraintBuilder(self.delegate.penalize(constraint_weight, - to_int_function_cast(match_weigher, self.a_type)), + return UniConstraintBuilder(self.delegate.penalizeLong(constraint_weight, + to_long_function_cast(match_weigher, self.a_type)), self.a_type) def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \ @@ -504,8 +504,8 @@ def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] if match_weigher is None: return UniConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type) else: - return UniConstraintBuilder(self.delegate.reward(constraint_weight, - to_int_function_cast(match_weigher, self.a_type)), + return UniConstraintBuilder(self.delegate.rewardLong(constraint_weight, + to_long_function_cast(match_weigher, self.a_type)), self.a_type) def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \ @@ -532,8 +532,9 @@ def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] if match_weigher is None: return UniConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type) else: - return UniConstraintBuilder(self.delegate.impact(constraint_weight, - to_int_function_cast(match_weigher, self.a_type)), + return UniConstraintBuilder(self.delegate.impactLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type)), self.a_type) def penalize_configurable(self, match_weigher: Callable[[A], int] = None) -> \ @@ -560,8 +561,8 @@ def penalize_configurable(self, match_weigher: Callable[[A], int] = None) -> \ if match_weigher is None: return UniConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type) else: - return UniConstraintBuilder(self.delegate.penalizeConfigurable(to_int_function_cast(match_weigher, - self.a_type)), + return UniConstraintBuilder(self.delegate.penalizeConfigurableLong(to_long_function_cast(match_weigher, + self.a_type)), self.a_type) def reward_configurable(self, match_weigher: Callable[[A], int] = None) -> \ @@ -588,8 +589,8 @@ def reward_configurable(self, match_weigher: Callable[[A], int] = None) -> \ if match_weigher is None: return UniConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type) else: - return UniConstraintBuilder(self.delegate.rewardConfigurable(to_int_function_cast(match_weigher, - self.a_type)), + return UniConstraintBuilder(self.delegate.rewardConfigurableLong(to_long_function_cast(match_weigher, + self.a_type)), self.a_type) def impact_configurable(self, match_weigher: Callable[[A], int] = None) -> \ @@ -616,8 +617,8 @@ def impact_configurable(self, match_weigher: Callable[[A], int] = None) -> \ if match_weigher is None: return UniConstraintBuilder(self.delegate.impactConfigurable(), self.a_type) else: - return UniConstraintBuilder(self.delegate.impactConfigurable(to_int_function_cast(match_weigher, - self.a_type)), + return UniConstraintBuilder(self.delegate.impactConfigurableLong(to_long_function_cast(match_weigher, + self.a_type)), self.a_type) @@ -1023,10 +1024,10 @@ def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], if match_weigher is None: return BiConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.penalize(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), + return BiConstraintBuilder(self.delegate.penalizeLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), self.a_type, self.b_type) def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \ @@ -1052,10 +1053,10 @@ def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], i if match_weigher is None: return BiConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.reward(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), + return BiConstraintBuilder(self.delegate.rewardLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), self.a_type, self.b_type) def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \ @@ -1082,10 +1083,10 @@ def impact(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], i if match_weigher is None: return BiConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.impact(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), + return BiConstraintBuilder(self.delegate.impactLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), self.a_type, self.b_type) def penalize_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ @@ -1112,11 +1113,11 @@ def penalize_configurable(self, match_weigher: Callable[[A, B], int] = None) -> if match_weigher is None: return BiConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.penalizeConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) + return BiConstraintBuilder(self.delegate.penalizeConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), + self.a_type, self.b_type) def reward_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ 'BiConstraintBuilder[A, B, ScoreType]': @@ -1142,11 +1143,11 @@ def reward_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ if match_weigher is None: return BiConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.rewardConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) + return BiConstraintBuilder(self.delegate.rewardConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), + self.a_type, self.b_type) def impact_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ 'BiConstraintBuilder[A, B, ScoreType]': @@ -1172,11 +1173,11 @@ def impact_configurable(self, match_weigher: Callable[[A, B], int] = None) -> \ if match_weigher is None: return BiConstraintBuilder(self.delegate.impactConfigurable(), self.a_type, self.b_type) else: - return BiConstraintBuilder(self.delegate.impactConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type)), - self.a_type, self.b_type) + return BiConstraintBuilder(self.delegate.impactConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type)), + self.a_type, self.b_type) class TriConstraintStream(Generic[A, B, C]): @@ -1568,11 +1569,11 @@ def penalize(self, constraint_weight: ScoreType, return TriConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type, self.b_type, self.c_type) else: - return TriConstraintBuilder(self.delegate.penalize(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), + return TriConstraintBuilder(self.delegate.penalizeLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), self.a_type, self.b_type, self.c_type) def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B, C], int] = None) -> \ @@ -1596,13 +1597,14 @@ def reward(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B, C] a `TriConstraintBuilder` """ if match_weigher is None: - return TriConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type, self.c_type) + return TriConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type, + self.c_type) else: - return TriConstraintBuilder(self.delegate.reward(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), + return TriConstraintBuilder(self.delegate.rewardLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), self.a_type, self.b_type, self.c_type) def impact(self, constraint_weight: ScoreType, @@ -1630,11 +1632,11 @@ def impact(self, constraint_weight: ScoreType, return TriConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type, self.b_type, self.c_type) else: - return TriConstraintBuilder(self.delegate.impact(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), + return TriConstraintBuilder(self.delegate.impactLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), self.a_type, self.b_type, self.c_type) def penalize_configurable(self, match_weigher: Callable[[A, B, C], int] = None) \ @@ -1662,12 +1664,12 @@ def penalize_configurable(self, match_weigher: Callable[[A, B, C], int] = None) return TriConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type, self.b_type, self.c_type) else: - return TriConstraintBuilder(self.delegate.penalizeConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) + return TriConstraintBuilder(self.delegate.penalizeConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), + self.a_type, self.b_type, self.c_type) def reward_configurable(self, match_weigher: Callable[[A, B, C], int] = None) -> \ 'TriConstraintBuilder[A, B, C, ScoreType]': @@ -1694,12 +1696,12 @@ def reward_configurable(self, match_weigher: Callable[[A, B, C], int] = None) -> return TriConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type, self.b_type, self.c_type) else: - return TriConstraintBuilder(self.delegate.rewardConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) + return TriConstraintBuilder(self.delegate.rewardConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), + self.a_type, self.b_type, self.c_type) def impact_configurable(self, match_weigher: Callable[[A, B, C], int] = None) \ -> 'TriConstraintBuilder[A, B, C, ScoreType]': @@ -1726,12 +1728,12 @@ def impact_configurable(self, match_weigher: Callable[[A, B, C], int] = None) \ return TriConstraintBuilder(self.delegate.impactConfigurable(), self.a_type, self.b_type, self.c_type) else: - return TriConstraintBuilder(self.delegate.impactConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type)), - self.a_type, self.b_type, self.c_type) + return TriConstraintBuilder(self.delegate.impactConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type)), + self.a_type, self.b_type, self.c_type) class QuadConstraintStream(Generic[A, B, C, D]): @@ -2107,12 +2109,12 @@ def penalize(self, constraint_weight: ScoreType, return QuadConstraintBuilder(self.delegate.penalize(constraint_weight), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.penalize(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), + return QuadConstraintBuilder(self.delegate.penalizeLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), self.a_type, self.b_type, self.c_type, self.d_type) def reward(self, constraint_weight: ScoreType, @@ -2139,12 +2141,12 @@ def reward(self, constraint_weight: ScoreType, return QuadConstraintBuilder(self.delegate.reward(constraint_weight), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.reward(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), + return QuadConstraintBuilder(self.delegate.rewardLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), self.a_type, self.b_type, self.c_type, self.d_type) def impact(self, constraint_weight: ScoreType, @@ -2172,12 +2174,12 @@ def impact(self, constraint_weight: ScoreType, return QuadConstraintBuilder(self.delegate.impact(constraint_weight), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.impact(constraint_weight, - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), + return QuadConstraintBuilder(self.delegate.impactLong(constraint_weight, + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), self.a_type, self.b_type, self.c_type, self.d_type) def penalize_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ @@ -2205,13 +2207,13 @@ def penalize_configurable(self, match_weigher: Callable[[A, B, C, D], int] = Non return QuadConstraintBuilder(self.delegate.penalizeConfigurable(), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.penalizeConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) + return QuadConstraintBuilder(self.delegate.penalizeConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), + self.a_type, self.b_type, self.c_type, self.d_type) def reward_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': @@ -2237,13 +2239,13 @@ def reward_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) return QuadConstraintBuilder(self.delegate.rewardConfigurable(), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.rewardConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) + return QuadConstraintBuilder(self.delegate.rewardConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), + self.a_type, self.b_type, self.c_type, self.d_type) def impact_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) \ -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]': @@ -2270,13 +2272,13 @@ def impact_configurable(self, match_weigher: Callable[[A, B, C, D], int] = None) return QuadConstraintBuilder(self.delegate.impactConfigurable(), self.a_type, self.b_type, self.c_type, self.d_type) else: - return QuadConstraintBuilder(self.delegate.impactConfigurable( - to_int_function_cast(match_weigher, - self.a_type, - self.b_type, - self.c_type, - self.d_type)), - self.a_type, self.b_type, self.c_type, self.d_type) + return QuadConstraintBuilder(self.delegate.impactConfigurableLong( + to_long_function_cast(match_weigher, + self.a_type, + self.b_type, + self.c_type, + self.d_type)), + self.a_type, self.b_type, self.c_type, self.d_type) # Must be on the bottom, .group_by depends on this module diff --git a/timefold-solver-python-core/src/main/python/score/_function_translator.py b/timefold-solver-python-core/src/main/python/score/_function_translator.py index 623cd5c7..ed80bbd6 100644 --- a/timefold-solver-python-core/src/main/python/score/_function_translator.py +++ b/timefold-solver-python-core/src/main/python/score/_function_translator.py @@ -1,8 +1,9 @@ from .._jpype_type_conversions import (_convert_to_java_compatible_object, PythonFunction, PythonBiFunction, PythonTriFunction, PythonQuadFunction, PythonPentaFunction, PythonToIntFunction, PythonToIntBiFunction, PythonToIntTriFunction, PythonToIntQuadFunction, - PythonPredicate, PythonBiPredicate, PythonTriPredicate, PythonQuadPredicate, - PythonPentaPredicate) + PythonToLongFunction, PythonToLongBiFunction, PythonToLongTriFunction, + PythonToLongQuadFunction, PythonPredicate, PythonBiPredicate, + PythonTriPredicate, PythonQuadPredicate, PythonPentaPredicate) from _jpyinterpreter import translate_python_bytecode_to_java_bytecode, check_current_python_version_supported import jpype.imports # noqa from jpype import JImplements, JOverride @@ -205,6 +206,46 @@ def default_to_int_function_cast(function, arg_count): raise ValueError(f'Unexpected argument count: {arg_count}') +def to_long_function_cast(function, *type_args): + arg_count = len(inspect.signature(function).parameters) + if len(type_args) != arg_count: + raise ValueError(f'Invalid function: expected {len(type_args)} arguments but got {arg_count}') + + if _check_if_type_args_are_python_object_wrappers(type_args): + return default_to_long_function_cast(function, arg_count) + + from java.util.function import ToLongFunction, ToLongBiFunction + from ai.timefold.solver.core.api.function import ToLongTriFunction, ToLongQuadFunction + try: + _check_if_bytecode_translation_possible() + if arg_count == 1: + return translate_python_bytecode_to_java_bytecode(function, ToLongFunction, *type_args) + elif arg_count == 2: + return translate_python_bytecode_to_java_bytecode(function, ToLongBiFunction, *type_args) + elif arg_count == 3: + return translate_python_bytecode_to_java_bytecode(function, ToLongTriFunction, *type_args) + elif arg_count == 4: + return translate_python_bytecode_to_java_bytecode(function, ToLongQuadFunction, *type_args) + except: # noqa + return default_to_long_function_cast(function, arg_count) + + raise ValueError(f'Unexpected argument count: {arg_count}') + + +def default_to_long_function_cast(function, arg_count): + if arg_count == 1: + return PythonToLongFunction(lambda a: _convert_to_java_compatible_object(function(a))) + elif arg_count == 2: + return PythonToLongBiFunction(lambda a, b: _convert_to_java_compatible_object(function(a, b))) + elif arg_count == 3: + return PythonToLongTriFunction(lambda a, b, c: _convert_to_java_compatible_object(function(a, b, c))) + elif arg_count == 4: + return PythonToLongQuadFunction(lambda a, b, c, d: _convert_to_java_compatible_object(function(a, b, c, d))) + else: + raise ValueError(f'Unexpected argument count: {arg_count}') + + __all__ = ['predicate_cast', 'function_cast', - 'to_int_function_cast'] + 'to_int_function_cast', + 'to_long_function_cast'] diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java index 96e77b88..7b890fa4 100644 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java +++ b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/BendableScorePythonJavaTypeMappingTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.bendable.BendableScore; +import ai.timefold.solver.core.api.score.buildin.bendablelong.BendableLongScore; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,12 +23,12 @@ void getPythonType() { @Test void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(BendableScore.class); + assertThat(typeMapping.getJavaType()).isEqualTo(BendableLongScore.class); } @Test void toPythonObject() { - var initializedScore = BendableScore.of(new int[] { 10, 20, 30 }, new int[] { 4, 5 }); + var initializedScore = BendableLongScore.of(new long[] { 10, 20, 30 }, new long[] { 4, 5 }); var initializedPythonScore = (PythonBendableScore) typeMapping.toPythonObject(initializedScore); @@ -43,7 +43,7 @@ void toPythonObject() { assertThat(initializedPythonScore.soft_scores.get(0)).isEqualTo(PythonInteger.valueOf(4)); assertThat(initializedPythonScore.soft_scores.get(1)).isEqualTo(PythonInteger.valueOf(5)); - var uninitializedScore = BendableScore.ofUninitialized(-300, new int[] { 10, 20, 30 }, new int[] { 4, 5 }); + var uninitializedScore = BendableLongScore.ofUninitialized(-300, new long[] { 10, 20, 30 }, new long[] { 4, 5 }); var uninitializedPythonScore = (PythonBendableScore) typeMapping.toPythonObject(uninitializedScore); assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java index 6890cba6..b86374d8 100644 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java +++ b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardMediumSoftScorePythonJavaTypeMappingTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore; +import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,12 +23,12 @@ void getPythonType() { @Test void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardMediumSoftScore.class); + assertThat(typeMapping.getJavaType()).isEqualTo(HardMediumSoftLongScore.class); } @Test void toPythonObject() { - var initializedScore = HardMediumSoftScore.of(300, 20, 1); + var initializedScore = HardMediumSoftLongScore.of(300, 20, 1); var initializedPythonScore = (PythonHardMediumSoftScore) typeMapping.toPythonObject(initializedScore); @@ -37,7 +37,7 @@ void toPythonObject() { assertThat(initializedPythonScore.medium_score).isEqualTo(PythonInteger.valueOf(20)); assertThat(initializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(1)); - var uninitializedScore = HardMediumSoftScore.ofUninitialized(-4000, 300, 20, 1); + var uninitializedScore = HardMediumSoftLongScore.ofUninitialized(-4000, 300, 20, 1); var uninitializedPythonScore = (PythonHardMediumSoftScore) typeMapping.toPythonObject(uninitializedScore); assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-4000)); diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java index a9c7f27a..6926c3bd 100644 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java +++ b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/HardSoftScorePythonJavaTypeMappingTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import ai.timefold.solver.core.api.score.buildin.hardsoftlong.HardSoftLongScore; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,12 +23,12 @@ void getPythonType() { @Test void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(HardSoftScore.class); + assertThat(typeMapping.getJavaType()).isEqualTo(HardSoftLongScore.class); } @Test void toPythonObject() { - var initializedScore = HardSoftScore.of(10, 2); + var initializedScore = HardSoftLongScore.of(10, 2); var initializedPythonScore = (PythonHardSoftScore) typeMapping.toPythonObject(initializedScore); @@ -36,7 +36,7 @@ void toPythonObject() { assertThat(initializedPythonScore.hard_score).isEqualTo(PythonInteger.valueOf(10)); assertThat(initializedPythonScore.soft_score).isEqualTo(PythonInteger.valueOf(2)); - var uninitializedScore = HardSoftScore.ofUninitialized(-300, 20, 1); + var uninitializedScore = HardSoftLongScore.ofUninitialized(-300, 20, 1); var uninitializedPythonScore = (PythonHardSoftScore) typeMapping.toPythonObject(uninitializedScore); assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-300)); diff --git a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java index 406bc9ea..d0536fc0 100644 --- a/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java +++ b/timefold-solver-python-core/src/test/java/ai/timefold/solver/python/score/SimpleScorePythonJavaTypeMappingTest.java @@ -1,10 +1,9 @@ package ai.timefold.solver.python.score; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; import ai.timefold.jpyinterpreter.types.numeric.PythonInteger; -import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore; +import ai.timefold.solver.core.api.score.buildin.simplelong.SimpleLongScore; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -24,19 +23,19 @@ void getPythonType() { @Test void getJavaType() { - assertThat(typeMapping.getJavaType()).isEqualTo(SimpleScore.class); + assertThat(typeMapping.getJavaType()).isEqualTo(SimpleLongScore.class); } @Test void toPythonObject() { - var initializedScore = SimpleScore.of(10); + var initializedScore = SimpleLongScore.of(10); var initializedPythonScore = (PythonSimpleScore) typeMapping.toPythonObject(initializedScore); assertThat(initializedPythonScore.init_score).isEqualTo(PythonInteger.ZERO); assertThat(initializedPythonScore.score).isEqualTo(PythonInteger.valueOf(10)); - var uninitializedScore = SimpleScore.ofUninitialized(-5, 20); + var uninitializedScore = SimpleLongScore.ofUninitialized(-5, 20); var uninitializedPythonScore = (PythonSimpleScore) typeMapping.toPythonObject(uninitializedScore); assertThat(uninitializedPythonScore.init_score).isEqualTo(PythonInteger.valueOf(-5));