From 3fc38460c275ad6d2363cd332c3b71085d82f6c7 Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Wed, 4 Sep 2024 16:33:38 -0700 Subject: [PATCH 1/4] Generator constraint pattern --- .../jpl/aerie/e2e/TimelineRemoteTests.java | 17 +++-- .../procedural/constraints/Constraint.kt | 8 +- .../constraints/GeneratorConstraint.kt | 76 +++++++++++++++++++ .../aerie/procedural/constraints/Violation.kt | 2 +- .../procedural/constraints/Violations.kt | 18 ++--- .../procedural/constraints/GeneratorTest.kt | 61 +++++++++++++++ .../constraints/NotImplementedPlan.kt | 26 +++++++ .../fooprocedures/constraints/ConstFruit.java | 18 ++--- 8 files changed, 194 insertions(+), 32 deletions(-) create mode 100644 procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt create mode 100644 procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt create mode 100644 procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt diff --git a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java index d53d30cecd..0cdef7f02e 100644 --- a/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java +++ b/e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/TimelineRemoteTests.java @@ -3,6 +3,8 @@ import com.microsoft.playwright.Playwright; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan; +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults; import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests; import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests; import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; @@ -12,7 +14,6 @@ import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment; import gov.nasa.ammos.aerie.procedural.remote.AeriePostgresPlan; import gov.nasa.ammos.aerie.procedural.remote.AeriePostgresSimulationResults; -import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulatedPlan; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -40,7 +41,8 @@ public class TimelineRemoteTests { private int planId; private int activityId; - private SimulatedPlan simulatedPlan; + private Plan plan; + private SimulationResults simResults; private Connection connection; private HikariDataSource dataSource; @BeforeAll @@ -101,9 +103,8 @@ void beforeEach() throws IOException, InterruptedException { // Connect to the database - final var plan = new AeriePostgresPlan(connection, planId); - final var simResults = new AeriePostgresSimulationResults(connection, simDatasetId, plan, false); - simulatedPlan = new SimulatedPlan(plan, simResults); + plan = new AeriePostgresPlan(connection, planId); + simResults = new AeriePostgresSimulationResults(connection, simDatasetId, plan, false); } @AfterEach @@ -114,7 +115,7 @@ void afterEach() throws IOException { @Test void queryActivityInstances() { - final var instances = simulatedPlan.instances().collect(); + final var instances = simResults.instances().collect(); assertEquals(1, instances.size()); final var instance = instances.get(0); assertEquals("BiteBanana", instance.getType()); @@ -126,7 +127,7 @@ void queryActivityInstances() { @Test void queryActivityDirectives() { - final var directives = simulatedPlan.directives().collect(); + final var directives = plan.directives().collect(); assertEquals(1, directives.size()); final var directive = directives.get(0); assertEquals("BiteBanana", directive.getType()); @@ -136,7 +137,7 @@ void queryActivityDirectives() { @Test void queryResources() { - final var fruit = simulatedPlan.resource("/fruit", Real.deserializer()).collect(); + final var fruit = simResults.resource("/fruit", Real.deserializer()).collect(); assertIterableEquals( List.of( Segment.of(Interval.betweenClosedOpen(Duration.ZERO, Duration.HOUR), new LinearEquation(4.0)), diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt index e08f5c2aa2..e1bc550382 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt @@ -1,7 +1,7 @@ package gov.nasa.ammos.aerie.procedural.constraints -import gov.nasa.ammos.aerie.procedural.timeline.CollectOptions -import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulatedPlan +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults /** The interface that all constraints must satisfy. */ interface Constraint { @@ -12,7 +12,7 @@ interface Constraint { * the constraint is run. The constraint does not need to use the options unless it collects a timeline prematurely. * * @param plan the plan to check the constraint on - * @param options the [CollectOptions] that the result will be collected with + * @param simResults the [SimulationResults] that the result will be collected with */ - fun run(plan: SimulatedPlan, options: CollectOptions): Violations + fun run(plan: Plan, simResults: SimulationResults): Violations } diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt new file mode 100644 index 0000000000..c2546966c1 --- /dev/null +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt @@ -0,0 +1,76 @@ +package gov.nasa.ammos.aerie.procedural.constraints + +import gov.nasa.ammos.aerie.procedural.timeline.collections.Windows +import gov.nasa.ammos.aerie.procedural.timeline.collections.profiles.Real +import gov.nasa.ammos.aerie.procedural.timeline.ops.GeneralOps +import gov.nasa.ammos.aerie.procedural.timeline.ops.ParallelOps +import gov.nasa.ammos.aerie.procedural.timeline.ops.SerialConstantOps +import gov.nasa.ammos.aerie.procedural.timeline.payloads.IntervalLike +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults + +/** + * A generator-style implementation of [Constraint]. + * + * The subclass must implement [generate], and within it call [violate] to produce violations. + * Or if you are using Kotlin, you can use the timeline extension functions such as [windows.violateInside()][violateInside] + * to more easily submit violations. + */ +abstract class GeneratorConstraint: Constraint { + private var violations = mutableListOf() + + /** Finalizes one or more violations. */ + protected fun violate(vararg v: Violation) { + violate(v.toList()) + } + + /** Finalizes a list of violations. */ + protected fun violate(l: List) { + violations.addAll(l) + } + + /** Collects a [Violations] timeline and finalizes the result. */ + protected fun violate(tl: Violations) { + violate(tl.collect()) + } + + /** Creates a [Violations] object that violates when this profile equals a given value. */ + protected fun SerialConstantOps.violateOn(v: V) = violate(Violations.on(this, v)) + + /** Creates a [Violations] object that violates when this profile equals a given value. */ + protected fun Real.violateOn(n: Number) = violate(Violations.on(this, n)) + + /** + * Creates a [Violations] object that violates on every object in the timeline. + * + * If the object is an activity, it will record the directive or instance id. + */ + protected fun > ParallelOps.violateOnAll() { + violate(Violations.onAll(this)) + } + + /** Creates a [Violations] object that violates inside each interval. */ + protected fun Windows.violateInside() = violate(Violations.inside(this)) + /** Creates a [Violations] object that violates outside each interval. */ + protected fun Windows.violateOutside() = violate(Violations.outside(this)) + + /** + * Creates a [Violations] object from two timelines, that violates whenever they have overlap. + * + * If either object is an activity, it will record the directive or instance id. + */ + protected fun , W: IntervalLike> GeneralOps.mutexViolations(other: GeneralOps) { + violate(Violations.mutex(this, other)) + } + + /** + * A generator function that calls [violate] to produce violations. + */ + abstract fun generate(plan: Plan, simResults: SimulationResults) + + final override fun run(plan: Plan, simResults: SimulationResults): Violations { + violations = mutableListOf() + generate(plan, simResults) + return Violations(violations) + } +} diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt index 3fcbc3e9cd..bb323e97dd 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt @@ -5,7 +5,7 @@ import gov.nasa.ammos.aerie.procedural.timeline.payloads.IntervalLike import gov.nasa.jpl.aerie.types.ActivityId /** A single violation of a constraint. */ -data class Violation( +data class Violation @JvmOverloads constructor( /** Interval on which the violation occurs. */ override val interval: Interval, diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt index 3a5c894cd6..9629b1616d 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt @@ -32,19 +32,19 @@ data class Violations(private val timeline: Timeline): fun mapIds(f: (Violation) -> List) = unsafeMap(BoundsTransformer.IDENTITY, false) { it.withNewIds(f(it)) } /***/ companion object { - /** Creates a [Violations] object that violates when this profile equals a given value. */ - @JvmStatic fun SerialConstantOps.violateOn(v: V) = isolateEqualTo(v).violations() + /** Creates a [Violations] object that violates when the profile equals a given value. */ + @JvmStatic fun on(tl: SerialConstantOps, v: V) = onAll(tl.isolateEqualTo(v)) /** Creates a [Violations] object that violates when this profile equals a given value. */ - @JvmStatic fun Real.violateOn(n: Number) = equalTo(n).violateOn(true) + @JvmStatic fun on(tl: Real, n: Number) = on(tl.equalTo(n), true) /** * Creates a [Violations] object that violates on every object in the timeline. * * If the object is an activity, it will record the directive or instance id. */ - @JvmStatic fun > ParallelOps.violations() = - unsafeMap(::Violations, BoundsTransformer.IDENTITY, false) { + @JvmStatic fun > onAll(tl: ParallelOps) = + tl.unsafeMap(::Violations, BoundsTransformer.IDENTITY, false) { Violation( it.interval, listOfNotNull(it.getActivityId()) @@ -52,17 +52,17 @@ data class Violations(private val timeline: Timeline): } /** Creates a [Violations] object that violates inside each interval. */ - @JvmStatic fun Windows.violateInside() = unsafeCast(::Universal).violations() + @JvmStatic fun inside(tl: Windows) = onAll(tl.unsafeCast(::Universal)) /** Creates a [Violations] object that violates outside each interval. */ - @JvmStatic fun Windows.violateOutside() = complement().violateInside() + @JvmStatic fun outside(tl: Windows) = inside(tl.complement()) /** * Creates a [Violations] object from two timelines, that violates whenever they have overlap. * * If either object is an activity, it will record the directive or instance id. */ - @JvmStatic infix fun , W: IntervalLike> GeneralOps.mutex(other: GeneralOps) = - unsafeMap2(::Violations, other) { l, r, i -> Violation( + @JvmStatic fun , W: IntervalLike> mutex(left: GeneralOps, right: GeneralOps) = + left.unsafeMap2(::Violations, right) { l, r, i -> Violation( i, listOfNotNull( l.getActivityId(), diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt new file mode 100644 index 0000000000..0d5e56e268 --- /dev/null +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt @@ -0,0 +1,61 @@ +package gov.nasa.ammos.aerie.procedural.constraints + +import gov.nasa.ammos.aerie.procedural.timeline.Interval +import gov.nasa.ammos.aerie.procedural.timeline.collections.profiles.Numbers +import gov.nasa.ammos.aerie.procedural.timeline.ops.coalesce.CoalesceSegmentsOp +import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults +import gov.nasa.ammos.aerie.procedural.timeline.util.duration.rangeTo +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration.seconds +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue +import org.junit.jupiter.api.Assertions.assertIterableEquals +import kotlin.test.Test + +class GeneratorTest: GeneratorConstraint() { + override fun generate(plan: Plan, simResults: SimulationResults) { + violate(Violation(Interval.at(seconds(0)))) + simResults.resource("/plant", Numbers.deserializer()) + .greaterThan(0) + .violateOn(false) + } + + @Test + fun testGenerator() { + val plan = NotImplementedPlan() + val simResults = object : SimulationResults { + override fun isStale() = TODO() + + override fun simBounds() = TODO() + + override fun > resource( + name: String, + deserializer: (List>) -> TL + ): TL { + if (name == "/plant") { + val list = listOf( + Segment(seconds(-4) .. seconds(-2), SerializedValue.of(-3)), + Segment(seconds(0) .. seconds(1), SerializedValue.of(3)), + Segment(seconds(1) .. seconds(2), SerializedValue.of(-1)), + ) + return deserializer(list) + } else { + TODO("Not yet implemented") + } + } + + override fun instances(type: String?, deserializer: (SerializedValue) -> A) = TODO() + } + + val result = run(plan, simResults).collect() + + assertIterableEquals( + listOf( + Violation(seconds(-4) .. seconds(-2)), + Violation(Interval.at(seconds(0))), + Violation(seconds(1) .. seconds(2)) + ), + result + ) + } +} diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt new file mode 100644 index 0000000000..3349b11523 --- /dev/null +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt @@ -0,0 +1,26 @@ +package gov.nasa.ammos.aerie.procedural.constraints + +import gov.nasa.ammos.aerie.procedural.timeline.Interval +import gov.nasa.ammos.aerie.procedural.timeline.collections.Directives +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue +import java.time.Instant + +class NotImplementedPlan: Plan { + override fun totalBounds(): Interval { + TODO("Not yet implemented") + } + + override fun toRelative(abs: Instant): Duration { + TODO("Not yet implemented") + } + + override fun toAbsolute(rel: Duration): Instant { + TODO("Not yet implemented") + } + + override fun directives(type: String?, deserializer: (SerializedValue) -> A): Directives { + TODO("Not yet implemented") + } +} diff --git a/procedural/examples/foo-procedures/src/main/java/gov/nasa/ammos/aerie/procedural/examples/fooprocedures/constraints/ConstFruit.java b/procedural/examples/foo-procedures/src/main/java/gov/nasa/ammos/aerie/procedural/examples/fooprocedures/constraints/ConstFruit.java index 9a2e7b9f93..762cc9f1fd 100644 --- a/procedural/examples/foo-procedures/src/main/java/gov/nasa/ammos/aerie/procedural/examples/fooprocedures/constraints/ConstFruit.java +++ b/procedural/examples/foo-procedures/src/main/java/gov/nasa/ammos/aerie/procedural/examples/fooprocedures/constraints/ConstFruit.java @@ -1,22 +1,20 @@ package gov.nasa.ammos.aerie.procedural.examples.fooprocedures.constraints; +import gov.nasa.ammos.aerie.procedural.constraints.GeneratorConstraint; import gov.nasa.ammos.aerie.procedural.constraints.Violations; -import gov.nasa.ammos.aerie.procedural.constraints.Constraint; -import gov.nasa.ammos.aerie.procedural.timeline.CollectOptions; import gov.nasa.ammos.aerie.procedural.timeline.collections.profiles.Real; -import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulatedPlan; +import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan; +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults; import org.jetbrains.annotations.NotNull; -public class ConstFruit implements Constraint { - @NotNull +public class ConstFruit extends GeneratorConstraint { @Override - public Violations run(SimulatedPlan plan, @NotNull CollectOptions options) { - final var fruit = plan.resource("/fruit", Real.deserializer()); + public void generate(@NotNull Plan plan, @NotNull SimulationResults simResults) { + final var fruit = simResults.resource("/fruit", Real.deserializer()); - - return Violations.violateOn( + violate(Violations.on( fruit.equalTo(4), false - ); + )); } } From 498d8c3028a474bc93c99670a0c64d369cd760bf Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Wed, 4 Sep 2024 17:40:44 -0700 Subject: [PATCH 2/4] Support violation messages --- .../procedural/constraints/Constraint.kt | 8 +++++ .../constraints/GeneratorConstraint.kt | 30 +++++++++++-------- .../aerie/procedural/constraints/Violation.kt | 9 ++++-- .../procedural/constraints/Violations.kt | 2 ++ .../procedural/constraints/GeneratorTest.kt | 2 ++ 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt index e1bc550382..be3545eb7f 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt @@ -15,4 +15,12 @@ interface Constraint { * @param simResults the [SimulationResults] that the result will be collected with */ fun run(plan: Plan, simResults: SimulationResults): Violations + + /** + * Default violation message to be displayed to user. + * + * Can be overridden on a violation-by-violation basis by manually specifying + * it in the [Violation] object. + */ + fun message(): String? = null } diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt index c2546966c1..26f3aa0b42 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt @@ -20,47 +20,53 @@ abstract class GeneratorConstraint: Constraint { private var violations = mutableListOf() /** Finalizes one or more violations. */ - protected fun violate(vararg v: Violation) { + @JvmOverloads protected fun violate(vararg v: Violation, message: String? = null) { violate(v.toList()) } /** Finalizes a list of violations. */ - protected fun violate(l: List) { - violations.addAll(l) + @JvmOverloads protected fun violate(l: List, message: String? = null) { + violations.addAll(l.map { + if (it.message == null) Violation( + it.interval, + message, + it.ids + ) else it + }) } /** Collects a [Violations] timeline and finalizes the result. */ - protected fun violate(tl: Violations) { + @JvmOverloads protected fun violate(tl: Violations, message: String? = null) { violate(tl.collect()) } /** Creates a [Violations] object that violates when this profile equals a given value. */ - protected fun SerialConstantOps.violateOn(v: V) = violate(Violations.on(this, v)) + @JvmOverloads protected fun SerialConstantOps.violateOn(v: V, message: String? = null) = violate(Violations.on(this, v), message) /** Creates a [Violations] object that violates when this profile equals a given value. */ - protected fun Real.violateOn(n: Number) = violate(Violations.on(this, n)) + @JvmOverloads protected fun Real.violateOn(n: Number, message: String? = null) = violate(Violations.on(this, n), message) /** * Creates a [Violations] object that violates on every object in the timeline. * * If the object is an activity, it will record the directive or instance id. */ - protected fun > ParallelOps.violateOnAll() { - violate(Violations.onAll(this)) + @JvmOverloads protected fun > ParallelOps.violateOnAll(message: String? = null) { + violate(Violations.onAll(this), message) } /** Creates a [Violations] object that violates inside each interval. */ - protected fun Windows.violateInside() = violate(Violations.inside(this)) + @JvmOverloads protected fun Windows.violateInside(message: String? = null) = violate(Violations.inside(this), message) /** Creates a [Violations] object that violates outside each interval. */ - protected fun Windows.violateOutside() = violate(Violations.outside(this)) + @JvmOverloads protected fun Windows.violateOutside(message: String? = null) = violate(Violations.outside(this), message) /** * Creates a [Violations] object from two timelines, that violates whenever they have overlap. * * If either object is an activity, it will record the directive or instance id. */ - protected fun , W: IntervalLike> GeneralOps.mutexViolations(other: GeneralOps) { - violate(Violations.mutex(this, other)) + @JvmOverloads protected fun , W: IntervalLike> GeneralOps.violateWhenSimultaneous(other: GeneralOps, message: String? = null) { + violate(Violations.whenSimultaneous(this, other), message) } /** diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt index bb323e97dd..e6a1d351ed 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violation.kt @@ -9,15 +9,18 @@ data class Violation @JvmOverloads constructor( /** Interval on which the violation occurs. */ override val interval: Interval, + /** Violation message to be displayed to user. */ + val message: String? = null, + /** List of associated activities (directives or instances) that are related to the violation. */ val ids: List = listOf() ) : IntervalLike { - override fun withNewInterval(i: Interval) = Violation(i, ids) + override fun withNewInterval(i: Interval) = Violation(i, message, ids) /** Constructs a violation on the same interval with a different list of ids. */ - fun withNewIds(vararg id: ActivityId) = Violation(interval, id.asList()) + fun withNewIds(vararg id: ActivityId) = Violation(interval, message, id.asList()) /** Constructs a violation on the same interval with a different list of ids. */ - fun withNewIds(ids: List) = Violation(interval, ids) + fun withNewIds(ids: List) = Violation(interval, message, ids) } diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt index 9629b1616d..2cf0950e20 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt @@ -47,6 +47,7 @@ data class Violations(private val timeline: Timeline): tl.unsafeMap(::Violations, BoundsTransformer.IDENTITY, false) { Violation( it.interval, + null, listOfNotNull(it.getActivityId()) ) } @@ -64,6 +65,7 @@ data class Violations(private val timeline: Timeline): @JvmStatic fun , W: IntervalLike> mutex(left: GeneralOps, right: GeneralOps) = left.unsafeMap2(::Violations, right) { l, r, i -> Violation( i, + null, listOfNotNull( l.getActivityId(), r.getActivityId() diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt index 0d5e56e268..e6586891aa 100644 --- a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt @@ -20,6 +20,8 @@ class GeneratorTest: GeneratorConstraint() { .violateOn(false) } + override fun message() = "Plant must be greater than 0" + @Test fun testGenerator() { val plan = NotImplementedPlan() From bfdb0f5d5ba00820663a59293ac90863993964cf Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Wed, 4 Sep 2024 17:41:26 -0700 Subject: [PATCH 3/4] Rename mutex --- .../gov/nasa/ammos/aerie/procedural/constraints/Violations.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt index 2cf0950e20..b1c1fabac0 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt @@ -62,7 +62,7 @@ data class Violations(private val timeline: Timeline): * * If either object is an activity, it will record the directive or instance id. */ - @JvmStatic fun , W: IntervalLike> mutex(left: GeneralOps, right: GeneralOps) = + @JvmStatic fun , W: IntervalLike> whenSimultaneous(left: GeneralOps, right: GeneralOps) = left.unsafeMap2(::Violations, right) { l, r, i -> Violation( i, null, From f02a8dd562c6fbddc2f18d898d7dd631554bcc56 Mon Sep 17 00:00:00 2001 From: JoelCourtney Date: Thu, 12 Sep 2024 17:44:43 -0700 Subject: [PATCH 4/4] Fix violation message passing --- .../procedural/constraints/Constraint.kt | 8 +------ .../constraints/GeneratorConstraint.kt | 24 +++++++++++++++---- .../procedural/constraints/Violations.kt | 10 ++++++++ .../procedural/constraints/GeneratorTest.kt | 19 ++++++--------- .../constraints/NotImplementedPlan.kt | 21 ++++------------ .../NotImplementedSimulationResults.kt | 18 ++++++++++++++ 6 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedSimulationResults.kt diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt index be3545eb7f..4381694ec9 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Constraint.kt @@ -16,11 +16,5 @@ interface Constraint { */ fun run(plan: Plan, simResults: SimulationResults): Violations - /** - * Default violation message to be displayed to user. - * - * Can be overridden on a violation-by-violation basis by manually specifying - * it in the [Violation] object. - */ - fun message(): String? = null + } diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt index 26f3aa0b42..31eebc042d 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorConstraint.kt @@ -1,5 +1,6 @@ package gov.nasa.ammos.aerie.procedural.constraints +import gov.nasa.ammos.aerie.procedural.timeline.Interval import gov.nasa.ammos.aerie.procedural.timeline.collections.Windows import gov.nasa.ammos.aerie.procedural.timeline.collections.profiles.Real import gov.nasa.ammos.aerie.procedural.timeline.ops.GeneralOps @@ -19,9 +20,14 @@ import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults abstract class GeneratorConstraint: Constraint { private var violations = mutableListOf() + /** Finalizes one or more intervals as violations. */ + @JvmOverloads protected fun violate(vararg i: Interval, message: String? = null) { + violate(i.map { Violation(it) }, message) + } + /** Finalizes one or more violations. */ @JvmOverloads protected fun violate(vararg v: Violation, message: String? = null) { - violate(v.toList()) + violate(v.toList(), message) } /** Finalizes a list of violations. */ @@ -29,7 +35,7 @@ abstract class GeneratorConstraint: Constraint { violations.addAll(l.map { if (it.message == null) Violation( it.interval, - message, + message ?: defaultMessage(), it.ids ) else it }) @@ -37,7 +43,7 @@ abstract class GeneratorConstraint: Constraint { /** Collects a [Violations] timeline and finalizes the result. */ @JvmOverloads protected fun violate(tl: Violations, message: String? = null) { - violate(tl.collect()) + violate(tl.collect(), message) } /** Creates a [Violations] object that violates when this profile equals a given value. */ @@ -74,9 +80,19 @@ abstract class GeneratorConstraint: Constraint { */ abstract fun generate(plan: Plan, simResults: SimulationResults) + /** + * Default violation message to be displayed to user. + * + * Can be overridden on a violation-by-violation basis by manually specifying + * it in the [Violation] object. + */ + open fun defaultMessage(): String? = null + final override fun run(plan: Plan, simResults: SimulationResults): Violations { violations = mutableListOf() generate(plan, simResults) - return Violations(violations) + val message = defaultMessage() + return if (message == null) Violations(violations) + else Violations(violations).withDefaultMessage(message) } } diff --git a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt index b1c1fabac0..cae0b26e7c 100644 --- a/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt +++ b/procedural/constraints/src/main/kotlin/gov/nasa/ammos/aerie/procedural/constraints/Violations.kt @@ -31,6 +31,16 @@ data class Violations(private val timeline: Timeline): */ fun mapIds(f: (Violation) -> List) = unsafeMap(BoundsTransformer.IDENTITY, false) { it.withNewIds(f(it)) } + /** + * Sets a default violation message for violations that don't already have one. + * + * @param message the default message to give to the user + */ + fun withDefaultMessage(message: String) = unsafeMap(BoundsTransformer.IDENTITY, false) { + if (it.message == null) Violation(it.interval, message, it.ids) + else it + } + /***/ companion object { /** Creates a [Violations] object that violates when the profile equals a given value. */ @JvmStatic fun on(tl: SerialConstantOps, v: V) = onAll(tl.isolateEqualTo(v)) diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt index e6586891aa..91c10bac81 100644 --- a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/GeneratorTest.kt @@ -14,22 +14,18 @@ import kotlin.test.Test class GeneratorTest: GeneratorConstraint() { override fun generate(plan: Plan, simResults: SimulationResults) { - violate(Violation(Interval.at(seconds(0)))) + violate(Interval.at(seconds(0)), message = "other message") simResults.resource("/plant", Numbers.deserializer()) .greaterThan(0) .violateOn(false) } - override fun message() = "Plant must be greater than 0" + override fun defaultMessage() = "Plant must be greater than 0" @Test fun testGenerator() { val plan = NotImplementedPlan() - val simResults = object : SimulationResults { - override fun isStale() = TODO() - - override fun simBounds() = TODO() - + val simResults = object : NotImplementedSimulationResults() { override fun > resource( name: String, deserializer: (List>) -> TL @@ -45,17 +41,16 @@ class GeneratorTest: GeneratorConstraint() { TODO("Not yet implemented") } } - - override fun instances(type: String?, deserializer: (SerializedValue) -> A) = TODO() } val result = run(plan, simResults).collect() + val defaultMessage = "Plant must be greater than 0"; assertIterableEquals( listOf( - Violation(seconds(-4) .. seconds(-2)), - Violation(Interval.at(seconds(0))), - Violation(seconds(1) .. seconds(2)) + Violation(seconds(-4) .. seconds(-2), defaultMessage), + Violation(Interval.at(seconds(0)), "other message"), + Violation(seconds(1) .. seconds(2), defaultMessage) ), result ) diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt index 3349b11523..786cf5f370 100644 --- a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedPlan.kt @@ -7,20 +7,9 @@ import gov.nasa.jpl.aerie.merlin.protocol.types.Duration import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue import java.time.Instant -class NotImplementedPlan: Plan { - override fun totalBounds(): Interval { - TODO("Not yet implemented") - } - - override fun toRelative(abs: Instant): Duration { - TODO("Not yet implemented") - } - - override fun toAbsolute(rel: Duration): Instant { - TODO("Not yet implemented") - } - - override fun directives(type: String?, deserializer: (SerializedValue) -> A): Directives { - TODO("Not yet implemented") - } +open class NotImplementedPlan: Plan { + override fun totalBounds(): Interval = TODO() + override fun toRelative(abs: Instant): Duration = TODO() + override fun toAbsolute(rel: Duration): Instant = TODO() + override fun directives(type: String?, deserializer: (SerializedValue) -> A): Directives = TODO() } diff --git a/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedSimulationResults.kt b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedSimulationResults.kt new file mode 100644 index 0000000000..6833e30051 --- /dev/null +++ b/procedural/constraints/src/test/kotlin/gov/nasa/ammos/aerie/procedural/constraints/NotImplementedSimulationResults.kt @@ -0,0 +1,18 @@ +package gov.nasa.ammos.aerie.procedural.constraints + +import gov.nasa.ammos.aerie.procedural.timeline.Interval +import gov.nasa.ammos.aerie.procedural.timeline.collections.Instances +import gov.nasa.ammos.aerie.procedural.timeline.ops.coalesce.CoalesceSegmentsOp +import gov.nasa.ammos.aerie.procedural.timeline.payloads.Segment +import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults +import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue + +open class NotImplementedSimulationResults: SimulationResults { + override fun isStale(): Boolean = TODO() + override fun simBounds(): Interval = TODO() + override fun > resource( + name: String, + deserializer: (List>) -> TL + ): TL = TODO() + override fun instances(type: String?, deserializer: (SerializedValue) -> A): Instances = TODO() +}