Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the concurrent usage collector #800

Merged
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,263 @@ 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 from=2, to=4] [Equipment from=3, to=5] [Equipment from=6, to=7] [Equipment from=7, to=8]}
* returns the following information:
*
* <pre>
* {@code
* ConcurrentUsage: [minConcurrentUsage: 1, maxConcurrentUsage: 2,
* [Equipment from=2, to=4] [Equipment from=3, to=5]],
* [minConcurrentUsage: 1, maxConcurrentUsage: 1,
* [Equipment from=6, to=7] [Equipment from=7, to=8]]
triceo marked this conversation as resolved.
Show resolved Hide resolved
* Breaks: [[Break from=5, to=6, length=1]]
* }
* </pre>
*
* This can be used to ensure a limited resource is not over-assigned.
*
* @param startMap Maps the fact to its start
* @param endMap 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_> startMap, Function<A, PointType_> endMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerUniConstraintCollectors.toConnectedRanges(ConstantLambdaUtils.identity(), startMap, endMap,
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 startMap Maps the fact to its start
* @param endMap Maps the fact to its end
* @return never null
*/
public static <A, PointType_ extends Temporal & Comparable<PointType_>>
UniConstraintCollector<A, ?, ConnectedRangeChain<A, PointType_, Duration>>
toConnectedRangesByTime(Function<A, PointType_> startMap, Function<A, PointType_> endMap) {
triceo marked this conversation as resolved.
Show resolved Hide resolved
return toConnectedRanges(startMap, endMap, Duration::between);
}

/**
* Specialized version of {@link #toConnectedRanges(Function,Function,BiFunction)} for Long.
*
* @param startMap Maps the fact to its start
* @param endMap 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> startMap, ToLongFunction<A> endMap) {
return toConnectedRanges(startMap::applyAsLong, endMap::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 startMap Maps the item to its start
* @param endMap 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_> startMap,
Function<IntervalType_, PointType_> endMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerBiConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction);
}

/**
* As defined by {@link #toConnectedRangesByTime(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startMap Maps the fact to its start
* @param endMap 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>>
toConnectedRangesByTime(BiFunction<A, B, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startMap, Function<IntervalType_, PointType_> endMap) {
return toConnectedRanges(intervalMap, startMap, endMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startMap Maps the fact to its start
* @param endMap 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_> startMap,
ToLongFunction<IntervalType_> endMap) {
return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::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 startMap Maps the item to its start
* @param endMap 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_> startMap,
Function<IntervalType_, PointType_> endMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerTriConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction);
}

/**
* As defined by {@link #toConnectedRangesByTime(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startMap Maps the fact to its start
* @param endMap 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>>
toConnectedRangesByTime(TriFunction<A, B, C, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startMap, Function<IntervalType_, PointType_> endMap) {
return toConnectedRanges(intervalMap, startMap, endMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startMap Maps the fact to its start
* @param endMap 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_> startMap,
ToLongFunction<IntervalType_> endMap) {
return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::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 startMap Maps the item to its start
* @param endMap 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_> startMap, Function<IntervalType_, PointType_> endMap,
BiFunction<PointType_, PointType_, DifferenceType_> differenceFunction) {
return InnerQuadConstraintCollectors.toConnectedRanges(intervalMap, startMap, endMap, differenceFunction);
}

/**
* As defined by {@link #toConnectedRangesByTime(Function,Function)}.
*
* @param intervalMap Maps the three facts to an item in the cluster
* @param startMap Maps the fact to its start
* @param endMap 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>>
toConnectedRangesByTime(QuadFunction<A, B, C, D, IntervalType_> intervalMap,
Function<IntervalType_, PointType_> startMap, Function<IntervalType_, PointType_> endMap) {
return toConnectedRanges(intervalMap, startMap, endMap, Duration::between);
}

/**
* As defined by {@link #toConnectedRanges(ToLongFunction, ToLongFunction)}.
*
* @param startMap Maps the fact to its start
* @param endMap 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_> startMap,
ToLongFunction<IntervalType_> endMap) {
return toConnectedRanges(intervalMap, startMap::applyAsLong, endMap::applyAsLong, (a, b) -> b - a);
}

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

public interface ConnectedRange<Interval_, Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>>
extends Iterable<Interval_> {
int getContainedRangeCount();

boolean hasOverlap();

int getMinimumOverlap();

int getMaximumOverlap();

Difference_ getLength();

Point_ getStart();

Point_ getEnd();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ai.timefold.solver.core.api.score.stream.common;

public interface ConnectedRangeChain<Interval_, Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>> {

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

/**
* @return never null, an iterable that iterates through the breaks contained in
* the collection in ascending order of their start points
*/
Iterable<RangeGap<Point_, Difference_>> getGaps();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ai.timefold.solver.core.api.score.stream.common;

/**
* A {@link RangeGap} is a gap between two consecutive {@link ConnectedRange}s.
* For instance, the list [(1,3),(2,4),(3,5),(7,8)] has a grap of length 2 between 5 and 7.
*
* @param <Point_> The type for the ranges' start and end points
* @param <Difference_> The type of difference between values in the sequence
*/
public interface RangeGap<Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>> {
/**
* Return the end of the sequence before this gap.
* For the gap between 6 and 10, this will return 6.
*
* @return never null, the item this gap is directly after
*/
Point_ getPreviousRangeEnd();

/**
* Return the start of the sequence after this gap.
* For the gap between 6 and 10, this will return 10.
*
* @return never null, the item this gap is directly before
*/
Point_ getNextRangeStart();

/**
* Return the length of the break, which is the difference
* between {@link #getNextRangeStart()} and {@link #getPreviousRangeEnd()}.
* For the gap between 6 and 10, this will return 4.
*
* @return never null, the length of this break
*/
Difference_ getLength();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ai.timefold.solver.core.impl.score.stream.collector;

import java.util.function.BiFunction;
import java.util.function.Function;

import ai.timefold.solver.core.api.score.stream.common.ConnectedRangeChain;
import ai.timefold.solver.core.impl.score.stream.collector.connected_ranges.IntervalTree;

public final class ConnectedRangesCalculator<Interval_, Point_ extends Comparable<Point_>, Difference_ extends Comparable<Difference_>>
implements ObjectCalculator<Interval_, ConnectedRangeChain<Interval_, Point_, Difference_>> {

private final IntervalTree<Interval_, Point_, Difference_> context;

public ConnectedRangesCalculator(Function<? super Interval_, ? extends Point_> startMap,
Function<? super Interval_, ? extends Point_> endMap,
BiFunction<? super Point_, ? super Point_, ? extends Difference_> differenceFunction) {
this.context = new IntervalTree<>(
startMap,
endMap,
differenceFunction);
}

@Override
public void insert(Interval_ result) {
context.add(context.getInterval(result));
}

@Override
public void retract(Interval_ result) {
context.remove(context.getInterval(result));
}

@Override
public ConnectedRangeChain<Interval_, Point_, Difference_> result() {
return context.getConnectedRangeChain();
}

}
Loading
Loading