Skip to content

Commit

Permalink
Add bounds-shifting operations
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCourtney committed Jan 26, 2024
1 parent a693dcd commit 1e77abc
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ interface GeneralOps<V: IntervalLike<V>, T: GeneralOps<V, T>>: Timeline<V, T> {
/**
* **UNSAFE**
*/
fun map(f: (V) -> V) = map(ctor, f)
fun <W: IntervalLike<W>, TInto: GeneralOps<W, TInto>> map(ctor: (TimelineOps<W, TInto>) -> 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 <W: IntervalLike<W>, TInto: GeneralOps<W, TInto>> map(ctor: (Timeline<W, TInto>) -> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: GeneralOps<Segment<RealDynamics>, P>>: SegmentOps<RealDynamics, P>
interface LinearOps<P: GeneralOps<Segment<RealDynamics>, P>>: SegmentOps<RealDynamics, P> {

}
Original file line number Diff line number Diff line change
@@ -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

/**
Expand All @@ -12,18 +10,18 @@ import gov.nasa.jpl.aerie.timeline.util.map2Serial
* Opposite of [SerialOps].
*/
interface ParallelOps<T: IntervalLike<T>, S: GeneralOps<T, S>>: GeneralOps<T, S> {
fun <V : Any, SInto : GeneralOps<Segment<V>, SInto>> mapIntoSegments(ctor: (TimelineOps<Segment<V>, SInto>) -> SInto, f: (T) -> V) =
map(ctor) { Segment(it.interval, f(it)) }
fun <V : Any, SInto : GeneralOps<Segment<V>, SInto>> mapIntoSegments(ctor: (Timeline<Segment<V>, SInto>) -> SInto, f: (T) -> V) =
map(ctor, BoundsTransformer.IDENTITY, false) { Segment(it.interval, f(it)) }

fun <R : Any, PInto : SerialOps<R, PInto>> flattenIntoProfile(ctor: (TimelineOps<Segment<R>, PInto>) -> PInto, f: (T) -> R) =
Timeline(ctor) { bounds ->
val result = collect(bounds).mapTo(mutableListOf<Segment<R>>()) { Segment(it.interval, f(it)) }
fun <R : Any, PInto : SerialOps<R, PInto>> flattenIntoProfile(ctor: (Timeline<Segment<R>, 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 <R : Any, PInto : SerialOps<R, PInto>> combineIntoProfile(ctor: (TimelineOps<Segment<R>, PInto>) -> PInto, op: BinaryOperation<R, T, R>) =
Timeline(ctor) { bounds ->
fun <R : Any, PInto : SerialOps<R, PInto>> combineIntoProfile(ctor: (Timeline<Segment<R>, PInto>) -> PInto, op: BinaryOperation<R, T, R>) =
BaseTimeline(ctor) { bounds ->
var acc: List<Segment<R>> = listOf()
var remaining = collect(bounds)
while (remaining.isNotEmpty()) {
Expand All @@ -45,4 +43,17 @@ interface ParallelOps<T: IntervalLike<T>, S: GeneralOps<T, S>>: GeneralOps<T, S>
}
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) }
}
Original file line number Diff line number Diff line change
@@ -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<V : Any, P: GeneralOps<Segment<V>, P>>: GeneralOps<Segment<V>, P> {
fun mapValues(f: (Segment<V>) -> V) = map { it.mapValue(f) }
fun <W: Any, TInto: GeneralOps<Segment<W>, TInto>> mapValues(ctor: (TimelineOps<Segment<W>, TInto>) -> TInto, f: (Segment<V>) -> W) =
map(ctor) { it.mapValue(f) }
fun mapValues(f: (Segment<V>) -> V) = map(IDENTITY, false) { it.mapValue(f) }
fun <W: Any, TInto: GeneralOps<Segment<W>, TInto>> mapValues(ctor: (Timeline<Segment<W>, TInto>) -> TInto, f: (Segment<V>) -> W) =
map(ctor, IDENTITY, false) { it.mapValue(f) }
}
Original file line number Diff line number Diff line change
@@ -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: GeneralOps<Segment<Boolean>, P>>: SerialOps<Boolean, P>, BooleanOps<P> {
fun and(other: SerialBooleanOps<P>) = map2Values(other, BinaryOperation.cases(
{ l, _ -> if (l) null else false },
{ r, _ -> if (r) null else false },
{ l, r, _ -> l && r }
))

fun or(other: SerialBooleanOps<P>) = 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)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -62,4 +63,6 @@ interface SerialOps<V : Any, P: GeneralOps<Segment<V>, P>>: CoalesceSegments<V,
)
}
}.specialize().coalesce()

fun shift(dur: Duration) = mapIntervals(BoundsTransformer.shift(dur), false) { s -> s.interval.shiftBy(dur) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <I: IntervalLike<I>> boundList(list: List<I>) = { bounds: Interval -> list.mapNotNull { it.bound(bounds) } }
fun <I: IntervalLike<I>> boundList(list: List<I>) = { bounds: Interval -> truncateList(list, bounds) }

fun <I: IntervalLike<I>> truncateList(list: List<I>, bounds: Interval) = list.mapNotNull { it.bound(bounds) }

0 comments on commit 1e77abc

Please sign in to comment.