Skip to content

Commit d07cfb2

Browse files
triceolee-carlon
andauthored
feat: add support for fairness constraints (#918)
This PR introduces fairness support. To that end, it: - Enables custom padding for `concat()`. - Allows `if(Not)Exists` to be used with a stream. - Introduces a new CS building block `complement()`. - Adds the fairness collector. - Introduces `ConstraintVerifier` capability to check for more/less penalties/rewards. --------- Co-authored-by: lee-carlon <[email protected]>
1 parent e7fae26 commit d07cfb2

File tree

78 files changed

+4685
-6416
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+4685
-6416
lines changed

core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintCollectors.java

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
import ai.timefold.solver.core.api.function.ToLongTriFunction;
3737
import ai.timefold.solver.core.api.function.TriFunction;
3838
import ai.timefold.solver.core.api.function.TriPredicate;
39+
import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
3940
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector;
4041
import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain;
42+
import ai.timefold.solver.core.api.score.stream.common.LoadBalance;
4143
import ai.timefold.solver.core.api.score.stream.common.SequenceChain;
4244
import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector;
4345
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector;
@@ -2225,6 +2227,141 @@ public static <A, ResultContainer_, Result_> UniConstraintCollector<A, ResultCon
22252227
return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a);
22262228
}
22272229

