Skip to content

Commit

Permalink
Merge pull request #1363 from NASA-AMMOS/feat/procedural-constraints-lib
Browse files Browse the repository at this point in the history
Procedural constraints library
  • Loading branch information
JoelCourtney authored Mar 28, 2024
2 parents 2930bf9 + 264bb1e commit 0b098c7
Show file tree
Hide file tree
Showing 81 changed files with 251 additions and 21 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ configure(subprojects.findAll { it.projectDir.toPath().resolve('sql').toFile().e

// Remove distributed SQL as part of `clean` task
task undoDistributeSql(type: Delete) {
doLast { // Explicity do last to avoid running during configuration step
doLast { // Explicitly do last to avoid running during configuration step
file("${sp.projectDir}/sql").list().each {
delete "$rootDir/deployment/postgres-init-db/sql/$it"
}
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ task e2eTest(type: Test) {
}

dependencies {
testImplementation project(":timeline")
testImplementation project(":procedural:timeline")
testImplementation "com.zaxxer:HikariCP:5.1.0"
testImplementation("org.postgresql:postgresql:42.6.0")
testImplementation project(':merlin-driver')
Expand Down
7 changes: 7 additions & 0 deletions procedural/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Procedural post-simulation libraries

This subproject holds the libraries provided to the users for procedural scheduling / constraints / conditions.

## Documentation

To generate the unified docs, run `./gradlew :procedural:dokkaHtmlMultiModule`. It will be available in `procedural/build/dokka/htmlMultiModule/index.html`. To view it locally, you'll need a static file server to avoid CORS problems. In IntelliJ, you can right-click on `index.html` and select `Open In -> Browser -> ...` and this will start a server for you.
8 changes: 8 additions & 0 deletions procedural/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.9.22"
id 'org.jetbrains.dokka' version '1.9.10'
}

repositories {
mavenCentral()
}
6 changes: 6 additions & 0 deletions procedural/constraints/MODULE_DOCS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Module Constraints

This library provides basic tools for creating constraints, based on the timeline library.
To define a constraint, just create a class that implements the Constraint interface.
Then, follow the tutorial documentation (TODO: link to tutorial documentation once written)
to package and upload your constraint to Aerie.
9 changes: 9 additions & 0 deletions procedural/constraints/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Constraints

This library provides the `Constraint` interface and `Violations` timeline type, for users to create procedural constraints.

- Building and testing: `./gradlew :procedural:constraints:build`
- Generating a jar for local experimentation: `./gradlew :procedural:constraints:shadowJar`
- jar will be available at `procedural/constraints/build/libs/constraints-all.jar`

See `/procedural/README.md` for instructions on generating viewing documentation.
54 changes: 54 additions & 0 deletions procedural/constraints/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
id "org.jetbrains.kotlin.jvm" version "1.9.22"
id 'java-library'
id 'org.jetbrains.dokka' version '1.9.10'
}

repositories {
mavenCentral()
}

dependencies {
implementation project(':procedural:timeline')

testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType(KotlinJvmCompile.class).configureEach {
jvmTargetValidationMode = JvmTargetValidationMode.WARNING
}

tasks.named('test') {
useJUnitPlatform()
}

kotlin {
jvmToolchain(21)
}

var timelineSource = "${project(":procedural:timeline").projectDir}/src/main/kotlin"

dokkaHtmlPartial.configure {
dokkaSourceSets {
configureEach {
// used as project name in the header
moduleName.set("Constraints")

reportUndocumented.set(true)
failOnWarning.set(true)

// contains descriptions for the module and the packages
includes.from("MODULE_DOCS.md")

sourceRoots.from(timelineSource)
suppressedFiles.from(timelineSource)
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gov.nasa.jpl.aerie.procedural.constraints

/** An activity ID, referencing either a directive or instance. */
sealed interface ActivityId {
/***/ data class InstanceId(/***/ val id: Long): ActivityId
/***/ data class DirectiveId(/***/ val id: Long): ActivityId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gov.nasa.jpl.aerie.procedural.constraints

import gov.nasa.jpl.aerie.timeline.CollectOptions
import gov.nasa.jpl.aerie.timeline.plan.Plan

/** The interface that all constraints must satisfy. */
interface Constraint {
/**
* Run the constraint on a plan.
*
* The provided collect options are the options that the [Violations] result will be collected on after
* 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
*/
fun run(plan: Plan, options: CollectOptions): Violations
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gov.nasa.jpl.aerie.procedural.constraints

import gov.nasa.jpl.aerie.timeline.Interval
import gov.nasa.jpl.aerie.timeline.payloads.IntervalLike

/** A single violation of a constraint. */
data class Violation(
/** Interval on which the violation occurs. */
override val interval: Interval,

/** List of associated activities (directives or instances) that are related to the violation. */
val ids: List<ActivityId> = listOf()
) : IntervalLike<Violation> {

override fun withNewInterval(i: Interval) = Violation(i, ids)

/** Constructs a violation on the same interval with a different list of ids. */
fun withNewIds(vararg id: ActivityId) = Violation(interval, id.asList())

/** Constructs a violation on the same interval with a different list of ids. */
fun withNewIds(ids: List<ActivityId>) = Violation(interval, ids)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package gov.nasa.jpl.aerie.procedural.constraints

import gov.nasa.jpl.aerie.procedural.constraints.ActivityId.DirectiveId
import gov.nasa.jpl.aerie.procedural.constraints.ActivityId.InstanceId
import gov.nasa.jpl.aerie.timeline.BaseTimeline
import gov.nasa.jpl.aerie.timeline.BoundsTransformer
import gov.nasa.jpl.aerie.timeline.Timeline
import gov.nasa.jpl.aerie.timeline.collections.Intervals
import gov.nasa.jpl.aerie.timeline.collections.Windows
import gov.nasa.jpl.aerie.timeline.collections.profiles.Real
import gov.nasa.jpl.aerie.timeline.ops.*
import gov.nasa.jpl.aerie.timeline.ops.coalesce.CoalesceNoOp
import gov.nasa.jpl.aerie.timeline.payloads.IntervalLike
import gov.nasa.jpl.aerie.timeline.payloads.activities.Directive
import gov.nasa.jpl.aerie.timeline.payloads.activities.Instance
import gov.nasa.jpl.aerie.timeline.util.preprocessList

/** A timeline of [Violations][Violation]. */
data class Violations(private val timeline: Timeline<Violation, Violations>):
Timeline<Violation, Violations> by timeline,
ParallelOps<Violation, Violations>,
NonZeroDurationOps<Violation, Violations>,
CoalesceNoOp<Violation, Violations>
{
constructor(vararg violation: Violation): this(violation.asList())
constructor(violations: List<Violation>): this(BaseTimeline(::Violations, preprocessList(violations, null)))

/**
* Maps the list of associated activity ids on each violation.
*
* @param f a function which takes a [Violation] and returns a new list of ids.
*/
fun mapIds(f: (Violation) -> List<ActivityId>) = 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 <V: Any> SerialConstantOps<V, *>.violateOn(v: V) = isolateEqualTo(v).violations()

/** Creates a [Violations] object that violates when this profile equals a given value. */
@JvmStatic fun Real.violateOn(n: Number) = equalTo(n).violateOn(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 <I: IntervalLike<I>> ParallelOps<I, *>.violations() =
unsafeMap(::Violations, BoundsTransformer.IDENTITY, false) {
Violation(
it.interval,
listOfNotNull(it.getActivityId())
)
}

/** Creates a [Violations] object that violates inside each interval. */
@JvmStatic fun Windows.violateInside() = unsafeCast(::Intervals).violations()
/** Creates a [Violations] object that violates outside each interval. */
@JvmStatic fun Windows.violateOutside() = complement().violateInside()

/**
* 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 <V: IntervalLike<V>, W: IntervalLike<W>> GeneralOps<V, *>.mutex(other: GeneralOps<W, *>) =
unsafeMap2(::Violations, other) { l, r, i -> Violation(
i,
listOfNotNull(
l.getActivityId(),
r.getActivityId()
)
)}

private fun <V: IntervalLike<V>> V.getActivityId() = when (this) {
is Instance<*> -> InstanceId(id)
is Directive<*> -> DirectiveId(id)
else -> null
}
}
}


File renamed without changes.
27 changes: 27 additions & 0 deletions procedural/timeline/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Timelines

This library provides tools for querying and manipulating "timelines" from an Aerie plan or set of
simulation results. This includes things like resource profiles, activity instances, and activity directives,
but can be extended to support more kinds if needed.

See [MODULE_DOCS.md](./MODULE_DOCS.md) for a description of the architecture and design of the library.

- Building and testing: `./gradlew :procedural:timeline:build`
- Generating a jar for local experimentation: `./gradlew :procedural:timeline:shadowJar`
- jar will be available at `procedural/timeline/build/libs/timeline-all.jar`

See `/procedural/README.md` for instructions on generating viewing documentation.

## Potential future optimizations

- **caching/memoization:** currently collecting a timeline twice will result in it being
computed twice. Automatically memoizing the results has potential for unsoundness,
but to be fair, so does the entire concept of evaluating on restricted bounds. But if
we do it right, it could give theoretically unbounded speedup for complex timelines.
- **inlining durations:** Turn the Duration class into an `inline value` class. This speeds
up the entire library by about 20%, based on a rudimentary benchmark. On the downside,
it makes the interface in Java use a bunch of longs instead of duration objects. Should
be possible to do cleanly, but will take some cleverness.
- **streaming:** rework the core to be create streams instead of lists. It should be somewhat faster,
but I don't know how much. The main utility is that with segment streaming from simulation,
you could evaluate timelines in parallel with simulation.
10 changes: 5 additions & 5 deletions timeline/build.gradle → procedural/timeline/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ kotlin {
jvmToolchain(21)
}

dokkaHtml.configure {
dokkaHtmlPartial.configure {
dokkaSourceSets {
named("main") {
configureEach {
// used as project name in the header
moduleName.set("Timeline")

Expand All @@ -49,10 +49,10 @@ dokkaHtml.configure {

// adds source links that lead to this repository, allowing readers
// to easily find source code for inspected declarations
sourceLink.configure {
sourceLink {
localDirectory.set(file("timeline/src/main/kotlin"))
remoteUrl.set(URL(
"https://github.com/NASA-AMMOS/aerie/blob/feat/java-timeline-library/timeline/src/main/kotlin"
remoteUrl.set(new URL(
"https://github.com/NASA-AMMOS/aerie/blob/feat/java-timeline-library/timeline/src/main/kotlin/"
))
remoteLineSuffix.set("#L")
}
Expand Down
5 changes: 4 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ include 'contrib'
// Service support
include 'parsing-utilities'
include 'permissions'
include 'timeline'

// Procedural post-simulation libraries
include 'procedural:timeline'
include 'procedural:constraints'

// Services for deployment within the Aerie infrastructure
include 'merlin-server'
Expand Down
13 changes: 0 additions & 13 deletions timeline/README.md

This file was deleted.

0 comments on commit 0b098c7

Please sign in to comment.