Description
This is the second version of this ticket. This writeup is as much of a plan as it is a basis for my own reflection, sorry for the endless rambling. This is in coordination with the grouping work #1073 #1451 #870 as it also involves declarative modelling in the mission model.
We believe Aerie needs a declarative task framework to allow for better automated reasoning which will lead to a better performance and will also allow us to move from low-level goal specification (current goals based on batch insertion of activities) to high-level goal specification (e.g. state based).
What’s not declarative right now ?
the behavior of a task is pretty much entirely contained in its run()
method. There’s no way of knowing what an activity is doing without simulating it. And simulating it in one set of initial conditions is not sufficient to know all of its potential behaviors, as conditional statements are allowed and may change the behavior of the task. Let's qualify the current task specification framework as procedural. The behavior of a task is not explicitly modeled, it’s the result of the execution of a piece of code.
As I see it, there are two sides to this work, the aspects related to changes in the mission model and the aspects related to the uses of this new knowledge about tasks in the scheduler (and planner in the future hopefully).
On the mission model side
What do we need to express in this framework ?
The behavior of a task, what it does.
- What resources are read, why are they read (in a conditional statement ?)
- What resources are written, how much there are affected (is it increased ? decreased ? by how much ?)
- Tasks can be durative so we can also describe when things happen.
And we need to record the relationships/dependencies between resources and tasks.
In a traditional/academic planning and scheduling language, this is straightforward because there is a small number of well defined ways of expressing task behaviors and resource types. And these languages have been slowly improved/enriched year after year, their creators having in mind the consequences of adding each new block to the language. In our case, we start from the full expressivity of java and the endless imagination of mission modelers. Let's say right away that we won't be able to represent the current expressivity of mission models, as we would not be able to perform tractable reasoning over all the java constructs and so on and so on.
Let's talk first about resources. I looked at our toy models and David Legg also helped me look at what Clipper does with resources. What we found is that:
- some resources are registered because the user is interested in seeing them in the UI
- but other resources are usually complex in that they form dependency chains with anonymous resources in the middle that are often mappings/transformations of the input into another output (one resource X is a sample version of Y which is the Z multiplied by 2 which is A divided by 3...) which implies many resources are not registered, they may not have a name that can be referenced from the activity model
This is a problem as an activity might reference an anonymous resource for its behavior. So how can we make sure to reference all resources affected by an activity ?
I exclude the following technical approaches:
- static code analysis that I deem not feasible technically at this time (please contradict me and show me how to do it on a realistic model) because of the arbitrary nature of
run()
methods and calls. - post-simulation analysis because the behavior of a task depends on initial conditions.
Here, there is a design decision point:
- either we adapt to this existing expressivity when resources can be anonymous and we have to work with objects
- or we force to register all resources so we can reference them by name
The current path I am following is inspired by David Legg’s work on dependencies in the streamline framework and is more in line with item 1 above:
- we record all internal resource dependencies (between anonymous/anonymous and anonymous/registered couples) in a singleton object every time a resource is created as it is done in the
Dependencies
class - we record resource object-name associations when registering them in
Registrar
to be able to reference resource object by name - in every activity definition, we add a new dependency-related block/method that describes the behavior of the task/model part
I have tried prototyping what it could look like for the FooActivity
activity type which is interestingly complex for this purpose. Here is its run()
method :
@EffectModel
public void run(final Mission mission) {
final var data = mission.data;
final var complexData = mission.complexData;
complexData.beginImaging(ImagerMode.HI_RES, 60);
if (y.equals("test")) {
data.rate.add(x);
} else if (y.equals("spawn")) {
call(mission, new FooActivity());
}
spawn(l);
data.rate.add(1.0);
delay(1, SECOND);
waitUntil(data.isBetween(50.0, 100.0));
mission.simpleData.downlinkData();
data.rate.add(2.0);
data.rate.add(data.rate.get());
delay(10, SECOND);
complexData.endImaging();
mission.simpleData.a.deactivate();
mission.simpleData.b.deactivate();
delay(1, SECOND);
mission.activitiesExecuted.add(1);
}
It has:
- model calls (e.g.
mission.simpleData.downlinkData()
orcomplexData.endImaging()
), parts of behaviors that are not directly in the activity, and highlights that the whole model needs to be supplemented with declarative knowledge - conditional behavior (if, else)
- resource effects
- resource reads
- different types of decomposition (call and spawn)
- durative behavior (
delay
). Note that effects can be applied not only at the start or end of a task but at any time during the duration of the task. - uncontrollable behavior (
waitUtil
) as it might depend on other activities/model in the plan
For future reference, in traditional planning, conditional behavior + resource effects is denoted as conditional effects. I think we could call it that way here as well but I am wondering if there can be conditional behavior that does not end up in effects.
Here is an example of how some of the behaviors could be written:
public List<Behavior> getBehaviors(Mission mission){
// we reference a task by its name
final var self = "foo";
final var behaviors = new ArrayList<Behavior>();
//this gathers dependencies directly in the `model` object and adds them
behaviors.addAll(ActivityBehavior.modelCall(mission.complexData, "beginImaging"));
behaviors.addAll(List.of(
//this says that the mission.data.rate is increased by x but this is dependent on y. And it happens at the start of the task
ActivityBehavior.increases(self, mission.data.rate, "x", "y", atStart()),
//this says that this task generates a FooActivity at the start, depending on the value of y
ActivityBehavior.generates(self,"FooActivity", atStart(), "y"),
ActivityBehavior.increases(self, mission.data.rate, 1.0, atStart()),
//as there is a waitUntil that has an uncontrollable duration, we reference the time from the end of the activity
//this is not great
ActivityBehavior.increases(self, mission.data.rate, 2.0, offsetBeforeEnd(Duration.of(11, SECONDS))),
ActivityBehavior.increases(self, mission.data.rate, mission.data.rate, offsetBeforeEnd(Duration.of(11, SECONDS))),
ActivityBehavior.increases(self, mission.activitiesExecuted, 1.0, atEnd())
));
return behaviors;
}
Some notes:
- the
getBehaviors
method has theMission
object as argument to mimic therun()
method and allow to access the same elements. The intent is that the translation fromrun()
togetBehaviors()
would be more straightforward this way. - each behavioral statement is timestamped instead of registering an order and delays. This makes each statement independant and ultimately allows to generate the
run()
method from a list ofActivityBehavior
-ActivityBehavior.modelCall
would extract dependencies (recursively) from model classes. The model class could implement anIntrospectable
interface that would allow querying runtime dependencies for each method. An example for theSimpleData
class of the foo mission model:
@Override
public List<Behavior> getBehaviors(String methodName){
switch(methodName){
case "downlinkData":
return List.of(
Behavior.resourceWrite(object(this), object(this.a), EffectType.undefinedEffect()),
Behavior.resourceWrite(object(this), object(this.b), EffectType.undefinedEffect())
);
default:
return List.of();
}
}
It's a little bit cumbersome as the user has to discriminate between names...
There are a number of things unsupported so far. It's a work in progress at this point.
I think there is more work to be done on the modelling of resources. Right now, it's a little bit abstract, we are not describing their behavior but more of their dependencies. It is desirable for the user to be able to model any kind of resource but it would also be desirable to have a sizeable subset of defined resource types that we could reason about.
Resource types to consider
- Numerical fluents
- Boolean fluents
- Enum fluents
- Discrete resources
- Consumable resources
- Renewable resources
On the scheduler’s side
From all of this information about the behaviors of tasks and relationships between resources, we can build a dependency graph that can be accessed from outside the mission model via the SchedulerModel
.
The constraints/goals would be augmented with more dependency introspection. Basically the extractResources
would be transformed into an extractDependencies
that would track various types of dependencies (resources but also activity type (mutexes), time (for valueAt…)).
This information allows to build another dependency graph between resources, activities and expressions/constraints/goals.
With that, after inserting a new activity A in the plan and before evaluating anything afterwards, we could check whether A would have any impact on what we want to evaluate/do before doing any simulation. Some examples:
- If we want to evaluate an expression with resource R, we can ask the question "has A a chance of impacting R?". Here "a chance" is important as the annotations would always represent the union of all possible activity impact across all possible executions, it's conservative. If we have an extensive representation of conditional statements, we can relax this hypothesis.
- if we want to evaluate the satisfaction/conflicts of a goal and that goal depends on the duration of some activity B, we can ask the question, "has A (in the past of B otherwise it's not relevant) any chance of impacting B's behavior (then duration) ?".
Risks
An issue might be that there is a disconnect between what the run() method is doing and what resources these methods provide:
- this is inherent to these purely declarative/voluntary annotations
- in future work, there is a path to detect when an activity is acting on resource even though their dependency has not been reported so the user can make the change. This involves having a special mode in which every time we execute run methods, we do it in a “activity context” so we can detect when it is an activity that acts on a resource. On the resource side, if a change is made in an activity context and the dependency is not present in the graph, we report it to the user. There’s already some similar work in the Context class, again in the streamline library.
To me, there’s 2 ways of doing this:
- either we build an ad-hoc declarative task specification framework that looks nothing like the one we have right now. From that declarative specification, we can generate the code to run simulations (the declarative behaviors will probably be a subset of all the behaviors currently available, see below for some limitations I am seeing). That way, the user can't be wrong, either he is using the new framework and he does not write wrong run() methods.
- or we build a framework on top of the current procedural task specification that can explicit knowledge.
What happens at the limits of this framework ?
Let's say we don't cover some resource types and resource R is one of them. Basically, the only (low-level) information we would get is that R is being written and/or read. We don't know what is the essence of these read and writes (as opposed to higher level info such as increases
decreases
or sets
).
Metadata
Assignees
Labels
Type
Projects
Status
In Progress
Activity