Skip to content
This repository has been archived by the owner on Jul 17, 2024. It is now read-only.

feat: introduce fairness #94

Merged
merged 9 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions tests/test_constraint_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
from timefold.solver.config import *
from timefold.solver.test import *

import inspect
import re
from typing import Annotated, List
from dataclasses import dataclass, field

from ai.timefold.solver.test.api.score.stream import (ConstraintVerifier as JavaConstraintVerifier,
SingleConstraintAssertion as JavaSingleConstraintAssertion,
SingleConstraintVerification as JavaSingleConstraintVerification,
MultiConstraintAssertion as JavaMultiConstraintAssertion,
MultiConstraintVerification as JavaMultiConstraintVerification)

def verifier_suite(verifier: ConstraintVerifier, same_value, is_value_one,
solution, e1, e2, e3, v1, v2, v3):
Expand Down Expand Up @@ -268,3 +275,37 @@ class Solution:

verifier_suite(verifier, same_value, is_value_one,
solution, e1, e2, e3, v1, v2, v3)

Christopher-Chianelli marked this conversation as resolved.
Show resolved Hide resolved

ignored_java_functions = {
'equals',
'getClass',
'hashCode',
'notify',
'notifyAll',
'toString',
'wait',
'with_constraint_stream_impl_type'
}


def test_has_all_methods():
for python_type, java_type in ((ConstraintVerifier, JavaConstraintVerifier),
(SingleConstraintAssertion, JavaSingleConstraintAssertion),
(SingleConstraintVerification, JavaSingleConstraintVerification),
(MultiConstraintAssertion, JavaMultiConstraintAssertion),
(MultiConstraintVerification, JavaMultiConstraintVerification)):
triceo marked this conversation as resolved.
Show resolved Hide resolved
missing = []
for function_name, function_impl in inspect.getmembers(java_type, inspect.isfunction):
if function_name in ignored_java_functions:
continue
snake_case_name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', function_name)
# change h_t_t_p -> http
snake_case_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake_case_name).lower()
if not hasattr(python_type, snake_case_name):
missing.append(snake_case_name)

if missing:
raise AssertionError(f'{python_type} is missing methods ({missing}) '
f'from java_type ({java_type}).)')

Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,20 @@ def concat(self, other):
else:
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')

def complement(self, cls: type[A]) -> 'UniConstraintStream[A]':
"""
Adds to the stream all instances of a given class which are not yet present in it.
These instances must be present in the solution,
which means the class needs to be either a planning entity or a problem fact.

Parameters
----------
cls : Type[A]
the type of the instances to add to the stream.
"""
result = self.delegate.complement(get_class(cls))
return TriConstraintCollector(result, self.package, self.a_type)

def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \
'UniConstraintBuilder[A, ScoreType]':
"""
Expand Down Expand Up @@ -1000,6 +1014,40 @@ def concat(self, other):
else:
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')

@overload
triceo marked this conversation as resolved.
Show resolved Hide resolved
def complement(self, cls: type[A]) -> 'BiConstraintStream[A, B]':
...

@overload
def complement(self, cls: type[A], padding: Callable[[A], B]) -> 'BiConstraintStream[A, B]':
...

def complement(self, cls: type[A], padding=None):
"""
Adds to the stream all instances of a given class which are not yet present in it.
These instances must be present in the solution,
which means the class needs to be either a planning entity or a problem fact.

The instances will be read from the first element of the input tuple.
When an output tuple needs to be created for the newly inserted instances,
the first element will be the new instance.
The rest of the tuple will be padded with the result of the padding function.

Parameters
----------
cls : Type[A]
the type of the instances to add to the stream.

padding : Callable[[A], B]
a function that computes the padding value for the second fact in the new tuple.
"""
if None == padding:
result = self.delegate.complement(get_class(cls))
return TriConstraintCollector(result, self.package, self.a_type, self.b_type)
java_padding = function_cast(padding, self.a_type)
result = self.delegate.complement(get_class(cls), java_padding)
return TriConstraintCollector(result, self.package, self.a_type, self.b_type)

def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \
'BiConstraintBuilder[A, B, ScoreType]':
"""
Expand Down Expand Up @@ -1544,6 +1592,51 @@ def concat(self, other):
else:
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')

@overload
def complement(self, cls: type[A]) -> 'TriConstraintStream[A, B, C]':
...

@overload
def complement(self, cls: type[A], padding_b: Callable[[A], B], padding_c: Callable[[A], C]) \
-> 'TriConstraintStream[A, B, C]':
...

