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

Commit 0a08865

Browse files
committed
feat: introduce fairness
1 parent 5aa2cbd commit 0a08865

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

timefold-solver-python-core/src/main/python/score/_constraint_stream.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,20 @@ def concat(self, other):
454454
else:
455455
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')
456456

457+
def complement(self, item_type: Type[A]) -> 'UniConstraintStream[A]':
458+
"""
459+
Adds to the stream all instances of a given class which are not yet present in it.
460+
These instances must be present in the solution,
461+
which means the class needs to be either a planning entity or a problem fact.
462+
463+
Parameters
464+
----------
465+
item_type : Type[A]
466+
the type of the instances to add to the stream.
467+
"""
468+
item_type = get_class(item_type)
469+
return UniConstraintStream(self.delegate.complement(item_type), self.package, self.a_type)
470+
457471
def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A], int] = None) -> \
458472
'UniConstraintBuilder[A, ScoreType]':
459473
"""
@@ -1000,6 +1014,29 @@ def concat(self, other):
10001014
else:
10011015
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')
10021016

1017+
def complement(self, item_type: Type[A], padding_function: Callable[[A], B] = None) -> 'BiConstraintStream[A, B]':
1018+
"""
1019+
Adds to the stream all instances of a given class which are not yet present in it.
1020+
These instances must be present in the solution,
1021+
which means the class needs to be either a planning entity or a problem fact.
1022+
1023+
The instances will be read from the first element of the input tuple.
1024+
When an output tuple needs to be created for the newly inserted instances,
1025+
the first element will be the new instance.
1026+
The rest of the tuple will be padded with the result of the padding function,
1027+
applied on the new instance.
1028+
1029+
Parameters
1030+
----------
1031+
item_type : Type[A]
1032+
the type of the instances to add to the stream.
1033+
1034+
padding_function : Callable[[A], B]
1035+
a function that computes the padding value for the new tuple.
1036+
"""
1037+
item_type = get_class(item_type)
1038+
return BiConstraintStream(self.delegate.complement(item_type, padding_function), self.package, self.a_type, self.b_type)
1039+
10031040
def penalize(self, constraint_weight: ScoreType, match_weigher: Callable[[A, B], int] = None) -> \
10041041
'BiConstraintBuilder[A, B, ScoreType]':
10051042
"""
@@ -1544,6 +1581,34 @@ def concat(self, other):
15441581
else:
15451582
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')
15461583

1584+
def complement(self, item_type: Type[A], padding_function_b: Callable[[A], B] = None,
1585+
padding_function_c: Callable[[A], C] = None) -> 'TriConstraintStream[A, B, C]':
1586+
"""
1587+
Adds to the stream all instances of a given class which are not yet present in it.
1588+
These instances must be present in the solution,
1589+
which means the class needs to be either a planning entity or a problem fact.
1590+
1591+
The instances will be read from the first element of the input tuple.
1592+
When an output tuple needs to be created for the newly inserted instances,
1593+
the first element will be the new instance.
1594+
The rest of the tuple will be padded with the result of the padding function,
1595+
applied on the new instance.
1596+
1597+
Parameters
1598+
----------
1599+
item_type : Type[A]
1600+
the type of the instances to add to the stream.
1601+
1602+
padding_function_b : Callable[[A], B]
1603+
a function that computes the padding value for the second fact in the new tuple.
1604+
1605+
padding_function_c : Callable[[A], C]
1606+
a function that computes the padding value for the third fact in the new tuple.
1607+
"""
1608+
item_type = get_class(item_type)
1609+
return TriConstraintStream(self.delegate.complement(item_type, padding_function_b, padding_function_c),
1610+
self.package, self.a_type, self.b_type, self.c_type)
1611+
15471612
def penalize(self, constraint_weight: ScoreType,
15481613
match_weigher: Callable[[A, B, C], int] = None) -> 'TriConstraintBuilder[A, B, C, ScoreType]':
15491614
"""
@@ -2083,6 +2148,39 @@ def concat(self, other):
20832148
else:
20842149
raise RuntimeError(f'Unhandled constraint stream type {type(other)}.')
20852150

2151+
def complement(self, item_type: Type[A], padding_function_b: Callable[[A], B] = None,
2152+
padding_function_c: Callable[[A], C] = None,
2153+
padding_function_d: Callable[[A], D] = None) -> 'QuadConstraintStream[A, B, C, D]':
2154+
"""
2155+
Adds to the stream all instances of a given class which are not yet present in it.
2156+
These instances must be present in the solution,
2157+
which means the class needs to be either a planning entity or a problem fact.
2158+
2159+
The instances will be read from the first element of the input tuple.
2160+
When an output tuple needs to be created for the newly inserted instances,
2161+
the first element will be the new instance.
2162+
The rest of the tuple will be padded with the result of the padding function,
2163+
applied on the new instance.
2164+
2165+
Parameters
2166+
----------
2167+
item_type : Type[A]
2168+
the type of the instances to add to the stream.
2169+
2170+
padding_function_b : Callable[[A], B]
2171+
a function that computes the padding value for the second fact in the new tuple.
2172+
2173+
padding_function_c : Callable[[A], C]
2174+
a function that computes the padding value for the third fact in the new tuple.
2175+
2176+
padding_function_d : Callable[[A], D]
2177+
a function that computes the padding value for the fourth fact in the new tuple.
2178+
"""
2179+
item_type = get_class(item_type)
2180+
return QuadConstraintStream(
2181+
self.delegate.complement(item_type, padding_function_b, padding_function_c, padding_function_d),
2182+
self.package, self.a_type, self.b_type, self.c_type, self.d_type)
2183+
20862184
def penalize(self, constraint_weight: ScoreType,
20872185
match_weigher: Callable[[A, B, C, D], int] = None) -> 'QuadConstraintBuilder[A, B, C, D, ScoreType]':
20882186
"""

