Skip to content

Commit

Permalink
feat: Add the concurrent usage collector (#800)
Browse files Browse the repository at this point in the history
- Renamed the consecutive interval collector to the connected range collector and move it to core.
- Added methods for finding maximum and minimum connected ranges.
- Fixed bugs relating to duplicate ranges.
- Added docs.
  • Loading branch information
Christopher-Chianelli authored Apr 24, 2024
1 parent e7f9bb2 commit d5f1d06
Show file tree
Hide file tree
Showing 53 changed files with 2,284 additions and 1,717 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.math.RoundingMode;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.Temporal;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
Expand Down Expand Up @@ -36,6 +37,7 @@
import ai.timefold.solver.core.api.function.TriFunction;
import ai.timefold.solver.core.api.function.TriPredicate;
import ai.timefold.solver.core.api.score.stream.bi.BiConstraintCollector;
import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain;
import ai.timefold.solver.core.api.score.stream.common.SequenceChain;
import ai.timefold.solver.core.api.score.stream.quad.QuadConstraintCollector;
import ai.timefold.solver.core.api.score.stream.tri.TriConstraintCollector;
Expand Down Expand Up @@ -1955,6 +1957,274 @@ public static <A, ResultContainer_, Result_> UniConstraintCollector<A, ResultCon
return InnerQuadConstraintCollectors.toConsecutiveSequences(resultMap, indexMap);
}

// *****************************************************************
// toConnectedRanges
// *****************************************************************
/**
* Creates a constraint collector that returns {@link ConnectedRangeChain} about the first fact.
*
* For instance, {@code [Equipment fromInclusive=2, toExclusive=4] [Equipment fromInclusive=3, toExclusive=5]
* [Equipment fromInclusive=6, toExclusive=7] [Equipment fromInclusive=7, toExclusive=8]}
* returns the following information:
*
* <pre>
* {@code
* ConnectedRanges: [minOverlap: 1, maxOverlap: 2,
* [Equipment fromInclusive=2, toExclusive=4] [Equipment fromInclusive=3, toExclusive=5]],
* [minConcurrentUsage: 1, maxConcurrentUsage: 1,
* [Equipment fromInclusive=6, toExclusive=7] [Equipment fromInclusive=7, toExclusive=8]]
* Breaks: [[Break from=5, to=6, length=1]]
* }
* </pre>
*
* This can be used to ensure a limited resource is not over-assigned.
*
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param differenceFunction Computes the difference between two points. The second argument is always
* larger than the first (ex: {@link Duration#between}
* or {@code (a,b) -> b - a}).
* @param <A> type of the first mapped fact
* @param <PointType_> type of the fact endpoints
* @param <DifferenceType_> type of difference between points
* @return never null
*/
public static <A, PointType_ extends Comparable<PointType_>, DifferenceType_ extends Comparable<DifferenceType_>>
UniConstraintCollector<A, ?, ConnectedRangeChain<A, PointType_, DifferenceType_>>
toConnectedRanges(Function<A, PointType_> startInclusiveMap, Function<A, PointType_> endExclusiveMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerUniConstraintCollectors.toConnectedRanges(ConstantLambdaUtils.identity(), startInclusiveMap,
endExclusiveMap,
differenceFunction);
}

/**
* Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for
* {@link Temporal} types.
*
* @param <A> type of the first mapped fact
* @param <PointType_> temporal type of the endpoints
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @return never null
*/
public static <A, PointType_ extends Temporal & Comparable<PointType_>>
UniConstraintCollector<A, ?, ConnectedRangeChain<A, PointType_, Duration>>
toConnectedTemporalRanges(Function<A, PointType_> startInclusiveMap, Function<A, PointType_> endExclusiveMap) {
return toConnectedRanges(startInclusiveMap, endExclusiveMap, Duration::between);
}

/**
* Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for Long.
*
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @return never null
*/
public static <A> UniConstraintCollector<A, ?, ConnectedRangeChain<A, Long, Long>>
toConnectedRanges(ToLongFunction<A> startInclusiveMap, ToLongFunction<A> endExclusiveMap) {
return toConnectedRanges(startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a);
}

