diff --git a/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java b/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java index a7fa73ebbb..5a42056ce4 100644 --- a/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java +++ b/merlin-sdk/src/main/java/gov/nasa/jpl/aerie/merlin/protocol/types/Duration.java @@ -356,6 +356,10 @@ public static java.time.Instant addToInstant(final java.time.Instant instant, fi .plusNanos(1000 * duration.remainderOf(Duration.MILLISECONDS).dividedBy(Duration.MICROSECONDS)); } + public Duration negate() { + return Duration.negate(this); + } + /** @see Duration#add(Duration, Duration) */ public Duration plus(final Duration other) throws ArithmeticException { return Duration.add(this, other); diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformer.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformer.kt new file mode 100644 index 0000000000..4719afb978 --- /dev/null +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/BoundsTransformer.kt @@ -0,0 +1,15 @@ +package gov.nasa.jpl.aerie.timeline + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration + +fun interface BoundsTransformer { + operator fun invoke(bounds: Interval): Interval + + companion object { + @JvmStatic + val IDENTITY: BoundsTransformer = BoundsTransformer { i -> i } + + @JvmStatic + fun shift(dur: Duration) = BoundsTransformer { i -> i.shiftBy(dur.negate()) } + } +} diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt index d446ae80be..011f82c356 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/GeneralOps.kt @@ -26,15 +26,19 @@ interface GeneralOps, T: GeneralOps>: Timeline { /** * **UNSAFE** */ - fun map(f: (V) -> V) = map(ctor, f) - fun , TInto: GeneralOps> map(ctor: (TimelineOps) -> TInto, f: (V) -> W) = - Timeline(ctor) { bounds -> collect(bounds).map { f(it) }}.specialize().coalesce() + fun map(boundsTransformer: BoundsTransformer, truncate: Boolean, f: (V) -> V) = map(ctor, boundsTransformer, truncate, f) + fun , TInto: GeneralOps> map(ctor: (Timeline) -> TInto, boundsTransformer: BoundsTransformer, truncate: Boolean, f: (V) -> W) = + BaseTimeline(ctor) { bounds -> + val mapped = collect(boundsTransformer(bounds)).map { f(it) } + if (truncate) truncateList(mapped, bounds) + else mapped + }.specialize().coalesce() - fun mapIntervals(f: (V) -> Interval) = map { v -> v.changeInterval(f(v)) } + fun mapIntervals(boundsTransformer: BoundsTransformer, truncate: Boolean, f: (V) -> Interval) = map(boundsTransformer, truncate) { v -> v.changeInterval(f(v)) } - fun filter(f: (V) -> Boolean) = Timeline(ctor) { bounds -> collect(bounds).filter(f) }.specialize() + fun filter(f: (V) -> Boolean) = BaseTimeline(ctor) { bounds -> collect(bounds).filter(f) }.specialize() - fun shiftBy(dur: Duration) = map { v -> v.changeInterval(v.interval.shiftBy(dur)) } + fun shiftBy(dur: Duration) = map(BoundsTransformer.shift(dur), false) { v -> v.changeInterval(v.interval.shiftBy(dur)) } fun all(bounds: Interval, f: (V) -> Boolean) = collect(bounds).all(f) fun any(bounds: Interval, f: (V) -> Boolean) = collect(bounds).any(f) diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/LinearOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/LinearOps.kt index 16ccc207ab..95548b27ed 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/LinearOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/LinearOps.kt @@ -10,4 +10,6 @@ import gov.nasa.jpl.aerie.timeline.Segment * Currently only used for Real profiles, but in the future could be used for * duration profiles or parallel real profiles. */ -interface LinearOps, P>>: SegmentOps +interface LinearOps, P>>: SegmentOps { + +} diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/ParallelOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/ParallelOps.kt index 2e740e54f9..909fa94b2f 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/ParallelOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/ParallelOps.kt @@ -1,9 +1,7 @@ package gov.nasa.jpl.aerie.timeline.ops -import gov.nasa.jpl.aerie.timeline.BinaryOperation -import gov.nasa.jpl.aerie.timeline.IntervalLike -import gov.nasa.jpl.aerie.timeline.Segment -import gov.nasa.jpl.aerie.timeline.Timeline +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.timeline.* import gov.nasa.jpl.aerie.timeline.util.map2Serial /** @@ -12,18 +10,18 @@ import gov.nasa.jpl.aerie.timeline.util.map2Serial * Opposite of [SerialOps]. */ interface ParallelOps, S: GeneralOps>: GeneralOps { - fun , SInto>> mapIntoSegments(ctor: (TimelineOps, SInto>) -> SInto, f: (T) -> V) = - map(ctor) { Segment(it.interval, f(it)) } + fun , SInto>> mapIntoSegments(ctor: (Timeline, SInto>) -> SInto, f: (T) -> V) = + map(ctor, BoundsTransformer.IDENTITY, false) { Segment(it.interval, f(it)) } - fun > flattenIntoProfile(ctor: (TimelineOps, PInto>) -> PInto, f: (T) -> R) = - Timeline(ctor) { bounds -> - val result = collect(bounds).mapTo(mutableListOf>()) { Segment(it.interval, f(it)) } + fun > flattenIntoProfile(ctor: (Timeline, PInto>) -> PInto, f: (T) -> R) = + BaseTimeline(ctor) { bounds -> + val result = collect(bounds).mapTo(mutableListOf()) { Segment(it.interval, f(it)) } result.sortWith { l, r -> l.interval.compareStarts(r.interval) } result }.specialize().coalesce() - fun > combineIntoProfile(ctor: (TimelineOps, PInto>) -> PInto, op: BinaryOperation) = - Timeline(ctor) { bounds -> + fun > combineIntoProfile(ctor: (Timeline, PInto>) -> PInto, op: BinaryOperation) = + BaseTimeline(ctor) { bounds -> var acc: List> = listOf() var remaining = collect(bounds) while (remaining.isNotEmpty()) { @@ -45,4 +43,17 @@ interface ParallelOps, S: GeneralOps>: GeneralOps } acc }.specialize().coalesce() + + fun shiftEdges(shiftStart: Duration, shiftEnd: Duration = shiftStart) = + mapIntervals( + { i -> + Interval.between( + Duration.min(i.start.minus(shiftStart), i.start.minus(shiftEnd)), + Duration.max(i.end.minus(shiftStart), i.end.minus(shiftEnd)), + i.startInclusivity, + i.endInclusivity + ) + }, + true + ) { t -> t.interval.shiftBy(shiftStart, shiftEnd) } } diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SegmentOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SegmentOps.kt index fcb6bf07fa..a1ea910421 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SegmentOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SegmentOps.kt @@ -1,12 +1,13 @@ package gov.nasa.jpl.aerie.timeline.ops import gov.nasa.jpl.aerie.timeline.* +import gov.nasa.jpl.aerie.timeline.BoundsTransformer.Companion.IDENTITY /** * Operations mixin for timelines of segments. */ interface SegmentOps, P>>: GeneralOps, P> { - fun mapValues(f: (Segment) -> V) = map { it.mapValue(f) } - fun , TInto>> mapValues(ctor: (TimelineOps, TInto>) -> TInto, f: (Segment) -> W) = - map(ctor) { it.mapValue(f) } + fun mapValues(f: (Segment) -> V) = map(IDENTITY, false) { it.mapValue(f) } + fun , TInto>> mapValues(ctor: (Timeline, TInto>) -> TInto, f: (Segment) -> W) = + map(ctor, IDENTITY, false) { it.mapValue(f) } } diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialBooleanOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialBooleanOps.kt new file mode 100644 index 0000000000..067698a225 --- /dev/null +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialBooleanOps.kt @@ -0,0 +1,41 @@ +package gov.nasa.jpl.aerie.timeline.ops + +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.timeline.BinaryOperation +import gov.nasa.jpl.aerie.timeline.Interval +import gov.nasa.jpl.aerie.timeline.Segment + +/** + * Operations mixin for timelines of booleans. + * + * Currently only used by Windows, but could be used for parallel boolean profiles in the future. + */ +interface SerialBooleanOps, P>>: SerialOps, BooleanOps

{ + fun and(other: SerialBooleanOps

) = map2Values(other, BinaryOperation.cases( + { l, _ -> if (l) null else false }, + { r, _ -> if (r) null else false }, + { l, r, _ -> l && r } + )) + + fun or(other: SerialBooleanOps

) = map2Values(other, BinaryOperation.cases( + { l, _ -> if (l) true else null }, + { r, _ -> if (r) true else null }, + { l, r, _ -> l || r } + )) + + fun shiftEdges(shiftRising: Duration, shiftFalling: Duration = shiftRising) = + mapIntervals( + { i -> + Interval.between( + Duration.min(i.start.minus(shiftRising), i.start.minus(shiftFalling)), + Duration.max(i.end.minus(shiftRising), i.end.minus(shiftFalling)), + i.startInclusivity, + i.endInclusivity + ) + }, + true + ) { t -> + if (t.value) t.interval.shiftBy(shiftRising, shiftFalling) + else t.interval.shiftBy(shiftFalling, shiftRising) + } +} diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt index 1500bcbbc9..942f92b05a 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/ops/SerialOps.kt @@ -1,5 +1,6 @@ package gov.nasa.jpl.aerie.timeline.ops +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration import gov.nasa.jpl.aerie.timeline.* import gov.nasa.jpl.aerie.timeline.collections.profiles.Windows import gov.nasa.jpl.aerie.timeline.ops.coalesce.CoalesceSegments @@ -62,4 +63,6 @@ interface SerialOps, P>>: CoalesceSegments s.interval.shiftBy(dur) } } diff --git a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt index e412167e0b..3bd80444f0 100644 --- a/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt +++ b/timeline/src/main/kotlin/gov/nasa/jpl/aerie/timeline/util/BoundList.kt @@ -3,4 +3,6 @@ package gov.nasa.jpl.aerie.timeline.util import gov.nasa.jpl.aerie.timeline.Interval import gov.nasa.jpl.aerie.timeline.IntervalLike -fun > boundList(list: List) = { bounds: Interval -> list.mapNotNull { it.bound(bounds) } } +fun > boundList(list: List) = { bounds: Interval -> truncateList(list, bounds) } + +fun > truncateList(list: List, bounds: Interval) = list.mapNotNull { it.bound(bounds) }