timefold-solver-python-core/src/main/python/score/_group_by.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Callable, Any, Sequence, TypeVar, List, Set, Dict, TYPE_CHECKING, overload
44
if TYPE_CHECKING:
55
from ai.timefold.solver.core.api.score.stream.common import SequenceChain
6+
from ai.timefold.solver.core.api.score.stream.common import LoadBalance
67
from ai.timefold.solver.core.api.score.stream.uni import UniConstraintCollector
78
from ai.timefold.solver.core.api.score.stream.bi import BiConstraintCollector
89
from ai.timefold.solver.core.api.score.stream.tri import TriConstraintCollector
@@ -135,13 +136,15 @@ class ConstraintCollectors:
135136
C = TypeVar('C')
136137
D = TypeVar('D')
137138
E = TypeVar('E')
139+
Balanced = TypeVar('Balanced')
138140

139141
# Method return type variables
140142
A_ = TypeVar('A_')
141143
B_ = TypeVar('B_')
142144
C_ = TypeVar('C_')
143145
D_ = TypeVar('D_')
144146
E_ = TypeVar('E_')
147+
Balanced_ = TypeVar('Balanced_')
145148

146149
@staticmethod
147150
def _delegate():
@@ -993,6 +996,34 @@ def to_sorted_map(key_mapper, value_mapper, merge_function_or_set_creator=None):
993996
else:
994997
raise ValueError
995998

999+
@overload
1000+
@staticmethod
1001+
def load_balance(balanced_item_function: Callable[[A], Balanced], load_function: Callable[[A], int] = None,
1002+
initial_load_function: Callable[[A], int] = None) -> \
1003+
'UniConstraintCollector[A, Any, LoadBalance[Balanced]]':
1004+
...
1005+
1006+
@overload
1007+
@staticmethod
1008+
def load_balance(balanced_item_function: Callable[[A, B], Balanced], load_function: Callable[[A, B], int] = None,
1009+
initial_load_function: Callable[[A, B], int] = None) -> \
1010+
'BiConstraintCollector[A, B, Any, LoadBalance[Balanced]]':
1011+
...
1012+
1013+
@overload
1014+
@staticmethod
1015+
def load_balance(balanced_item_function: Callable[[A, B, C], Balanced], load_function: Callable[[A, B, C], int] = None,
1016+
initial_load_function: Callable[[A, B, C], int] = None) -> \
1017+
'TriConstraintCollector[A, B, C, Any, LoadBalance[Balanced]]':
1018+
...
1019+
1020+
@overload
1021+
@staticmethod
1022+
def load_balance(balanced_item_function: Callable[[A, B, C, D], Balanced],
1023+
load_function: Callable[[A, B, C, D], int] = None,
1024+
initial_load_function: Callable[[A, B, C, D], int] = None) -> \
1025+
'QuadConstraintCollector[A, B, C, D, Any, LoadBalance[Balanced]]':
1026+
...
9961027

9971028
# Must be at the bottom, constraint_stream depends on this module
9981029
from ._constraint_stream import *

0 commit comments

Comments
 (0)