From 404826e7e1a2ced3dc2ee53d41f2288789044fa0 Mon Sep 17 00:00:00 2001 From: David Legg Date: Mon, 18 Dec 2023 18:44:56 -0800 Subject: [PATCH] Add the streamline-demo example model Add an executable example model used to test and demonstrate the framework. The example model comprises three main components: * ErrorTestingModel * DataModel * ApproximationModel The ErrorTestingModel demonstrates error-handling behavior for resources. Using the `CauseError` activity, we can trigger one of several kinds of errors from the plan, including effects which fail directly, concurrent effects which conflict, and resources which violate a constraint. We can also test out different combinations of naming and error handling behaviors, to see what information is propagated back to the user for debugging. Finally, we can observe that, when logging errors, portions of the model can fail while independent portions of the model continue to function normally. The DataModel demonstrates a complicated resource modeling problem, leveraging the LinearBoundaryConsistencySolver. This problem models a data system with bin space shared across multiple buckets, where some buckets have priority over others, and each bucket independently sets desired write or delete rates. The problem of allocating bin space to each bucket, allowing higher-priority buckets to overwrite lower-priority ones only if needed, is phrased as linear inequalities on polynomial resources and given to the solver. The `ChangeDesiredRate` activity can be used to set up different scenarios for this model. The ApproximationModel shows three resources, a polynomial, a rational function, and a complicated trig function, approximated various ways. The `ChangeApproximationInput` activity can be used to change the polynomials feeding this model, and the simulation configuration contains a setting for the approximation accuracy, affecting those resources which approximate by bounding their maximum error. In particular, the `approximation/trig/default` resource displays the most complicated resource function, comprising trig and exponentials, defined through the Java Math library, approximated by the default secant approximation method. --- examples/streamline-demo/build.gradle | 42 +++++++ .../streamline_demo/ApproximationModel.java | 102 +++++++++++++++++ .../jpl/aerie/streamline_demo/CauseError.java | 73 ++++++++++++ .../ChangeApproximationInput.java | 22 ++++ .../streamline_demo/ChangeDesiredRate.java | 29 +++++ .../aerie/streamline_demo/Configuration.java | 16 +++ .../jpl/aerie/streamline_demo/DataModel.java | 106 ++++++++++++++++++ .../streamline_demo/ErrorTestingModel.java | 39 +++++++ .../jpl/aerie/streamline_demo/Mission.java | 22 ++++ .../aerie/streamline_demo/package-info.java | 17 +++ ...l.aerie.merlin.protocol.model.MerlinPlugin | 1 + ...erie.merlin.protocol.model.SchedulerPlugin | 1 + merlin-framework/build.gradle | 1 + settings.gradle | 1 + 14 files changed, 472 insertions(+) create mode 100644 examples/streamline-demo/build.gradle create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ApproximationModel.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/CauseError.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeApproximationInput.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeDesiredRate.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Configuration.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/DataModel.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ErrorTestingModel.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java create mode 100644 examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/package-info.java create mode 100644 examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.MerlinPlugin create mode 100644 examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerPlugin diff --git a/examples/streamline-demo/build.gradle b/examples/streamline-demo/build.gradle new file mode 100644 index 0000000000..24ed559c1a --- /dev/null +++ b/examples/streamline-demo/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'java-library' + id 'jacoco' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(19) + } +} + +jar { + from { + configurations.runtimeClasspath.filter{ it.exists() }.collect{ it.isDirectory() ? it : zipTree(it) } + } { + exclude 'META-INF/LICENSE.txt', 'META-INF/NOTICE.txt' + } +} + +test { + useJUnitPlatform() +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + } +} + +dependencies { + annotationProcessor project(':merlin-framework-processor') + + implementation project(':merlin-framework') + implementation project(':contrib') + + testImplementation project(':merlin-framework-junit') + testImplementation 'org.assertj:assertj-core:3.23.1' + + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ApproximationModel.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ApproximationModel.java new file mode 100644 index 0000000000..b7355f0b35 --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ApproximationModel.java @@ -0,0 +1,102 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; +import gov.nasa.jpl.aerie.contrib.streamline.core.Resource; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.*; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.linear.Linear; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial; + +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.Approximation.approximate; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.Approximation.relative; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.DifferentiableResources.asDifferentiable; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.IntervalFunctions.byBoundingError; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.IntervalFunctions.byUniformSampling; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.SecantApproximation.ErrorEstimates.errorByOptimization; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.SecantApproximation.ErrorEstimates.errorByQuadraticApproximation; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.SecantApproximation.secantApproximation; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.UnstructuredResources.approximateAsLinear; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.UnstructuredResources.approximateAsLinear; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.UnstructuredResources.asUnstructured; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.black_box.monads.UnstructuredResourceApplicative.map; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources.*; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources.approximateAsLinear; +import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.*; +import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.MINUTE; + +public class ApproximationModel { + private static final double EPSILON = 1e-10; + + public MutableResource polynomial; + public MutableResource divisor; + + public Resource assumedLinear; + public Resource uniformApproximation; + public Resource differentiableApproximation; + public Resource directApproximation; + public Resource defaultApproximation; + + public Resource> rationalFunction; + public Resource uniformApproximation2; + public Resource directApproximation2; + public Resource defaultApproximation2; + + public Resource> trigFunction; + public Resource trigDefaultApproximation; + + public ApproximationModel(final Registrar registrar, final Configuration config) { + final double tolerance = config.approximationTolerance; + + polynomial = polynomialResource(1); + divisor = polynomialResource(1); + + assumedLinear = assumeLinear(polynomial); + defaultApproximation = approximateAsLinear(polynomial, tolerance); + uniformApproximation = approximate( + polynomial, + SecantApproximation.secantApproximation(byUniformSampling(MINUTE))); + differentiableApproximation = approximate( + asDifferentiable(polynomial), + secantApproximation(byBoundingError( + tolerance, + SECOND, + HOUR.times(24), + relative(errorByQuadraticApproximation(), EPSILON)))); + directApproximation = approximate( + polynomial, + secantApproximation(byBoundingError( + tolerance, + SECOND, + HOUR.times(24), + Approximation.relative(errorByOptimization(), EPSILON)))); + + rationalFunction = map( + asUnstructured(polynomial), asUnstructured(divisor), (p, q) -> p / q); + + defaultApproximation2 = approximateAsLinear(rationalFunction, tolerance); + uniformApproximation2 = approximate(rationalFunction, + SecantApproximation.>secantApproximation(byUniformSampling(MINUTE))); + directApproximation2 = approximate(rationalFunction, + secantApproximation(byBoundingError( + tolerance, + SECOND, + HOUR.times(24), + Approximation.>relative(errorByOptimization(), EPSILON)))); + + trigFunction = map(asUnstructured(polynomial), asUnstructured(divisor), + (p, q) -> Math.sin(p * Math.exp(-q / Math.PI))); + trigDefaultApproximation = approximateAsLinear(trigFunction, tolerance); + + registrar.real("approximation/polynomial/assumedLinear", assumedLinear); + registrar.real("approximation/polynomial/default", defaultApproximation); + registrar.real("approximation/polynomial/uniform", uniformApproximation); + registrar.real("approximation/polynomial/differentiable", differentiableApproximation); + registrar.real("approximation/polynomial/direct", directApproximation); + + registrar.real("approximation/rational/default", defaultApproximation2); + registrar.real("approximation/rational/uniform", uniformApproximation2); + registrar.real("approximation/rational/direct", directApproximation2); + + registrar.real("approximation/trig/default", trigDefaultApproximation); + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/CauseError.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/CauseError.java new file mode 100644 index 0000000000..8f07dd4945 --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/CauseError.java @@ -0,0 +1,73 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; +import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics; +import gov.nasa.jpl.aerie.contrib.streamline.core.DynamicsEffect; +import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType; +import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType.EffectModel; +import gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Parameter; + +import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.DynamicsMonad.effect; +import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Context.contextualized; +import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Context.inContext; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteEffects.set; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial.polynomial; +import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.delay; +import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.spawn; +import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.HOUR; + +@ActivityType("CauseError") +public class CauseError { + @Parameter + public ResourceSelection selection; + + @Parameter + public String effectName = ""; + + @Parameter + public String activityName = ""; + + @EffectModel + public void run(Mission mission) { + inContext("CauseError" + (activityName.isEmpty() ? "" : " " + activityName), () -> { + delay(HOUR); + switch (selection) { + case Bool -> causeError(mission.errorTestingModel.bool); + case Counter -> causeError(mission.errorTestingModel.counter); + case Continuous -> causeError(mission.errorTestingModel.continuous); + case NonCommuting -> { + spawn(contextualized("Branch 1", () -> { + set(mission.errorTestingModel.counter, 5); + })); + spawn(contextualized("Branch 2", () -> { + set(mission.errorTestingModel.counter, 6); + })); + } + case DoomedClamp -> { + MutableResource.set(mission.errorTestingModel.lowerBound, polynomial(-20, 0.001)); + MutableResource.set(mission.errorTestingModel.upperBound, polynomial(20, -0.001)); + } + } + delay(HOUR); + }); + } + + private > void causeError(MutableResource resource) { + DynamicsEffect effect = effect($ -> { + throw new IllegalStateException("Pretend this is a more informative error message."); + }); + if (effectName.isEmpty()) { + resource.emit(effect); + } else { + resource.emit(effectName, effect); + } + } + + public enum ResourceSelection { + Bool, + Counter, + Continuous, + NonCommuting, + DoomedClamp + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeApproximationInput.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeApproximationInput.java new file mode 100644 index 0000000000..da9ae8334d --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeApproximationInput.java @@ -0,0 +1,22 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType; +import gov.nasa.jpl.aerie.merlin.framework.annotations.Export; + +import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.set; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial.polynomial; + +@ActivityType("ChangeApproximationInput") +public class ChangeApproximationInput { + @Export.Parameter + public double[] numeratorCoefficients; + + @Export.Parameter + public double[] denominatorCoefficients; + + @ActivityType.EffectModel + public void run(Mission mission) { + set(mission.approximationModel.polynomial, polynomial(numeratorCoefficients)); + set(mission.approximationModel.divisor, polynomial(denominatorCoefficients)); + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeDesiredRate.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeDesiredRate.java new file mode 100644 index 0000000000..d9cc417afd --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ChangeDesiredRate.java @@ -0,0 +1,29 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType; +import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType.EffectModel; +import gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Parameter; + +import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.set; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial.polynomial; + +@ActivityType("ChangeDesiredRate") +public class ChangeDesiredRate { + @Parameter + public Bucket bucket; + + @Parameter + public double rate; + + @EffectModel + public void run(Mission mission) { + var rateToChange = switch (bucket) { + case A -> mission.dataModel.desiredRateA; + case B -> mission.dataModel.desiredRateB; + case C -> mission.dataModel.desiredRateC; + }; + set(rateToChange, polynomial(rate)); + } + + public enum Bucket { A, B, C } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Configuration.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Configuration.java new file mode 100644 index 0000000000..fbdf8a0a7a --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Configuration.java @@ -0,0 +1,16 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Parameter; +import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; + +public final class Configuration { + @Parameter + public boolean traceResources = false; + + @Parameter + public double approximationTolerance = 1e-2; + + @Parameter + public Duration profilingDumpTime = Duration.ZERO; + +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/DataModel.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/DataModel.java new file mode 100644 index 0000000000..64bf344c6a --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/DataModel.java @@ -0,0 +1,106 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; +import gov.nasa.jpl.aerie.contrib.streamline.core.Resource; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.LinearBoundaryConsistencySolver; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.LinearBoundaryConsistencySolver.Domain; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources; + +import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.eraseExpiry; +import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.forward; +import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.*; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.choose; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.LinearBoundaryConsistencySolver.Comparison.*; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.LinearBoundaryConsistencySolver.LinearExpression.lx; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources.*; + +public class DataModel { + public MutableResource desiredRateA = PolynomialResources.polynomialResource(0); + public MutableResource desiredRateB = PolynomialResources.polynomialResource(0); + public MutableResource desiredRateC = PolynomialResources.polynomialResource(0); + public MutableResource upperBoundOnTotalVolume = PolynomialResources.polynomialResource(10); + + public Resource actualRateA, actualRateB, actualRateC; + public MutableResource volumeA, volumeB, volumeC; + public Resource totalVolume; + + public DataModel(final Registrar registrar, final Configuration config) { + // Adding a "derivative" operation makes the constraint problem no longer solvable without backtracking. + // Instead of solving for it directly, we'll solve in two steps, one for rate, one for volume. + + // Set up the rate solver + var rateSolver = new LinearBoundaryConsistencySolver("DataModel Rate Solver"); + var rateA = rateSolver.variable("rateA", Domain::upperBound); + var rateB = rateSolver.variable("rateB", Domain::upperBound); + var rateC = rateSolver.variable("rateC", Domain::upperBound); + this.actualRateA = rateA.resource(); + this.actualRateB = rateB.resource(); + this.actualRateC = rateC.resource(); + + // Use a simple feedback loop on volumes to do the integration and clamping. + this.volumeA = PolynomialResources.polynomialResource(0); + this.volumeB = PolynomialResources.polynomialResource(0); + this.volumeC = PolynomialResources.polynomialResource(0); + var clampedVolumeA = clamp(this.volumeA, constant(0), upperBoundOnTotalVolume); + var volumeB_ub = subtract(upperBoundOnTotalVolume, clampedVolumeA); + var clampedVolumeB = clamp(this.volumeB, constant(0), volumeB_ub); + var volumeC_ub = subtract(volumeB_ub, clampedVolumeB); + var clampedVolumeC = clamp(this.volumeC, constant(0), volumeC_ub); + var correctedVolumeA = map(clampedVolumeA, actualRateA, (v, r) -> r.integral(v.extract())); + var correctedVolumeB = map(clampedVolumeB, actualRateB, (v, r) -> r.integral(v.extract())); + var correctedVolumeC = map(clampedVolumeC, actualRateC, (v, r) -> r.integral(v.extract())); + // Use the corrected integral values to set volumes, but erase expiry information in the process to avoid loops: + forward(eraseExpiry(correctedVolumeA), this.volumeA); + forward(eraseExpiry(correctedVolumeB), this.volumeB); + forward(eraseExpiry(correctedVolumeC), this.volumeC); + + // Integrate the actual rates. + totalVolume = add(this.volumeA, this.volumeB, this.volumeC); + + // Then use the solver to adjust the actual rates + + // When full, we never write more than the upper bound will tolerate, in total + var isFull = greaterThanOrEquals(totalVolume, upperBoundOnTotalVolume); + var totalRate_ub = choose(isFull, differentiate(upperBoundOnTotalVolume), constant(Double.POSITIVE_INFINITY)); + rateSolver.declare(lx(rateA).add(lx(rateB)).add(lx(rateC)), LessThanOrEquals, lx(totalRate_ub)); + + // We only exceed the desired rate when we try to delete from an empty bucket. + var isEmptyA = lessThanOrEquals(this.volumeA, 0); + var isEmptyB = lessThanOrEquals(this.volumeB, 0); + var isEmptyC = lessThanOrEquals(this.volumeC, 0); + var rateA_ub = choose(isEmptyA, max(desiredRateA, constant(0)), desiredRateA); + var rateB_ub = choose(isEmptyB, max(desiredRateB, constant(0)), desiredRateB); + var rateC_ub = choose(isEmptyC, max(desiredRateC, constant(0)), desiredRateC); + rateSolver.declare(lx(rateA), LessThanOrEquals, lx(rateA_ub)); + rateSolver.declare(lx(rateB), LessThanOrEquals, lx(rateB_ub)); + rateSolver.declare(lx(rateC), LessThanOrEquals, lx(rateC_ub)); + + // We cannot delete from an empty bucket + var rateA_lb = choose(isEmptyA, constant(0), constant(Double.NEGATIVE_INFINITY)); + var rateB_lb = choose(isEmptyB, constant(0), constant(Double.NEGATIVE_INFINITY)); + var rateC_lb = choose(isEmptyC, constant(0), constant(Double.NEGATIVE_INFINITY)); + rateSolver.declare(lx(rateA), GreaterThanOrEquals, lx(rateA_lb)); + rateSolver.declare(lx(rateB), GreaterThanOrEquals, lx(rateB_lb)); + rateSolver.declare(lx(rateC), GreaterThanOrEquals, lx(rateC_lb)); + + registerStates(registrar, config); + } + + private void registerStates(Registrar registrar, Configuration config) { + registrar.real("desiredRateA", assumeLinear(desiredRateA)); + registrar.real("desiredRateB", assumeLinear(desiredRateB)); + registrar.real("desiredRateC", assumeLinear(desiredRateC)); + + registrar.real("actualRateA", assumeLinear(actualRateA)); + registrar.real("actualRateB", assumeLinear(actualRateB)); + registrar.real("actualRateC", assumeLinear(actualRateC)); + + registrar.real("volumeA", assumeLinear(volumeA)); + registrar.real("volumeB", assumeLinear(volumeB)); + registrar.real("volumeC", assumeLinear(volumeC)); + registrar.real("totalVolume", assumeLinear(totalVolume)); + registrar.real("maxVolume", assumeLinear(upperBoundOnTotalVolume)); + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ErrorTestingModel.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ErrorTestingModel.java new file mode 100644 index 0000000000..ce2693751f --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/ErrorTestingModel.java @@ -0,0 +1,39 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.serialization.mappers.BooleanValueMapper; +import gov.nasa.jpl.aerie.contrib.serialization.mappers.IntegerValueMapper; +import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; +import gov.nasa.jpl.aerie.contrib.streamline.core.Resource; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.Polynomial; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources; + +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.discreteResource; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.monads.DiscreteResourceMonad.map; +import static gov.nasa.jpl.aerie.contrib.streamline.modeling.polynomial.PolynomialResources.*; + +public class ErrorTestingModel { + public MutableResource> bool = DiscreteResources.discreteResource(true); + public MutableResource> counter = DiscreteResources.discreteResource(5); + public MutableResource continuous = PolynomialResources.polynomialResource(1); + public Resource derived = multiply( + continuous, + asPolynomial(map(counter, c -> (double) c)), + asPolynomial(map(bool, $ -> $ ? 1.0 : -1.0))); + + public MutableResource upperBound = PolynomialResources.polynomialResource(5); + public MutableResource lowerBound = PolynomialResources.polynomialResource(-5); + public Resource clamped = clamp(constant(10), lowerBound, upperBound); + + public ErrorTestingModel(final Registrar registrar, final Configuration config) { + registrar.discrete("errorTesting/bool", bool, new BooleanValueMapper()); + registrar.discrete("errorTesting/counter", counter, new IntegerValueMapper()); + registrar.real("errorTesting/continuous", assumeLinear(continuous)); + registrar.real("errorTesting/derived", assumeLinear(derived)); + registrar.real("errorTesting/lowerBound", assumeLinear(lowerBound)); + registrar.real("errorTesting/upperBound", assumeLinear(upperBound)); + registrar.real("errorTesting/clamped", assumeLinear(clamped)); + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java new file mode 100644 index 0000000000..2021ed3c57 --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/Mission.java @@ -0,0 +1,22 @@ +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.streamline.debugging.Profiling; +import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar; +import gov.nasa.jpl.aerie.merlin.framework.ModelActions; + +public final class Mission { + public final DataModel dataModel; + public final ErrorTestingModel errorTestingModel; + public final ApproximationModel approximationModel; + + public Mission(final gov.nasa.jpl.aerie.merlin.framework.Registrar registrar$, final Configuration config) { + var registrar = new Registrar(registrar$, Registrar.ErrorBehavior.Log); + if (config.traceResources) registrar.setTrace(); + dataModel = new DataModel(registrar, config); + errorTestingModel = new ErrorTestingModel(registrar, config); + approximationModel = new ApproximationModel(registrar, config); + if (config.profilingDumpTime.isPositive()) { + ModelActions.defer(config.profilingDumpTime, Profiling::dump); + } + } +} diff --git a/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/package-info.java b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/package-info.java new file mode 100644 index 0000000000..310237e0bc --- /dev/null +++ b/examples/streamline-demo/src/main/java/gov/nasa/jpl/aerie/streamline_demo/package-info.java @@ -0,0 +1,17 @@ +@MissionModel(model = Mission.class) + +@WithConfiguration(Configuration.class) + +@WithMappers(BasicValueMappers.class) + +@WithActivityType(ChangeDesiredRate.class) +@WithActivityType(CauseError.class) +@WithActivityType(ChangeApproximationInput.class) + +package gov.nasa.jpl.aerie.streamline_demo; + +import gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers; +import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel; +import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel.WithActivityType; +import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel.WithConfiguration; +import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel.WithMappers; diff --git a/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.MerlinPlugin b/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.MerlinPlugin new file mode 100644 index 0000000000..01d58f055f --- /dev/null +++ b/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.MerlinPlugin @@ -0,0 +1 @@ +gov.nasa.jpl.aerie.streamline_demo.generated.GeneratedMerlinPlugin diff --git a/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerPlugin b/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerPlugin new file mode 100644 index 0000000000..178d917e84 --- /dev/null +++ b/examples/streamline-demo/src/main/resources/META-INF/services/gov.nasa.jpl.aerie.merlin.protocol.model.SchedulerPlugin @@ -0,0 +1 @@ +gov.nasa.jpl.aerie.streamline_demo.generated.GeneratedSchedulerPlugin diff --git a/merlin-framework/build.gradle b/merlin-framework/build.gradle index 5cf2f53462..d025f5b35a 100644 --- a/merlin-framework/build.gradle +++ b/merlin-framework/build.gradle @@ -32,6 +32,7 @@ javadoc.options.addStringOption('Xdoclint:none', '-quiet') dependencies { compileOnlyApi project(':merlin-sdk') implementation 'org.apache.commons:commons-lang3:3.13.0' + implementation 'org.apache.commons:commons-math3:3.6.1' testImplementation project(':merlin-sdk') testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' diff --git a/settings.gradle b/settings.gradle index 03d90f18ed..48b2422579 100644 --- a/settings.gradle +++ b/settings.gradle @@ -33,3 +33,4 @@ include 'examples:foo-missionmodel' include 'examples:config-with-defaults' include 'examples:config-without-defaults' include 'examples:minimal-mission-model' +include 'examples:streamline-demo'