def complement(self, cls: type[A], padding_b=None, padding_c=None):
"""
Adds to the stream all instances of a given class which are not yet present in it.
These instances must be present in the solution,
which means the class needs to be either a planning entity or a problem fact.

The instances will be read from the first element of the input tuple.
When an output tuple needs to be created for the newly inserted instances,
the first element will be the new instance.
The rest of the tuple will be padded with the result of the padding function,
applied on the new instance.

Padding functions are optional, but if one is provided, then both must-be provided.

Parameters
----------
cls : Type[A]
the type of the instances to add to the stream.

padding_b : Callable[[A], B]
a function that computes the padding value for the second fact in the new tuple.

padding_c : Callable[[A], C]
a function that computes the padding value for the third fact in the new tuple.
"""
if None == padding_b == padding_c:
result = self.delegate.complement(get_class(cls))
return TriConstraintCollector(result, self.package, self.a_type, self.b_type, self.c_type)
specified_count = sum(x is not None for x in [padding_b, padding_c])
if specified_count != 0:
raise ValueError(f'If a padding function is provided, both are expected, got {specified_count} instead.')
java_padding_b = function_cast(padding_b, self.a_type)
java_padding_c = function_cast(padding_c, self.a_type)
result = self.delegate.complement(get_class(cls), java_padding_b, java_padding_c)
return TriConstraintCollector(result, self.package, self.a_type, self.b_type, self.c_type)

def penalize(self, constraint_weight: ScoreType,
match_weigher: Callable[[A, B, C], int] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]':
"""
Expand Down Expand Up @@ -2016,7 +2109,6 @@ def map(self, *mapping_functions):
JClass('java.lang.Object'))
if len(mapping_functions) == 4:
return QuadConstraintStream(self.delegate.map(*translated_functions), self.package,

JClass('java.lang.Object'), JClass('java.lang.Object'),
JClass('java.lang.Object'), JClass('java.lang.Object'))
raise RuntimeError(f'Impossible state: missing case for {len(mapping_functions)}.')
Expand All @@ -2027,7 +2119,6 @@ def flatten_last(self, flattening_function) -> 'QuadConstraintStream[A,B,C,D]':
"""
translated_function = function_cast(flattening_function, self.d_type)
return QuadConstraintStream(self.delegate.flattenLast(translated_function), self.package,

self.a_type, self.b_type, self.c_type, JClass('java.lang.Object'))

def distinct(self) -> 'QuadConstraintStream[A,B,C,D]':
Expand Down Expand Up @@ -2083,6 +2174,55 @@ def concat(self, other):
else:
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')

@overload
def complement(self, cls: type[A]) -> 'QuadConstraintStream[A, B, C, D]':
...

@overload
def complement(self, cls: type[A], padding_b: Callable[[A], B], padding_c: Callable[[A], C],
padding_d: Callable[[A], D]) -> 'QuadConstraintStream[A, B, C, D]':
...

def complement(self, cls: type[A], padding_b=None, padding_c=None, padding_d=None):
"""
Adds to the stream all instances of a given class which are not yet present in it.
These instances must be present in the solution,
which means the class needs to be either a planning entity or a problem fact.

The instances will be read from the first element of the input tuple.
When an output tuple needs to be created for the newly inserted instances,
the first element will be the new instance.
The rest of the tuple will be padded with the result of the padding function,
applied on the new instance.

Padding functions are optional, but if one is provided, then all three must-be provided.

Parameters
----------
cls : Type[A]
the type of the instances to add to the stream.

padding_b : Callable[[A], B]
a function that computes the padding value for the second fact in the new tuple.

padding_c : Callable[[A], C]
a function that computes the padding value for the third fact in the new tuple.

padding_d : Callable[[A], D]
a function that computes the padding value for the fourth fact in the new tuple.
"""
if None == padding_b == padding_c == padding_d:
result = self.delegate.complement(get_class(cls))
return QuadConstraintCollector(result, self.package, self.a_type, self.b_type, self.c_type, self.d_type)
specified_count = sum(x is not None for x in [padding_b, padding_c, padding_d])
if specified_count != 0:
raise ValueError(f'If a padding function is provided, all 3 are expected, got {specified_count} instead.')
java_padding_b = function_cast(padding_b, self.a_type)
java_padding_c = function_cast(padding_c, self.a_type)
java_padding_d = function_cast(padding_d, self.a_type)
result = self.delegate.complement(get_class(cls), java_padding_b, java_padding_c, java_padding_d)
return QuadConstraintCollector(result, self.package, self.a_type, self.b_type, self.c_type, self.d_type)

def penalize(self, constraint_weight: ScoreType,
match_weigher: Callable[[A, B, C, D], int] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]':
"""
Expand Down
Loading
Loading