/**
* As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}.
*
* @param intervalMap Maps both facts to an item in the cluster
* @param startInclusiveMap Maps the item to its start
* @param endExclusiveMap Maps the item to its end
* @param differenceFunction Computes the difference between two points. The second argument is always
* larger than the first (ex: {@link Duration#between}
* or {@code (a,b) -> b - a}).
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> type of the item endpoints
* @param <DifferenceType_> type of difference between points
* @return never null
*/
public static <A, B, IntervalType_, PointType_ extends Comparable<PointType_>, DifferenceType_ extends Comparable<DifferenceType_>>
BiConstraintCollector<A, B, ?, ConnectedRangeChain<IntervalType_, PointType_, DifferenceType_>>
toConnectedRanges(BiFunction<A, B, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap,
Function<IntervalType_, PointType_> endExclusiveMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerBiConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap,
differenceFunction);
}

/**
* As defined by {@link #toConnectedTemporalRanges(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> temporal type of the endpoints
* @return never null
*/
public static <A, B, IntervalType_, PointType_ extends Temporal & Comparable<PointType_>>
BiConstraintCollector<A, B, ?, ConnectedRangeChain<IntervalType_, PointType_, Duration>>
toConnectedTemporalRanges(BiFunction<A, B, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap,
Function<IntervalType_, PointType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <IntervalType_> type of the item in the cluster
* @return never null
*/
public static <A, B, IntervalType_>
BiConstraintCollector<A, B, ?, ConnectedRangeChain<IntervalType_, Long, Long>>
toConnectedRanges(BiFunction<A, B, IntervalType_> intervalMap, ToLongFunction<IntervalType_> startInclusiveMap,
ToLongFunction<IntervalType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a);
}

/**
* As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startInclusiveMap Maps the item to its start
* @param endExclusiveMap Maps the item to its end
* @param differenceFunction Computes the difference between two points. The second argument is always
* larger than the first (ex: {@link Duration#between}
* or {@code (a,b) -> b - a}).
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> type of the item endpoints
* @param <DifferenceType_> type of difference between points
* @return never null
*/
public static <A, B, C, IntervalType_, PointType_ extends Comparable<PointType_>, DifferenceType_ extends Comparable<DifferenceType_>>
TriConstraintCollector<A, B, C, ?, ConnectedRangeChain<IntervalType_, PointType_, DifferenceType_>>
toConnectedRanges(TriFunction<A, B, C, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap,
Function<IntervalType_, PointType_> endExclusiveMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerTriConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap,
differenceFunction);
}

/**
* As defined by {@link #toConnectedTemporalRanges(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> temporal type of the endpoints
* @return never null
*/
public static <A, B, C, IntervalType_, PointType_ extends Temporal & Comparable<PointType_>>
TriConstraintCollector<A, B, C, ?, ConnectedRangeChain<IntervalType_, PointType_, Duration>>
toConnectedTemporalRanges(TriFunction<A, B, C, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap,
Function<IntervalType_, PointType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <IntervalType_> type of the item in the cluster
* @return never null
*/
public static <A, B, C, IntervalType_>
TriConstraintCollector<A, B, C, ?, ConnectedRangeChain<IntervalType_, Long, Long>>
toConnectedRanges(TriFunction<A, B, C, IntervalType_> intervalMap, ToLongFunction<IntervalType_> startInclusiveMap,
ToLongFunction<IntervalType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a);
}

/**
* As defined by {@link #toConnectedRanges(Function,Function,BiFunction)}.
*
* @param intervalMap Maps the four facts to an item in the cluster
* @param startInclusiveMap Maps the item to its start
* @param endExclusiveMap Maps the item to its end
* @param differenceFunction Computes the difference between two points. The second argument is always
* larger than the first (ex: {@link Duration#between}
* or {@code (a,b) -> b - a}).
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <D> type of the fourth mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> type of the item endpoints
* @param <DifferenceType_> type of difference between points
* @return never null
*/
public static <A, B, C, D, IntervalType_, PointType_ extends Comparable<PointType_>, DifferenceType_ extends Comparable<DifferenceType_>>
QuadConstraintCollector<A, B, C, D, ?, ConnectedRangeChain<IntervalType_, PointType_, DifferenceType_>>
toConnectedRanges(QuadFunction<A, B, C, D, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap, Function<IntervalType_, PointType_> endExclusiveMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerQuadConstraintCollectors.toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap,
differenceFunction);
}

