Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a mission model behavioral framework #1364

Open
adrienmaillard opened this issue Mar 14, 2024 · 5 comments
Open

Add a mission model behavioral framework #1364

adrienmaillard opened this issue Mar 14, 2024 · 5 comments
Assignees
Labels
feature A new feature or feature request performance A code change that improves performance scheduling Anything related to the scheduling domain simulation Anything related to the simulation domain soon Not quite next up, but soon

Comments

@adrienmaillard
Copy link
Contributor

adrienmaillard commented Mar 14, 2024

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:

  1. either we adapt to this existing expressivity when resources can be anonymous and we have to work with objects
  2. 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() or complexData.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 the Mission object as argument to mimic the run() method and allow to access the same elements. The intent is that the translation from run() to getBehaviors() 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 of ActivityBehavior
    -ActivityBehavior.modelCall would extract dependencies (recursively) from model classes. The model class could implement an Introspectable interface that would allow querying runtime dependencies for each method. An example for the SimpleData 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).

@github-project-automation github-project-automation bot moved this to Todo in Aerie Mar 14, 2024
@adrienmaillard adrienmaillard self-assigned this Mar 14, 2024
@adrienmaillard adrienmaillard added performance A code change that improves performance feature A new feature or feature request scheduling Anything related to the scheduling domain simulation Anything related to the simulation domain labels Mar 14, 2024
@dandelany dandelany moved this from Todo to In Progress in Aerie Mar 28, 2024
@adrienmaillard
Copy link
Contributor Author

Some prototyping happening on branch https://github.com/NASA-AMMOS/aerie/tree/1364-dependency-framework

@adrienmaillard
Copy link
Contributor Author

adrienmaillard commented Apr 3, 2024

For the FooActivity activity type

 @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);
  }

Here is an example of dependencies that could be written :

  public List<Dependency> dependencies(Mission mission){
    //unsupported : downlinkdata/beginimaging/endimaging
    final var self = "foo";
    final var dependencies = new ArrayList<Dependency>();
    dependencies.addAll(ActivityBehavior.modelCall(mission.complexData, "beginImaging"));
    dependencies.addAll(List.of(
        //anonymousTask(l),
        //line 54 with if from line 53
        ActivityBehavior.increases(self, mission.data.rate, "x", "y", atStart()),
        //line 55 and 56
        ActivityBehavior.generates(self,"FooActivity", atStart(), "y"),
        //line 59
        ActivityBehavior.increases(self, mission.data.rate, 1.0, atStart()),
        //line 62
        ActivityBehavior.increases(self, mission.data.rate, 2.0, offsetBeforeEnd(Duration.of(11, SECONDS))),
        //line 66
        ActivityBehavior.increases(self, mission.data.rate, mission.data.rate, offsetBeforeEnd(Duration.of(11, SECONDS))),
        //line 76
        ActivityBehavior.increases(self, mission.activitiesExecuted, 1.0, atEnd())
    ));
    return dependencies;
  }

with modelCall extracting dependencies (recursively) from model classes. The model class could implement an Introspectable interface that would allow querying runtime dependencies for each method. An example for the SimpleData class of the foo mission model:

 @Override
  public List<Dependency> getDependencies(String methodName){
    switch(methodName){
      case "downlinkData":
        return List.of(
            Dependency.resourceWrite(object(this), object(this.a), EffectType.undefinedEffect()),
            Dependency.resourceWrite(object(this), object(this.b), EffectType.undefinedEffect())
        );
      default:
        return List.of();
    }
  } 

@adrienmaillard
Copy link
Contributor Author

Note: the temporal arguments are mainly to get the ability to build activities from a behavior definition only (without user-defined run() method).

@jmdelfa
Copy link
Contributor

jmdelfa commented Apr 16, 2024

Summary of a conversation some time ago with @adrienmaillard :
Besides scheduling, we need a mechanism that doesn't require simulation to express constraints between actions. The word constraint is intentional as a generalization of classical planning pre and post conditions, as in our case we can have concurrent constraints (e.g heater_on DURING take_picture). That probably should live as an additional method outside the run that allows a future planner to reason about the activities that need to be added to a plan to satisfy the constrains of a goal.

@dandelany dandelany added the next label Apr 25, 2024
@joswig joswig added this to the FY24 Q3 - Ad Hoc Improvements milestone May 28, 2024
@adrienmaillard adrienmaillard changed the title Add a mission model dependency framework and allow user to control scheduler guarantees Add a mission model behavioral framework Jun 26, 2024
@adrienmaillard
Copy link
Contributor Author

@jmdelfa and I discussed and did not find that this was in the scope for the MVP of #1073. This is attached to provide a declarative behavior of the activities, resources and models in mission models. The MVP of #1073 will require a disjoint set of knowledge about hierarchy/sub-tasks/temporal constraints between sub-tasks. Beyond MVP, there will be some overlaps as we will need declarative information about the behavior of primitive activities but not before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A new feature or feature request performance A code change that improves performance scheduling Anything related to the scheduling domain simulation Anything related to the simulation domain soon Not quite next up, but soon
Projects
Status: In Progress
Development

No branches or pull requests

4 participants