2230+
// ************************************************************************
2231+
// load balancing
2232+
// ************************************************************************
2233+
2234+
/**
2235+
* As defined by {@link #loadBalance(Function, ToLongFunction, ToLongFunction)},
2236+
* where the current load for each balanced item is set to one
2237+
* and the starting load for each balanced item is set to zero.
2238+
*/
2239+
public static <A, Balanced_> UniConstraintCollector<A, ?, LoadBalance<Balanced_>> loadBalance(
2240+
Function<A, Balanced_> balancedItemFunction) {
2241+
return loadBalance(balancedItemFunction, ConstantLambdaUtils.uniConstantOneLong());
2242+
}
2243+
2244+
/**
2245+
* As defined by {@link #loadBalance(Function, ToLongFunction, ToLongFunction)},
2246+
* where the starting load for each balanced item is set to zero.
2247+
*/
2248+
public static <A, Balanced_> UniConstraintCollector<A, ?, LoadBalance<Balanced_>> loadBalance(
2249+
Function<A, Balanced_> balancedItemFunction, ToLongFunction<A> loadFunction) {
2250+
return loadBalance(balancedItemFunction, loadFunction, ConstantLambdaUtils.uniConstantZeroLong());
2251+
}
2252+
2253+
/**
2254+
* Returns a collector that takes a stream of items and calculates the unfairness measure from them
2255+
* (see {@link LoadBalance#unfairness()}).
2256+
* The load for every item is provided by the loadFunction,
2257+
* with the starting load provided by the initialLoadFunction.
2258+
* <p>
2259+
* When this collector is used in a constraint stream,
2260+
* it is recommended that the score type be one of those based on {@link BigDecimal},
2261+
* such as {@link HardSoftBigDecimalScore}.
2262+
* This is so that the unfairness measure keeps its precision
2263+
* without forcing the other constraints to be multiplied by a large constant,
2264+
* which would otherwise be required to implement fixed-point arithmetic.
2265+
*
2266+
* @param balancedItemFunction The function that returns the item which should be load-balanced.
2267+
* @param loadFunction How much the item should count for in the formula.
2268+
* @param initialLoadFunction The initial value of the metric,
2269+
* allowing to provide initial state
2270+
* without requiring the entire previous planning windows in the working memory.
2271+
* @param <A> type of the matched fact
2272+
* @param <Balanced_> type of the item being balanced
2273+
* @return never null
2274+
*/
2275+
public static <A, Balanced_> UniConstraintCollector<A, ?, LoadBalance<Balanced_>> loadBalance(
2276+
Function<A, Balanced_> balancedItemFunction, ToLongFunction<A> loadFunction,
2277+
ToLongFunction<A> initialLoadFunction) {
2278+
return InnerUniConstraintCollectors.loadBalance(balancedItemFunction, loadFunction, initialLoadFunction);
2279+
}
2280+
2281+
/**
2282+
* As defined by {@link #loadBalance(BiFunction, ToLongBiFunction, ToLongBiFunction)},
2283+
* where the current load for each balanced item is set to one
2284+
* and the starting load for each balanced item is set to zero.
2285+
*/
2286+
public static <A, B, Balanced_> BiConstraintCollector<A, B, ?, LoadBalance<Balanced_>> loadBalance(
2287+
BiFunction<A, B, Balanced_> balancedItemFunction) {
2288+
return loadBalance(balancedItemFunction, ConstantLambdaUtils.biConstantOneLong());
2289+
}
2290+
2291+
/**
2292+
* As defined by {@link #loadBalance(BiFunction, ToLongBiFunction, ToLongBiFunction)},
2293+
* where the starting load for each balanced item is set to zero.
2294+
*/
2295+
public static <A, B, Balanced_> BiConstraintCollector<A, B, ?, LoadBalance<Balanced_>> loadBalance(
2296+
BiFunction<A, B, Balanced_> balancedItemFunction, ToLongBiFunction<A, B> loadFunction) {
2297+
return loadBalance(balancedItemFunction, loadFunction, ConstantLambdaUtils.biConstantZeroLong());
2298+
}
2299+
2300+
/**
2301+
* As defined by {@link #loadBalance(Function, ToLongFunction, ToLongFunction)}.
2302+
*/
2303+
public static <A, B, Balanced_> BiConstraintCollector<A, B, ?, LoadBalance<Balanced_>> loadBalance(
2304+
BiFunction<A, B, Balanced_> balancedItemFunction, ToLongBiFunction<A, B> loadFunction,
2305+
ToLongBiFunction<A, B> initialLoadFunction) {
2306+
return InnerBiConstraintCollectors.loadBalance(balancedItemFunction, loadFunction, initialLoadFunction);
2307+
}
2308+
2309+
/**
2310+
* As defined by {@link #loadBalance(TriFunction, ToLongTriFunction, ToLongTriFunction)},
2311+
* where the current load for each balanced item is set to one
2312+
* and the starting load for each balanced item is set to zero.
2313+
*/
2314+
public static <A, B, C, Balanced_> TriConstraintCollector<A, B, C, ?, LoadBalance<Balanced_>> loadBalance(
2315+
TriFunction<A, B, C, Balanced_> balancedItemFunction) {
2316+
return loadBalance(balancedItemFunction, ConstantLambdaUtils.triConstantOneLong());
2317+
}
2318+
2319+
/**
2320+
* As defined by {@link #loadBalance(TriFunction, ToLongTriFunction, ToLongTriFunction)},
2321+
* where the starting load for each balanced item is set to zero.
2322+
*/
2323+
public static <A, B, C, Balanced_> TriConstraintCollector<A, B, C, ?, LoadBalance<Balanced_>> loadBalance(
2324+
TriFunction<A, B, C, Balanced_> balancedItemFunction, ToLongTriFunction<A, B, C> loadFunction) {
2325+
return loadBalance(balancedItemFunction, loadFunction, ConstantLambdaUtils.triConstantZeroLong());
2326+
}
2327+
2328+
/**
2329+
* As defined by {@link #loadBalance(Function, ToLongFunction, ToLongFunction)}.
2330+
*/
2331+
public static <A, B, C, Balanced_> TriConstraintCollector<A, B, C, ?, LoadBalance<Balanced_>> loadBalance(
2332+
TriFunction<A, B, C, Balanced_> balancedItemFunction, ToLongTriFunction<A, B, C> loadFunction,
2333+
ToLongTriFunction<A, B, C> initialLoadFunction) {
2334+
return InnerTriConstraintCollectors.loadBalance(balancedItemFunction, loadFunction, initialLoadFunction);
2335+
}
2336+
2337+
/**
2338+
* As defined by {@link #loadBalance(QuadFunction, ToLongQuadFunction, ToLongQuadFunction)},
2339+
* where the current load for each balanced item is set to one
2340+
* and the starting load for each balanced item is set to zero.
2341+
*/
2342+
public static <A, B, C, D, Balanced_> QuadConstraintCollector<A, B, C, D, ?, LoadBalance<Balanced_>> loadBalance(
2343+
QuadFunction<A, B, C, D, Balanced_> balancedItemFunction) {
2344+
return loadBalance(balancedItemFunction, ConstantLambdaUtils.quadConstantOneLong());
2345+
}
2346+
2347+
/**
2348+
* As defined by {@link #loadBalance(QuadFunction, ToLongQuadFunction, ToLongQuadFunction)},
2349+
* where the starting load for each balanced item is set to zero.
2350+
*/
2351+
public static <A, B, C, D, Balanced_> QuadConstraintCollector<A, B, C, D, ?, LoadBalance<Balanced_>> loadBalance(
2352+
QuadFunction<A, B, C, D, Balanced_> balancedItemFunction, ToLongQuadFunction<A, B, C, D> loadFunction) {
2353+
return loadBalance(balancedItemFunction, loadFunction, ConstantLambdaUtils.quadConstantZeroLong());
2354+
}
2355+
2356+
/**
2357+
* As defined by {@link #loadBalance(Function, ToLongFunction, ToLongFunction)}.
2358+
*/
2359+
public static <A, B, C, D, Balanced_> QuadConstraintCollector<A, B, C, D, ?, LoadBalance<Balanced_>> loadBalance(
2360+
QuadFunction<A, B, C, D, Balanced_> balancedItemFunction, ToLongQuadFunction<A, B, C, D> loadFunction,
2361+
ToLongQuadFunction<A, B, C, D> initialLoadFunction) {
2362+
return InnerQuadConstraintCollectors.loadBalance(balancedItemFunction, loadFunction, initialLoadFunction);
2363+
}
2364+
22282365
private ConstraintCollectors() {
22292366
}
22302367
}

core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import ai.timefold.solver.core.impl.score.stream.common.quad.FilteringQuadJoiner;
2525
import ai.timefold.solver.core.impl.score.stream.common.tri.DefaultTriJoiner;
2626
import ai.timefold.solver.core.impl.score.stream.common.tri.FilteringTriJoiner;
27+
import ai.timefold.solver.core.impl.util.ConstantLambdaUtils;
2728

2829
/**
2930
* Creates an {@link BiJoiner}, {@link TriJoiner}, ... instance
@@ -45,7 +46,7 @@ public final class Joiners {
4546
* @return never null
4647
*/
4748
public static <A> BiJoiner<A, A> equal() {
48-
return equal(Function.identity());
49+
return equal(ConstantLambdaUtils.identity());
4950
}
5051

5152
/**

0 commit comments

Comments
 (0)