/**
* As defined by {@link #toConnectedTemporalRanges(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <D> type of the fourth mapped fact
* @param <IntervalType_> type of the item in the cluster
* @param <PointType_> temporal type of the endpoints
* @return never null
*/
public static <A, B, C, D, IntervalType_, PointType_ extends Temporal & Comparable<PointType_>>
QuadConstraintCollector<A, B, C, D, ?, ConnectedRangeChain<IntervalType_, PointType_, Duration>>
toConnectedTemporalRanges(QuadFunction<A, B, C, D, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startInclusiveMap,
Function<IntervalType_, PointType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap, endExclusiveMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startInclusiveMap Maps the fact to its start
* @param endExclusiveMap Maps the fact to its end
* @param <A> type of the first mapped fact
* @param <B> type of the second mapped fact
* @param <C> type of the third mapped fact
* @param <D> type of the fourth mapped fact
* @param <IntervalType_> type of the item in the cluster
* @return never null
*/
public static <A, B, C, D, IntervalType_>
QuadConstraintCollector<A, B, C, D, ?, ConnectedRangeChain<IntervalType_, Long, Long>>
toConnectedRanges(QuadFunction<A, B, C, D, IntervalType_> intervalMap,
ToLongFunction<IntervalType_> startInclusiveMap,
ToLongFunction<IntervalType_> endExclusiveMap) {
return toConnectedRanges(intervalMap, startInclusiveMap::applyAsLong, endExclusiveMap::applyAsLong, (a, b) -> b - a);
}

private ConstraintCollectors() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ai.timefold.solver.core.api.score.stream.common;

/**
* Represents a collection of ranges that are connected, meaning
* the union of all the ranges results in the range
* [{@link #getStart()}, {@link #getEnd()}) without gaps.
*
* @param <Range_> The type of range in the collection.
* @param <Point_> The type of the start and end points for each range.
* @param <Difference_> The type of difference between start and end points.
*/
public interface ConnectedRange<Range_, Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>>
extends Iterable<Range_> {
/**
* Get the number of ranges contained by this {@link ConnectedRange}.
*
* @return never null, the number of ranges contained by this {@link ConnectedRange}.
*/
int getContainedRangeCount();

/**
* True if this {@link ConnectedRange} has at least one pair of
* ranges that overlaps each other, false otherwise.
*
* @return never null, true iff there at least one pair of overlapping ranges in this {@link ConnectedRange}.
*/
boolean hasOverlap();

/**
* Get the minimum number of overlapping ranges for any point contained by
* this {@link ConnectedRange}.
*
* @return never null, the minimum number of overlapping ranges for any point
* in this {@link ConnectedRange}.
*/
int getMinimumOverlap();

/**
* Get the maximum number of overlapping ranges for any point contained by
* this {@link ConnectedRange}.
*
* @return never null, the maximum number of overlapping ranges for any point
* in this {@link ConnectedRange}.
*/
int getMaximumOverlap();

/**
* Get the length of this {@link ConnectedRange}.
*
* @return The difference between {@link #getEnd()} and {@link #getStart()}.
*/
Difference_ getLength();

/**
* Gets the first start point represented by this {@link ConnectedRange}.
*
* @return never null, the first start point represented by this {@link ConnectedRange}.
*/
Point_ getStart();

/**
* Gets the last end point represented by this {@link ConnectedRange}.
*
* @return never null, the last end point represented by this {@link ConnectedRange}.
*/
Point_ getEnd();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ai.timefold.solver.core.api.score.stream.common;

/**
* Contains info regarding {@link ConnectedRange}s and {@link RangeGap}s for a collection of ranges.
*
* @param <Range_> The type of range in the collection.
* @param <Point_> The type of the start and end points for each range.
* @param <Difference_> The type of difference between start and end points.
*/
public interface ConnectedRangeChain<Range_, Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>> {

/**
* @return never null, an iterable that iterates through the {@link ConnectedRange}s
* contained in the collection in ascending order of their start points
*/
Iterable<ConnectedRange<Range_, Point_, Difference_>> getConnectedRanges();

/**
* @return never null, an iterable that iterates through the {@link RangeGap}s contained in
* the collection in ascending order of their start points
*/
Iterable<RangeGap<Point_, Difference_>> getGaps();
}
Loading

0 comments on commit d5f1d06

Please sign in to comment.