diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml deleted file mode 100644 index 1ecffaec..00000000 --- a/.github/workflows/build-pr.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: 'Build Docs on PR' - -on: - pull_request: - branches: - - develop - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - lfs: true - - uses: actions/setup-node@v3 - with: - node-version: 'lts/*' - cache: npm - - name: Install Dependencies - run: npm ci - - name: Build - id: build - run: npm run build - - name: Upload Artifact - if: ${{ failure() && steps.build.conclusion == 'failure' }} - uses: actions/upload-pages-artifact@v3.0.1 - with: - path: build/ - retention-days: 7 diff --git a/.github/workflows/deploy-to-gh-pages.yml b/.github/workflows/deploy-to-gh-pages.yml index 09f638ca..cb1f7eb5 100644 --- a/.github/workflows/deploy-to-gh-pages.yml +++ b/.github/workflows/deploy-to-gh-pages.yml @@ -14,31 +14,21 @@ permissions: pages: write jobs: - build: + upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - lfs: true - - uses: actions/setup-node@v3 - with: - node-version: 'lts/*' - cache: npm - - name: Install Dependencies - run: npm ci - - name: Build - run: npm run build - name: Upload Artifact uses: actions/upload-pages-artifact@v3.0.1 with: - path: build/ + path: dist/ deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest - needs: build + needs: upload permissions: pages: write id-token: write diff --git a/.github/workflows/generate-pdfs.yml b/.github/workflows/generate-pdfs.yml deleted file mode 100644 index aec51056..00000000 --- a/.github/workflows/generate-pdfs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Generate PDFs - -on: - push: - tags: - - v* - workflow_dispatch: - -jobs: - generate-pdfs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Install Prince - run: | - curl https://www.princexml.com/download/prince-15.4.1-linux-generic-x86_64.tar.gz -O - tar zxf prince-15.4.1-linux-generic-x86_64.tar.gz - cd prince-15.4.1-linux-generic-x86_64 - yes "" | sudo ./install.sh - - name: Generate PDFs - run: sh ./generate-pdfs/generate-pdfs.sh - - name: Upload PDFs - uses: actions/upload-artifact@v4 - with: - name: aerie-docs-pdfs - path: pdf - if-no-files-found: error diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index d5b283a3..00000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22.13.1 diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 0a55a6f6..00000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -docs/upgrade-guides/0-13-2-to-1-0-0.md diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 004be146..00000000 --- a/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "arrowParens": "avoid", - "printWidth": 120, - "semi": true, - "singleQuote": true, - "trailingComma": "all" -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5a8edcc2..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,146 +0,0 @@ -# Contributing - -We would love for you to contribute to the Aerie documentation and help make it even better than it is today! - -- [Prerequisite Knowledge](#prerequisite-knowledge) -- [Prerequisite Software](#prerequisite-software) -- [Editor](#editor) -- [Clone the Repository](#clone) -- [Install Dependencies](#install) -- [Start Documentation Server](#start) -- [Adding a New Document](#new) -- [Adding Images or Videos](#assets) -- [Submitting a Pull Request](#submit-pr) -- [Question or Problem?](#question) - -## Prerequisite Knowledge - -The Aerie documentation is built with [Docusaurus](https://docusaurus.io/). Documents can be written in either vanilla [Markdown](https://www.markdownguide.org/) (`.md` files), or [MDX](https://mdxjs.com/) (`.mdx` files). Most of the time you will want to use `.mdx` to [add assets](#assets), or things like [admonitions](https://docusaurus.io/docs/2.0.1/markdown-features/admonitions). Please see the [Docusaurus documentation](https://docusaurus.io/docs/next/markdown-features) for the complete set of available markdown features. - -## Prerequisite Software - -Before you can run the Aerie documentation locally you must install and configure the following products on your development machine: - -- [Git](http://git-scm.com) and/or the [GitHub app](https://desktop.github.com/); [GitHub's Guide to Installing Git](https://help.github.com/articles/set-up-git) is a good source of information. - -- [Git LFS](https://git-lfs.com/) which is used to store images and video. Make sure after installing you also run `git lfs install` to initialize Git LFS. - -- [Node.js LTS](http://nodejs.org) which is used to run a development web server, and generate distributable files. We recommend using the [Node Version Manager (NVM)](https://github.com/nvm-sh/nvm) to install Node.js and [NPM](https://www.npmjs.com/) on your machine. Once you have NVM installed you can use the required Node.js/NPM versions via: - - ```shell - cd aerie-docs - nvm use - ``` - -## Editor - -The recommended editor for writing Aerie documentation is [VS Code](https://code.visualstudio.com/) with the following settings and extensions. You can easily use another editor of your choice as long as you can replicate the formatting and spell-check settings. - -### Settings.json - -Your editor should follow the same settings found in [.vscode/settings.json](.vscode/settings.json). - -### Extensions - -1. [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) -1. [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -1. [MDX](https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx) - -## Clone the Repository - -[Clone](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the aerie-docs repository: - -```shell -git clone https://github.com/NASA-AMMOS/aerie-docs.git -cd aerie-docs -``` - -## Install Dependencies - -```sh -cd aerie-docs -npm install -``` - -## Start Documentation Server - -```sh -cd aerie-docs -npm start -``` - -Visit http://localhost:3000/aerie-docs/ to view the documentation in your local browser. Any changes you make to the documentation will auto-reload the page with your changes. - -## Adding a New Document - -If you need to add a new document you need to update the [sidebar.js file](./sidebars.js) with the name of your new document. Please see the [Docusaurus documentation on the sidebar](https://docusaurus.io/docs/sidebar) for more detailed instructions. - -## Adding Images or Videos - -Images should be added to the repository using the [.png](https://en.wikipedia.org/wiki/Portable_Network_Graphics) format. Videos should be added to the repository using the [.webm](https://www.webmproject.org/) or [.mov](https://en.wikipedia.org/wiki/QuickTime_File_Format) format. - -Typically images and videos should be placed in an [assets](https://docusaurus.io/docs/markdown-features/assets) directory in the section of documentation they correspond to. For example see the [command-expansion documentation](./docs/command-expansion/) and [planning documentation](./docs/planning/). - -In rare cases you may need to add [static assets](https://docusaurus.io/docs/static-assets) to the [static](./static) directory if they do not easily correspond to a documentation section. - -## Submitting a Pull Request - -Please follow these instructions when submitting a Pull Request: - -1. Search [GitHub](https://github.com/NASA-AMMOS/aerie-docs/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate effort. -1. Make your changes in a new git branch: - - ```sh - git checkout develop - git pull origin develop - git checkout -b my-fix-branch develop - ``` - -1. Commit your changes using a descriptive commit message. - - ```sh - git commit -a - ``` - - Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. - -1. Push your branch to GitHub: - - ```sh - git push origin my-fix-branch - ``` - -1. In GitHub, send a pull request to `aerie-docs:develop`. -1. If we suggest changes then: - -- Make the required updates. -- [Rebase your branch](https://dev.to/maxwell_dev/the-git-rebase-introduction-i-wish-id-had) and force push to your branch to GitHub (this will update your Pull Request): - - ```sh - git rebase develop -i - git push -f - ``` - -After your pull request is merged, you can safely delete your branch and pull the changes from the repository: - -- Check out the develop branch: - - ```shell - git checkout develop - ``` - -- Update your develop with the latest version: - - ```shell - git pull origin develop - ``` - -- Delete the local branch: - - ```shell - git branch -D my-fix-branch - ``` - -## Got a Question or Problem? - -If you would like to chat about the question in real-time, you can reach out via [our Slack channel](https://join.slack.com/t/nasa-ammos/shared_invite/zt-1mlgmk5c2-MgqVSyKzVRUWrXy87FNqPw). diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index e00595da..00000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/dist/404.html b/dist/404.html new file mode 100644 index 00000000..44bc93fb --- /dev/null +++ b/dist/404.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 00000000..44bc93fb --- /dev/null +++ b/dist/index.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/docs/api/assets/api-playground-admin-secret.mov b/docs/api/assets/api-playground-admin-secret.mov deleted file mode 100644 index 8448c479..00000000 --- a/docs/api/assets/api-playground-admin-secret.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce3914cb1baa1963297b0cc293bfbadf8f3b7d8ee695175afddb2ff1b39ea1b5 -size 2121965 diff --git a/docs/api/assets/api-playground-pre-request-script.mov b/docs/api/assets/api-playground-pre-request-script.mov deleted file mode 100644 index 08747fde..00000000 --- a/docs/api/assets/api-playground-pre-request-script.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79eddeb20cf03fa9fb0ad8ce0398c5ac7a7ed318805ce45fc44b69a1e5984600 -size 2768358 diff --git a/docs/api/assets/api-playground.png b/docs/api/assets/api-playground.png deleted file mode 100644 index a342b3d7..00000000 --- a/docs/api/assets/api-playground.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a2758299a507e6ebd40d87d8e657bf9b5ddd0ea205976e3abf1c6251b58a107 -size 728245 diff --git a/docs/api/assets/gateway-auth-login.png b/docs/api/assets/gateway-auth-login.png deleted file mode 100644 index 34d30eca..00000000 --- a/docs/api/assets/gateway-auth-login.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4569c862328e4701a076339bc6e1435860dfd27f1f658788bb4a727b83495551 -size 165946 diff --git a/docs/api/assets/hasura-console-login.png b/docs/api/assets/hasura-console-login.png deleted file mode 100644 index c92822b2..00000000 --- a/docs/api/assets/hasura-console-login.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:786098d60d7fc8753f72d3ded19c6a98d1e297352000ef2c8d147ae15a96bc70 -size 645735 diff --git a/docs/api/examples/activity-presets.mdx b/docs/api/examples/activity-presets.mdx deleted file mode 100644 index 97159206..00000000 --- a/docs/api/examples/activity-presets.mdx +++ /dev/null @@ -1,121 +0,0 @@ -# Activity Presets - -Activity presets are used to quickly apply a set of commonly-used arguments to an activity. - -## Creating an Activity Preset - -```graphql -mutation CreateActivityPreset($preset: activity_presets_insert_input!) { - insert_activity_presets_one(object: $preset) { - id - } -} -``` - -Below is an example query variable that creates an activity preset called "Gluten Free Bread" for the activity type BakeBananaBread in Model 1. Note that all of BakeBananaBread's arguments are defined. - -```json -{ - "preset": { - "model_id": 1, - "name": "Gluten Free Bread", - "associated_activity_type": "BakeBananaBread", - "arguments": { - "temperature": 375, - "tbSugar": 16, - "glutenFree": true - } - } -} -``` - -## Applying an Activity Preset - -:::caution - -Applying an activity preset will replace **all** arguments of the activity directive. - -::: - -The following mutation will apply an activity preset to an activity directive: - -```graphql -mutation ApplyPreset($preset_id: Int!, $activity_id: Int!, $plan_id: Int!) { - apply_preset_to_activity(args: { _preset_id: $preset_id, _activity_id: $activity_id, _plan_id: $plan_id }) { - arguments - } -} -``` - -## Removing an Activity Preset - -:::info Note - -Removing an activity preset will **not** modify the arguments of the activity directive. - -::: - -The following mutation will remove an activity preset from an activity directive: - -```graphql -mutation DeletePreset($preset_id: Int!, $activity_id: Int!, $plan_id: Int!) { - delete_preset_to_directive_by_pk(activity_id: $activity_id, plan_id: $plan_id, preset_id: $preset_id) { - preset_id - activity_id - plan_id - } -} -``` - -## Querying an Activity Preset - -### Get an Activity Preset - -```graphql -query GetActivityPreset($preset_id: Int!) { - activity_preset: activity_presets_by_pk(id: $preset_id) { - id - model_id - name - associated_activity_type - arguments - } -} -``` - -### Get the Activity Preset Assigned to an Activity Directive - -```graphql -query GetAppliedActivityPreset($activity_id: Int!, $plan_id: Int!) { - preset_to_directive(where: { _and: { activity_id: { _eq: $activity_id }, plan_id: { _eq: $plan_id } } }) { - applied_preset: presets_applied { - id - model_id - name - associated_activity_type - arguments - } - } -} -``` - -### Get all Activity Directives Assigned to an Activity Preset - -```graphql -query GetAssociatedDirectives($preset_id: Int!) { - associated_directives: preset_to_directive(where: { preset_id: { _eq: $preset_id } }) { - directives_applied_to { - id - plan_id - name - type - anchor_id - anchored_to_start - start_offset - metadata - tags - arguments - } - } -} -``` diff --git a/docs/api/examples/advanced-extensions.mdx b/docs/api/examples/advanced-extensions.mdx deleted file mode 100644 index e667cfaa..00000000 --- a/docs/api/examples/advanced-extensions.mdx +++ /dev/null @@ -1,82 +0,0 @@ -# Extensions - -## Adding an Extension - -```graphql -mutation InsertExtension($extension: extensions_insert_input!) { - insert_extensions_one(object: $extension) { - id - label - url - description - } -} -``` - -Below is an example query variable that creates an extension called "My External Application". - -```json -{ - "extension": { - "label": "My External Application", - "url": "https://externalapp/api/test", - "description": "An application that exists outside of Aerie." - } -} -``` - -## Permitting Roles to run Extensions - -```graphql -mutation InsertExtensionRoles($roles: [extension_roles_insert_input!]!) { - insert_extension_roles(objects: $roles) { - returning { - extension { - id - label - } - role - } - } -} -``` - -Below is an example query variable that grants the `aerie_admin` and `user` roles to run the extension with an ID of 1. - -```json -{ - "roles": [ - { "extension_id": 1, "role": "aerie_admin" }, - { "extension_id": 1, "role": "user" } - ] -} -``` - -## Deleting an Extension - -```graphql -mutation DeleteExtension($extension_id: Int!) { - delete_extensions_by_pk(id: $extension_id) { - id - } -} -``` - -## Get Extensions With Roles - -```graphql -query GetExtensions { - extensions { - description - extension_roles { - id - role - } - id - label - url - } -} -``` - -Invocation payloads are discussed on [Planning / Advanced - Extensions](/planning/advanced-extensions). diff --git a/docs/api/examples/constraints.mdx b/docs/api/examples/constraints.mdx deleted file mode 100644 index e25e29f4..00000000 --- a/docs/api/examples/constraints.mdx +++ /dev/null @@ -1,318 +0,0 @@ -# Constraints - -## Check Constraints - -```graphql -query CheckConstraints($planId: Int!, $simulationDatasetId: Int) { - constraintViolations(planId: $planId, simulationDatasetId: $simulationDatasetId) { - constraintId - constraintName - constraintRevision - success - results { - violations { - activityInstanceIds - windows { - end - start - } - } - gaps { - end - start - } - resourceIds - } - errors { - stack - message - location { - column - line - } - } - } -} -``` - -Returns a list of constraint violations for a given plan by `$planId`. -The violations will include the corresponding constraint name, type, and id. An optional `$simulationDatasetId` can be provided which limits the constraint violation checking to a single simulation dataset, which is useful when external datasets are added that only reference a single simulation dataset, as constraint violations would otherwise be reported for every uploaded external dataset. - -## Create Constraint - -
- For Aerie deployments before v2.4.0 - - ```graphql - mutation CreateConstraint($constraint: constraint_insert_input!) { - insert_constraint_one(object: $constraint) { - id - } - } - ``` - - The `$constraint` query variable has the following type definition: - - ```ts - type Constraint = { - definition: string; - description: string; - model_id: number | null; - name: string; - plan_id: number | null; - summary: string; - }; - ``` - -
- -```graphql -mutation CreateConstraint($constraint: constraint_metadata_insert_input!) { - insert_constraint_metadata_one(object: $constraint) { - id - public - description - versions { - definition - } - } -} -``` - -The `$constraint` query variable has the following structure: - -```json -{ - "constraint": { - "name": string, - "public": boolean, // Default false - "description": string, // Optional - "versions": { - "data": { - "definition": string // EDSL Code - } - } - } -} -``` - -The `public` field controls whether all users can add your constraint to their constraint specifications -or if only you can do that. -Additionally, if `public` is set to `true`, the constraint _must_ have a unique name. - -The `description` field, if present, contains a human-readable description of the constraint. - -The `definition` field contains your EDSL constraint code. - -## Update Constraint Metadata - -***Introduced in v2.4.0*** - -A constraint's metadata includes its name, public status, owner, and description. -In order to update a constraint's metadata, you must be either an admin user or the owner of the constraint. - -```graphql -mutation UpdateConstraintMetadata($id: Int!, $constraint: constraint_set_input!) { - update_constraint_by_pk(pk_columns: { id: $id }, _set: $constraint) { - id - } -} -``` - -The `$constraint` query variable has the same type definition as the [create constraint](#create-constraint) query above, but less strict. You can leave properties out that you do not need to update. - -## Add Constraint Version - -***Introduced in v2.4.0*** - -Constraint definitions are versioned and cannot be changed once created. - -```graphql -mutation AddConstraintVersion($constraintId: Int!, $definition: String!) { - insert_constraint_definition_one(object: {constraint_id: $constraintId, definition: $definition}) { - revision - } -} -``` - -## Delete Constraint Version - -***Introduced in v2.4.0*** - -In order to delete a constraint version, it must not be in use on any constraint specifications (see [below](#manage-constraint-specifications)). - -```graphql -mutation DeleteConstraintVersion($constraintId: Int!, $revsion: Int!) { - delete_constraint_definition_by_pk(constraint_id: $constraintId, revision: $revsion){ - definition - } -} -``` - -## Delete Constraint - -
- For Aerie deployments before v2.4.0 - - ```graphql - mutation DeleteConstraint($id: Int!) { - delete_constraint_by_pk(id: $id) { - id - } - } - ``` - -
- -In order to delete a constraint, it must not be in use on any constraint specifications (see [below](#manage-constraint-specifications)). - -```graphql -mutation DeleteConstraint($id: Int!) { - delete_constraint_metadata_by_pk(id: $id){ - id - } -} -``` - -## Manage Constraint Specifications - -***Introduced in v2.4.0.*** - -There are two types of constraint specifications in Aerie: -a **model specification** and a **plan specification**. - -- A **constraint plan specification** is the set of constraints that -will be checked against the plan when you [Check Constraints](#check-constraints). -- A **constraint model specification** is used as a guideline for -the constraint specification of every plan that uses the model. -All new plans created from this model will start with their constraint -specification prepopulated with the contents of their model's constraint specification. -Note that updating a model's constraint specification will not automatically update -the constraint specification of plans using the model. - -### Add Constraint to Model Specification - -```graphql -mutation AddConstraintModelSpec($constraint: constraint_model_specification_insert_input!) { - insert_constraint_model_specification_one(object: $constraint) { - model_id - constraint_id - constraint_revision - } -} -``` - -The `$constraint` query variable has the following structure. - -```json -{ - "model_id": integer, - "constraint_id": integer, - "constraint_revision": integer, // optional -} -``` - -The `constraint_revision` field is used to specify which version of the constraint to use when checking constraints. -If absent, the latest version of the constraint will be used. - -### Update Constraint Version on a Plan Specification - -Excluding the revision will set the recommended version of the constraint to the latest version. - -```graphql -mutation SetConstraintVersion($modelId: Int!, $constraintId: Int!, $constraintVersion: Int) { - update_constraint_model_specification_by_pk( - pk_columns: {model_id: $modelId, constraint_id: $constraintId}, - _set: {constraint_revision: $constraintVersion} - ) { - model_id - constraint_id - constraint_revision - } -} -``` - -### Remove a Constraint from the Model Specification - -```graphql -mutation RemoveConstraintFromModelSpec($modelId: Int!, $constraintId: Int!) { - delete_constraint_model_specification_by_pk(model_id: $modelId, constraint_id: $constraintId) { - constraint_id - } -} -``` - -### Add Constraint to Plan Specification - -```graphql -mutation AddConstraintPlanSpec($constraint: constraint_specification_insert_input!) { - insert_constraint_specification_one(object: $constraint) { - plan_id - enabled - constraint_id - constraint_revision - } -} -``` - -The `$constraint` query variable has the following structure. - -```json -{ - "plan_id": integer, - "constraint_id": integer, - "constraint_revision": integer, // optional - "enabled": boolean, // optional, defaults to true -} -``` - -The `constraint_revision` field is used to specify which version of the constraint to use when checking constraints. -If absent, the latest version of the constraint will be used. - -The `enabled` field controls whether a constraint will be checked. - -### Update Constraint Version on a Plan Specification - -Excluding the revision will set the plan to use the latest version of the constraint. - -```graphql -mutation SetConstraintVersion($planId: Int!, $constraintId: Int!, $constraintVersion: Int) { - update_constraint_specification_by_pk( - pk_columns: {plan_id: $planId, constraint_id: $constraintId}, - _set: {constraint_revision: $constraintVersion} - ) { - plan_id - constraint_id - constraint_revision - enabled - } -} -``` - -### Enable/Disable Constraints on a Plan Specification - -Constraints that are disabled will not be checked when checking constraints. - -```graphql -mutation SetConstraintVersion($planId: Int!, $constraintId: Int!, $enabled: Boolean!) { - update_constraint_specification_by_pk( - pk_columns: {plan_id: $planId, constraint_id: $constraintId}, - _set: {enabled: $enabled} - ) { - plan_id - constraint_id - constraint_revision - enabled - } -} -``` - -### Remove a Constraint from the Plan Specification - -```graphql -mutation RemoveConstraintFromSpec($planId: Int!, $constraintId: Int!) { - delete_constraint_specification_by_pk(plan_id: $planId, constraint_id: $constraintId) { - constraint_id - } -} -``` diff --git a/docs/api/examples/external-events.mdx b/docs/api/examples/external-events.mdx deleted file mode 100644 index 81524a74..00000000 --- a/docs/api/examples/external-events.mdx +++ /dev/null @@ -1,269 +0,0 @@ -# External Events - -Refer to [External Events](../../../planning/external-events/introduction) for more information on External Events & Sources. - -## API Gateway -External Events & their respective External Sources utilize routes located on [`aerie-gateway`](https://github.com/NASA-AMMOS/aerie-gateway) to perform data validation and upon success, an upload through Hasura/GQL. - - -### Creating External Source & Event Types -To upload schemas for External Sources and Events, users should POST to the `/uploadExternalSourceEventTypes` route in `aerie-gateway`. - -The body of the request should be a JSON object with this structure: - -``` -{ - event_types: "{\"ExampleEvent\":{\"type\":\"object\" ... \}\}" - source_types: "{\"ExampleSource\":{\"type\":\"object\" ... \}\}" -} -``` - -The values of `event_types` and `source_types` **must be encoded JSON strings** (not objects) containing the schemas for your External Event -and Source Types (either or both). The structure of these schemas is defined on the [External Event and Source Attributes](/planning/external-events/external-events-attributes/) -page. **Note** that this differs from the file structure expected to be uploaded via the UI, which is a combined flat JSON structure. - -Uploading a definition of a single External Source Type (`ExampleSource`) and a single External Event Type (`ExampleEvent`) should yield the following response: - -```json -{ - "createExternalEventTypes": { - "returning": [ - { - "name": "ExampleEvent" - } - ] - }, - "createExternalSourceTypes": { - "returning": [ - { - "name": "ExampleSource" - } - ] - } -} -``` - -### Creating an External Source -To upload External Sources, users should POST to the `/uploadExternalSource` route in `aerie-gateway`. - -This endpoint expects data provided in [`multipart/form-data` format](https://www.ietf.org/rfc/rfc2388.txt). - - - - -When uploading through Aerie's UI, the External Source & it's contained External Events are packaged together in a single `JSON` file which adheres to the meta-schema defined [here](https://github.com/NASA-AMMOS/aerie-gateway/blob/develop/src/schemas/external-event-validation-schemata.ts). A `derivation_group_name` can be passed in as an argument with the request which will override any value for `derivation_group_name` in the External Source. This argument can be ignored if there is a value for `derivation_group_name` in the External Source. - -:::info Note - -In order to upload an External Source, the **External Source Type** and **External Event Type** must exist. For uploading types, see [above](#creating-external-source--event-types). - -::: - -Uploading a simple definition of an External Source with a single External Event should yield the following result: - -**Example Input** - -`external_source_file` -```json -{ - "source": { - "attributes": { "example": "Example attribute" }, - "derivation_group_name": "ExampleSource Default", - "key": "ExampleSource:1", - "period": { - "end_time": "2024-008T00:00:00Z", - "start_time": "2024-001T00:00:00Z" - }, - "source_type_name": "ExampleSource", - "valid_at": "2024-001T00:00:00Z" - }, - "events": { - [ - { - "attributes": { "example": "Example attribute" }, - "duration": "00:00:01", - "event_type_name": "ExampleEvent", - "key": "ExampleEvent:1", - "start_time": "2024-002T00:00:00Z" - } - ] - } -} -``` - -**Example Output** -```json -{ - "upsertDerivationGroup": { - "name": "ExampleSource Default" - }, - "createExternalSource": { - "attributes": { - "example": "Example attribute" - }, - "derivation_group_name": "ExampleSource Default", - "end_time": "2024-01-08T00:00:00+00:00", - "key": "ExampleSource:1", - "source_type_name": "ExampleSource", - "start_time": "2024-01-01T00:00:00+00:00", - "valid_at": "2024-01-01T00:00:00+00:00" - } -} -``` - -## GQL -The following operations can be performed directly through GQL/Hasura and do not need to interact with Gateway like the above section. - -### Associating a Derivation Group to a Plan -If a plan and a derivation group exist, they can be associated using the following GraphQL mutation: - -```graphql -mutation CreatePlanDerivationGroup($source: plan_derivation_group_insert_input!) { - planExternalSourceLink: insert_plan_derivation_group_one( - object: $source, - on_conflict: { - constraint: plan_derivation_group_pkey - } - ) { - derivation_group_name - } -} -``` - -This mutation requires a an argument, `$source`, which includes: 1) the name of the derivation group to link, and 2) the ID of the plan to link it to. See below for an example input: - -```json -{ - "source": { - "derivation_group_name": "ExampleSource Default", - "plan_id": 1 - } -} -``` - -This request will yield the following response: - -```json -{ - "data": { - "planExternalSourceLink": { - "derivation_group_name": "ExampleSource Default" - } - } -} -``` - -### Disassociating a Derivation Group from a Plan -To disassociate a derivation group from a plan, the following GraphQL mutation can be used: - -```graphql -mutation DeletePlanExternalSource($where: plan_derivation_group_bool_exp!) { - planDerivationGroupLink: delete_plan_derivation_group(where: $where) { - returning { - derivation_group_name - } - } -} -``` - -This mutations requires an argument which is an expression returning a boolean such as: - -```json -{ - "where": { - "_and": { - "derivation_group_name": { "_eq": "ExampleSource Default" }, - "plan_id": { "_eq": 1} - } - } -} -``` - -This request will yield the following response: - -```json -{ - "data": { - "planDerivationGroupLink": { - "returning": [ - { - "derivation_group_name": "ExampleSource Default" - } - ] - } - } -} -``` - -### Get External Events -```graphql -query GetExternalEvents { - external_event { - attributes - event_type_name - key - duration - start_time - source_key - } -} -``` - -### Get External Events from an External Source -```graphql -query GetExternalEventsFromExternalSource($sourceKey: String!, $derivationGroupName: String!) { - external_event(where: {source_key: {_eq: $sourceKey}, derivation_group_name: {_eq: $derivationGroupName}}) { - attributes - event_type_name - key - duration - start_time - source_key - } -} -``` - -### Get External Sources -```graphql -query GetExternalSources { - external_source { - attributes - created_at - derivation_group_name - end_time - key - owner - start_time - source_type_name - valid_at - } -} -``` - -### Get External Sources from a Derivation Group -```graphql -query GetExternalSourcesFromDerivationGroup($derivationGroupName: String!) { - external_source(where: {derivation_group_name: {_eq: $derivationGroupName}}) { - attributes - created_at - derivation_group_name - end_time - key - owner - start_time - source_type_name - valid_at - } -} -``` - -### Get Derivation Groups -```graphql -query GetDerivationGroup { - derivation_group { - name - owner - source_type_name - } -} -``` diff --git a/docs/api/examples/introduction.mdx b/docs/api/examples/introduction.mdx deleted file mode 100644 index 4f17285c..00000000 --- a/docs/api/examples/introduction.mdx +++ /dev/null @@ -1,9 +0,0 @@ -# Examples - -This section of documentation contains examples of common GraphQL queries, mutations, and subscriptions in the Aerie API schema. - -:::tip - -These examples are **not exhaustive**, and you can do a lot more with the Aerie API by exploring our [GraphQL schema](../../introduction#graphql-schema). - -::: diff --git a/docs/api/examples/planning/anchors.mdx b/docs/api/examples/planning/anchors.mdx deleted file mode 100644 index cb54c6f4..00000000 --- a/docs/api/examples/planning/anchors.mdx +++ /dev/null @@ -1,155 +0,0 @@ -# Anchors - -It is possible to define the start time of an activity directive as relative to the start or end time of another directive. This is known as "Anchoring". - -## Anchor to Another Activity Directive - -Anchors can be declared while inserting an activity directive: - -```graphql -mutation InsertDirectiveWithAnchor( - $plan_id: Int! - $name: String! - $tags: _text - $type: String! - $arguments: jsonb! - $metadata: jsonb - $anchor_id: Int - $anchored_to_start: Boolean! - $start_offset: interval! -) { - insert_activity_directive( - objects: { - plan_id: $plan_id - name: $name - tags: $tags - type: $type - arguments: $arguments - metadata: $metadata - anchor_id: $anchor_id - anchored_to_start: $anchored_to_start - start_offset: $start_offset - } - ) { - returning { - id - plan_id - name - type - created_at - anchor_id - anchored_to_start - start_offset - anchor_validations { - reason_invalid - } - } - } -} -``` - -Or can be added as an update to an existing directive: - -```graphql -mutation UpdateAnchor( - $id: Int! - $plan_id: Int! - $anchor_id: Int - $anchored_to_start: Boolean! - $start_offset: interval! -) { - update_activity_directive_by_pk( - pk_columns: { id: $id, plan_id: $plan_id } - _set: { anchor_id: $anchor_id, anchored_to_start: $anchored_to_start, start_offset: $start_offset } - ) { - id - anchor_id - anchored_to_start - start_offset - anchor_validations { - reason_invalid - } - } -} -``` - -If you have previously anchored an activity directive to another directive and wish to remove the anchor, you can anchor it back to plan by setting its `anchor_id` to `null`. - -## Invalid Anchors - -For ease of use, Aerie allows anchors that are not able to be simulated to exist. These anchors are considered to be invalid. - -:::info Note - -A plan **cannot** be simulated or scheduled while there are invalid anchors. - -::: - -To get all invalid anchors for a plan, along with the reason why each anchor is invalid, use the following query: - -```graphql -query QueryAllInvalidAnchors($plan_id: Int!) { - anchor_validation_status( - where: { plan_id: { _eq: $plan_id }, _and: { reason_invalid: { _neq: "" } } } - order_by: { activity_id: asc } - ) { - activity_id - reason_invalid - } -} -``` - -## Delete Activity Directive with Anchors - -If an activity directive has other directives anchored to it, it cannot be deleted directly. -Instead, all anchors onto it must either be manually removed from the dependent directives or handled in bulk -using one of the following options: - -### Delete Entire Subtree - -This option deletes both the directive and all directives anchored to it at once. - -```graphql -mutation DeleteSubtree($plan_id: Int!, $activity_id: Int!) { - delete_activity_by_pk_delete_subtree(args: { _plan_id: $plan_id, _activity_id: $activity_id }) { - change_type - affected_row - } -} -``` - -### Reanchor Subtree - -This option keeps the directives that were anchored to the activity directive to be deleted (referred to here as the "root directive"), -but modifies their anchors so they are no longer anchored to the root directive. It then deletes the root directive. - -:::caution - -When reanchoring the subtrees, Aerie **does not** account for the duration the root directive might have when simulated. -If any activity directives were anchored to the end of the root directive, they may be simulated earlier than expected. - -::: - -#### Reanchor Subtree to Plan Start - -```graphql -mutation ReanchorPlanStart($plan_id: Int!, $activity_id: Int!) { - delete_activity_by_pk_reanchor_plan_start(args: { _plan_id: $plan_id, _activity_id: $activity_id }) { - change_type - affected_row - } -} -``` - -#### Reanchor Subtree to Root Directive's Anchor - -If the root directive was anchored to another activity directive, you can choose to reanchor the subtrees to that directive. - -```graphql -mutation ReanchorRootAnchor($plan_id: Int!, $activity_id: Int!) { - delete_activity_by_pk_reanchor_to_anchor(args: { _plan_id: $plan_id, _activity_id: $activity_id }) { - change_type - affected_row - } -} -``` diff --git a/docs/api/examples/planning/collaboration.md b/docs/api/examples/planning/collaboration.md deleted file mode 100644 index 01af4fbb..00000000 --- a/docs/api/examples/planning/collaboration.md +++ /dev/null @@ -1,161 +0,0 @@ -# Collaboration - -## Branch Plan - -```graphql -mutation DuplicatePlan($new_plan_name: String!, $plan_id: Int!) { - duplicate_plan(args: { new_plan_name: $new_plan_name, plan_id: $plan_id }) { - new_plan_id - } -} -``` - -In order to perform scheduling tasks on a branch, it is necessary to also [create a scheduling specification](../../examples/scheduling.md#create-scheduling-specification). - -## Create Merge Request - -```graphql -mutation CreateMergeRequest($source_plan_id: Int!, $target_plan_id: Int!) { - create_merge_request( - args: { - target_plan_id: $target_plan_id, - source_plan_id: $source_plan_id - }) { - merge_request_id - } -} -``` - -## Withdraw Merge Request - -You can withdraw a merge request so long as it is in the `pending` state. -A withdrawn merge request cannot be used to begin a merge. - -```graphql -mutation WithdrawMergeRequest($merge_request_id: Int!) { - withdraw_merge_request(args: {_merge_request_id: $merge_request_id}) { - merge_request_id - } -} -``` - -## Begin Merge - -You can begin a merge from a `pending` merge request so long as the plan is not currently locked. -Beginning a merge locks the target plan until the merge is either cancelled, denied, or committed. -Once you have a `pending` merge request between two plans, you can use the following mutation to begin the merge: - -```graphql -mutation BeginMerge($merge_request_id: Int!) { - begin_merge(args: {_merge_request_id: $merge_request_id}) { - merge_request_id - non_conflicting_activities - conflicting_activities - } -} -``` - -By default, the above mutation will return the list of non-conflicting activities and the list of conflicting activities. If you would like neither, you can instead perform the following mutation: - -```graphql -mutation BeginMerge($merge_request_id: Int!) { - begin_merge(args: {_merge_request_id: $merge_request_id}) { - merge_request_id - } -} -``` - -If you want the list of non-conflicting activities of an in-progress merge at a later point, perform the following query: - -```graphql -query GetNonConflictingActivities($merge_request_id: Int!) { - get_non_conflicting_activities(args: {_merge_request_id: $merge_request_id}) { - activity_id - change_type - source - target - } -} -``` - -If you want the list of conflicting activities of an in-progress merge, perform the following query: - -```graphql -query GetConflictingActivities($merge_request_id: Int!) { - get_conflicting_activities(args: {_merge_request_id: $merge_request_id}) { - activity_id - change_type_source - change_type_target - resolution - merge_base - source - target - } -} -``` - -## Cancel Merge - -You can cancel any `in-progress` merge. - -```graphql -mutation CancelMerge($merge_request_id: Int!) { - cancel_merge(args: { _merge_request_id: $merge_request_id }) { - merge_request_id - } -} -``` - -## Resolving Merge Conflicts - -Before a merge can be committed, all conflicts must be resolved to either `source` or `target`. - -```graphql -mutation ResolveConflict($_activity_id: Int!, $_merge_request_id: Int!, $_resolution: resolution_type!) { - set_resolution( - args: { _activity_id: $_activity_id, _merge_request_id: $_merge_request_id, _resolution: $_resolution } - ) { - activity_id - change_type_source - change_type_target - resolution - } -} -``` - -You can also resolve conflicts in bulk: - -```graphql -mutation ResolveConflictBulk($_merge_request_id: Int!, $_resolution: resolution_type!) { - set_resolution_bulk(args: { _merge_request_id: $_merge_request_id, _resolution: $_resolution }) { - activity_id - change_type_source - change_type_target - resolution - } -} -``` - -## Deny Merge - -It is possible to deny an in-progress merge, for example, if a request is outdated. Once a merge has been denied, that request cannot be used to begin a merge. - -```graphql -mutation DenyMerge($merge_request_id: Int!) { - deny_merge(args: { merge_request_id: $merge_request_id }) { - merge_request_id - } -} -``` - -## Commit Merge - -Once all conflicts have been resolved, you can commit a merge. - -```graphql -mutation CommitMerge($merge_request_id: Int!) { - commit_merge(args: { _merge_request_id: $merge_request_id }) { - merge_request_id - } -} -``` diff --git a/docs/api/examples/planning/introduction.mdx b/docs/api/examples/planning/introduction.mdx deleted file mode 100644 index 6e60fc6f..00000000 --- a/docs/api/examples/planning/introduction.mdx +++ /dev/null @@ -1,240 +0,0 @@ -# Planning - -## Query for All Plans - -This example queries for all plans in the Aerie database's `plan` table with some common columns. It also demonstrates the [order_by](https://hasura.io/docs/latest/queries/postgres/sorting/#the-order_by-argument) query filter, and using an [alias](https://hasura.io/docs/latest/queries/postgres/variables-aliases-fragments-directives/#using-aliases) to rename `plan` to `plans` since the query returns a list of plans. - -```graphql -query GetPlans { - plans: plan(order_by: { id: desc }) { - duration - id - model_id - name - revision - start_time - } -} -``` - -## Query a Single Plan - -```graphql -query GetPlan($id: Int!) { - plan_by_pk(id: $id) { - duration - id - model_id - name - start_time - } -} -``` - -## Create a Single Plan - -Here is a mutation that creates a single plan and returns the `id` of the new plan: - -```graphql -mutation CreatePlan($plan: plan_insert_input!) { - insert_plan_one(object: $plan) { - id - } -} -``` - -Here is an example query variable for the mutation above that creates a 24-hour long plan called "New Plan" starting on January 1st, 2030. It is for a model with ID 1: - -```json -{ - "plan": { - "duration": "24:00:00", - "model_id": 1, - "name": "New Plan", - "start_time": "2030-001T00:00:00" - } -} -``` - -Notice the `duration` is a [Postgres interval](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-interval/) of the format `hh:mm:ss`. -The `start_time` is in the [day-of-year](https://nsidc.org/data/user-resources/help-center/day-year-doy-calendar) format `YYYY-DDDThh:mm:ss`. - -:::info - -If you want to run **scheduling** against your plan you need to [create an associated scheduling specification](../../scheduling#create-scheduling-specification). - -::: - -## Query for All Activity Directives for a Plan - -Notice how in this query we get the same `plan` data as the previous query, but also the nested `activity_directives`. If you are familiar with relational databases you can think of this as a join query between the `plan` table and `activity_directive` table (in Hasura these joins are made via [relationships](https://hasura.io/learn/graphql/hasura/relationships/)). This is the secret sauce behind GraphQL. It allows us to query for deeply-nested data across the Aerie system in a single unified way. - -```graphql -query GetActivityDirectivesForPlan($id: Int!) { - plan_by_pk(id: $id) { - activity_directives { - arguments - created_at - id - last_modified_arguments_at - last_modified_at - metadata - name - plan_id - source_scheduling_goal_id - start_offset - tags - type - } - duration - id - model_id - name - start_time - } -} -``` - -## Query the Mission Model of a Plan - -```graphql -query GetMissionModelForPlan($id: Int!) { - plan_by_pk(id: $id) { - mission_model { - id - mission - name - owner - version - } - name - } -} -``` - -## Query for All Activity Types within a Mission Model of a Plan - -This query is using the relationship between the `plan` table, the `mission_model` table, and the `activity_type` table to return one object with the activity type properties requested. - -```graphql -query GetActivityTypesForPlan($id: Int!) { - plan_by_pk(id: $id) { - mission_model { - activity_types { - computed_attributes_value_schema - name - parameters - required_parameters - } - } - } -} -``` - -## Create Activity Directive - -TODO: Finish - -```graphql -mutation CreateDirectives($directives: [activity_directive_insert_input!]! ) { - insert_activity_directive(objects: $directives) { - returning { - id - start_offset - } - } -} -``` - -The query variable `directives` is a list of activity directives. -Each directive has the following shape: -```json -{ - "name": string, // optional - "plan_id": integer, - "type": string, - "start_offset": duration, // format: "HH:MM:SS.sss" - "arguments": jsonb -} -``` - -## Query for Activity Effective Arguments - -This query returns a set of effective arguments given a set of required (and overridden) arguments. - -```graphql -query GetEffectiveArgs($modelId: Int!, $activityType: String!, $arguments: ActivityArguments!) { - getActivityEffectiveArguments( - missionModelId: $modelId - activityTypeName: $activityType - activityArguments: $arguments - ) { - arguments - errors - success - } -} -``` - -For example, running the query with the arguments of: - -```json -{ - "modelId": 1, - "activityType": "BakeBananaBread", - "arguments": { "tbSugar": 1, "glutenFree": false } -} -``` - -Results in: - -```json -{ - "data": { - "getActivityEffectiveArguments": { - "arguments": { - "glutenFree": false, - "tbSugar": 1, - "temperature": 350 - }, - "success": true - } - } -} -``` - -When a required argument is not provided, the returned JSON will indicate which argument is missing. -With ``examples/banananation``'s ``BakeBananaBread``, where only the ``temperature`` parameter has a default value: - -```graphql -{ - "modelId": 1, - "activityType": "BakeBananaBread", - "arguments": {} -} -``` - -Results in: - -```json -{ - "data": { - "getActivityEffectiveArguments": { - "arguments": { - "temperature": 350 - }, - "errors": { - "tbSugar": { - "schema": {"type": "int"} , - "message": "Required argument for activity \"BakeBananaBread\" not provided: \"tbSugar\" of type ValueSchema.INT" - }, - "glutenFree": { - "schema": {"type": "boolean"}, - "message": "Required argument for activity \"BakeBananaBread\" not provided: \"glutenFree\" of type ValueSchema.BOOLEAN" - } - }, - "success": false - } - } -} -``` diff --git a/docs/api/examples/planning/snapshots.mdx b/docs/api/examples/planning/snapshots.mdx deleted file mode 100644 index c99bcfe1..00000000 --- a/docs/api/examples/planning/snapshots.mdx +++ /dev/null @@ -1,74 +0,0 @@ -# Snapshots - -A snapshot of a plan is a named record of the state of all activities in a plan at a certain time. -Snapshots are useful for recording and returning to known "good" states of a plan. - -## Taking a Snapshot - -```graphql -mutation createSnapshot($planId: Int!, $snapshotName: String!, $description: String) { - create_snapshot(args: { - _plan_id: $planId, - _snapshot_name: $snapshotName, - _description: $description - }) { - snapshot_id - } -} -``` - -Below is an example set of query variables for this function. Note that `description` is optional and may be excluded. - -```json -{ - "planId": 1, - "snapshotName": "Snapshot of My Plan", - "description": "Optional description for this snapshot" -} -``` - -## Tagging a Snapshot - -[Tags](../../tags) can be applied to snapshots. - -### Adding a Tag - -```graphql -mutation AddPlanSnapshotTag($snapshotId:Int!, $tagId: Int!){ - insert_plan_snapshot_tags_one(object: {snapshot_id: $snapshotId, tag_id: $tagId}) { - plan_snapshot { - snapshot_name - description - } - tag { - name - color - owner - } - } -} -``` - -### Removing a Tag - -```graphql -mutation RemovePlanSnapshotTag($snapshotId: Int!, $tagId: Int!) { - delete_plan_snapshot_tags_by_pk(snapshot_id: $snapshotId, tag_id: $tagId) { - plan_snapshot { - snapshot_name - description - } - tag { name } - } -} -``` - -## Restoring a Snapshot - -```graphql -mutation restoreSnapshot($planId: Int!, $snapshotId: Int!) { - restore_from_snapshot(args: {_plan_id: $planId, _snapshot_id: $snapshotId}) { - snapshot_id - } -} -``` diff --git a/docs/api/examples/scheduling.md b/docs/api/examples/scheduling.md deleted file mode 100644 index c7bd473b..00000000 --- a/docs/api/examples/scheduling.md +++ /dev/null @@ -1,41 +0,0 @@ -# Scheduling - -## Create Scheduling Specification - -A scheduling specification is required by the scheduler to execute scheduling goals. If you want to run scheduling for a plan you need to create a specification for the plan using the following mutation: - -```graphql -mutation CreateSchedulingSpec($spec: scheduling_specification_insert_input!) { - insert_scheduling_specification_one(object: $spec) { - id - } -} -``` - -The `$spec` query variable has the following type definition: - -```ts -type SchedulingSpec = { - analysis_only: boolean; - horizon_end: string; - horizon_start: string; - plan_id: number; - plan_revision: number; - simulation_arguments: { [name: string]: any }; -}; -``` - -Here is an example query variable that implements the `SchedulingSpec` type above: - -```json -{ - "spec": { - "analysis_only": false, - "horizon_end": "2030-002T00:00:00", - "horizon_start": "2030-001T00:00:00", - "plan_id": 1, - "plan_revision": 1, - "simulation_arguments": {} - } -} -``` diff --git a/docs/api/examples/simulation.mdx b/docs/api/examples/simulation.mdx deleted file mode 100644 index b9e4b44a..00000000 --- a/docs/api/examples/simulation.mdx +++ /dev/null @@ -1,262 +0,0 @@ -# Simulation - -A Simulation Configuration is automatically created alongside a [Plan](../planning/introduction/#create-a-single-plan). - -## Update Simulation Configuration Arguments - -Before a plan can be simulated, the simulation arguments must be set. - -:::info Note - -If your Mission Model [defines default values](../../../mission-modeling/parameters/#export-with-defaults) for an argument, -you do not need to set those arguments in order to use the default value. - -::: - -```graphql -mutation updateSimulationArguments($plan_id: Int!, $arguments: jsonb!) { - update_simulation(where: { plan_id: { _eq: $plan_id } }, _set: { arguments: $arguments }) { - affected_rows - } -} -``` - -Query for Mission Model Configuration Effective Arguments ---------------------------------------------------------- - -The `getModelEffectiveArguments` returns the same structure as [`getActivityEffectiveArguments`](../planning/introduction#query-for-activity-effective-arguments); -a set of effective arguments given a set of required (and overridden) arguments. - -```graphql -query getModelEffectiveArgs($modelId: Int!, $arguments: ModelArguments!) { - getModelEffectiveArguments(missionModelId: $modelId, modelArguments: $arguments) { - arguments - errors - success - } -} -``` - -## Update Simulation Bounds - -By default, a simulation will go from plan start to plan end. If you would like to run simulation on only a subset of the plan, you must first update the simulation bounds in the Simulation Configuration. -The following mutation will update the bounds. Notice that simulation bounds are in the [timestamptz](https://www.postgresql.org/docs/current/datatype-datetime.html) format `YYYY-MM-DD hh:mm:ss+0` or `YYYY-MM-DDThh:mm:ssZ`. - -```graphql -mutation updateSimulationBounds( - $plan_id: Int! - $simulation_start_time: timestamptz! - $simulation_end_time: timestamptz! -) { - update_simulation( - where: { plan_id: { _eq: $plan_id } } - _set: { simulation_start_time: $simulation_start_time, simulation_end_time: $simulation_end_time } - ) { - affected_rows - } -} -``` - -## Create a Simulation Configuration Preset - -Rather than defining the simulation arguments in the Simulation Configuration, they can be assigned in a Simulation Configuration Preset, also known as a Simulation Preset. -This mutation will create a Simulation Configuration Preset and returns its ID: - -```graphql -mutation CreateSimulationPreset($simulationPresetInsertInput: simulation_template_insert_input!) { - insert_simulation_template_one(object: $simulationPresetInsertInput) { - id - } -} -``` - -Here is an example query variable that creates a Simulation Configuration Preset that sets the initial fruit count to 50. - -```json -{ - "simulationPresetInsertInput": { - "description": "Only 50 Starting Fruit", - "model_id": 1, - "arguments": { - "initialFruitCount": 50 - } - } -} -``` - -### Update a Simulation Configuration Preset - -```graphql -mutation UpdateSimulationPreset($simulationPresetId: Int!, $updatedPreset: simulation_template_set_input!) { - update_simulation_template_by_pk(pk_columns: { id: $simulationPresetId }, _set: $updatedPreset) { - id - } -} -``` - -## Assign a Simulation Configuration Preset to a Simulation Configuration - -If you know the ID of a Simulation Configuration Preset, you can use the following mutation to assign it to a Plan's Simulation Configuration: - -```graphql -mutation AssignSimulationConfigurationPreset($plan_id: Int!, $simulation_template_id: Int!) { - update_simulation(where: { plan_id: { _eq: $plan_id } }, _set: { simulation_template_id: $simulation_template_id }) { - simulation_template_id - } -} -``` - -## Run Simulation - -```graphql -query Simulate($planId: Int!) { - simulate(planId: $planId) { - reason - simulationDatasetId - status - } -} -``` - -### Forcibly Rerun Simulation - -Occasionally, there may be a need to resimulate a plan without changing any arguments, -for example if there was a network failure during a prior run. -In these cases, simulation can be forcibly rerun by providing the `force` argument to the `simulate` endpoint. - -```graphql -query ForceResimulate($planId: Int!) { - simulate(planId: $planId, force: true) { - reason - simulationDatasetId - status - } -} -``` - -## Query for Simulation Results - -The output of a simulation is stored in a `simulation_dataset`. A `simulation_dataset` has both profiles (aka states or resources), and simulated activities. -The following query fetches the latest `simulation_dataset` for a given plan: - -```graphql -query GetLatestSimulationDataset($planId: Int!) { - latest_dataset: simulation(where: { plan_id: { _eq: $planId } }) { - simulation_datasets(order_by: { id: desc }, limit: 1) { - id - status - reason - arguments - simulation_start_time - simulation_end_time - dataset { - id - profiles { - id - name - type - profile_segments { - profile_id - dynamics - start_offset - } - } - } - simulated_activities { - activity_directive { - id - metadata - } - activity_type_name - attributes - directive_id - duration - end_time - id - parent_id - start_offset - start_time - } - } - } -} -``` - -### Query for Profile Segments - -Note for performance it can sometimes be beneficial to request profile segments separately. The profile segments are the actual simulated resource data: - -```graphql -query GetProfileSegments($datasetId: Int!) { - profile_segment(where: { dataset_id: { _eq: $datasetId } }) { - profile_id - dynamics - start_offset - } -} -``` - -You can further filter this query to only fetch a subset of profiles: - -```graphql -query GetProfileSegments($dataset_id: Int!, $resources: [String!]!) { - profile(where: {_and: {name: {_in: $resources}, dataset_id: {_eq: $dataset_id}}}) { - name - profile_segments { - profile_id - dynamics - start_offset - } - } -} -``` - -### Query for Simulated Activities (Spans) - -For performance, it can sometimes be beneficial to request the simulated activity directives (otherwise known as "simulated activities" or "spans") separately. - -```graphql -query GetSpans($simulationDatasetId: Int!) { - simulated_activity(where: {simulation_dataset_id: {_eq: $simulationDatasetId}}) { - activity_type_name - attributes - duration - id - parent_id - simulation_dataset_id - start_offset - } -} -``` - -## Query for Resource Types in a Mission Model - -```graphql -query GetResourceTypes($modelId: Int!) { - resource_type(where: {model_id: {_eq: $modelId}}) { - name - schema - } -} -``` - -## Query for Simulation Resource Data at a Given Time - -Once a simulation has been run, you can query for the profile segments at a certain duration into the results. -This can be useful for seeding initial conditions when running a Temporal Subset Simulation. - -```graphql -query GetResources($dataset_id: Int!, $start_offset: interval!) { - getResourcesAtStartOffset(args: { _dataset_id: $dataset_id, _start_offset: $start_offset }) { - id - dataset_id - dynamics - is_gap - name - start_offset - type - } -} -``` - -Notice that `start_offset` is a [Postgres interval](https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-interval/) of the format `hh:mm:ss`. diff --git a/docs/api/examples/tags.md b/docs/api/examples/tags.md deleted file mode 100644 index 97333415..00000000 --- a/docs/api/examples/tags.md +++ /dev/null @@ -1,52 +0,0 @@ -# Tags - -Tags are metadata that can be used to mark certain things in Aerie, including Plans, Plan Snapshots, Activity Directives, Scheduling Goals, Constraints, and Command Expansion Runs. -Tags are shared across an Aerie instance. - -## Creating a Tag - -```graphql -mutation CreateTag($name: String!, $color: String) { - insert_tags_one(object: {name: $name, color: $color}) { - id - name - color - } -} -``` - -The `color` field is optional, however, if specified, it must be a hex color code. -This field is used to determine what color the tag appears as when displayed in the UI. - -## Updating a Tag - -```graphql -mutation UpdateTag($tagId: Int!, $set: tags_set_input!) { - update_tags_by_pk(pk_columns: {id: $tagId}, _set: $set) { - id - name - color - owner - } -} -``` - -The `set` object is a JSON containing any of the following fields: - - `name`: the new name for the tag - - `owner`: the new owner of the tag - - `color`: either `null` or a hex color code - -## Deleting a Tag - -```graphql -mutation DeleteTag($tagId: Int!) { - delete_tags_by_pk(id: $tagId) { - id - name - color - owner - } -} -``` - - diff --git a/docs/api/introduction.mdx b/docs/api/introduction.mdx deleted file mode 100644 index 552e99e2..00000000 --- a/docs/api/introduction.mdx +++ /dev/null @@ -1,332 +0,0 @@ -import apiPlayground from './assets/api-playground.png'; -import gatewayAuthLogin from './assets/gateway-auth-login.png'; -import hasuraConsoleLogin from './assets/hasura-console-login.png'; -import adminSecretPlayground from './assets/api-playground-admin-secret.mov'; -import preRequestScript from './assets/api-playground-pre-request-script.mov'; - -# Introduction - -The Aerie API uses [GraphQL](https://graphql.org/) via [Hasura](https://hasura.io/) to allow for complex querying of **any Aerie data**. If this is your first time using GraphQL we recommend the following resources: - -1. [Introduction to GraphQL](https://graphql.org/learn/) -1. [GraphQL Basics](https://hasura.io/learn/graphql/intro-graphql/introduction/) -1. [Hasura Basics](https://hasura.io/learn/graphql/hasura/introduction/) - -## GraphQL Schema - -GraphQL uses a schema to describe the shape of available API data. Aerie employs the [Hasura](https://hasura.io/) GraphQL engine to generate the Aerie GraphQL API schema. The schema is too large to include in our documentation. If you are running Aerie locally the schema for your Aerie installation can be viewed at [http://localhost:8080/console/api/api-explorer](http://localhost:8080/console/api/api-explorer) (open the "< Docs" tab on the right). - -It is important to understand the significance and power of a data-graph based API schema. Below are more detailed resources for helping yourself get familiar with the GraphQL schema and querying capabilities. - -### Queries & Subscriptions - -- [Postgres: GraphQL Queries](https://hasura.io/docs/latest/queries/postgres/index/) -- [Postgres: GraphQL Subscriptions](https://hasura.io/docs/latest/subscriptions/postgres/index/) -- [API Reference - Query / Subscription](https://hasura.io/docs/latest/api-reference/graphql-api/query/) - -### Mutations - -- [Postgres: GraphQL Mutations](https://hasura.io/docs/latest/mutations/postgres/index/) -- [API Reference - Mutation](https://hasura.io/docs/latest/api-reference/graphql-api/mutation/) - -## GraphQL Requests - -A round trip usage of the API consists of the following three steps: - -1. Composing a request (query, mutation, or subscription) -2. Submitting the request via an [HTTP POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) request -3. Receiving the result as [JSON](https://www.json.org/json-en.html) - -### HTTP POST Request - -A standard GraphQL HTTP POST request should use the `application/json` content type, and include a JSON-encoded body of the following form: - -```json -{ - "query": "...", - "operationName": "...", - "variables": { "myVariable": "someValue" } -} -``` - -`operationName` and `variables` are optional fields. The `operationName` field is only required if multiple operations are present in the query. - -### HTTP Response - -Regardless of the method by which the query and variables are sent, the response is returned in the body of the request in JSON format. A query’s results may include some data and some errors, and those are returned in a JSON object of the form: - -```json -{ - "data": {}, - "errors": [] -} -``` - -If there were no errors returned, the `"errors"` field is not present in the response. If no data is returned, the `"data"` field is only included if the error occurred during execution. - -## Authentication - -To interact with the API you need to be authenticated. For each request Hasura requires an [Authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to be set, which contains a token in the [bearer format](https://hasura.io/docs/latest/auth/authentication/jwt/#hasura-jwt-format): `"Bearer YOUR_TOKEN_HERE"`. To get a token you can make an HTTP request to the gateway with your credentials either using the gateway UI, or an HTTP library of your choosing. If you are using Aerie locally you can just use the admin secret and skip using a token. - -### Gateway UI - -To get a token from the gateway UI you can visit [http://localhost:9000/#/Auth/post_auth_login](http://localhost:9000/#/Auth/post_auth_login), and enter your Aerie username and password (change `localhost` do your deployment URL if you are running Aerie on a different domain). - -
- Aerie Gateway UI - Auth Login -
Figure 1: Aerie Gateway UI - Auth Login
-
- -The JSON response contains a token, which you can use in the `Authorization` request header in downstream tools to query Hasura. - -```json -{ - "message": "Login successful", - "success": true, - "token": "YOUR_TOKEN_HERE" -} -``` - -### Python - -You can also query the gateway from Python using the [requests](https://pypi.org/project/requests/) library (or any other HTTP library) to obtain a token. - -```python -import json -import requests - -# Query the gateway to get a token. -response = requests.post( - url='http://localhost:9000/auth/login', # Change from 'localhost' as needed. - json={ 'username': 'YOUR_USERNAME_HERE', 'password': 'YOUR_PASSWORD_HERE' } -) - -# Grab the token from the gateway response. -token = response.json().get('token') - -# Print the token in Bearer format. -# You can copy and paste this into the 'Authorization' header for Hasura requests. -print(f'Bearer {token}') -``` - -### Admin Mode - -If you are using Aerie locally, or have the [admin secret](https://hasura.io/docs/latest/deployment/graphql-engine-flags/reference/#admin-secret-key), you can query Hasura without a token and instead set the [x-hasura-admin-secret](https://hasura.io/docs/latest/auth/authentication/admin-secret-access/) header. This allows you to skip querying the gateway for a token. This is also good if you have a highly-privileged service that needs to query Hasura since it allows access without needing to give the service a user account. - -## GraphQL Clients - -Since a GraphQL API has more underlying structure than a REST API, there are a range of methods by which a client application may choose to interact with the API. A simple usage could use the [curl](https://curl.se/) command line tool, whereas a full featured web application may integrate a more powerful client library like [Apollo Client](https://www.apollographql.com/docs/react/) or [Relay](https://relay.dev/) which automatically handle query building, batching and caching. - -
-The Hasura Console - -Previously our recommendation was to use the Hasura Console to explore the Aerie API. As of Aerie `1.7.0` the Hasura Console requires the admin secret to access, which makes it unavailable to most for non-local use cases. -If you have the admin secret you can still use the Hasura Console by supplying the secret on the login page. - -
- Aerie Hasura Console - Admin Login -
Figure 4: Aerie Hasura Console - Admin Login
-
-
- -### Playground - -Aerie provides an API playground via the [Altair](https://altairgraphql.dev/) GraphQL client. You can use the playground user interface to query the Aerie API directly. It is a great way to start exploring Aerie data and get familiar with GraphQL. If you are running Aerie locally you can view the playground (via the gateway server) at [http://localhost:9000/api-playground/](http://localhost:9000/api-playground/) (change the `localhost` domain as needed). - -
- Aerie API Playground - Altair -
Figure 2: Aerie API Playground - Altair
-
- -To use the API playground you need to set the proper authorization header (either `Authorization` or `x-hasura-admin-secret` - see the [Authentication section above](#authentication)). - -#### Admin Secret - -Access the Global Environment by clicking on "No Environment" -> "Environments..." in the top-right of the page. -Set it to the following: - -```json -{ - "headers": { - "x-hasura-admin-secret": "", - "x-hasura-user-id": "", - "x-hasura-role": "viewer" - } -} -``` - - - -#### Authorization - -If you do not know the admin secret for your venue, you can instead run a pre-request script to authorize yourself against the Gateway. - -
- For Aerie versions before v2.2.0 - -1. Access the Global Environment by clicking on "No Environment" -> "Environments..." in the top-right of the page. - Set it to the following: - -```json -{ - "headers": { - "Authorization": "Bearer {{user}}", - "x-hasura-role": "viewer" - } -} -``` - -2. In the Query Window, click on Pre-request. Select Enable Pre-request script. - -3. Enter the following pre-request script: - -```js -// Fetch a new token from the Gateway -const res = await altair.helpers.request( - 'POST', - '/auth/login', // AUTH ENDPOINT OF THE DEPLOYMENT - { - body: { username: '', password: '' }, // CREDENTIALS TO LOG IN AS - headers: { 'Content-Type': 'application/json' }, - }, -); -if (res.success) { - const token = res.token; - await altair.helpers.setEnvironment('user', token); -} else { - altair.log(res); -} -``` - -The Playground will now automatically fetch an updated token before each query. -To learn more about pre-request scripts, visit [the Altair GQL docs](https://altairgraphql.dev/docs/features/prerequest-scripts.html). - - - -
- -:::info Note -If you find yourself asked to install the extension `altair-graphql-plugin-graphql-explorer`, allow this. -This extension allows you to view and explore the GQL schema by opening the `GraphiQL Explorer` using the button midway down the left-side sidebar. -The Explorer is useful for discovering and constructing available queries, mutations, and subscriptions. -::: - -In the Query Window, click on "Pre-Request". Find this part of the script: - -```typescript -const res = await altair.helpers.request( - 'POST', - '/auth/login', // AUTH ENDPOINT OF THE DEPLOYMENT - { - body: { username: '', password: '' }, // CREDENTIALS TO LOG IN AS - headers: { 'Content-Type': 'application/json' }, - }, -); -``` - -Replace `` and `` with the username and password you use to sign in to Aerie. - -:::info Setting Your Role -By default, you will make queries using the `viewer` role. If you want to use a different role, -select `Set Headers` from the top of the left-side sidebar and update the header `x-hasura-role` to a different role you are permitted to use. - -To see the list of your permitted roles, use the following query: - -```gql -{ - users_and_roles { - username - hasura_allowed_roles - } -} -``` - -After changing your role, you will need to press the `Reload Docs` button to the left of the `Send Request` button -to update the list of available queries and mutations in the GraphiQL Explorer. -::: - -### Python - -You can query the API in Python using the [requests](https://pypi.org/project/requests/) library. Here is a simple example querying for plan data: - -```python -import json -import os -import requests - -token = os.getenv('AERIE_TOKEN') - -response = requests.post( - url='http://localhost:8080/v1/graphql', # Change from 'localhost' as needed. - headers={ 'Authorization': f'Bearer {token}' }, # See 'Authorization' section above to get this. - json={ 'query': 'query { plan { id } }' } -) - -print(json.dumps(response.json(), indent=2)) -``` - -If you have the admin secret you can alternatively use it to make your request. Set the `x-hasura-admin-secret` header (default admin secret shown below): - -```python -headers={ 'x-hasura-admin-secret': 'aerie' } -``` - -### Command Line - -One may build and send a query or mutation via any means that enable an HTTP POST request to be made against the API. For example, this can be easily done using the command line tool [graphqurl](https://github.com/hasura/graphqurl). There is also a community maintained tool called [aerie-cli](https://github.com/NASA-AMMOS/aerie-cli) that exposes common Aerie queries (e.g. create plan, run simulation, etc.) on the command line. It is a good option if you need to automate common tasks without implementing queries yourself. - -### Browser Developer Console - -Requests can also be tested using JavaScript from a web-browser (or Node.js). For example the following JavaScript can be used to make a simple query using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API): - -```js -const response = await fetch('http://localhost:8080/v1/graphql', { - body: JSON.stringify({ query: 'query { plan { id } }' }), - headers: { - Authorization: `Bearer YOUR_TOKEN_HERE`, // See the 'Authentication' section above to get this. - 'Content-Type': 'application/json', - }, - method: 'POST', -}); -const data = await response.json(); -console.log(data); -``` - -Here is an example of a possible shape of the data logged to the console: - -```json -{ - "data": { - "plans": [{ "id": 1 }, { "id": 2 }] - } -} -``` - -This JavaScript can then be used as a hard-coded query within a client tool/script. For more complex and dynamic interactions with the Aerie API it is recommended to use a GraphQL client library. - -### Client Libraries - -When developing a full featured application that requires integration with the Aerie API it is advisable that the tool make use of one of the many powerful GraphQL client libraries like Apollo Client or Relay. These libraries provide an application functionality to manage both local and remote data, automatically handle batching, and caching. - -In general, it will take more time to set up a GraphQL client. However, when building an Aerie integrated application, a client library offers significant time savings as the features of the application grow. One might choose to begin using HTTP requests as the client's API integration mechanism and later switch to a client library as the application becomes more complex. - -GraphQL clients exist for the following programming languages: - -- C# / .NET -- Clojurescript -- Elm -- Flutter -- Go -- Java / Android -- JavaScript -- Julia -- Kotlin -- Swift / Objective-C iOS -- Python -- R - -A full description of these clients is found at [https://graphql.org/code/#language-support](https://graphql.org/code/#language-support) diff --git a/docs/command-expansion/assets/command-dictionary-page.png b/docs/command-expansion/assets/command-dictionary-page.png deleted file mode 100644 index 4565ea1f..00000000 --- a/docs/command-expansion/assets/command-dictionary-page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0c147dba61860494079534fe277b2d58d8a926a7ada66ebfe26dd5be4e6c0914 -size 472545 diff --git a/docs/command-expansion/assets/navigation-menu.png b/docs/command-expansion/assets/navigation-menu.png deleted file mode 100644 index 6b7185b1..00000000 --- a/docs/command-expansion/assets/navigation-menu.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49fe6d9cdf468b4aa95134d961cf902c8d7c4abdfbc4dc4f37ed3251019e28ae -size 486699 diff --git a/docs/command-expansion/assets/plan-create-sequence.png b/docs/command-expansion/assets/plan-create-sequence.png deleted file mode 100644 index 7577ed4c..00000000 --- a/docs/command-expansion/assets/plan-create-sequence.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d740fae7b0f9a11e2aeadde4b2545ffb133b1ba3c3549fb0d42204c8d88f46f -size 730048 diff --git a/docs/command-expansion/assets/plan-expansion-assign-sequence.png b/docs/command-expansion/assets/plan-expansion-assign-sequence.png deleted file mode 100644 index db463d44..00000000 --- a/docs/command-expansion/assets/plan-expansion-assign-sequence.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c2373e4c0ae6b33b59744c9856eb5e28ef5d671a10c79c001bc53eb0188ab576 -size 992309 diff --git a/docs/command-expansion/assets/plan-expansion-filter-submission.png b/docs/command-expansion/assets/plan-expansion-filter-submission.png deleted file mode 100644 index a6e48df1..00000000 --- a/docs/command-expansion/assets/plan-expansion-filter-submission.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1fd244499ac20d50db5fd86f40f0df0058bce2e58d44622a3a51951113a2aa96 -size 961441 diff --git a/docs/command-expansion/assets/plan-expansion-new-filter.png b/docs/command-expansion/assets/plan-expansion-new-filter.png deleted file mode 100644 index f2919ffd..00000000 --- a/docs/command-expansion/assets/plan-expansion-new-filter.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b826e3023e93b818719bef547b6382aba4f559bf217229156b203b7190886f56 -size 916669 diff --git a/docs/command-expansion/assets/plan-expansion-panel-new.png b/docs/command-expansion/assets/plan-expansion-panel-new.png deleted file mode 100644 index 6b2d4e3e..00000000 --- a/docs/command-expansion/assets/plan-expansion-panel-new.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9de6455c1475cdae0ce496b7dd7c15b813530b9fda4579d2984e54f9f2023d2e -size 1034709 diff --git a/docs/command-expansion/assets/plan-expansion-panel.png b/docs/command-expansion/assets/plan-expansion-panel.png deleted file mode 100644 index ce3e7bc6..00000000 --- a/docs/command-expansion/assets/plan-expansion-panel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abc08aefefb0e0ab89f17f6c2229268a5a9cd17fb2c4e8bdfe6dc9f1455d58fb -size 799963 diff --git a/docs/command-expansion/assets/plan-expansion-sequence-creation-from-filter.png b/docs/command-expansion/assets/plan-expansion-sequence-creation-from-filter.png deleted file mode 100644 index f72305a4..00000000 --- a/docs/command-expansion/assets/plan-expansion-sequence-creation-from-filter.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:90fa934168be3c35c52889d1a099af54f6f3da753fb19ad29a5ab75a50d5689f -size 1046750 diff --git a/docs/command-expansion/assets/plan-run-expansion.png b/docs/command-expansion/assets/plan-run-expansion.png deleted file mode 100644 index 34b849a7..00000000 --- a/docs/command-expansion/assets/plan-run-expansion.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cfaa2d8a8a37156d375ed6799a65890e5f2e872602398e072fc27b1eb7a80220 -size 747532 diff --git a/docs/command-expansion/assets/plan-view-sequence.png b/docs/command-expansion/assets/plan-view-sequence.png deleted file mode 100644 index bb1b2e52..00000000 --- a/docs/command-expansion/assets/plan-view-sequence.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8c70e59aa015254cc1ea7b684ccaafa1b78b68563095fc997df69ce38998d7d8 -size 762445 diff --git a/docs/command-expansion/assets/rules-editor-command-args-1.png b/docs/command-expansion/assets/rules-editor-command-args-1.png deleted file mode 100644 index dde96749..00000000 --- a/docs/command-expansion/assets/rules-editor-command-args-1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32dd11357f41ae0541001428d5dfff98ca283d2307082aecc2dfa2dccb5dbb72 -size 344605 diff --git a/docs/command-expansion/assets/rules-editor-command-args-2.png b/docs/command-expansion/assets/rules-editor-command-args-2.png deleted file mode 100644 index aab2f14b..00000000 --- a/docs/command-expansion/assets/rules-editor-command-args-2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fd0556a5c91eb15a5c1e5734e211e8fc4adbe17314494fc52596ff1cb26fdce -size 328797 diff --git a/docs/command-expansion/assets/rules-editor-command-args-error.png b/docs/command-expansion/assets/rules-editor-command-args-error.png deleted file mode 100644 index 4e814373..00000000 --- a/docs/command-expansion/assets/rules-editor-command-args-error.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5000820d4be7b2dba83ba9d50756b5801fe9ae1f5b82c641119d332687cd53d3 -size 353925 diff --git a/docs/command-expansion/assets/rules-editor-command-autocomplete.png b/docs/command-expansion/assets/rules-editor-command-autocomplete.png deleted file mode 100644 index d4d33705..00000000 --- a/docs/command-expansion/assets/rules-editor-command-autocomplete.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:009273bb0508f55ca599f3fed4c76edc46cd056988f79b24860d8d1c35a6f628 -size 414860 diff --git a/docs/command-expansion/assets/rules-editor.png b/docs/command-expansion/assets/rules-editor.png deleted file mode 100644 index a3500867..00000000 --- a/docs/command-expansion/assets/rules-editor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40c753e5b49c12dc1046def9be94c360ebf1aa0953949522c944c6331a9f2a49 -size 601576 diff --git a/docs/command-expansion/assets/rules-page.png b/docs/command-expansion/assets/rules-page.png deleted file mode 100644 index 8ffd416e..00000000 --- a/docs/command-expansion/assets/rules-page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9640c4ccf4bc6fdf832214bc0a18089426142f54a7d6fabc36c380317e9431eb -size 488819 diff --git a/docs/command-expansion/assets/sets-create-page.png b/docs/command-expansion/assets/sets-create-page.png deleted file mode 100644 index b86cb729..00000000 --- a/docs/command-expansion/assets/sets-create-page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91b153bd0c8a6ffc6f985e4cfc1ce5118526269238e358baa24be855c7e71e88 -size 593945 diff --git a/docs/command-expansion/assets/sets-page.png b/docs/command-expansion/assets/sets-page.png deleted file mode 100644 index 343ec1b4..00000000 --- a/docs/command-expansion/assets/sets-page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:93a052ef1be68a02301073aa23307abb85b0f6811b7b2ce3cd85044ae526ba5f -size 452677 diff --git a/docs/command-expansion/expansion-rules.mdx b/docs/command-expansion/expansion-rules.mdx deleted file mode 100644 index 186fbcea..00000000 --- a/docs/command-expansion/expansion-rules.mdx +++ /dev/null @@ -1,91 +0,0 @@ -import rulesPage from './assets/rules-page.png'; -import rulesEditor from './assets/rules-editor.png'; -import rulesEditorCommandAutocomplete from './assets/rules-editor-command-autocomplete.png'; -import rulesEditorCommandArgsError from './assets/rules-editor-command-args-error.png'; -import rulesEditorCommandArgs1 from './assets/rules-editor-command-args-1.png'; -import rulesEditorCommandArgs2 from './assets/rules-editor-command-args-2.png'; - -# Expansion Rules - -Expansion rules describe a set of commands corresponding to a given activity type. Every (relevant) instance of that activity type can then be expanded according to this rule. - -:::info Note -There is an experimental feature that allows users to specify how an activity type should be expanded into commands using a template, instead of the rules discussed on this page. Using this mode requires a separate configuration, which is discussed further in [Experimental: Sequence Templates](./template-expansion/introduction.md). -::: - -## Managing - -Navigate to the "Expansion" page from the Aerie UI top-level menu, to view, edit or delete existing expansion rules. -The expansion rules page shown in Figure 1 below lists all expansion rules authored by any user who has access to the Aerie deployment. -Click on "New" above the table to the right to create a new expansion rule. - -
- Aerie UI - Expansion Rules Page -
Figure 1: Aerie UI Expansion Rules Page - View, create, or delete expansion rules
-
- -## Authoring - -Navigate to the expansion rule editor by clicking "New" on the expansion rules page, or by clicking the edit icon for any existing expansion rule listed there. -The expansion rule editor page allows selecting a command dictionary version and a mission model version. -Once the mission model version is selected, the "Activity Type" drop-down menu lists all the activity types defined in the selected mission model. -Once the activity type is selected, its parameters can be accessed within the expansion rule. - -
- Aerie UI - Expansion Rules Editor -
Figure 2: Aerie UI Expansion Rules Editor
-
- -An expansion rule starts with some boilerplate code, and users are expected to add their commands as comma-separated objects in a `return []` statement structured as the following: - -```ts -return [ - Timing.CommandStem(argument_1, argument_2, ...), - ... -]; -``` - -Each command stem must be preceded by a time type including one of the following: `C command complete`, `A(YYYY-DDDThh:mm:ss) absolute time`, `E(durationOffset) epoch relative`, or `R(durationOffset) command relative`. -All of these time types are supported in the [AMPCS command dictionary spec](https://github.com/NASA-AMMOS/ampcs-dict-schemas/blob/main/CommandDictionary.rnc). -Duration offset for epoch and command relative time types should be defined as a [Temporal.Duration](https://tc39.es/proposal-temporal/docs/duration.html) object. -To learn more about the specific behavior of these time tags, please refer to the flight software sequence behavior functional documentation of your mission. - -Once a time type is selected and ended with a `.`, the editor provides a drop-down list of all commands available in the selected dictionary. -The list of commands will be filtered as the user starts typing the command stem. -Once a command stem is selected the editor will list all the arguments and expected types for those arguments. -Values for ENUMs will be listed as strings, and must be input as strings. See the below figures for details. - -
- Aerie UI Expansion Rules Editor - Command Autocomplete -
Figure 3: Aerie UI Expansion Rules Editor - Command Autocomplete
-
- -
- Aerie UI Expansion Rules Editor - Command Arguments Error -
Figure 4: Aerie UI Expansion Rules Editor - Command Arguments Error
-
- -The true power of command expansion is to be able to map activity parameter values to command arguments. -Note that the beginning of the expansion rule block provides a reference to an activity instance `const { activityInstance } = props`. -This reference allows accessing various attributes of instances of the activity type selected. -Typing `props.activityInstance.` lists properties like start time, end time, duration and type, which can be used to inform rule statements or timing of commands. -`props.activityInstance.attributes.arguments.` lists all parameter names of the type. -Note that expansion request will replace these with actual values from simulated activity instances. - -
- Aerie UI Expansion Rules Editor - Command Arguments for Activity - Aerie UI Expansion Rules Editor - Command Arguments for Activity -
Figure 5: Aerie UI Expansion Rules Editor - Passing Command Arguments from Activity
-
- -Expansion rules can also access computed attributes of an activity. -These are values computed during the activity simulation and returned. -The mechanism can be used to report value of a specific resource at the time of activity simulation, or an internal parameter derived from other parameters along with other inputs, such as a static lookup table. - -:::caution - -The Aerie expansion API exposes duration, computed attributes and similar data that is **only** available post simulation. -Hence input to expansion is a simulation dataset. -Any change to activity plan that invalidates simulation also invalidates command expansion outputs. - -::: diff --git a/docs/command-expansion/expansion-sets.mdx b/docs/command-expansion/expansion-sets.mdx deleted file mode 100644 index 9d0867c5..00000000 --- a/docs/command-expansion/expansion-sets.mdx +++ /dev/null @@ -1,47 +0,0 @@ -import setsCreatePage from './assets/sets-create-page.png'; -import setsPage from './assets/sets-page.png'; - -# Expansion Sets - -An expansion set is merely a collection of expansion rules with the restriction of one rule per activity type. -Aerie requires an expansion set to expand simulated activities into time ordered commands. -All simulated instances of activity types that has an expansion rule in the set will be expanded. -It is possible to create different expansion sets for different groups of activity types, such that they can be expended independently. - -## Managing - -To view existing expansion sets or to create a new one, click on the "Sets" tab in the upper right corner of the "Expansion" page. -This view lists all existing expansion sets along with the mission model and command dictionary `id` that they were created with. -This means that all the expansion rules in the set are valid against the given mission model and command dictionaries. -Clicking on the expansion set lists all of its contents in a read-only mode. -To create a new expansion set click the "New" button on the upper right corner of the left pane with the table. - -
- Aerie UI - Expansion Sets Page -
Figure 1: Aerie UI Expansion Sets Page - View or delete expansion sets
-
- -:::info - -In the future Aerie plans to provide more metadata like a name or user defined version number to differentiate expansion sets from mission models and command dictionaries more effectively. - -::: - -## Creating - -On the expansion sets create page, users must select a command dictionary and mission model version. -This action filters all expansion rules valid for those two inputs. -In the figure below users can select as many activity types, but must select a single expansion rule per type. - -
- Aerie UI - Expansion Sets Create Page -
Figure 2: Aerie UI Expansion Sets Create Page
-
- -:::info - -Expansion sets are immutable objects such that their contents can't be altered for the purposes of keeping a record for what produced the expansion outputs. -Currently users need to create the expansion set from scratch if they need to modify it. -Options for duplicating an existing set in editable mode or versioning expansion sets are under consideration. - -::: diff --git a/docs/command-expansion/introduction.md b/docs/command-expansion/introduction.md deleted file mode 100644 index 26814827..00000000 --- a/docs/command-expansion/introduction.md +++ /dev/null @@ -1,5 +0,0 @@ -# Command Expansion - -Aerie provides command expansion capability that can translate a simulated activity into time ordered commands using a rule provided by users. -An expansion rule (or template) is provided for an activity type, but applied to all simulated instances of the activity during the expansion. -You can get started by first [uploading a command dictionary](../../command-expansion/upload-command-dictionary). diff --git a/docs/command-expansion/run-expansion.mdx b/docs/command-expansion/run-expansion.mdx deleted file mode 100644 index 3e731f6c..00000000 --- a/docs/command-expansion/run-expansion.mdx +++ /dev/null @@ -1,21 +0,0 @@ -import planRunExpansion from './assets/plan-run-expansion.png'; -import planViewSequence from './assets/plan-view-sequence.png'; - -# Run Expansion - -Once seqIDs are declared and associated with simulated activities, an expansion set must be selected from the "Expansion Set" drop-down menu. -Once the expansion set is selected, clicking on the "Expand" button in the upper right corner of the panel will run command expansion. - -
- Aerie UI - Plan Run Expansion -
Figure 1: Plan Run Expansion
-
- -## Viewing Expansion Results - -Once expansion is complete, users can view the resulting sequence in the seqJSON format by clicking the "Open Sequence" icon as shown below. - -
- Aerie UI - Plan View Sequence -
Figure 2: Plan View Sequence
-
diff --git a/docs/command-expansion/sequences.mdx b/docs/command-expansion/sequences.mdx deleted file mode 100644 index 4204bbc2..00000000 --- a/docs/command-expansion/sequences.mdx +++ /dev/null @@ -1,88 +0,0 @@ -import planExpansionPanel from './assets/plan-expansion-panel-new.png'; -import planExpansionFilter from './assets/plan-expansion-new-filter.png'; -import planExpansionFilterFinish from './assets/plan-expansion-filter-submission.png'; -import planSequenceCreationFromFilter from './assets/plan-expansion-sequence-creation-from-filter.png'; -import planSequenceAssignment from './assets/plan-expansion-assign-sequence.png'; - -# Sequences - -The output of command expansion is not only time-ordered commands per activity, but rather complete sequence products. -These sequences can contain commands from multiple activities from a simulation dataset. -Users can query these sequences through the Aerie API to get them in the `seqJSON` format. -In order to generate these sequences, Aerie requires sequence ids (seqIDs) to be declared, and mapped to simulated activities. -To do so users should first open the "Expansion" panel in the [Aerie Planning UI](../../planning/create-plan-and-simulate), from any open panel as shown below. - -
- Aerie UI - Plan Expansion Panel -
Figure 1: Plan Expansion Panel
-
- -## Adding Sequences to a Simulation Dataset - -There are two ways to create a sequence - from a filter, or directly. Independent of this, the recommended order of operations is: - -1. Finalize activity planning -1. Run simulation -1. Optionally, create filters selecting simulated activities -1. Create sequences for simulated activities (either directly or from the filters from the previous step) -1. (If sequence made without a filter) Manually map activities to sequences -1. Run command expansion - -:::caution - -Since Aerie expands simulated activities from a simulation dataset, currently we only allow seqIDs to be associated with the simulation dataset. -This means that when you re-run a simulation, seqIDs have to be declared again for the new simulation dataset. -We are exploring several options to mitigate the rework. - -::: - -This page will pick up after the third step. - -To create a filter, select the following: - -
- Aerie UI - Sequence Filter Creation -
Figure 2: Sequence Filter Creation
-
- -name it, and then select all relevant options to narrow down the set of simulated activities that you would like to associate with the sequence you will be creating. - -
- Aerie UI - Sequence Filter Options -
Figure 3: Sequence Filter Options
-
- -From here, mouse over the filter, click the play icon, and then select the dates you want the sequence to apply over and then you will create a sequence from this filter. - -
- Aerie UI - Create a Sequence from a Sequence Filter -
Figure 4: Create a Sequence from a Sequence Filter
-
- -The created sequence will bear an ID of `"{filter name} Sequence"`. -Alternatively, you can directly create a sequence by selecting `Sequence` instead in the `New` button field. The name of this sequence is its ID. - -## Manually assigning Simulated Activities to Sequences - -Simulated activities can be manually assigned to sequences if a filter isn't used, or if associations with a sequence created from a filter need to be tweaked manually. - -To do so, open the "Selected Activity" panel, and scroll down to the "Sequencing" section as shown on the bottom right corner of Figure 5 below. -Clicking on the Sequence ID input field lists all of the declared sequence IDs and allows the user to choose one. -Note that this association is the mechanism for mapping expansion output of multiple activities to the same sequence output. - -
- Aerie UI - Assign a Simulated Activity to a Sequence -
Figure 5: Assign a Simulated Activity to a Sequence
-
- -:::note - -Aerie recommends automating the seqID declaration and seqID-activity association steps using Aerie API, such that the information can be recreated automatically after every simulation run. - -::: - -## Command Sorting - -As of Aerie [v1.5.0](https://github.com/NASA-AMMOS/aerie/releases/tag/v1.5.0), during expansion if more than one activity targets a single sequence then the commands are sorted time-ascending if all commands use either absolute or relative time tags, and the first command for each activity uses an absolute time tag. Otherwise none of the commands are sorted. If the commands are sorted then all time tags are converted to absolute time. - -Additionally a `timeSorted` boolean property has been added to the expanded sequence `metadata` indicating if the commands have been sorted (true for sorted, false otherwise). diff --git a/docs/command-expansion/template-expansion/assets/authoring-template-create-sequence-template.png b/docs/command-expansion/template-expansion/assets/authoring-template-create-sequence-template.png deleted file mode 100644 index 8c306c97..00000000 --- a/docs/command-expansion/template-expansion/assets/authoring-template-create-sequence-template.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb4f841afeb7d80aeb610db878fdb4c687710fe57ce5f3fb44222f757cdc95da -size 763628 diff --git a/docs/command-expansion/template-expansion/assets/authoring-template-editor.png b/docs/command-expansion/template-expansion/assets/authoring-template-editor.png deleted file mode 100644 index aea84f81..00000000 --- a/docs/command-expansion/template-expansion/assets/authoring-template-editor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f874a5a8ff60b0af103ef688f906e825716e8f1847f817f518377bdb92e00a56 -size 848390 diff --git a/docs/command-expansion/template-expansion/assets/authoring-template-helpers.png b/docs/command-expansion/template-expansion/assets/authoring-template-helpers.png deleted file mode 100644 index 6523f714..00000000 --- a/docs/command-expansion/template-expansion/assets/authoring-template-helpers.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b8afd73518bb86cc6920e49f2685abfefd7c0d7bd9fd17b0027ce202f5aa83d3 -size 917162 diff --git a/docs/command-expansion/template-expansion/assets/authoring-template-sequence-templates-option.png b/docs/command-expansion/template-expansion/assets/authoring-template-sequence-templates-option.png deleted file mode 100644 index 5183888a..00000000 --- a/docs/command-expansion/template-expansion/assets/authoring-template-sequence-templates-option.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0aeaa416e0b34db823a4e3c2677740bd461ce2197e1381cae03bce81747e245c -size 786370 diff --git a/docs/command-expansion/template-expansion/assets/expanding-templates-filter.png b/docs/command-expansion/template-expansion/assets/expanding-templates-filter.png deleted file mode 100644 index 04b150f0..00000000 --- a/docs/command-expansion/template-expansion/assets/expanding-templates-filter.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e6ac02e8759b1986627a7dbcf1780d220a937d62bdda760487e7fcb2b066d29 -size 885365 diff --git a/docs/command-expansion/template-expansion/assets/expanding-templates-plan.png b/docs/command-expansion/template-expansion/assets/expanding-templates-plan.png deleted file mode 100644 index 883eac80..00000000 --- a/docs/command-expansion/template-expansion/assets/expanding-templates-plan.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f2975e345d559e455e403d92e698bd02cdb0dabecd36c401db7eadd703739b5b -size 1039503 diff --git a/docs/command-expansion/template-expansion/assets/expanding-templates-result.png b/docs/command-expansion/template-expansion/assets/expanding-templates-result.png deleted file mode 100644 index 893f1c1e..00000000 --- a/docs/command-expansion/template-expansion/assets/expanding-templates-result.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:332f38e5417985647420a3ed5caf3c5a0cf222806d24ec28ab169a7c2ba9f119 -size 1025690 diff --git a/docs/command-expansion/template-expansion/authoring-templates.mdx b/docs/command-expansion/template-expansion/authoring-templates.mdx deleted file mode 100644 index 77638011..00000000 --- a/docs/command-expansion/template-expansion/authoring-templates.mdx +++ /dev/null @@ -1,370 +0,0 @@ -import templatePage from './assets/authoring-template-sequence-templates-option.png'; -import createNewTemplate from './assets/authoring-template-create-sequence-template.png'; -import templateEditor from './assets/authoring-template-editor.png'; -import templateHelpers from './assets/authoring-template-helpers.png'; - -# Authoring Templates - -Template authoring follows a similar process to Typescript expansion rule authoring. - -To create a new sequence template for an activity type (or to edit existing ones) navigate to the **Sequence Templates** page of Aerie. - -
- Aerie UI - Templates Page -
Figure 1: Navigating to the sequence templates page
-
- -To create a new template, select `New Template`, and provide your template with: - -- a name -- a sequencing language, currently restricted to: `STOL`, `SeqN`, or `Text` - - (this setting enables editor syntax highlighting for supported languages, but arbitrary languages may be used with the "Text" setting) -- a parcel ID (to get the set of allowed commands from the dictionary) -- a model ID (to enable access to the activity types) -- and the activity type that this template is for - -To edit an existing template, mouse over it in the table and select the edit button. - -
- Aerie UI - Create New Template -
Figure 2: Creating a new sequence template
-
- -This editor has autocomplete functionality, whitespace formatting, and error visibility for supported languages. There is also a panel that describes the available commands, one describing the template, and the ability to switch to editing a different sequence template. - -
- Aerie UI - Template Editor -
Figure 3: Template Editor
-
- -Finally, you can save your result, and you are now ready to expand using this template. - -## Using the Handlebars template language - -Sequence templates are written in the [Handlebars template language](https://handlebarsjs.com/guide/). This language allows users to embed variables from _some_ input (more on the input shortly) into a body of text. For example, if I had the following: - -**Input** - -``` -{ input: "bananas" } -``` - -**Template** - -``` -I like to eat {{ input }}. -``` - -and compiled these together using Handlebars, I would get as a result: - -**Result** - -``` -I like to eat bananas. -``` - -This is useful for expanding activities because, as discussed in the [Introduction](./introduction), it allows us directly write sequence templates with expressions, which more closely resemble the final expanded sequences than rules did. To reuse our earlier example, the template: - -``` -C BOOT_COPY_NOR_IMAGE "ZONE_0" "ZONE_1" -A2024-123T00:00:00 AVS_PCE_MEMORY_POKE {{ attributes.arguments.quantity }} "string" -C EP_XFC_LV_CLOSE "XFC_A" -C DP_PRIORITIZE "STRING" {{ attributes.arguments.quantity }} -``` - -compiles to this very similar looking result: - -**Result** - -``` -C BOOT_COPY_NOR_IMAGE "ZONE_0" "ZONE_1" -A2024-123T00:00:00 AVS_PCE_MEMORY_POKE 5 "string" -C EP_XFC_LV_CLOSE "XFC_A" -C DP_PRIORITIZE "STRING" 5 -``` - -## Template input data - -During expansion, Aerie goes through your plan looking for activities which have a template associated with them, and compiles the template with the following input data structure from the activity, which may be used in the template: - -``` -{ - id: number; // ID of the activity instance - simulationDatasetId: number; // ID of the associated simulation dataset - simulationDataset: { - simulation: { - planId: number // ID of the associated plan - } - }; - attributes: { - arguments: { ... }; // arguments provided to the activity - directiveId: number; // ID of the associated activity directive - computed: { ... }; // activity's computed attributes - }; - duration: Temporal.Duration; // duration of the activity - startOffset: Temporal.Duration; // start offset of the activity from its parent, or the beginning of the plan - startTime: Temporal.Instant; // start time of the activity - endTime: Temporal.Instant; // end time of the activity - activityTypeName: string; // name of the associated activity type -} -``` - -So, to access the activity type name, followed by the end time, and then a computed argument by the name of `quantity`, one could write: - -``` -CMD {{ activityTypeName }} endsAt={{ endTime }} quantity={{ attributes.arguments.quantity }} -``` - -The actual compilation of templates is discussed in [Expanding Templates](../expanding-templates). - -## Template Helpers - -We also introduce with Handlebars formatting the notion of helpers. Helper functions (discussed in the Handlebars documentation [here](https://handlebarsjs.com/guide/builtin-helpers.html)), allow a user to invoke a function with arguments and produce a result in the template. -Aerie defines 4 helpers for use in templates. User-defined helpers are not yet supported, so only these 4 are available at the moment. - -Template helpers for times output according to the configured language. The time formats for the supported languages are: - -| Language | Format | -| :------- | :------------------------------ | -| STOL | `YYYY-ddd/HH:MM:SS[.sss]` | -| SeqN | `YYYY-dddTHH:MM:SS[.sss[sss]]` | -| Text | `YYYY-MM-DDTHH:MM:SS[.ssssss]Z` | - -### `add-time` - -This helper allows a user to compute a new time string from an existing one and a duration to be added to that time. For example: - -**Input** - -``` -{ - ..., - startTime: "2024-001T00:00:00", - attributes: { - ..., - arguments: { - ..., - growingDuration: "01:00:00" - } - } -} -``` - -**Template** - -``` -A{{ add-time startTime attributes.arguments.growingDuration }} -``` - -compiles to: - -**Result** - -``` -A2024-001T01:00:00 -``` - -### `subtract-time` - -This helper allows a user to compute a new time string from an existing one and a duration to be subtracted from that time. For example: - -**Input** - -``` -{ - ..., - startTime: "2024-001T00:00:00", - attributes: { - ..., - arguments: { - ..., - growingDuration: "01:00:00" - } - } -} -``` - -**Template** - -``` -A{{ subtract-time startTime attributes.arguments.growingDuration }} -``` - -which compiles to: - -**Result** - -``` -A2023-365T23:00:00 -``` - -### `format-as-date` - -This helper formats a time value such as activity start time into the correct format for the template's specified sequencing language. For example, given a template configured for STOL, the start time may be formatted as follows: - -**Input** - -``` -{ - ..., - startTime: "2025-001T00:00:00.00 -} -``` - -**Template** - -``` -CMD AT={{ format-as-date startTime }} -``` - -**Result** - -``` -CMD AT=2025-001/00:00:00Z -``` - -:::info Note -If the sequencing language is `Text`, it is preferred that users include the Zulu time abbreviation in their input data. Currently, if it is left out, and the date is formatted as a YYYY-MM-DD string, the date is automatically converted to the current time zone, which produces some inconsistency in output. -::: - -### `flatten` - -This helper allows a user to format arrays correctly according to the target sequencing language. SeqN array formatting looks a little different than conventional array formatting in Handlebars, which we illustrate below: - -**Input** - -``` -{ - ..., - attributes: { - ..., - arguments: { - ..., - array: [1, 2, 3, 3.5, "string"] - } - } -} -``` - -**Template** - -``` -Unflattened: {{ firstArraySet }}; Flattened: {{ flatten firstArraySet }} -``` - -compiles to: - -**Result** - -``` -Unflattened: 1,2,3.5,string; Flattened: [1 2 3.5 string] -``` - ---- - -Putting this all together in AERIE, we might have the following: - -
- Aerie UI - Template With Helpers -
Figure 4: A sequence template using helpers
-
- -## Other Notes - -There are some quirks to our application of the Handlebars language to sequence templates. Some of the least intuitive parts are listed below, please refer to the official [Handlebars documentation](https://handlebarsjs.com/guide/) for language details before filing an issue for any quirks you may notice. - -#### Missing inputs/helpers - -If you refer to an input variable or helper that is not present in the input object or the helpers library, it is considered invalid and will produce an empty string in the compiled output, for example: - -**Template** - -``` -ECHO "{{ badInputVariable }}" -``` - -compiles to - -**Result** - -``` -ECHO "" -``` - -#### HTML/XML-like String Escaping - -HTML string escaping (should any inputs coming from a simulated activity have XML-like strings) is automatic in Handlebars. That is, if we have: - -**Input** -``` -{ - name: "" -} -``` - -**Template** -``` -I am {{ name }}. -``` - -it will compile to: - -**Result** -``` -I am <name>. -``` - -This can be avoided by using triple brackets: - -**Input** -``` -{ - name: "" -} -``` - -**Template** -``` -I am {{{ name }}}. -``` - -will compile to: - -**Result** -``` -I am . -``` - -Helper functions will still be correctly applied to expressions in triple brackets. - -#### Arrays and Objects - -Arrays and objects also have particular behavior. There currently exists no well-defined behavior for objects, so the default behavior of resolving to the string `[Object object]` for any given object is used. For arrays, if the `flatten` helper _isn't_ used, the array is serialized as a list of comma-separated elements WITHOUT brackets. That is: - -**Input** -``` -{ - array: [1, 2, 3], - object: { - arbitrary: { - property1: "a", - property2: "b", - }, - important: "item" - } -} -``` - -**Template** -``` -Array: {{ array }} -Object: {{ object }} -``` - -will compile to: - -**Result** -``` -Array: 1,2,3 -Object: [Object object] -``` diff --git a/docs/command-expansion/template-expansion/expanding-templates.mdx b/docs/command-expansion/template-expansion/expanding-templates.mdx deleted file mode 100644 index 54b12ef3..00000000 --- a/docs/command-expansion/template-expansion/expanding-templates.mdx +++ /dev/null @@ -1,30 +0,0 @@ -import plan from './assets/expanding-templates-plan.png'; -import filter from './assets/expanding-templates-filter.png'; -import result from './assets/expanding-templates-result.png'; - -# Expanding Templates - -Now that our templates have been authored (see [Authoring Templates](../authoring-templates)), we can expand them into sequences in the Aerie UI. The process is very similar to the process for Typescript rules outlined on the [Sequences](../../sequences) page, but with some differences. - -Assume we have sequence templates authored for activity types `GrowBanana` and `ThrowBanana`, and have now created the following plan: - -
- Aerie UI - The Plan -
Figure 1: The plan
-
- -We only wish to expand for the activity types we have a template defined for, so we create a sequence filter with these types in the **Sequences & Expansion** tab. - -
- Aerie UI - The Filter -
Figure 2: The sequence filter
-
- -From this, we mouse over that sequence filter and click the play button to create a sequence from that filter, and go along with the default range. - -Finally, we mouse over the new sequence and click its play button to run expansion. We can now view our result: - -
- Aerie UI - The Result -
Figure 2: The result of expanding our sequence
-
diff --git a/docs/command-expansion/template-expansion/introduction.md b/docs/command-expansion/template-expansion/introduction.md deleted file mode 100644 index b9a61e33..00000000 --- a/docs/command-expansion/template-expansion/introduction.md +++ /dev/null @@ -1,66 +0,0 @@ -# Sequence Templates - -:::danger -Template Expansion is an *experimental feature* as of Aerie v3.4.0. -Development is active, and the API may be subject to further change. Please let us know if you have feedback -on its future development! -::: - -Sequence templates introduce an alternative way to expand activities into a set of commands, using templates instead of [Typescript rules](../../expansion-rules). They make use of the Mustache templating language. - -This feature was introduced with the goal of providing users with a more straightforward and intuitive tool for expansion, for simpler rules which do not require the power of a procedural programming language. For example, using traditional [expansion rules](../../expansion-rules), the `ThrowBanana` activity might have its expansion defined like this: - -``` -export default function MyExpansion(props: { - activityInstance: ActivityType -}): ExpansionReturn { - const { activityInstance } = props; - return [ - C.BOOT_COPY_NOR_IMAGE("ZONE_0", "ZONE_1"), - A('2024-123T00:00:00').AVS_PCE_MEMORY_POKE(activityInstance.attributes.arguments.quantity, "string"), - C.EP_XFC_LV_CLOSE("XFC_A"), - C.DP_PRIORITIZE("STRING", activityInstance.attributes.arguments.quantity) - ]; -} -``` - -Sequence template expansion allows users to instead specify the `ThrowBanana` expansion using the following _template_: - -``` -C BOOT_COPY_NOR_IMAGE "ZONE_0" "ZONE_1" -A2024-123T00:00:00 AVS_PCE_MEMORY_POKE {{ attributes.arguments.quantity }} "string" -C EP_XFC_LV_CLOSE "XFC_A" -C DP_PRIORITIZE "STRING" {{ attributes.arguments.quantity }} -``` - -These both produce the following sequence (assuming `quantity` is equal to `5`): - -``` -C BOOT_COPY_NOR_IMAGE "ZONE_0" "ZONE_1" -A2024-123T00:00:00 AVS_PCE_MEMORY_POKE 5 "string" -C EP_XFC_LV_CLOSE "XFC_A" -C DP_PRIORITIZE "STRING" 5 -``` - -## Configuration -In order to use sequence templates instead of the existing Typescript EDSL sequence expansion rules, you need to make a configuration change to your `aerie-ui` container. - -:::caution -Sequence templating and EDSL rules are exclusive of each other. They cannot coexist in the same instance of Aerie. -::: - -If you are running `aerie-ui` locally, then within the `aerie-ui` directory, navigate to `{aerie-ui path}/.env` and update the variable `PUBLIC_COMMAND_EXPANSION_MODE` to `templating`, instead of its default (`legacy`). Then restart the server process. - -If you are running `aerie` in docker, then within the `aerie` directory, navigate to `{aerie path}/docker-compose.yml`, and update the variable `PUBLIC_COMMAND_EXPANSION_MODE` under the `aerie-ui` container's settings to `templating`, instead of its default (`legacy`). Then, redeploy the container. - -If you are accessing `aerie` remotely via an externally managed host, please reach out to that system's administrator about changing the deployment configuration. - -## Sequence Templates in Aerie -After having enabled sequence templates, you are ready to author your own templates and expand with them. Prior to doing so, however, it might be instructive to provide a brief discussion of what these templates require and how they differ from Typescript expansion rules: - -* Like Typescript rules, sequence templates must be associated with a [**mission model**](/mission-modeling/introduction) and **a command dictionary/dictionaries** (via a [**parcel**](/sequencing/editor/#parcel)), since they directly refer to activity types defined in the model, and commands in the dictionaries. -* Like Typescript rules, you should only define **one template per activity type** in your model. -* Sequence templates **do not use [expansion sets](../../expansion-sets)** - instead they are *directly* associated with a mission model and a parcel, and their scope is mission-model-wide instead of expansion-set wide. -* As a result, instead of explicitly assigning an expansion set to a plan, sequence templates are **implicitly assigned to a plan** (and its simulation outputs) based on the **mission model**. Any plan using a given model will have the same sequence templates associated with it. - -All other concepts related to sequences and sequence filter creation are the same in both systems. diff --git a/docs/command-expansion/upload-command-dictionary.mdx b/docs/command-expansion/upload-command-dictionary.mdx deleted file mode 100644 index e1c68359..00000000 --- a/docs/command-expansion/upload-command-dictionary.mdx +++ /dev/null @@ -1,27 +0,0 @@ -import commandDictionaryPagePng from './assets/command-dictionary-page.png'; -import navigationMenuPng from './assets/navigation-menu.png'; - -# Upload Command Dictionary - -To do command expansion Aerie must have a mission model and command dictionary uploaded. -To upload a mission model first complete [the instructions here](../../planning/upload-mission-model) and then return to this page. - -To upload a command dictionary in the Aerie UI, open the top-level menu at the upper left corner as shown in the first figure, and navigate to the "Command Expansions" page. -All command dictionaries previously uploaded to Aerie will be listed on this page. -If you would like to upload a dictionary yourself you can click "Choose File" to upload a command dictionary from your local machine. - -Aerie only supports [AMPCS command dictionary schema](https://github.com/NASA-AMMOS/ampcs-dict-schemas/blob/main/CommandDictionary.rnc) standard. -This schema provides a mission name and version in the command dictionary file. -Aerie does not allow uploading multiple command dictionary files with the same mission name and version. - -
- Aerie UI - Navigation Menu -
Figure 1: Aerie UI Navigation Menu - Click to the 'Command Dictionaries' menu item
-
- -
- Aerie UI - Command Dictionary Page -
- Figure 2: Aerie UI Command Dictionary Page - View, upload, or delete AMPCS command dictionaries -
-
diff --git a/docs/deployment/advanced-authentication.mdx b/docs/deployment/advanced-authentication.mdx deleted file mode 100644 index 0a27fd7b..00000000 --- a/docs/deployment/advanced-authentication.mdx +++ /dev/null @@ -1,71 +0,0 @@ -import uiLoginPage from './assets/ui-login-page.png'; - -# Advanced - Password Authentication - -Aerie currently only supports password authentication for applications internal to JPL via CAM. This document describes how to enable CAM username + password authentication. - -Note that Hasura _authorization_ is enabled by default (even without CAM authentication enabled), so you need either an admin token or JWT provided by the gateway to access it. - -:::info - -To learn how to programmatically authenticate with Aerie to get a token, see the [API docs on the topic](../../api/introduction/#authentication). - -::: - -## Service Environment Variables - -The default Aerie [docker-compose.yml](https://github.com/NASA-AMMOS/aerie/blob/develop/deployment/docker-compose.yml) file requires certain environment variables to be set to enable authentication. This section outlines the variable definitions and the services that require them for authentication. - -### AUTH_GROUP_ROLE_MAPPINGS -- Description: Optional JSON object that maps LDAP groups to allowed Aerie roles. -- Service: aerie_gateway -- Accepted values: Stringified JSON. Auth provider groups are keys, values are list of allowed Aerie roles -- Example: -``` -{ - "team_leads": [ "aerie_admin" ], - "power_users": [ "user", "viewer" ], - "business_users": [ "viewer" ] -} -``` -- Default: `{}` -- See: [SSO auth docs](../advanced-sso#mapping-auth-provider-groups-to-aerie-roles) for more information. - -### AUTH_TYPE - -- Description: Authentication method (currently only supports CAM or none) -- Service: aerie_gateway -- Accepted values: `cam` or `none` -- Default: `none` - -### AUTH_URL - -- Description: URL of CAM server -- Service: aerie_gateway -- Accepted values: Any valid CAM API URL -- Default: `https://atb-ocio-12b.jpl.nasa.gov:8443/` - -### HASURA_GRAPHQL_JWT_SECRET - -- Description: A JSON string containing a type property set equal to the method of encryption (HS256) and the JWK (key) -- Services: hasura, aerie_gateway -- Accepted values: `{"type": "HS256", "key": "SET_SECRET_PRIVATE_KEY_HERE"}` -- Default: `null` -- See: https://hasura.io/docs/latest/deployment/graphql-engine-flags/reference/#jwt-secret - -### HASURA_GRAPHQL_ADMIN_SECRET - -- Description: Secret password that gives admin access to query Hasura -- Services: hasura, aerie_scheduler, aerie_scheduler_workers, aerie_sequencing -- Accepted values: Any password string -- Default: `null` -- See: https://hasura.io/docs/latest/deployment/graphql-engine-flags/reference/#admin-secret-key - -## Aerie UI Login Page - -With CAM authentication enabled you can log in to the Aerie UI with your JPL username and password. - -
- Aerie UI - Login Page -
Figure 1: Aerie UI - Login Page
-
diff --git a/docs/deployment/advanced-database-migrations-OLD.mdx b/docs/deployment/advanced-database-migrations-OLD.mdx deleted file mode 100644 index 60f4ebe2..00000000 --- a/docs/deployment/advanced-database-migrations-OLD.mdx +++ /dev/null @@ -1,192 +0,0 @@ -# Advanced - Database Migrations pre-Aerie v3.3.0 - -:::info Note -For Aerie deployments AFTER to v3.3.0, please reference [this page](../advanced-database-migrations) -for accurate information as to how the migration script works. -::: - -To aid in migrating between database versions we provide a Python script called [aerie_db_migration.py](https://github.com/NASA-AMMOS/aerie/blob/develop/deployment/aerie_db_migration). In order to run it, the following software is required: - -- [Python 3.9](https://www.python.org/downloads/) or later -- [Hasura CLI 2.35.1](https://hasura.io/docs/latest/hasura-cli/install-hasura-cli/) or later - -Additional Python package requirements can be installed by running the following command in the [deployment](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment) directory: - -```sh -python -m pip install -r requirements.txt -``` - -Once the prerequisite software has been installed, the script can be executed by running the following command from inside the Aerie [deployment](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment) directory: - -```sh -python aerie_db_migration.py -``` - -Alternatively, you can add the script to your `PATH` and run it from anywhere with the command `aerie_db_migration.py`: - -```sh -export PATH="$HOME/path/to/deployment:$PATH" -``` - -The script expects all migration files present on the server to also exist on the local machine. In order to reset the state of all migrations (for example, to be able to remove old migration files), see the [Hasura documentation](https://hasura.io/docs/latest/migrations-metadata-seeds/resetting-migrations-metadata/). - -## Arguments - -
- For Aerie deployments before v2.8.0 - -`aerie_db_migration` takes the following command line arguments: - -```sh -aerie_db_migration.py [-h] (-a | -r) [--all] [-db DB_NAMES [DB_NAMES ...]] [-p HASURA_PATH] [-e ENV_PATH] [-n NETWORK_LOCATION] -``` - -| Option Name | Option Description | -|--------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| -| `-h`, `--help` | Shows the help message. | -| `-a`, `--apply` | Apply migration steps to specified databases. | -| `-r`, `--revert` | Revert migration steps to specified databases. | -| `--all` | Apply (Revert) _ALL_ unapplied (applied) migration steps to specified databases. | -| `-db DB_NAMES [DB_NAMES ...]`,
`--db-names DB_NAMES [DB_NAMES ...]` | List of databases to migrate. Migrates all available databases if unspecified. | -| `-p HASURA_PATH`, `--hasura-path HASURA_PATH` | The path to the directory containing the `config.yaml` for Aerie. Defaults to `./hasura` | -| `-e ENV_PATH`, `--env-path ENV_PATH` | The path to the `.env` file used to deploy Aerie. **Must** define `AERIE_USERNAME` and `AERIE_PASSWORD`. Defaults to `.env`. | -| `-n NETWORK_LOCATION`, `--network-location NETWORK_LOCATION` | The network location of the database. Defaults to `localhost`. | - -It is necessary to at least specify `--apply` or `--revert`. - -If you are not running `aerie_db_migration` from within `deployment`, or if the `hasura` directory containing the `config.yaml` and the `migrations` directory is not in the directory that `aerie_db_migration` is being run from, then it is necessary to specify `--hasura-path`. -
- -`aerie_db_migration.py` takes the following command line arguments: - -```sh -aerie_db_migration.py [-h] (-a | -r) [--all] [-p HASURA_PATH] [-e ENV_PATH] [-n NETWORK_LOCATION] -``` - -| Option Name | Option Description | -|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| -| `-h`, `--help` | Shows the help message. | -| `-a`, `--apply` | Apply migration steps to the database. | -| `-r`, `--revert` | Revert migration steps to the database. | -| `--all` | Apply (Revert) _ALL_ unapplied (applied) migration steps to the database. | -| `-p HASURA_PATH`, `--hasura-path HASURA_PATH` | The path to the directory containing the `config.yaml` for Aerie. Defaults to `./hasura` | -| `-e ENV_PATH`, `--env-path ENV_PATH` | The path to the `.env` file used to deploy Aerie. **Must** define `AERIE_USERNAME` and `AERIE_PASSWORD`. Defaults to `.env`. | -| `-n NETWORK_LOCATION`, `--network-location NETWORK_LOCATION` | The network location of the database. Defaults to `localhost`. | - -It is necessary to at least specify `--apply` or `--revert`. - -If you are not running `aerie_db_migration.py` from within `deployment`, -or if the `hasura` directory containing the `config.yaml` and the `migrations` directory is not in the directory -that `aerie_db_migration.py` is being run from, then it is necessary to specify `--hasura-path`. - -## Migrating a Database - -
- For Aerie deployments before v2.8.0 - -If `--all` has been specified, then `aerie_db_migration.py` will automatically apply or revert all applicable changes to the specified databases (or all available databases if none are specified). It will then output the details of each change applied, followed by the overall status. For example: - -```sh -> aerie_db_migration.py -a -db AerieMerlin --all -########### -AerieMerlin -########### -VERSION TYPE NAME -1667319761264 up test_migration -INFO migrations applied -INFO Metadata reloaded -INFO Metadata is consistent - -############### -Database Status -############### - -Database: AerieMerlin -VERSION NAME SOURCE STATUS DATABASE STATUS -1667319761264 test_migration Present Present -``` - -Otherwise, `aerie_db_migration` will enter a step-by-step mode, where you first select which database to migrate, and then select whether -to apply each available migration step. For example: - -```sh -> aerie_db_migration.py -a -db AerieMerlin -############################### -AERIE DATABASE MIGRATION HELPER -############################### - -0) Quit the migration helper - -1) Database: AerieMerlin -VERSION NAME SOURCE STATUS DATABASE STATUS -1667319761264 test_migration Present Not Present - -Select a database to migrate (0-1): 1 - -########### -AerieMerlin -########### - -MIGRATION STEPS AVAILABLE: -VERSION NAME SOURCE STATUS DATABASE STATUS -1667319761264 test_migration Present Not Present - -CURRENT STEP: - -VERSION TYPE NAME -1667319761264 up test_migration - -Apply 1667319761264_test_migration? (y/n): _ -``` - -Entering `y` will apply the migration and then proceed to the next step, -should one exist. Entering `n` will return to the Database Selection screen. - -Entering `q` at any point will exit the program. - -
- -If `--all` has been specified, then `aerie_db_migration.py` will automatically apply or revert all applicable changes to the database. -It will then output the details of each change applied, followed by the overall status. For example: - -```sh -> aerie_db_migration.py -a --all -############################### -AERIE DATABASE MIGRATION HELPER -############################### -VERSION TYPE NAME -1 up sample_migration -INFO migrations applied -INFO Metadata reloaded -INFO Metadata is consistent - -############### -Database Status -############### -VERSION NAME SOURCE STATUS DATABASE STATUS -1 sample_migration Present Present -``` - -Otherwise, `aerie_db_migration` will enter a step-by-step mode, where you will be prompted -whether to apply each available migration step. For example: - -```sh -> aerie_db_migration.py -a -############################### -AERIE DATABASE MIGRATION HELPER -############################### - -MIGRATION STEPS AVAILABLE: -VERSION NAME SOURCE STATUS DATABASE STATUS -1 sample_migration Present Not Present - -CURRENT STEP: - -VERSION TYPE NAME -1 up sample_migration - -Apply 1_sample_migration? (y/n/quit): _ -``` - -Entering `y` will apply the migration and then proceed to the next step, should one exist. -Entering `n`, `q`, or `quit` will exit the program. diff --git a/docs/deployment/advanced-database-migrations.mdx b/docs/deployment/advanced-database-migrations.mdx deleted file mode 100644 index 6a7070a5..00000000 --- a/docs/deployment/advanced-database-migrations.mdx +++ /dev/null @@ -1,121 +0,0 @@ -# Advanced - Database Migrations - -:::info Note -For Aerie deployments PRIOR to v3.3.0, please reference [this page](../advanced-database-migrations-OLD) -for accurate information as to how the migration script works. -::: - -To aid in migrating between database versions we provide a Python script called [aerie_db_migration.py](https://github.com/NASA-AMMOS/aerie/blob/develop/deployment/aerie_db_migration). In order to run it, the following software is required: - -- [Python 3.9](https://www.python.org/downloads/) or later -- [Hasura CLI 2.35.1](https://hasura.io/docs/latest/hasura-cli/install-hasura-cli/) or later - -Additional Python package requirements can be installed by running the following command in the [deployment](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment) directory: - -```sh -python -m pip install -r requirements.txt -``` - -Once the prerequisite software has been installed, the script can be executed by running the following command from inside the Aerie [deployment](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment) directory: - -```sh -python aerie_db_migration.py -``` - -Where `` is one of `status` or `migrate` ([see below](#subcommands)). -Alternatively, you can add the script to your `PATH` and run it from anywhere with the command `aerie_db_migration.py`: - -```sh -export PATH="$HOME/path/to/deployment:$PATH" -``` - -The script expects all migration files present on the server to also exist on the local machine. -In order to reset the state of all migrations (for example, to be able to remove old migration files), see the [Hasura documentation](https://hasura.io/docs/latest/migrations-metadata-seeds/resetting-migrations-metadata/). - -## Subcommands - -`aerie_db_migration.py` contains two subcommands: `status` and `migrate`. - -### Status - -Displays the current migration status of the database. - -```sh -aerie_db_migration.py status [-h] [-p HASURA_PATH] [-e ENV_PATH] [--endpoint ENDPOINT] [--admin-secret ADMIN_SECRET] -``` - -| Option Name | Option Description | -|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------| -| `-h`, `--help` | Shows the help message. | -| `-p HASURA_PATH`, `--hasura-path HASURA_PATH` | The path to the directory containing the `config.yaml` and migrations folder for Aerie. Defaults to `./hasura` | -| `-e ENV_PATH`, `--env-path ENV_PATH` | Envfile to load envvars from. | -| `--endpoint ENDPOINT ` | The http(s) endpoint for the Hasura instance. | -| `--admin-secret ADMIN_SECRET` | The admin secret | - -### Migrate - -Migrates the database. - -```sh -aerie_db_migration.py migrate [-h] [-p HASURA_PATH] [-e ENV_PATH] [--endpoint ENDPOINT] [--admin-secret ADMIN_SECRET] (-a | -r) [--all] -``` - -| Option Name | Option Description | -|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------| -| `-h`, `--help` | Shows the help message. | -| `-a`, `--apply` | Apply migration steps to the database. | -| `-r`, `--revert` | Revert migration steps to the database. | -| `--all` | Apply (Revert) _ALL_ unapplied (applied) migration steps to the database. | -| `-p HASURA_PATH`, `--hasura-path HASURA_PATH` | The path to the directory containing the `config.yaml` and migrations folder for Aerie. Defaults to `./hasura` | -| `-e ENV_PATH`, `--env-path ENV_PATH` | Envfile to load envvars from. | -| `--endpoint ENDPOINT ` | The http(s) endpoint for the Hasura instance. | -| `--admin-secret ADMIN_SECRET` | The admin secret | - -It is necessary to at least specify `--apply` or `--revert`. - -## Migrating a Database - -If `--all` has been specified, then `aerie_db_migration.py` will automatically apply or revert all applicable changes to the database. -It will then output the details of each change applied, followed by the overall status. For example: - -```sh -> aerie_db_migration.py migrate -a --all -############################### -AERIE DATABASE MIGRATION HELPER -############################### -VERSION TYPE NAME -1 up sample_migration -INFO migrations applied -INFO Metadata reloaded -INFO Metadata is consistent - -############### -Database Status -############### -VERSION NAME SOURCE STATUS DATABASE STATUS -1 sample_migration Present Present -``` - -Otherwise, `aerie_db_migration` will enter a step-by-step mode, where you will be prompted -whether to apply each available migration step. For example: - -```sh -> aerie_db_migration.py migrate -a -############################### -AERIE DATABASE MIGRATION HELPER -############################### - -MIGRATION STEPS AVAILABLE: -VERSION NAME SOURCE STATUS DATABASE STATUS -1 sample_migration Present Not Present - -CURRENT STEP: - -VERSION TYPE NAME -1 up sample_migration - -Apply 1_sample_migration? (y/n/quit): _ -``` - -Entering `y` will apply the migration and then proceed to the next step, should one exist. -Entering `n`, `q`, or `quit` will exit the program. diff --git a/docs/deployment/advanced-kubernetes.mdx b/docs/deployment/advanced-kubernetes.mdx deleted file mode 100644 index 7b0f1982..00000000 --- a/docs/deployment/advanced-kubernetes.mdx +++ /dev/null @@ -1,164 +0,0 @@ -import kubeDemo from './assets/kube-demo.webm'; - -# Advanced - Kubernetes - -If you're already familiar with Kubernetes, feel free to jump to [Within Aerie Context](/aerie-docs/deployment/advanced-kubernetes/#within-aerie-context) - -## Introduction - -[Kubernetes](https://kubernetes.io/) (often shortened to k8s) is an open-source platform for automating deployment, scaling, and management of containerized applications. It provides abstractions around container primitives like networking, storage, and runtimes, allowing deployment operators to declartively define the desired state of the cluster, which Kubernetes works to maintain. - - - -## Features - -### Declarative desired state - -Desired state for a Kubernetes cluster is defined using the `yaml` format, in files referred to as manifests. An example manifest for creating an `aerie-ui` container looks like this: - -```yaml ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: aerie-ui-deployment - labels: - app: aerie-ui -spec: - replicas: 2 - selector: - matchLabels: - app: aerie-ui - template: - metadata: - labels: - app: aerie-ui - spec: - containers: - - name: aerie-ui - image: ghcr.io/nasa-ammos/aerie-ui:v1.0.1 - ports: - - containerPort: 80 - env: - - name: EXAMPLE_VAR - value: 'example_value' -``` - -_These manifests are quite long-winded, but much of that is boilerplate._ - -This manifest defines an "aerie-ui" `Pod`, which is a wrapper around a container, that listens on port 80 and has the `EXAMPLE_VAR` environment variable available to it. This `Pod` template is wrapped by a `Deployment` object, which allows defining how many replicas of this `Pod` we want. In this example, the Kubernetes engine will work to ensure 2 instances of this UI `Pod` are running in our cluster at all times. - -Kubernetes manifests can be applied to the cluster using the `kubectl` command line tool. We could apply the above example using `kubectl apply -f aerie-ui-deployment.yaml`. `kubectl` will need to be configured to point at and authenticate with an existing Kubernetes cluster. - -Read more about Kubernetes abstractions and objects that can be defined in manifests [here](https://kubernetes.io/docs/concepts/). - -### DNS and load balancing - -`Deployments` can be wrapped in a `Service` object, which abstracts away the underlying `Pods` and instead exposes a single endpoint and DNS entry. The following example exposes our `aerie-ui` `Deployment` from before and will make it available to all other pods in the cluster at `http://aerie-ui`. - -```yaml ---- -apiVersion: v1 -kind: Service -metadata: - name: aerie-ui - labels: - app: aerie-ui -spec: - selector: - app: aerie-ui - ports: - - name: http - port: 80 -``` - -If a `Service` points to a `Deployment` that has multiple replicas, Kubernetes will intelligently load balance between the `Pods` replicas. - -### Orchestration and healing - -The example `Deployment` above defined the cluster's desired state to have 2 replicas of `aerie-ui`. If one of these `Pods` were to go down, i.e. stop responding to health checks, Kubernetes would automatically spin up a new instance of that `Pod` and start diverting traffic from the unhealthy `Pod` to the newly created one. - -### Rollout and rollback - -Kubernetes allows for simple yet robust rollouts of new container image versions. - -The following would initiate a rolling deployment of a new `aerie-ui` version. Kubernetes would spin up new `Pods` with version `v1.0.2`, slowly divert traffic to them, and then terminate the old `v1.0.1` `Pods`. - -```sh -kubectl set image deployment/aerie-ui-deployment aerie-ui=ghcr.io/nasa-ammos/aerie-ui:v1.0.2 -``` - -## Within Aerie context - -Aerie is provided as a set of container images, which makes it a perfect match for a Kubernetes deployment. Manifests for Aerie on Kubernetes are provided in the [Aerie repo](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment/kubernetes). These manifests create `Deployments` for each of the Aerie services, e.g. `aerie-merlin`, `hasura`, etc. with one replica of each `Pod`. An existing Kubernetes cluster will be needed. See one of the following sections for instructions on spinning up a cluster varying environments. - -## Local Kubernetes deployment - -### Cluster creation - -First, a local Kubernetes cluster is needed. An easy way to get a development cluster running is to run Kubernetes components within Docker. A tool called [k3d](https://k3d.io) can easily spin up a lightweight distrubution of Kubernetes called [k3s](https://k3s.io/). Create a cluster with the following command - -```sh -k3d cluster create -p "80:80@loadbalancer" -p "8080:30080@server:0" -p "9000:30000@server:0" aerie-dev -``` - -This creates two Docker containers, running Kubernetes services as well as a proxy that can forward traffic to our cluster. The `-p` options exposes and maps port from our local machine to the Kubernetes docker container. - -Then, assuming you already have `kubectl` installed, you can access the cluster with - -```sh -kubectl get nodes -``` - -and you should see one Kubernetes worker node running within Docker. - -### Manifest prerequisites - -A few objects need to be created before applying the provided aerie manifests. Navigate to `deployment/kubernetes`. - -First, create the `aerie-dev` namespace by issuing the following command: - -```sh -kubectl create namespace aerie-dev -``` - -Then, create a Kubernetes `Secret` that holds username and password info for Postgres: - -```sh -kubectl -n aerie-dev create secret generic dev-env --from-env-file=.env -``` - -The referenced `.env` file will need to be created or copied from `deployment/.env` and filled in with your desired username/passwords. The follwing environment variables are required (shown with resonable defaults): - -```sh -AERIE_USERNAME=aerie -AERIE_PASSWORD=aerie -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -``` - -### Manifest application - -Then, apply aerie manifests within `deployment/kubernetes` by running: - -```sh -kubectl apply -f . -``` - -Wait a few minutes for images to be pulled and deployed, then visit `http://localhost` in your browser to see the familiar `aerie-ui`! - -### Spinning Aerie down - -To remove the Aerie deployment, simply run: - -```sh -kubectl delete -f . -``` - -This will remove all Aerie `Pods`, but preserve the `aerie-dev` namespace and `Secret` we created earlier, since we created those ad-hoc, not from a manifest. This allows you to quickly iterate on manifests. - -## AWS - -Coming Soon... diff --git a/docs/deployment/advanced-permissions.mdx b/docs/deployment/advanced-permissions.mdx deleted file mode 100644 index eeb56bb7..00000000 --- a/docs/deployment/advanced-permissions.mdx +++ /dev/null @@ -1,371 +0,0 @@ -# Advanced - User Role Permissions - -User Roles are sets of permissions that defines how a user is a allowed to interact with the Aerie system. -By default, Aerie provides three User Roles: - -- `viewer`, which can view all data -- `user`, which can view all data and perform all actions based on ownership -- `aerie_admin`, which can view all data and perform all actions - -## Creating New User Roles - -Should the default three not be sufficient for your deployment, it is possible to define new User Roles. - -:::info -The `viewer` role is the most restricted role Aerie supports. To ensure Aerie works as expected when using your new role, -it is strongly recommended to give your role *at least* the same permissions as `viewer`. -::: - -### Adding the Role to Hasura - -In order to interact with the Aerie system, a role must first be provided access via the [Hasura Metadata](https://github.com/NASA-AMMOS/aerie/tree/develop/deployment/hasura/metadata). - -There are three parts of the Hasura Metadata that a role needs to be added to: -Functions, Actions, and individual tables. The Hasura Metadata can be found in `hasura/metadata`. - -#### Function Permissions - -Function Permissions can be found in `databases//functions/functions.yaml`. -Currently, all functions are in the AerieMerlin Database. -The inside of a `functions.yaml` will look like a sequence of entries in the following pattern: - -```yaml -- function: - name: - schema: hasura_functions - configuration: - custom_root_fields: - function: - session_argument: hasura_session - permissions: - - role: aerie_admin -``` - -To give the new role permission to see a given Function, -you must add them to the list of roles listed under the `permissions` key, like so: - -```yaml - permissions: - - role: aerie_admin - - role: -``` - -#### Action Permissions - -Action permissions can be found in `actions.yaml`. Similar to Functions, the file will be a series of entries. -The pattern for these entries is: - -```yaml - - name: - definition: - kind: synchronous - handler: "" - timeout: 300 - permissions: - - role: aerie_admin - - role: user -``` - -To give a role permission to see an Action, add them to the list of roles under the `permissions` key: - -```yaml - permissions: - - role: aerie_admin - - role: user - - role: -``` - -#### Table Permissions - -Table permissions are specified per table and can be found under `databases//tables`. - -There are four permissions that may be specified for a role on a table: Select, Insert, Update, and Delete. -The follow section goes over how to configure these permissions. -More information on [configuring table permissions](https://hasura.io/docs/latest/auth/authorization/permissions/) -and [the YAML syntax](https://hasura.io/docs/latest/api-reference/syntax-defs/#insertpermission) may be found in the Hasura Docs. - -##### Select Permissions: -- `columns` determines which fields a role can see on all rows it can read. It is generally expected that all roles can read all columns in a table, abbreviated as `'*'`. -- `filter` determines which rows a role can read. It is generally expected that all roles can read all rows in a table, represented as `{}`. -- `allow_aggregations` determines whether a role can run aggregation functions, such as `count` or `max` on parts of the table it can read. - -Example Select Permission: -```yaml -select_permissions: - - role: - permission: - columns: '*' - filter: {} - allow_aggregations: true -``` - -##### Insert Permissions: -- `columns` determines which fields a role can provide values for while setting a row. It is expected that `columns` includes at least those specified in the `user` role. -- `check` allows for validation of the row being inserted. -For example, `{"name":{"_like":"aerie--%"}}` means that the row being inserted must have its `name` field begin with "aerie--". -- `set` automatically sets the value of certain fields on the row to be inserted. -For example, `owner: "x-hasura-user-id"` will set `owner` as the current user's username (`"x-hasura-user-id"`). -These fields **must not** be included in `columns`. - -Example Insert Permission: -```yaml -insert_permissions: - - role: - permission: - columns: [name, definition] - check: {"name":{"_like":"aerie--%"}} - set: - owner: "x-hasura-user-id" -``` - -##### Update Permissions: -- `columns` determines which fields a role may update for a given row. -It is expected that `columns` includes at least those specified in the `user` role. -- `filter` determines which rows a role may update. -For example, `{"owner":{"_eq":"x-hasura-user-id"}}` means that users using the role can only delete rows -where the `owner` field is their username. -- `check` allows for validation of the updated row. -For example, `{"name":{"_like":"aerie--%"}}` means that the row must have its `name` field begin with "aerie--" after being updated. -This is optional. -- `set` automatically sets the value of certain fields on the row being updated. -For example, `updated_by: "x-hasura-user-id"` will set `owner` as the current user's username. -These fields **must not** be included in `columns`. - -Example Update Permission: -```yaml -update_permissions: - - role: - permission: - columns: [name, owner] - filter: {"owner":{"_eq":"x-hasura-user-id"}} - check: {"name":{"_like":"aerie--%"}} - set: - updated_by: "x-hasura-user-id" -``` - -##### Delete Permissions: -- `filter` determines which rows a role can delete. -For example, `{"owner":{"_eq":"x-hasura-user-id"}}` means that users using the role can only delete rows -where the `owner` field is their username. - -Example Delete Permission: -```yaml -delete_permissions: - - role: - permission: - filter: {"owner":{"_eq":"x-hasura-user-id"}} -``` - -### Adding the Role to the Database - -Before a role can be applied to users, it must be added to the Database. - -This can be done using the following GraphQL mutation: - -```graphql -mutation InsertUserRole { - insert_user_roles_one(object: { - role: "", - description: ""}) - { - role - description - } -} -``` - -After adding a new role, [it is currently necessary to refresh the Hasura Metadata](https://hasura.io/docs/latest/schema/postgres/enums/#current-limitations). -Instructions on how to do so can be found [in the Hasura Docs](https://hasura.io/docs/latest/migrations-metadata-seeds/manage-metadata/#reload-metadata). - - -#### Action and Function Permissions - -In addition to the coarse-grained permissions granted by the Hasura Metadata, -certain Functions and Actions have what are known as fine-grained permissions. -For these Functions and Actions **it is not sufficient** to only allow the role -to see the function via the Hasura metadata. -This section will go over how to specify these fine-grained permissions -for a custom role. - -Action and Function Permissions are represented as two JSONs, one for Actions and one for Functions. -The structure of these JSONs are `{ KEY_1: PERMISSION, KEY_2: PERMISSION, ... , KEY_N: PERMISSION }`. -The absence of a Key will be considered equivalent to a role **NOT** having permission to perform the Action or Function. - -
-Keys Permitted for Action Permissions - -Any Actions not included here only have coarse-grained control. - -| Key | Hasura Action | -| -: | :- | -| `assign_activities_by_filter` | assignActivitiesByFilter -| `check_constraints` | constraintViolations -| `create_expansion_rule` | addCommandExpansionTypeScript -| `create_expansion_set` | createExpansionSet -| `expand_all_activities` | expandAllActivities -| `expand_all_templates` | expandAllTemplates -| `insert_ext_dataset` | addExternalDataset -| `resource_samples` | resourceSamples -| `schedule` | schedule -| `sequence_seq_json_bulk` | getSequenceSeqJsonBulk -| `simulate` | simulate - - -
- -
-Keys Permitted for Function Permissions - -Any Functions not included here only have coarse-grained control. - -| Key | Hasura Function | -| -: | :- | -| `apply_preset` | `apply_preset_to_activity` -| `begin_merge` | `begin_merge` -| `branch_plan` | `duplicate_plan` -| `cancel_merge` | `cancel_merge` -| `commit_merge` | `commit_merge` -| `create_merge_rq` | `create_merge_request` -| `create_snapshot` | `create_snapshot` -| `deny_merge` | `deny_merge` -| `delete_activity_reanchor` | `delete_activity_by_pk_reanchor_to_anchor` -| `delete_activity_reanchor_bulk` | `delete_activity_by_pk_reanchor_to_anchor_bulk` -| `delete_activity_reanchor_plan` | `delete_activity_by_pk_reanchor_plan_start` -| `delete_activity_reanchor_plan_bulk` | `delete_activity_by_pk_reanchor_plan_start_bulk` -| `delete_activity_subtree` | `delete_activity_by_pk_delete_subtree` -| `delete_activity_subtree_bulk` | `delete_activity_by_pk_delete_subtree_bulk` -| `get_conflicting_activities` | `get_conflicting_activities` -| `get_non_conflicting_activities` | `get_non_conflicting_activities` -| `get_plan_history` | `get_plan_history` | -| `migrate_plan_to_model` | `migrate_plan_to_model` | -| `restore_activity_changelog` | `restore_activity_changelog` -| `restore_snapshot` | `restore_from_snapshot` -| `set_resolution` | `set_resolution` -| `set_resolution_bulk` | `set_resolution_bulk` -| `withdraw_merge_rq`| `withdraw_merge_request` - -
- -
-Values Permitted for PERMISSION - -| Permission | Meaning | -| -: | :- | -| `NO_CHECK` | The user may run the action/function with no restrictions. **The `aerie_admin` role will always be treated as having `NO_CHECK` permissions on all Actions and Functions.** | -| `OWNER` | The user must be the `owner` of **all** relevant objects **directly** used by the KEY | -| `MISSION_MODEL_OWNER` | The user must own the relevant Mission Model | -| `PLAN_OWNER` | The user must be the Owner of the relevant Plan | -| `PLAN_COLLABORATOR` | The user must be a Collaborator of the relevant Plan. **The Plan Owner is NOT considered a Collaborator of the Plan** | -| `PLAN_OWNER_COLLABORATOR` | The user must be either the Owner or a Collaborator of the relevant Plan | - -In general, `OWNER` is equivalent to `PLAN_OWNER`. Exceptions are as follows: -- `apply_preset`, where `OWNER` means a user must own both the plan and the preset - -#### On Plan Merge Keys - -The following Function Keys are in a group known as "Plan Merge Functions" and are involved in [Plan Merging](../../planning/collaboration/merging-plans/): -`begin_merge`, `cancel_merge`, `create_merge_rq`, `commit_merge`, `deny_merge`, `get_conflicting_activities`, -`get_non_conflicting_activities`, `set_resolution`, `set_resolution_bulk`, `withdraw_merge_rq`. - -Plan Merge Functions are special, as they involve multiple plans. -As such, there are special Permissions that may be used on these Keys. -Additionally, the other Permissions take on a slightly different meaning when applied to a Plan Merge Function. - -A complete list of what Permissions are allowed on Plan Merge Functions follows. -Keep in mind that in a merge 'Source Plan' refers to the plan *providing* changes, -and 'Target Plan' refers to the plan *receiving* changes. - -| Permission | Meaning | -|-:|:-| -| `NO_CHECK` | The user may run the action/function with no restrictions. **The `aerie_admin` role will always be treated as having `NO_CHECK` permissions on all Plan Merge Functions.** | -| `OWNER` | The user must be the Owner of **both** Plans -| `MISSION_MODEL_OWNER` | The user must be the Owner of the relevant Mission Model -| `PLAN_OWNER` | The user must be the Owner of **either** Plan -| `PLAN_COLLABORATOR` | The user must be a Collaborator of **either** Plan. **The Plan Owner is NOT considered a Collaborator of the Plan** -| `PLAN_OWNER_COLLABORATOR` | The user must be either the Owner or a Collaborator of **either** Plan -| `PLAN_OWNER_SOURCE` | The user must be the Owner of the Source Plan. -| `PLAN_COLLABORATOR_SOURCE` | The user must be a Collaborator of the Source Plan. -| `PLAN_OWNER_COLLABORATOR_SOURCE` | The user must be either the Owner or a Collaborator of the Source Plan. -| `PLAN_OWNER_TARGET` | The user must be the Owner of the Target Plan. -| `PLAN_COLLABORATOR_TARGET` | The user must be a Collaborator of the Target Plan. -| `PLAN_OWNER_COLLABORATOR_TARGET` | The user must be either the Owner or a Collaborator of the Target Plan. - -
- -Once you have devised your Action Permissions and Function Permissions JSONs, -you can use this GraphQL Mutation to apply them to your custom role: - -```graphql -mutation updateUserRolePermissions($function_permissions: jsonb!, $action_permissions: jsonb!) { - update_user_role_permission_by_pk( - pk_columns: {role: }, - _set: { - function_permissions: $function_permissions, - action_permissions: $action_permissions} - ) { - function_permissions - action_permissions - } -} -``` - -Below are sample Query Variables. These are equivalent to the `user` role's Permissions. - -```json -{ - "action_permissions": { - "assign_activities_by_filter": "NO_CHECK", - "check_constraints": "PLAN_OWNER_COLLABORATOR", - "create_expansion_rule": "NO_CHECK", - "create_expansion_set": "NO_CHECK", - "expand_all_activities": "NO_CHECK", - "expand_all_templates": "NO_CHECK", - "insert_ext_dataset": "PLAN_OWNER", - "resource_samples": "NO_CHECK", - "schedule":"PLAN_OWNER_COLLABORATOR", - "sequence_seq_json_bulk": "NO_CHECK", - "simulate":"PLAN_OWNER_COLLABORATOR" - }, - "function_permissions": { - "apply_preset": "PLAN_OWNER_COLLABORATOR", - "begin_merge": "PLAN_OWNER_TARGET", - "branch_plan": "NO_CHECK", - "cancel_merge": "PLAN_OWNER_TARGET", - "commit_merge": "PLAN_OWNER_TARGET", - "create_merge_rq": "PLAN_OWNER_SOURCE", - "create_snapshot": "PLAN_OWNER_COLLABORATOR", - "delete_activity_reanchor": "PLAN_OWNER_COLLABORATOR", - "delete_activity_reanchor_bulk": "PLAN_OWNER_COLLABORATOR", - "delete_activity_reanchor_plan": "PLAN_OWNER_COLLABORATOR", - "delete_activity_reanchor_plan_bulk": "PLAN_OWNER_COLLABORATOR", - "delete_activity_subtree": "PLAN_OWNER_COLLABORATOR", - "delete_activity_subtree_bulk": "PLAN_OWNER_COLLABORATOR", - "deny_merge": "PLAN_OWNER_TARGET", - "get_conflicting_activities": "NO_CHECK", - "get_non_conflicting_activities": "NO_CHECK", - "get_plan_history": "NO_CHECK", - "migrate_plan_to_model": "PLAN_OWNER_COLLABORATOR", - "restore_activity_changelog": "PLAN_OWNER_COLLABORATOR", - "restore_snapshot": "PLAN_OWNER_COLLABORATOR", - "set_resolution": "PLAN_OWNER_TARGET", - "set_resolution_bulk": "PLAN_OWNER_TARGET", - "withdraw_merge_rq": "PLAN_OWNER_SOURCE" - } -} -``` - -## Configuring Roles for New Users - -The first time a user not in the system logs in, they will be added to the Aerie system with a set of User Roles they -are allowed to use and one Role from this set that they use by default. -By default, users will use the `user` role and have access to the `user` and `viewer` roles. -To configure this, use the `ALLOWED_ROLES` and `DEFAULT_ROLE` environment variables. -**Note: `DEFAULT_ROLE` _must_ be an entry in `ALLOWED_ROLES`.** - -For more information, see the [Environment Variables document](https://github.com/NASA-AMMOS/aerie-gateway/blob/develop/docs/ENVIRONMENT.md) in the Gateway. - -:::info When Authentication is Disabled -[If authentication is disabled](../advanced-authentication/), then the following changes apply: -1. By default, users will use the `aerie_admin` role and have access to the `aerie_admin`, `user`, and `viewer` roles -2. To configure these roles for new users, use the `ALLOWED_ROLES_NO_AUTH` and `DEFAULT_ROLES_NO_AUTH` environment variables instead. -::: diff --git a/docs/deployment/advanced-reverse-proxy.md b/docs/deployment/advanced-reverse-proxy.md deleted file mode 100644 index 3943a53f..00000000 --- a/docs/deployment/advanced-reverse-proxy.md +++ /dev/null @@ -1,53 +0,0 @@ -# Advanced - Reverse Proxy - -# Deploying with HTTPS / Behind a reverse proxy - -The `./deployment/proxy` folder contains the following as a standard pattern for deploying Aerie with a reverse proxy: -``` -- nginx.conf # nginx (reverse proxy) configuration file that terminates TLS and proxies traffic to internal containers -- Dockerfile # runs an nginx instance as configured above -- docker-compose-proxy.yml # docker-compose that runs the above Docker container within the Aerie stack, closing unneccesary ports -``` - -In this example, an [`nginx`](https://nginx.org) instance runs within the Aerie container stack, acting as the single ingress point for all network traffic, which strips TLS and forwards HTTP traffic to internal containers. - -## Generating a self-signed TLS certificate for testing - -Running the following command will generate a self-signed certificate for testing TLS. It will prompt you for several fields which can all be left blank (i.e. just press enter). - -``` -openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 --nodes -``` - -Move these `key.pem` and `cert.pem` files into the `./deployment/proxy` folder, so `nginx` can read them. - -## Define Aerie deployment host -When running a production deployment of Aerie, you can use DNS records to point a domain to your deployment server's IP address. - -To emulate DNS on a local machine, you can edit `/etc/hosts` (on unix-based machines). Adding the following line will redirect `localhost.jpl.nasa.gov` to `127.0.0.1`, which will allow us to use a fake domain name for a local Aerie deployment. This can be any domain, but it's a best practice to use something that doesn't exist as a public site. - -``` -127.0.0.1 localhost.jpl.nasa.gov -``` - -## Run Aerie instance with reverse proxy -Edit your Aerie `./deployment/.env` file to define `AERIE_HOST=${whatever domain you put in your /etc/hosts or DNS}`. - -Running the following command from the `./deployment` directory of the Aerie repo will now deploy the regular Aerie stack with `nginx` proxying all connections. -``` -docker compose -f docker-compose.yml -f ./proxy/docker-compose-proxy.yml up -d --build -``` - -You can now visit `https://here-is-the-domain-you.set`, which will prompt you to trust a self-signed certificate, and then forward you to Aerie as usual! - -:::info Accepting self-signed certificates -You will need to accept self-signed certificates for Aerie UI, Hasura, _and_ the Gateway. -Visit the following three URLs and follow the instructions to allow self-signed certs: -- `https://here-is-the-domain-you.set` -- `https://here-is-the-domain-you.set:8080/console` -- `https://here-is-the-domain-you.set:9000` - -Aerie will *not* function correctly if all three certificates are not accepted. - -This manual process can be avoided if you are able to use a testing certificate for this domain that is signed by your authorities. -::: diff --git a/docs/deployment/advanced-sso.md b/docs/deployment/advanced-sso.md deleted file mode 100644 index ce748290..00000000 --- a/docs/deployment/advanced-sso.md +++ /dev/null @@ -1,232 +0,0 @@ -# Advanced - SSO Authentication - -Aerie is able to interoperate with external auth providers like JPL CAM, [Auth0](https://auth0.com/), [AWS Cognito](https://aws.amazon.com/cognito/), etc in order to provide single-sign on (SSO) support. Due to Aerie's open source and flexible nature, the SSO implementation is very extensible, and while this allows for organizations of many types to use Aerie, it will require some configuration on the deployment side. - -This is achieved through the use of several environment variables as well as "Auth Adapters", that read these environment variables, communicate with external auth providers, and translate their response to work with Aerie's existing auth. - -## Enabling SSO Authentication - -The following environment variables will need to be set in conjunction for SSO auth to work correctly. - -### Aerie Gateway variables - -#### AUTH_URL -- Description: URL that the Gateway will make auth API requests to. Likely found in auth provider docs. -- Accepted values: Any URL -- Example: `https://your-auth-provider-api-endpoint` -- Default: `https://atb-ocio-12b.jpl.nasa.gov:8443/` - -#### AUTH_UI_URL -- Description: Login UI URL that the Gateway will redirect the UI to if SSO auth fails. Likely found in auth provider docs. -- Accepted values: Any URL -- Example: `https://your-auth-provider-login-screen` -- Default: `https://atb-ocio-12b.jpl.nasa.gov:8443/cam-ui` - -#### AUTH_SSO_TOKEN_NAME -- Description: Name of the cookie(s) that the Gateway will parse for SSO token(s). See the [following tip](#setting-default_roles) to make sure you're setting array env vars correctly. -- Accepted values: List of strings -- Example: `["my_sso_token_cookie_name"]` -- Default: `["iPlanetDirectoryPro"]` - -#### AUTH_TYPE -- Description: Enum value that determines which Auth Adapter is loaded. -- Accepted values: `cam` or `none` -- Default: `none` - -### Aerie UI variables - -#### PUBLIC_AUTH_SSO_ENABLED -- Description: Boolean flag that enables the SSO-based auth flow instead of the JWT-only flow -- Accepted values: `true` or `false` -- Example: `true` -- Default: `false` - -## Sensible configurations - -Three main use cases exist for auth within Aerie, each with their own configuration and combination of env vars: - -### CAM API auth + Aerie login UI - -User is forwarded to Aerie UI /login page if no valid Aerie JWT is found. Credentials passed in here will be tested against the CAM API for validity. - -```sh -# Gateway vars -AUTH_TYPE=cam -AUTH_UI_URL="https://localhost.jpl.nasa.gov/login" # example Aerie UI /login URL -AUTH_URL="https://atb-ocio-12b.jpl.nasa.gov:8443/cam-api" # example SSO API URL - -# UI var -PUBLIC_AUTH_SSO_ENABLED=false # use /login prompt instead of SSO -``` - -### Aerie login UI without authentication - -User is forwarded to Aerie UI `/login` page if no valid Aerie JWT is found. Validity of username / password is never checked; automatically signed in as username with admin privileges. Useful for testing / dev environments - -```sh -# Gateway vars -AUTH_TYPE=none -AUTH_UI_URL="https://localhost.jpl.nasa.gov/login" # Aerie UI /login URL - -# UI var -PUBLIC_AUTH_SSO_ENABLED=false # use /login prompt instead of SSO -``` - -### SSO token-based authentication using external auth providers. - -User is forwarded to SSO login UI page if no valid Aerie JWT or SSO token is found. - -```sh -# Gateway vars -AUTH_TYPE=cam -AUTH_UI_URL="https://atb-ocio-12b.jpl.nasa.gov:8443/cam-ui" # example SSO login UI -AUTH_URL="https://atb-ocio-12b.jpl.nasa.gov:8443/cam-api" # example SSO API -AUTH_SSO_TOKEN_NAME='["iPlanetDirectoryPro"]' # example name of SSO token cookie - -# UI var -PUBLIC_AUTH_SSO_ENABLED=true # use SSO login page instead of Aerie's `/login` -``` - -## Mapping auth provider groups to Aerie roles - -:::info - -This feature was introduced in Aerie 2.5.0 - -::: - -:::note - -This is an optional feature. If `AUTH_GROUP_ROLE_MAPPINGS` isn't set (or is set to an empty JSON: `{}`), the Gateway will not use auth provder group membership and instead use `DEFAULT_ROLE` and `ALLOWED_ROLES` as the source of truth for assigning roles, as described in the [Password Authentication docs](../advanced-permissions/#configuring-roles-for-new-users) - -::: - -When authenticating with external auth providers like CAM, Aerie provides the ability to map auth groups (e.g. LDAP groups) to roles within Aerie (e.g. `user`, `viewer`). This is facilitated with the `AUTH_GROUP_ROLE_MAPPINGS` environment variable. When authenticating users, the loaded Auth Adapter within Aerie Gateway can query the external auth provider for available group membership (e.g. LDAP group membership in the CAM SSO case). - -The authentication role-mapping algorithm is roughly as follows: - -- Get all groups the user is a member of from external auth provider -- Take the intersection of user's groups and the groups that have mappings defined in `AUTH_GROUP_ROLE_MAPPINGS` -- Map these groups to Aerie roles using the defined mapping -- Take the union of all allowed roles, this becomes `allowed_roles` for the user -- Iterate through `DEFAULT_ROLE` list. The first role that is contained within the user's `allowed_roles` becomes the user's `default_role` - -In other words, `AUTH_GROUP_ROLE_MAPPINGS` is an unordered mapping that defines which roles are granted to users in a specific auth group. `DEFAULT_ROLE` is an ordered priority list, where the default role for a user is the first match. This means that each group -> role mapping in `AUTH_GROUP_ROLE_MAPPINGS` _must_ contain at least one of the roles in the `DEFAULT_ROLE` list, since a user must always have a default role defined. An error will be thrown from the Gateway on bootup if this constraint is not satisfied. - -### Creating an `AUTH_GROUP_ROLE_MAPPINGS` - -The `AUTH_GROUP_ROLE_MAPPINGS` environment variable must be valid JSON in the following format: - -```json -{ - "some_auth_group_name": ["aerie_admin", "user", "viewer"], - "another_auth_group_name": ["viewer"] -} -``` - -:::info -This variable will be run through `JSON.parse()` in the Auth Adapters, so it must be valid JSON (e.g. double quotes, no trailing comma, etc). An error will be thrown from the Gateway on boot if the environment variable is invalid JSON. View the [following tip](#setting-default_roles) to make sure you're setting env vars as literal values, e.g. in a `docker-compose.yml` file: -```yaml -aerie_gateway: - environment: - AUTH_GROUP_ROLE_MAPPINGS: | - { - "some_auth_group_name": ["aerie_admin", "user", "viewer"], - "another_auth_group_name": ["viewer"] - } -``` - -::: - -### Setting `DEFAULT_ROLE`'s - -:::tip -You'll need to be cognizant on how environment variables are set in your Aerie deployment (e.g. docker-compose.yml file v.s. .env file). Some ways of setting environment variables will escape or strip quotes, which can lead to invalid JSON being passed to Aerie services. - -e.g. in a docker-compose.yml file: - -```yaml -aerie_gateway: - environment: - DEFAULT_ROLE: "[\"viewer\"]" # won't work -``` - -DEFAULT_ROLE will be stripped of quotes, and passed to the Gateway as the literal ["viewer"], which isn't valid JSON. - -Instead, pass literal values when possible, like in the following docker-compose.yml example. - -```yaml -aerie_gateway: - environment: - DEFAULT_ROLE: | - ["viewer"] -``` -::: - -## Writing custom Auth Adapters - -The SSO-based auth flow is roughly as follows: - -- A user request is made from the client (e.g. navigate to `/plans/1`) -- The Aerie UI server intercepts this request, and forwards cookies to the Gateway -- The Gateway reads `AUTH_TYPE` to determine which auth adapter to load (CAM, Auth0, etc) -- The Gateway executes the `validate` function of the loaded auth adapter - - Cookies named within `AUTH_SSO_TOKEN_NAME` are read as SSO token(s) - - Authentication requests are made to `AUTH_URL` that determine token validity - - If the user has a valid SSO token, `validate` returns a JWT for use within Aerie - - Otherwise, `validate` returns a redirection to `AUTH_UI_URL` -- The Aerie UI server reads the response, and either redirects the user to the SSO login UI, or continues with the original request, using the returned JWT for authorization - -This architecture was chosen so that the Gateway can be easily extended with custom auth adapters that implement this interface for any arbitrary auth provider. The interface for the Auth Adapter is as follows (defined in `auth/types.ts` in the Gateway repo): - -```typescript -export interface AuthAdapter { - validate(req: Request): Promise; - logout(req: Request): Promise; -} -``` - -Advanced Aerie users can easily create their own e.g. `Auth0Adapter.ts` that implements this interface and runs a custom version of the previously defined algorithm, that will validate any existing SSO token cookies, and return a `ValidateResponse` that can be used downstream by Aerie UI. - -### Important Considerations - -1. **Auth Logic**: Auth is entirely handed off to your custom auth adapter. This means your implementation of the `AuthAdapter` interface is responsible for: - - Extracting the SSO token from cookies - - Determining if it's valid (using your auth providers API) - - Calculating and returning a redirection URL to your auth providers login UI if the SSO token is invalid - - Calculating allowed and default roles for the user if the token is valid - - Creating and returning a JWT with these roles set - - This flexibility means your implementation might not exactly follow the auth algorithms described above (e.g. default role priority list) unless your adapter implements those algorithms. - -2. **Helper Functions**: Several helper functions (listed below) are provided to assist in implementing custom auth adapters. See existing auth adapters (`CAMAuthAdapter.ts`) for example usage. - - - `getUserRoles` - - `mapGroupsToRoles` - - `syncRolesToDB` - - `generateJwt` - -3. **`getUserRoles` behavior**: The `getUserRoles` helper function will use the database as the source of truth if an entry for the user exists. It will not look at the `default_role` passed in unless the user does not exist yet. - -4. **User Role Updates**: If using `AUTH_GROUP_ROLE_MAPPINGS`, your auth provider's group membership is the source of truth, so Aerie roles should be recalculated whenever the user logs in (in case their auth groups have changed since last login) and upserted into the Aerie DB. If `AUTH_GROUP_ROLE_MAPPINGS` is unset, roles need only be calculated from env vars once on initial login / user creation, since the Aerie DB is the source of truth for membership. - -In other words, if you're using `AUTH_GROUP_ROLE_MAPPINGS` you'll need to upsert to the database on every `validate` call, otherwise if you aren't using group => role mappings, you only need to insert to the DB once on user creation. The allowed roles in your generated token need to match what's in the DB, since Hasura checks auth by comparing current role in the JWT with allowed roles in the DB for that user. - -5. **Gateway Modifications**: When adding a new custom auth adapter, the Gateway's `switch (AUTH_TYPE)` statement in `src/main.ts` needs to be modified to recognize and instantiate your new auth adapter. - -6. **Logout Behavior**: Ensure that the logout functionality in the custom adapter properly clears any existing sessions or tokens with your auth provider. - -7. **Upstreaming**: If your auth adapter could be used by other Aerie deployments, consider upstreaming it to the Aerie Gateway repo for better maintenance. - -8. **SSO support only**: Token based auth is the only supported flow for custom auth adapters. Leverge your auth provider to handle username / password / 2fa / smartcard support, and then link . The Aerie `/login` page that directly accepts username + passwords for auth is philosophically deprecated, as we encourage SSO token based auth as the path forward. - -### Testing and Troubleshooting - -When implementing a custom auth adapter: - -1. Test the adapter thoroughly with various user scenarios and group memberships. -2. If unexpected role assignments occur, check the DB for an entry under that user. Your adapter might be incorrectly pulling this information. -3. Verify that the `AUTH_TYPE` env var is correctly set and recognized by the Gateway's entrypoint in `main.ts`. -4. Ensure that after making changes, users fully log out and log back in to see the effects. - -For more detailed information or assistance with custom auth adapters, please refer to the Aerie development team. diff --git a/docs/deployment/advanced-ui-custom-base-path.md b/docs/deployment/advanced-ui-custom-base-path.md deleted file mode 100644 index be291ab3..00000000 --- a/docs/deployment/advanced-ui-custom-base-path.md +++ /dev/null @@ -1,62 +0,0 @@ -# Advanced - UI Custom Base Path - -This document lists the instructions for building an aerie-ui Docker image with a [custom base path](https://kit.svelte.dev/docs/configuration#paths). While the default path and resulting application URL are usually a good fit, some installations may choose a custom path to support deployment behind a gateway, to provide consistency with other non-Aerie applications that the mission is using, or this could be part of a configuration that supports running multiple instances of Aerie UI on the same machine. - -### Building - -1. Clone the [aerie-ui](https://github.com/NASA-AMMOS/aerie-ui) and install dependencies. Note that [Node LTS](https://nodejs.org/) is required (currently 18.13.0). - - ```sh - git clone https://github.com/NASA-AMMOS/aerie-ui.git - cd aerie-ui - npm install - ``` - - When you clone aerie-ui the default branch is [develop](https://github.com/NASA-AMMOS/aerie-ui/tree/develop). If you want to build an image from a [specific release](https://github.com/NASA-AMMOS/aerie-ui/releases) you have to checkout the proper tag. For example to checkout [v1.0.0](https://github.com/NASA-AMMOS/aerie-ui/releases/tag/v1.0.0) do: - - ```sh - git checkout tags/v1.0.0 -b v1.0.0 - ``` - -2. Update [svelte.config.js](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/svelte.config.js) with the [base path](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/svelte.config.js#L9) you want to use. Note that a leading `/` is required. So for example a valid base path is `/aerie`. - -3. Build the aerie-ui. - - ```sh - npm run build - ``` - -4. Build the aerie-ui Docker image. Change the tag as necessary. For example we tag the image here with `aerie-ui`: - - ```sh - docker build -t aerie-ui . - ``` - -5. Use the newly built image as part of your normal [Aerie Docker deployment](https://github.com/NASA-AMMOS/aerie/blob/develop/deployment/docker-compose.yml#L132). - -### Cleaning - -If you ever need to re-run through these instructions make sure you **always** start from a clean environment. Remove all dependencies and build artifacts in aerie-ui: - -```sh -rm -rf node_modules -rm -rf .svelte-kit -rm -rf build -``` - -Remove the built Docker image: - -```sh -docker rmi aerie-ui -``` - -### References - -1. [aerie-ui Developer.md](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/docs/DEVELOPER.md) -1. [aerie-ui Deployment.md](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/docs/DEPLOYMENT.md) - -### Svelte Kit Issues - -Once this issue is resolved we will no longer need this document. - -1. [Dynamic basepath](https://github.com/sveltejs/kit/issues/595) diff --git a/docs/deployment/assets/kube-demo.webm b/docs/deployment/assets/kube-demo.webm deleted file mode 100644 index 81ff824e..00000000 --- a/docs/deployment/assets/kube-demo.webm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3b892418e98a93034cd1fdca62f8861280403d8c73593673acfb109b0babf061 -size 22428631 diff --git a/docs/deployment/assets/ui-login-page.png b/docs/deployment/assets/ui-login-page.png deleted file mode 100644 index 25f21da4..00000000 --- a/docs/deployment/assets/ui-login-page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f0de1d9c613f7ca14e292f7a6e08bc4226ba843f53292eea4aeb3615cc352ac -size 601278 diff --git a/docs/deployment/introduction.md b/docs/deployment/introduction.md deleted file mode 100644 index bdf744e9..00000000 --- a/docs/deployment/introduction.md +++ /dev/null @@ -1,97 +0,0 @@ -# Deployment - -There are a few different ways to deploy Aerie: - -- To get Aerie running **quickly** on your computer, see the [fast track instructions](/introduction/#fast-track) for minimal setup. -- If you plan to deploy Aerie in a shared **production environment**, read this entire page and then see the [production deployment guide](/deployment/production-deployment). -- If you are a **developer** and you want to run Aerie locally & make changes to Aerie core code, read this page and then head to the [developer guide](https://github.com/NASA-AMMOS/aerie/blob/develop/docs/DEVELOPER.md) in the repository for local setup instructions. - -The rest of this document goes into more depth about the Aerie system and how it should be deployed, regardless of environment. - -## Aerie Releases - -Aerie releases are published on the [Github Releases page](https://github.com/NASA-AMMOS/aerie/releases), and each release has a `Deployment.zip` artifact attached. This folder contains everything necessary to deploy a version of Aerie - namely the **`docker-compose.yml`** and **`.env`** files, detailed below. These files are provided *as a starting point* and should be modified to suit your needs. - -## Environment Variables - -Each Aerie service is configured with environment variables, some of which are **required** to run. They are expected to be set in a `.env` file in the folder you're running Aerie from. The version of this file provided in `Deployment.zip` is an empty template that must be filled in with service usernames and passwords of your choosing. See [this .env.template file](https://github.com/NASA-AMMOS/aerie-mission-model-template/blob/main/.env.template) for a completed example. - -A description of allowed variables is found in the [Environment Variable Documentation](https://github.com/NASA-AMMOS/aerie/blob/develop/deployment/Environment.md) - it's recommended to read through these & determine which are relevant to your situation. - -Of note, the `aerie-merlin`, `aerie_merlin_worker`, `aerie-scheduler`, and `aerie-scheduler-worker` containers can be provided additional JVM arguments - for example, allocated heap size - as environment variables. Desired JVM flags should be added to the `JAVA_OPTS` environment variable for the container being configured. - -## Docker - -Aerie consists of multiple **services**, and uses [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) to manage and run them. The artifacts used to deploy Aerie are a collection of Docker **images**, one per service, which we publish to the public [GitHub Packages](https://github.com/orgs/NASA-AMMOS/packages?ecosystem=container&q=aerie) repository. Aerie images conform to the [OCI](https://opencontainers.org/) [Image Format](https://github.com/opencontainers/image-spec/blob/main/spec.md) and may be compatible with Docker alternatives, but only Docker is officially supported. - -[Docker Compose](https://docs.docker.com/compose/) commands are used to build and run the Aerie services **all together**, so in general you should only need to run `docker compose up` & `docker compose down` (along with some various [command flags](https://docs.docker.com/reference/cli/docker/compose/)) to start and stop Aerie. - -### `docker-compose.yml` - -Docker Compose uses a configuration file called **`docker-compose.yml`** to control all sorts of options for the Aerie services. The compose file provided in `Deployment.zip` should work as-is, but modifying this file is one of your most useful tools for controlling deployment-specific Aerie configuration options. Options in this file control: - -- The source & version (tag) of the image used for each service (in the `image` field) -- The network ports used by each service (in `ports`) -- The directories used as mounted file volumes (in `volumes`) -- and other various environment variables passed to each service (in `environment`) - -A full list of possible options can be found in the [Docker compose file reference](https://docs.docker.com/reference/compose-file/). - -### Aerie services & images - -The following is a list of all of the required Aerie services, their associated Docker images (to be run by Compose), and their default network ports. The `ui`, `gateway` and `hasura` services are all "public-facing", which means their ports must be exposed to the network when running in a shared/production environment. - -| Image | Description | Port | Public | -| ------------------------------------------ | --------------------------------------------------------------- | ----- | ------ | -| [aerie-ui][ui] | The web-based client application for Aerie. | 80 | ✅ | -| [aerie-gateway][gateway] | Gateway server used for file-upload and authentication. | 9000 | ✅ | -| [aerie-hasura][hasura] | Hasura Docker image with bundled Aerie-specific Hasura metadata | 8080 | ✅ | -| [aerie-merlin][merlin] | Service for planning and simulation | 27183 | ❌ | -| [aerie-merlin-worker][merlin-worker] | Worker for executing simulations | 27187 | ❌ | -| [aerie-postgres][postgres] | Postgres Docker image with bundled Aerie-specific SQL | 5432 | ❌ | -| [aerie-scheduler][scheduler] | Service for scheduling | 27185 | ❌ | -| [aerie-scheduler-worker][scheduler-worker] | Worker for executing scheduling goals | 27189 | ❌ | -| [aerie-sequencing][sequencing] | Service for sequence generation and management | 27184 | ❌ | - - -## System Requirements - -### Software - -| Name | Version | -| ------ | ------- | -| Docker | 20.x | - -### Hardware - -Note these numbers are lower bounds. You will need to scale Aerie based on your mission needs. - -| Hardware | Details | -| ------------------- | -------------------------------------- | -| CPU | 2 Gigahertz (GHZ) or above | -| RAM | 8 GB at minimum | -| Storage | 15 GB | -| Display resolution | 2560-BY-1600, recommended | -| Internet connection | High-Speed connection, at least 60MBPS | - -### Supported Browsers - -| Name | Version | -| ------- | ------- | -| Chrome | Latest | -| Firefox | Latest | - -## Defect Reporting Procedure - -Defect reports should be sent to: `aerie-support@googlegroups.com`. For chat-based support, please join us on the [NASA-AMMOS Slack](https://join.slack.com/t/nasa-ammos/shared_invite/zt-1mlgmk5c2-MgqVSyKzVRUWrXy87FNqPw), in the `#aerie-users` channel. - - -[gateway]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-gateway -[hasura]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-hasura -[merlin]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-merlin -[merlin-worker]: https://github.com/NASA-AMMOS/aerie/pkgs/container/aerie-merlin-worker -[postgres]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-postgres -[scheduler]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-scheduler -[scheduler-worker]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-scheduler-worker -[sequencing]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-sequencing -[ui]: https://github.com/orgs/NASA-AMMOS/packages/container/package/aerie-ui diff --git a/docs/deployment/production-deployment.md b/docs/deployment/production-deployment.md deleted file mode 100644 index 03b92af7..00000000 --- a/docs/deployment/production-deployment.md +++ /dev/null @@ -1,82 +0,0 @@ -# Production Deployment - -This document describes some of the things you'll need to consider when deploying Aerie to a shared production server, and provides a guide for configuring and running Aerie in your environment. - -:::danger - -Aerie allows execution of user-provided code in the simulation and scheduling environments, so it is important to protect your environment from being accessed by anonymous Internet users. The safest way to deploy Aerie is with *network-level access controls*, limiting access to eg. only IP addresses on your local network or VPN. You may want to also use an [authentication adapter](/deployment/advanced-sso/) to implement user-level access controls. Do not run Aerie on the public internet without one or both of these controls in place! - -::: - -## Infrastructure details - -Before deployment, consider how you plan to handle these details about your infrastructure: - -* **Domain name** - if you are using a domain name for your Aerie services, make sure you configure the DNS to point to your server's IP address, whether internal or public. This name will also be used in your docker-compose configuration - see below. -* **HTTPS** - we recommend that Aerie deployments, **especially** those on public networks, use HTTPS with a valid TLS certificate. You can use a TLS certificate from an authority like [Let's Encrypt](https://letsencrypt.org/), or a self-signed certificate from your organization for deployments on a private network. -* **Reverse proxy/load balancer** - _if_ you are using HTTPS, you will need to configure an additional service to be the single ingress point for all (HTTPS) network traffic, strip TLS and forward HTTP traffic to the Aerie services. One way to accomplish this is with the **[reverse proxy pattern](/deployment/advanced-reverse-proxy/)**, for which an example is provided. If you are using AWS infrastructure, you may find it easier to manage your certificate with [Amazon Certificate Manager](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html) and route all traffic through an [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) instead of using a reverse proxy. -* **Authentication/SSO** - Aerie **does not include** its own fully-featured implementation of user authentication, and should either be used with an **"authentication adapter,"** or in a private network with limited access. We provide an adapter for use with JPL CAM, a JPL-internal SSO solution - [instructions here](/deployment/advanced-sso/). Users needing to integrate with other SSO/auth systems may need to write their own auth adapters following the same pattern. - - -## Production Deployment Guide - -This checklist outlines a set of steps for running a minimal production Aerie deployment. Note that these will vary somewhat from one environment to another, so feel free to adapt them as needed. - -1. Ensure you have **Docker Engine and Docker Compose installed** on your server. We recommend following the official [Engine install guide](https://docs.docker.com/engine/install/) and [Compose install guide](https://docs.docker.com/compose/install/#scenario-two-install-the-docker-compose-plugin) for your platform, as simply running eg. `yum install docker` may install Podman instead of Docker on some platforms. -2. Ensure your server has the necessary **network ports exposed** for Aerie services - namely, ports **80, 8080, and 9000**, unless you plan to modify these default ports in the docker-compose file. Port rules are usually configured via your server's firewall settings. See [Aerie services & images](/deployment/introduction/#aerie-services--images) for details on services and their port assignments. If you are running on an AWS EC2 instance, you may need to set rules for the instance's *security group* to allow these ports to send & receive TCP traffic. -3. Copy the `Deployment.zip` file from an [Aerie release](https://github.com/NASA-AMMOS/aerie/releases) to your server and extract it, for example: - ``` - curl -sLO https://github.com/NASA-AMMOS/aerie/releases/download/v3.1.1/Deployment.zip - unzip Deployment.zip - tar -xf deployment.tar - ``` -4. Modify the `.env` file to fill in the required variables - see [Environment Variables](/deployment/introduction/#environment-variables). Importantly, all services need usernames and passwords set, and Hasura needs a secret key - see eg. [this completed example](https://github.com/NASA-AMMOS/aerie-mission-model-template/blob/main/.env.template). -5. Modify the [`docker-compose.yml` file](/deployment/introduction/#docker-composeyml) for your specific environment. - - A useful pattern is to **[merge Compose files](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/)** when running Aerie, to keep your custom compose file changes separate from the original file provided by the deployment, rather than modifying the original. You can create a file called eg. `docker-compose.prod.yml` which contains *only* the overriding changes you want to make to the original file. Then, when running your services, you can pass them both to Compose [with the `-f` flag](https://docs.docker.com/compose/how-tos/multiple-compose-files/merge/). - - If you are using [CAM/SSO authentication adapters](/deployment/advanced-authentication/) and/or [a reverse proxy](/deployment/advanced-reverse-proxy/), review their docs to determine the Compose file modifications they require. - - Regardless of your other settings, your compose file needs to provide the `aerie-ui` service with the **fully-qualified domain names** (FQDNs) it will use to make requests to the other services. This is generally done by adding an additional variable to the `.env` file with your base domain, eg.: - ``` - AERIE_HOST="myaerie.myorg.com" - ``` - and then adding the following lines to your `docker-compose.prod.yml` file: - ``` - aerie_ui: - environment: - ORIGIN: https://${AERIE_HOST} - PUBLIC_GATEWAY_CLIENT_URL: https://${AERIE_HOST}:9000 - PUBLIC_HASURA_CLIENT_URL: https://${AERIE_HOST}:8080/v1/graphql - PUBLIC_HASURA_WEB_SOCKET_URL: wss://${AERIE_HOST}:8080/v1/graphql - ``` - If you do not have a proper domain name set up yet, you can use your server's IP address or any other FQDN you have available. -6. Finally, use the `up` command to run all of the Aerie services in their docker containers, eg.: - ``` - docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d - ``` - After a few seconds, you can check on the status of the services by running `docker ps`. It may take a minute or two to fully initialize the system, but eventually all services should show an "Up" status. If not, check your Docker logs for errors from the services (see [Logging](#logging) below). - -## Other Considerations - -### Data persistence and backups -It's a good idea to have a strategy for backing up and restoring your Aerie data in case something goes wrong with your server. Aerie mainly persists data in two ways: -* The Postgres database, managed by the `aerie-postgres` container, stores the majority of user-created data such as plans and simulation runs. -* Some data is also stored in the filesystem of the Aerie container, such as uploaded mission model JAR files. - -Both types of data are persisted using [Docker Volumes](https://docs.docker.com/engine/storage/volumes/). One way to handle backups is simply to copy all data out of the Aerie volumes (to another location, off of your main instance) on a eg. nightly basis. - -It may be useful sometimes to just backup the state of your Postgres database using a utility like `pg_dump` eg. before performing complex database operations - just remember this does not backup the files in the filesystem and therefore is only useful for recovering from database issues. - -### Upgrading your Aerie Environment - -Aerie releases new versions roughly every two weeks, and eventually you may want to upgrade your environment to a new version. If you want to preserve your environment's data from the previous version, you should take care to upgrade and migrate your data forward in a safe way: -* Carefully read the [changelogs on the Releases page](https://github.com/NASA-AMMOS/aerie/releases) and the [upgrade guides](https://nasa-ammos.github.io/aerie-docs/upgrade-guides/3-1-1-to-3-2-0/) for all versions between your old version and the one you're upgrading to, and keep note of any breaking changes. -* Perform a backup of your database and/or Docker volumes before upgrading. -* To perform the upgrade: - - Stop your docker containers with `docker compose down` - - Update the `DOCKER_TAG` environment variable to the new desired Aerie version - - If necessary, modify your docker-compose or any other deployment options to deal with breaking changes - - Bring your docker containers back up with `docker compose up` - - Follow the instructions on the [Database Migrations page](/deployment/advanced-database-migrations/) to run the migration script, which will automatically migrate your data to be compatible with the new version. - -### Logging - -By default, Docker saves logs on the local filesystem for all of your Aerie services, and [displays them with the command `docker logs `](https://docs.docker.com/reference/cli/docker/container/logs/). However, these logs are somewhat ephemeral and may be overwritten in time. If you care about retroactively investigating and debugging issues encountered by your users, it's a good idea to have a log rotation strategy and to save your logs in a more permanent archive, outside of your environment. diff --git a/docs/deployment/upgrade-process.md b/docs/deployment/upgrade-process.md deleted file mode 100644 index 982615f6..00000000 --- a/docs/deployment/upgrade-process.md +++ /dev/null @@ -1,31 +0,0 @@ -# Upgrade Process - -This document describes how to upgrade your Aerie deployment when a new version is released. New releases are released periodically and can be found on our [Github Releases page](https://github.com/NASA-AMMOS/aerie/releases). We recommend staying up to date with new releases as much as is practical. - - -## Backups - -It's a good idea to keep regular backups of your Aerie instance, and to make a backup immediately before upgrading, so you have a restore point if anything goes wrong. See our notes about [backing up Aerie on the Production Deployment guide](/aerie-docs/deployment/production-deployment/#data-persistence-and-backups). - -## Upgrade Guide - -Before upgrading, determine the version you are upgrading *from* and *to*. Go to the [Upgrade Guides](/upgrade-guides/3-2-0-to-3-3-0/) section of the docs, and read through every guide between your old version and the new version you're updating to. These will describe any breaking changes that occurred between versions, and will outline any changes you need to make to your configuration. Look out for guides which are noted as **"Upgrade Checkpoint"** versions (e.g. [v2.8.0](/upgrade-guides/2-7-1-to-2-8-0/)) - if you encounter one, you will first need to complete an upgrade to that version before continuing to your desired version (or the next checkpoint). - -Once you're prepared, follow these instructions to upgrade: - -1. Find your desired release on our [Github Releases page](https://github.com/NASA-AMMOS/aerie/releases). Go to the **Assets** section, under the release notes, and download the attached **Deployment.zip** file. -2. Extract the file to a location on the server where you're deploying Aerie (or on your local machine for a local deployment). -3. Compare the contents of the new deployment directory to your existing deployment directory, and copy to the new directory any custom changes you have made to your deployment - for example, changes made while following our [Production Deployment Guide](/aerie-docs/deployment/production-deployment/#production-deployment-guide), any customizations to your `docker-compose` file, and the environment variables in `.env`. If you made a `docker-compose.prod.yml` file for your deployment, this should be copied as well. -4. Apply any necessary changes to your code or configuration that were outlined in the upgrade guides you read, to account for any breaking changes in the Aerie API since the last version. -5. Open the `.env` file in `deployment` and make sure you set the variable `DOCKER_TAG` to the new Aerie version you are deploying - eg. `DOCKER_TAG=v3.3.0` -6. Bring down your old Aerie containers, if they are still running, with the command: `docker compose down`. - - If this is a test deployment with disposable data that you don't care about preserving, you can run `docker compose down -v` and skip the migration step below. -7. Bring the new containers up with whatever `docker compose up` command you usually use, eg.: -``` -docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d -``` -8. If you are preserving your data, read through the [Database Migrations](advanced-database-migrations.mdx) guide and run the command to migrate your database forward to the new version, passing any flags as needed, eg: -``` -aerie_db_migration.py migrate -a --all -``` - diff --git a/docs/glossary.md b/docs/glossary.md deleted file mode 100644 index 68dd73f6..00000000 --- a/docs/glossary.md +++ /dev/null @@ -1,123 +0,0 @@ -# Glossary - -This is an alphabetized glossary of common term definitions used throughout the Aerie ecosystem and related domains. - -## A - -1. **Activity Directive** - A record in an activity plan that describes start time and arguments for an activity type. An activity directive is an input for simulation, that instantiates an object with the specified arguments and executes at the specified time. - -1. **Activity Plan (Plan)** - A collection of activity directives within a time span bounded with plan start and plan end time. - -1. **Activity Type** - A Java class in the mission model that is the archetype of a unit operation an activity simulation. - Activity types are units for modeling and planning for ground operations, and are not recognized by the flight system. An activity type in Aerie describes: - - - Parameters that define the range of execution for each instance of the activity - - Computed attributes that define values that will be computed - - An effect model that can query or modify modeled resources - - A start time and arguments for children activities that are called or spawned during the execution of the activity. - Note that the latter is referred to as _decomposition_ in our user interface and docs. - -1. **Argument** - A value given to an activity or simulation configuration parameter. - -## C - -1. **Call** - A function in the [Merlin simulation modeling API](../mission-modeling/activity-types/effect-model/). `Call()` executes a provided task and blocks until that task has completed. Typically `Call()` is used by a mission modeler who wants to execute an activity and wait for its completion before continuing execution of their modeling code. `Call()` has a few function signatures so that it can be used to call an activity type and arguments or a Java lambda/Runnable. - -1. **Cell** - Allows a mission model to express time-dependent state in a way that can be tracked and managed by the host system. - -1. **Command Expansion** - A mechanism to generate commands per simulated activity. - -1. **Command Expansion Logic** - A logic snippet that applies to all simulated instances of an activity type, returning a collection of commands. Generally it maps activity arguments to command arguments. Potentially can return different commands for different instances. - -1. **(Command) Expansion Set** - A collection of expansion logic which will be input for an expansion request. An expansion set can contain only one expansion logic per activity type. Expansion set can have logic for all activity types or for a subset. - -1. **Common Access Manager (CAM)** - A [NASA AMMOS](https://ammos.nasa.gov/) utility which provides application layer access control capabilities, including single sign-on (SSO), federation, authorization management, authorization checking & enforcement, identity data retrieval, and associated logging. - -1. **Constraint Checking** - A downstream analysis of a simulation dataset that evaluates a set of constraints and returns time windows of violation. - -1. **Constraint** - An expression built up with the [Aerie constraints](../scheduling-and-constraints/declarative/constraints/introduction) eDSL, which evaluates to a set of windows during which the condition(s) defined by the expression is true or false. Aerie supports various constraint types such as safe ranges for resources or necessary pre-conditions for safe execution of activities. Aerie constraints is a mechanism to represent and check many flight rule types. - -## D - -1. **Decomposition** - A method for modeling the behavior of an activity (root activity) by composing a set of activities. Each composed activity describes some smaller aspect of behavior. The root activity orchestrates the execution of each child activity by interleaving function calls to `spawn()`, `delay()`, and `call()`. The goal of decomposition is to allow mission modelers to modularize their activity modeling code and to provide greater visibility into the simulated behavior of an activity. For example, consider an activity which models taking an observation with a particular spacecraft instrument. The process of taking an observation may include the distinct phases of instrument startup, take observation, and instrument shutdown. The mission modeler can decide to modularize their modeling and use decomposition to model each of the three phases separately and then compose them with a root activity called "observation" which orchestrates the execution of each of the three activities. - -1. **Delay** - A function in the [Merlin simulation modeling API](../mission-modeling/activity-types/effect-model/). A modeler can delay (pause) an activity’s execution during simulation effectively modeling some passage of simulated time, before resuming further modeling. - -1. **Deployment** - We refer to a particular configuration of the Aerie system as an "Aerie Deployment", in the context of a broader [ground data system (GDS)](https://www.nasa.gov/smallsat-institute/sst-soa/ground-data-systems-and-mission-operations) deployment. -1. [Docker](https://www.docker.com/) - A set of platform as a service (PaaS) products that use OS-level virtualization to deliver software in packages called containers. - -## G - -1. [GraphQL](https://graphql.org/) - An open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. - -## H - -1. [Hasura](https://hasura.io/) - A GraphQL schema compiler and GraphQL API server. As a compiler, Hasura parses a PostgresDB schema and generates a GraphQL schema defining a data graph of Queries, Mutation, and Subscriptions. Aerie utilizes a Hasura component to expose select database tables as the Aerie GraphQL API. - -## I - -1. **Initial Conditions (incons)** - The initial value of resources before a simulation. - -## J - -1. [JUnit](https://junit.org/junit5/) - A unit testing framework for the Java programming language. - -## M - -1. **Mission Model** - The modeling code written in Java, packaged as a JAR that describes activity types and resource behavior models. Mission model is a required input for activity planning, simulation and scheduling performed by Aerie. - -## P - -1. **Parameter** - A named variable (and data type) in a function description or Activity definition, utilized within the function/Activity definition. - -1. **Planner** - A person responsible for deciding how to concretely achieve a set of mission objectives over the course of a span of time, formalized as a plan, with the expectation that this plan will be executed by other mission elements, including (and especially) by a spacecraft’s onboard system. The planner’s concern is with achieving those objectives while balancing reward against risk. Planners iteratively perform scheduling and simulation in a feedback loop to refine their plans. Automated schedulers may themselves utilize simulation artifacts to inform their decisions. - -1. [PostgreSQL](https://www.postgresql.org/) - Also known as Postgres. A free and open-source relational database management system emphasizing extensibility and SQL compliance. - -1. **Profile** - The time-dependent behavior of a resource. - -## R - -1. **Register** - A [model of a resource](../mission-modeling/resources-and-models/) which can have its value set and this value remains until it is set with a different value. The term register here is used to clearly indicate the semantics of this resource; the semantics of setting a memory register. - -1. **Resource** - Expresses the time-dependent evolution of quantities of interest to the mission. - -1. **Resource Types** - The schema describing the structure of data in a resource. - -## S - -1. [SPICE](https://naif.jpl.nasa.gov/naif/toolkit.html) - NASA's Navigation and Ancillary Information Facility (NAIF) offers NASA flight projects and NASA funded researchers the “SPICE” observation geometry information system to assist scientists in planning and interpreting scientific observations from space-based instruments aboard robotic planetary spacecraft. In the Merlin context SPICE is most commonly incorporated to a mission model as a Java library. The library is configured with data files called “kernels” and then a mission model will query the library for an assortment of mission specific geometric data. - -1. **Scheduling** - A scheduling mechanism that automatically creates activity directives in a plan by serially executing scheduling goals given in a priority order. - -1. **Scheduling Goal** - A rule snippet describing conditions for windows to add an activity directive to the plan along with the desired arguments for the directive. - -1. **Scheduling Specification** - A set of scheduling goals in priority order input for a scheduling run. - -1. **Sequence** - A data record in seqJSON schema that contains commands, meta data and other inputs for downstream command simulation tools. - -1. **Simulated Activity (Span)** - A record for an activity instance created and executed during a simulation run. It lists start time, end time, all arguments that are used during the simulation as well as the values for computed attributes. - -1. **Simulation** - Simulation is the act of predicting the usage and behavior of mission resources, over time, under the influence of planned activities. Put differently, simulation is an analysis of the effects of activities in a plan upon modeled resources. - -1. **Simulation Configuration** - A Java class in the mission model that allows planners to tweak parameters for a simulation run and input source files necessary to use during this initialization. While simulation configuration is a highly flexible mechanism the main use cases for it are: - - - Setting initial values for resources - - Tweaking modeling parameters such as computation fidelity - -1. **Simulation Dataset (Results)** - A data collection that stores the results of a simulation run including: - - - Start time, end time and arguments of all activities that are simulated. Includes both root activities that correspond to directives and activities that are result of a decomposition. - - Profiles (values over time) for all modeled resources. - -1. **State** - The value of a resource at an instant in time (e.g. "The state of the radioMode resource is ON at time T1"). - -## T - -1. **Task** - Allows a mission model to describe time-dependent processes that affect mission state. - -## W - -1. **Window** - The output of constraints checking. A boolean-type profile describing at what times a given constraint is violated. True means the state is nominal; false means the state is a violation. - -1. **Worker** - Aerie provides a multi-tenancy capability so that many users can run simulations concurrently. Simulation multi-tenancy is achieved by configuring Aerie to launch multiple simulation worker containers. Each simulation worker can execute a sand-boxed simulation run. diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index 43550c77..00000000 --- a/docs/introduction.md +++ /dev/null @@ -1,49 +0,0 @@ -# Introduction - -Aerie is an open source, extensible software system for planning, scheduling, and commanding space missions. Developed and maintained by NASA's [Advanced Multi-Mission Operation System (AMMOS)](https://ammos.nasa.gov/), it provides modeling and simulation capabilities that can be used for mission planning and analysis during project formulation all the way through operations, where it can be used to manage and validate spacecraft activity plans. Aerie is actively being used on flagship missions like [Europa Clipper](https://europa.nasa.gov/), but is equally suitable for smaller missions and constellations. - -Some of the main features of Aerie include: - -- A library to write mission models in the Java programming language -- A highly performant discrete-event simulator -- An embedded domain specific language (EDSL) for defining and executing goal-based scheduling rules -- An EDSL for defining and executing activity and resource constraints -- An EDSL for defining and executing activity command expansions -- An EDSL for defining sequences, and a fully-featured browser-based sequence editor -- A GraphQL API so you can easily build tools on top of Aerie -- A web-based [client application](https://github.com/NASA-AMMOS/aerie-ui) - -As a multi-tenant system, Aerie allows multiple distributed users to [collaborate](https://nasa-ammos.github.io/aerie-docs/planning/collaboration/introduction/) in real-time on a single plan or concurrently work on multiple plans for multiple missions. Additionally, Aerie's [service based architecture](https://nasa-ammos.github.io/aerie-docs/overview/software-design-document/#aerie-system-design) allows for efficient system deployment and scalability on the cloud. - -## Fast Track ⏱️ {#fast-track} - -Understand Aerie in **5 minutes** by trying it out! - -1. Before starting you first need to install [Docker](https://www.docker.com/get-started/) on your local machine. The Aerie system is essentially a collection of [OCI](https://opencontainers.org/) [images](https://github.com/orgs/NASA-AMMOS/packages?ecosystem=container&q=aerie). - -1. If you’re running macOS, Linux, or another Unix-like OS you can use following two commands in your terminal to download the [Docker Compose](https://docs.docker.com/compose/) file and `.env` file: - - ```sh - curl https://raw.githubusercontent.com/NASA-AMMOS/aerie-mission-model-template/main/docker-compose.yml --output docker-compose.yml - curl https://raw.githubusercontent.com/NASA-AMMOS/aerie-mission-model-template/refs/heads/main/.env.template --output .env - ``` - - If you're running a different OS and do not have [curl](https://curl.se/) available you can [download the docker-compose.yml here](https://raw.githubusercontent.com/NASA-AMMOS/aerie-mission-model-template/main/docker-compose.yml) and [the .env file here](https://raw.githubusercontent.com/NASA-AMMOS/aerie-mission-model-template/refs/heads/main/.env.template) (rename to `.env`). - - Note that this compose file starts the [latest](https://github.com/NASA-AMMOS/aerie/releases/latest) version of Aerie. The Aerie version can be specified by changing the `DOCKER_TAG` variable in the `.env` file to any valid Aerie release. - -1. To start the Aerie services you can use the following command in the same directory as the `docker-compose.yml` file from the previous step: - - ```sh - docker compose up - ``` - -1. Visit [http://localhost/](http://localhost/) to view the [Aerie UI](https://github.com/NASA-AMMOS/aerie-ui). - - Note that the fast track deployment is not configured to connect to an authentication and authorization provider, therefore any credentials are accepted. - -1. Head over to the planning documentation to learn how to [upload a mission model](../planning/upload-mission-model). - -## Cleanup - -Aerie is an actively developed application suite with interdepenencies between the server, the web ui, and the database schema. New capabilities may require ensuring that all parts of the application are on the same version. Breaking changes are stated in the release notes. If the above fast track instructions worked previously, but are now seeing an inconsistent schema consult the [developer docs](https://github.com/NASA-AMMOS/aerie/blob/develop/docs/DEVELOPER.md) for instructions on clearing container images. For long running hosts see [Production Deployment](https://nasa-ammos.github.io/aerie-docs/deployment/production-deployment/) for an overview of the data migration process. diff --git a/docs/java-docs/introduction.md b/docs/java-docs/introduction.md deleted file mode 100644 index a5697a00..00000000 --- a/docs/java-docs/introduction.md +++ /dev/null @@ -1,3 +0,0 @@ -# Java Docs - -This section contains external links to our generated Java documentation. Some of these components are public, and some are private. Please see our list of available public Java packages [here](https://github.com/orgs/NASA-AMMOS/packages?ecosystem=maven&q=aerie). diff --git a/docs/keyboard-shortcuts.mdx b/docs/keyboard-shortcuts.mdx deleted file mode 100644 index e7652f23..00000000 --- a/docs/keyboard-shortcuts.mdx +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Keyboard Shortcuts ---- - -export const Shortcut = ({ children }) => ( - - {children} - -); - -A list of keyboard shortcuts that can be used within the Aerie UI. - -### Plan View - -Start running simulation from anywhere in the plan view with + s (MacOS) or ctrl + s (Windows). - -**Timeline** - -The easiest way to navigate the timeline is by holding (MacOS) or ctrl (Windows) and then using click+drag to pan and scroll to zoom in and out. - -- middle mouse button + drag pans the timeline horizontally -- = zooms timeline in -- - zooms timeline out -- [ nudges timeline left -- ] nudges timeline right -- ctrl + T toggles the cursor visibility -- 0 resets the timeline zoom -- delete or backspace deletes selected activity directive -- shift when held shows additional time formats within the timeline tooltip - -
- -**Activity Table** - -- delete or backspace delete selected row from tables you have permissions to delete - -
- -**Activity Parameters** - -- + left click (MacOS) or ctrl + - left click - (Windows) resets paramater value to default - -
- -**Activity Error Rollup** - -- + left click (MacOS) or ctrl + - left click - (Windows) resets invalid parameters if possible - -
- -### Expansion Rule Form - -- + s (MacOS) or ctrl + s (Windows) - saves the form - -
- -### Sequence Editor - -- + s (MacOS) or ctrl + s (Windows) - saves the sequence diff --git a/docs/mission-modeling/activity-mappers.mdx b/docs/mission-modeling/activity-mappers.mdx deleted file mode 100644 index a4ca5a27..00000000 --- a/docs/mission-modeling/activity-mappers.mdx +++ /dev/null @@ -1,469 +0,0 @@ -import RemoteCodeBlock from '@site/src/components/RemoteCodeBlock'; - -# Activity Mappers - -An Activity Mapper is a Java class that implements the `ActivityMapper` interface for the `ActivityType` being mapped. -It is required that each Activity Type in an mission model have an associated Activity Mapper to provide several capabilities surrounding serialization/deserialization of activity instances. - -The default annotation processor can automatically be generated for every activity type, even for those with custom-typed parameters. -This document explains how to create a custom activity mapper interface if it is needed. - -## ActivityMapper Interface - -The `ActivityMapper` interface is shown below: - - - -The first thing to notice is that the interface takes a type parameter (here called `Instance`). When implementing the `ActivityMapper` interface, an activity mapper must supply the `ActivityType` being mapped. With that in mind, each of the methods shown must be implemented as such: - -- `getName()` returns the name of the activity type being mapped -- `getParameters()` provides the named parameter fields of the activity along with their corresponding `ValueSchema`, that describes their structure -- `getArguments(Instance activity)` provides the actual values for each parameter from a provided activity instance -- `instantiateDefault()` creates a default instance of the activity type without any values provided externally -- `instantiate(Map arguments)` constructs an instance of the activity type from a the provided arguments, if possible -- `getValidationFailures(Instance activity)` provides a list of reasons a constructed activity is invalid, if any. - Note that validation failures are different from instantiation errors. Validation failures occur when a constructed activity instance's parameters are outside acceptable range. - -The `getParameters()` method returns a `Map`. In this map should be a key for every parameter, with a `ValueSchema` describing the structure of that parameter. See our [Value Schema documentation](https://github.com/NASA-AMMOS/aerie/wiki/Value-Schemas#value-schemas-from-code) for more information on creating value schemas. - -## Generated Activity Mappers - -In most cases, you will likely want to let Merlin generate activity mappers for you. Thankfully, this is the done automatically when running the Merlin Annotation Processor. When compiling your code with the Merlin annotation processor, the processor will produce an activity mapper for each activity type. This is made possible by the use of the `@WithMappers()` annotations in your [package-info.java](https://github.com/NASA-AMMOS/aerie/wiki/Developing-a-Mission-Model#package-infojava). Each java-file specified by these annotations is parsed to determine what types of values can be mapped. As long as there is a mapper for each activity parameter type used in the model, the annotation processor should have no issues creating activity mappers. - -## Value Mappers - -Regardless of whether you create custom activity mappers or let Merlin -generate them for you, you will likely find the need to work with a -`ValueMapper` at some point. In fact, generating activity mappers is made -quite simple by considering the fact that an activity instance is wholly -defined by its parameter values. - -You may find yourself asking "Just what _is_ a value mapper?" A value mapper -is a small, focused class whose sole responsibility is to tell Merlin how to -handle a specific type of value. Value mappers allow all sorts of capabilities -from custom-typed activity parameters to custom-typed resources. - -One of the most convenient things about using value mappers is the fact that -Merlin comes with them already defined for all basic types. -Furthermore, value mappers for combinations of types can easily -be created by passing one `ValueMapper` into another during instantiation. - -Although we provide value mappers for basic types, it is -entirely acceptable to create custom value mappers for other types, -such as those imported from external libraries. -This can be done by writing a Java class which implements the -`ValueMapper` interface. Below is a value mapper for an apache `Vector3D` type as an example: - -```java -public class Vector3DValueMapper implements ValueMapper { - - @Override - public ValueSchema getValueSchema() { - return ValueSchema.ofSequence(ValueSchema.REAL); - } - - @Override - public Result deserializeValue(final SerializedValue serializedValue) { - return serializedValue - .asList() - .map(Result::, String>success) - .orElseGet(() -> Result.failure("Expected list, got " + serializedValue.toString())) - .match( - serializedElements -> { - if (serializedElements.size() != 3) return Result.failure("Expected 3 components, got " + serializedElements.size()); - final var components = new double[3]; - final var mapper = new DoubleValueMapper(); - for (int i=0; i<3; i++) { - final var result = mapper.deserializeValue(serializedElements.get(i)); - if (result.getKind() == Result.Kind.Failure) return result.mapSuccess(_left -> null); - - // SAFETY: `result` must be a Success variant. - components[i] = result.getSuccessOrThrow(); - } - return Result.success(new Vector3D(components)); - }, - Result::failure - ); - } - - @Override - public SerializedValue serializeValue(final Vector3D value) { - return SerializedValue.of( - List.of( - SerializedValue.of(value.getX()), - SerializedValue.of(value.getY()), - SerializedValue.of(value.getZ()) - ) - ); - } -} -``` - -Notice there are just 3 methods to implement for a `ValueMapper`. -The first is `getValueSchema()`, which should return a `ValueSchema` -describing the structure of the value being mapped (see [here](https://github.com/NASA-AMMOS/aerie/wiki/Value-Schemas) for more info) - -The next two methods are inverses of each other: `deserializeValue()` and `serializeValue()`. It is the job of `deserializeValue()` to take a `SerializedValue` and map it, if possible, into the mapper's supported value. Meanwhile, `serializeValue()` takes an instance of the mapper's supported value and turns it into a [`SerializedValue`](https://github.com/NASA-AMMOS/aerie/wiki/Activity-Mappers#what-is-a-serializedvalue). - -There are plenty of examples of value mappers over in the [contrib module](https://github.com/NASA-AMMOS/aerie/tree/develop/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/mappers). - -### AutoValueMapper.Record - -The merlin framework provides a straightforward way to generate a value mapper for Java records. During annotation processing, the merlin framework will look for any record classes annotated with `@AutoValueMapper.Record`, and generate a value mapper for them. - -```java -@AutoValueMapper.Record -record MyCustomDataType(Foo foo, Bar bar, Baz baz) {} -``` - -If merlin has a registered value mapper for Foo, Bar, and Baz, it will be able to use the generated value mapper to serialize and deserialize MyCustomDataType. - -### Registering Value Mappers - -As mentioned above, the `@WithMappers()` annotation is used to register value mappers for a mission model. -Value mappers are expected to be defined with static constructor methods within classes listed in `@WithMappers()` annotations. -For example, if `package-info.java` contains: - -```java -@WithMappers(BananaValueMappers.class) -``` - -Then the value mapper may define a custom `Configuration` value mapper with: - -```java -public final class BananaValueMappers { - public static ValueMapper configuration() { - return new ConfigurationValueMapper(); - } -} -``` - -Value mappers may be created for types that use parameterized types, but the parameterized types themselves must be either unbounded bounded or `Enum<>`. -For example: - -```java -@Parameter -public List test; -``` - -or - -```java -@Parameter -public List> test; -``` - -are not trivially resolved to a single value mapper due to the type constraints at play here. - -## What is a `SerializedValue` - -When working with a `ValueMapper` it is inevitable that you will come across the `SerializedValue` type. This is the type we use for serializing all values that need serialization, such as activity parameters and resource values. In crafting a value mapper, you will have to both create a `SerializedValue` and parse one. - -Constructing a `SerializedValue` tends to be more straightforward, because there are no questions about the structure of the value you are starting with. For basic types, you need only call `SerializedValue.of(value)` -and the `SerializedValue` class will handle the rest. This can be done for values of the following types: `long`, `double`, `String`, `boolean`. Note that integers and floats can be represented by `long` and `double` respectively. For more complex types, you can also provide a `List` or `Map` to `SerializedValue.of()`. It is clear that these can be used to serialize lists and maps themselves, but arbitrarily complex structures can be serialized in this way. Consider the following examples: - -```java -int exInt = 5; -SerializedValue serializedInt = SerializedValue.of(exInt); - -List exList = List.of("a", "b", "c") -SerializedValue serializedList = SerializedValue.of( - List.of( - SerializedValue.of(exList.get(0)), - SerializedValue.of(exList.get(1)), - SerializedValue.of(exList.get(2)) - ) -); - -Map exMap = Map.of( - "key1", true, - "key2", false, - "key3", true -); -SerializedValue serializedMap = SerializedValue.of( - Map.of( - "key1", SerializedValue.of(exMap.get("key1")), - "key2", SerializedValue.of(exMap.get("key2")), - "key3", SerializedValue.of(exMap.get("key3")) - ) -); - -Vector3D exampleVec = new Vector3D(0,0,0); - -SerializedValue serializedVec1 = SerializedValue.of( - List.of( - SerializedValue.of(exampleVec.getX()), - SerializedValue.of(exampleVec.getY()), - SerializedValue.of(exampleVec.getZ()) - ) -); - -SerializedValue serializedVec2 = SerializedValue.of( - Map.of( - "x", SerializedValue.of(exampleVec.getX()), - "y", SerializedValue.of(exampleVec.getY()), - "z", SerializedValue.of(exampleVec.getZ()) - ) -); -``` - -The first 3 examples here are straightforward mappings from their java type to their serialized form, however the vector example is more interesting. To highlight this, two forms of `SerializedValue` have been given for it. In the first case, we serialize the `Vector3D` as a list of three values. This will work fine as long as whoever deserializes it knows that the list contains each component in order of x, y and z. In the second example, however, the vector is serialized as a map. Either of these representations may fit better in different scenarios. Generally, the structure of a `SerializedValue` constructed by a `ValueMapper` should match the `ValueSchema` the `ValueMapper` provides. - -## Example Activity Mapper - -Below is an example of an Activity Type and its Activity mapper for reference: - -**Activity Type**: - -```java -package gov.nasa.jpl.aerie.foomissionmodel.activities; - -import gov.nasa.jpl.aerie.foomissionmodel.Mission; -import gov.nasa.jpl.aerie.foomissionmodel.models.ImagerMode; -import gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType; -import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; - -import java.util.List; - -import static gov.nasa.jpl.aerie.foomissionmodel.generated.ActivityActions.call; -import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.*; -import static gov.nasa.jpl.aerie.merlin.framework.annotations.ActivityType.EffectModel; -import static gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Parameter; -import static gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Validation; -import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.SECOND; - -@ActivityType("foo") -public final class FooActivity { - @Parameter - public int x = 0; - - @Parameter - public String y = "test"; - - @Parameter - public Integer z; // No default value specified, therefore this parameter is required - - @Parameter - public List vecs = List.of(new Vector3D(0.0, 0.0, 0.0)); - - @Validation("x cannot be exactly 99") - @Validation.Subject("x") - public boolean validateX() { - return (x != 99); - } - - @Validation("y cannot be 'bad'") - @Validation.Subject("y") - public boolean validateY() { - return !y.equals("bad"); - } - - @EffectModel - public void run(final Mission mission) { - ... - } -} -``` - -**Generated Activity Mapper Example**: - -```java -package gov.nasa.jpl.aerie.foomissionmodel.generated.activities; - -import gov.nasa.jpl.aerie.contrib.serialization.mappers.NullableValueMapper; -import gov.nasa.jpl.aerie.contrib.serialization.rulesets.BasicValueMappers; -import gov.nasa.jpl.aerie.foomissionmodel.Mission; -import gov.nasa.jpl.aerie.foomissionmodel.activities.FooActivity; -import gov.nasa.jpl.aerie.foomissionmodel.mappers.FooValueMappers; -import gov.nasa.jpl.aerie.merlin.framework.ActivityMapper; -import gov.nasa.jpl.aerie.merlin.framework.ModelActions; -import gov.nasa.jpl.aerie.merlin.framework.ValueMapper; -import gov.nasa.jpl.aerie.merlin.protocol.driver.Topic; -import gov.nasa.jpl.aerie.merlin.protocol.model.InputType; -import gov.nasa.jpl.aerie.merlin.protocol.model.OutputType; -import gov.nasa.jpl.aerie.merlin.protocol.model.TaskFactory; -import gov.nasa.jpl.aerie.merlin.protocol.types.InstantiationException; -import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue; -import gov.nasa.jpl.aerie.merlin.protocol.types.UnconstructableArgumentException; -import gov.nasa.jpl.aerie.merlin.protocol.types.Unit; -import gov.nasa.jpl.aerie.merlin.protocol.types.ValueSchema; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import javax.annotation.processing.Generated; -import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; - -@Generated("gov.nasa.jpl.aerie.merlin.processor.MissionModelProcessor") -public final class FooActivityMapper implements ActivityMapper { - private final Topic inputTopic = new Topic<>(); - - private final Topic outputTopic = new Topic<>(); - - @Override - public InputType getInputType() { - return new InputMapper(); - } - - @Override - public OutputType getOutputType() { - return new OutputMapper(); - } - - @Override - public Topic getInputTopic() { - return this.inputTopic; - } - - @Override - public Topic getOutputTopic() { - return this.outputTopic; - } - - @Override - public TaskFactory getTaskFactory(final Mission model, final FooActivity activity) { - return ModelActions.threaded(() -> { - ModelActions.emit(activity, this.inputTopic); - activity.run(model); - ModelActions.emit(Unit.UNIT, this.outputTopic); - return Unit.UNIT; - }); - } - - @Generated("gov.nasa.jpl.aerie.merlin.processor.MissionModelProcessor") - public final class InputMapper implements InputType { - private final ValueMapper mapper_x; - - private final ValueMapper mapper_y; - - private final ValueMapper mapper_z; - - private final ValueMapper> mapper_vecs; - - @SuppressWarnings("unchecked") - public InputMapper() { - this.mapper_x = - BasicValueMappers.$int(); - this.mapper_y = - new NullableValueMapper<>( - BasicValueMappers.string()); - this.mapper_z = - new NullableValueMapper<>( - BasicValueMappers.$int()); - this.mapper_vecs = - new NullableValueMapper<>( - BasicValueMappers.list( - FooValueMappers.vector3d( - BasicValueMappers.$double()))); - } - - @Override - public List getRequiredParameters() { - return List.of(); - } - - @Override - public ArrayList getParameters() { - final var parameters = new ArrayList(); - parameters.add(new InputType.Parameter("x", this.mapper_x.getValueSchema())); - parameters.add(new InputType.Parameter("y", this.mapper_y.getValueSchema())); - parameters.add(new InputType.Parameter("z", this.mapper_z.getValueSchema())); - parameters.add(new InputType.Parameter("vecs", this.mapper_vecs.getValueSchema())); - return parameters; - } - - @Override - public Map getArguments(final FooActivity input) { - final var arguments = new HashMap(); - arguments.put("x", this.mapper_x.serializeValue(input.x)); - arguments.put("y", this.mapper_y.serializeValue(input.y)); - arguments.put("z", this.mapper_z.serializeValue(input.z)); - arguments.put("vecs", this.mapper_vecs.serializeValue(input.vecs)); - return arguments; - } - - @Override - public FooActivity instantiate(final Map arguments) throws - InstantiationException { - final var template = new FooActivity(); - Optional x = Optional.ofNullable(template.x); - Optional y = Optional.ofNullable(template.y); - Optional z = Optional.ofNullable(template.z); - Optional> vecs = Optional.ofNullable(template.vecs); - - final var instantiationExBuilder = new InstantiationException.Builder("foo"); - - for (final var entry : arguments.entrySet()) { - try { - switch (entry.getKey()) { - case "x": - x = Optional.ofNullable(template.x = this.mapper_x.deserializeValue(entry.getValue()) - .getSuccessOrThrow(failure -> new UnconstructableArgumentException("x", failure))); - break; - case "y": - y = Optional.ofNullable(template.y = this.mapper_y.deserializeValue(entry.getValue()) - .getSuccessOrThrow(failure -> new UnconstructableArgumentException("y", failure))); - break; - case "z": - z = Optional.ofNullable(template.z = this.mapper_z.deserializeValue(entry.getValue()) - .getSuccessOrThrow(failure -> new UnconstructableArgumentException("z", failure))); - break; - case "vecs": - vecs = Optional.ofNullable(template.vecs = this.mapper_vecs.deserializeValue(entry.getValue()) - .getSuccessOrThrow(failure -> new UnconstructableArgumentException("vecs", failure))); - break; - default: - instantiationExBuilder.withExtraneousArgument(entry.getKey()); - } - } catch (final UnconstructableArgumentException e) { - instantiationExBuilder.withUnconstructableArgument(e.parameterName, e.failure); - } - } - - x.ifPresentOrElse( - value -> instantiationExBuilder.withValidArgument("x", this.mapper_x.serializeValue(value)), - () -> instantiationExBuilder.withMissingArgument("x", this.mapper_x.getValueSchema())); - y.ifPresentOrElse( - value -> instantiationExBuilder.withValidArgument("y", this.mapper_y.serializeValue(value)), - () -> instantiationExBuilder.withMissingArgument("y", this.mapper_y.getValueSchema())); - z.ifPresentOrElse( - value -> instantiationExBuilder.withValidArgument("z", this.mapper_z.serializeValue(value)), - () -> instantiationExBuilder.withMissingArgument("z", this.mapper_z.getValueSchema())); - vecs.ifPresentOrElse( - value -> instantiationExBuilder.withValidArgument("vecs", this.mapper_vecs.serializeValue(value)), - () -> instantiationExBuilder.withMissingArgument("vecs", this.mapper_vecs.getValueSchema())); - - instantiationExBuilder.throwIfAny(); - return template; - } - - @Override - public List getValidationFailures(final FooActivity input) { - final var notices = new ArrayList(); - if (!input.validateX()) notices.add(new InputType.ValidationNotice(List.of("x"), "x cannot be exactly 99")); - if (!input.validateY()) notices.add(new InputType.ValidationNotice(List.of("y"), "y cannot be 'bad'")); - return notices; - } - } - - public static final class OutputMapper implements OutputType { - private final ValueMapper computedAttributesValueMapper = BasicValueMappers.$unit(); - - @Override - public ValueSchema getSchema() { - return this.computedAttributesValueMapper.getValueSchema(); - } - - @Override - public SerializedValue serialize(final Unit returnValue) { - return this.computedAttributesValueMapper.serializeValue(returnValue); - } - } -} -``` diff --git a/docs/mission-modeling/activity-types/durations.md b/docs/mission-modeling/activity-types/durations.md deleted file mode 100644 index bf3fa4fa..00000000 --- a/docs/mission-modeling/activity-types/durations.md +++ /dev/null @@ -1,157 +0,0 @@ -# Duration Types - -The [Aerie scheduler](../../scheduling-and-constraints/declarative/scheduling/introduction.mdx) places activities in a plan to try to achieve scheduling goals, and to do that it must know the activity's duration. Since an activity's effect model is a black box, without more information the scheduler has to simulate the activity to see how long it lasts. This can be very expensive and best to avoid if possible. Satisfying temporal constraints associated with the scheduling of an activity (e.g. "activity A must end before activity B") may lead to multiple simulations to compute duration, and thus require even more computation time. - -However, it is possible to provide information about how the duration of an activity is determined to help the scheduler. The duration of an activity can be one of the following: - -1. `Controllable` if it is equal to one of the activity parameters. -2. `Parametric` if it is completely determined by its parameters. - - Controllable durations are a special case of Parametric. Since you can only give the activity one duration type, it is better to be more specific and use Controllable when it applies. -3. `Fixed` if it always has the same duration for all simulation states and parameters. -4. `Uncontrollable` otherwise, meaning the duration depends on simulation state and cannot be known without simulating. - - This option will incur simulations. For some models this might be prohibitively slow, so it is best to use any of the above options if possible; or in the worst case scenario, try to avoid scheduling uncontrollable duration activities. - -It is always better to provide this information if you can, because otherwise the scheduler may have to run extra simulations to find the duration of a possible activity. - -## Controllable Duration - -To specify that the duration of an activity is "controllable" (directly equal to one of its parameters), the `@ControllableDuration` annotation can be added to the effect model. For example: - -```java -@ActivityType("RunHeater") -public final class RunHeater { - private static final int energyConsumptionRate = 1000; - - @Parameter - public long durationInSeconds; - - @EffectModel - @ControllableDuration(parameterName = "durationInSeconds") - public void run(final Mission mission) { - // Spawning another activity does not affect the duration of this activity as they run in parallel. - spawn(new PowerOnHeater()); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - - final var durationPowerOff = 100L; - final var durationPowerOn = durationInSeconds - durationPowerOff; - - delay(durationPowerOn, Duration.SECONDS); - - // Assumes PowerOffHeater has a controllable duration. - call(new PowerOffHeater(durationPowerOff)); - } -} -``` - -Note in this example the `call(new PowerOffHeater(durationPowerOff))` at the end of the effect model: (1) makes the duration of `RunHeater` depend on the duration of `PowerOffHeater`, and (2) assumes `PowerOffHeater` has a controllable duration parameter. Even though the duration of `RunHeater` depends on the (fixed) duration of `PowerOffHeater` we know its duration will be equal to `durationInSeconds`, thus making `RunHeater` controllable. For more information on the `call` function, see the [effect model documentation](./effect-model.md). - -If `PowerOffHeater` had an uncontrollable duration we would have to remove the `call` since it would make `RunHeater` uncontrollable as well. - -The annotation `@ControllableDuration(parameterName = "durationInSeconds")` has no effect other than to tell the scheduler that the duration of an activity can be controlled. It acts like a contract between the mission model and the scheduler ensuring that the duration of an activity will be equal to the parameter specified in the annotation. - -This has a special behavior above and beyond the other duration annotations: the others inform the scheduler what the duration is, but `Controllable` allows the scheduler to decide the duration. This makes it a more powerful and more desirable option than `ParametricDuration`, if it applies. - -## Parametric Duration - -`ParametricDuration` is a more general version of `ControllableDuration`, which indicates that the duration depends solely on the activity's parameters, not on simulation state or start time. Instead of applying it to the effect model, it is applied to a public getter method that returns the duration. For example, lets change the above activity to use an enum parameter that determines duration: - -```java -@ActivityType("RunHeater") -public final class RunHeater { - - public enum Length { - Short, - Medium, - Long - } - - @Parameter - public Length length = Length.Medium; - - private static final int energyConsumptionRate = 1000; - - @ParametricDuration - public Duration duration() { - return switch (this.connection) { - case Short -> Duration.of(5, Duration.MINUTE); - case Medium -> Duration.HOUR; - case Long -> Duration.DAY; - }; - } - - @EffectModel - @ControllableDuration(parameterName = "durationInSeconds") - public void run(final Mission mission) { - // Spawning another activity does not affect the duration of this activity as they run in parallel. - spawn(new PowerOnHeater()); - - final long durationInSeconds = duration().dividedBy(Duration.SECOND); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - - final var durationPowerOff = 100L; - final var durationPowerOn = durationInSeconds - durationPowerOff; - - delay(durationPowerOn, Duration.SECONDS); - - // Assumes PowerOffHeater has a controllable duration. - call(new PowerOffHeater(durationPowerOff)); - } -} -``` - -The scheduler will create an instance of this activity and call the annotated `duration()` function. This is not the same instance of the class created during simulation (since the whole purpose is to avoid simulation), so the effect model will not be run. If the effect model makes any internal state changes to the activity instance object, those changes will not be present when `duration()` is called. - -## Fixed Duration - -If an activity has the same duration for all simulation states, start times, and parameters, you can tell the scheduler this with the `@FixedDuration` annotation. Unlike `@ControllableDuration` which is applied to the effect model method, `@FixedDuration` is applied to either a static `Duration` field or no-argument static method that returns a `Duration`. Both kinds must be public. For example, let's change the above activity to always take 1 hour: - -```java -@ActivityType("RunHeater") -public final class RunHeater { - private static final int energyConsumptionRate = 1000; - - @FixedDuration - public static final Duration TOTAL_DURATION = Duration.HOUR; - - @EffectModel - public void run(final Mission mission) { - // Spawning another activity does not affect the duration of this activity as they run in parallel. - spawn(new PowerOnHeater()); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - - final Duration durationPowerOff = Duration.of(100, Duration.SECONDS); - final Duration durationPowerOn = TOTAL_DURATION.minus(durationPowerOff); - - delay(durationPowerOn); - - // Assumes PowerOffHeater has a controllable duration. - call(new PowerOffHeater(durationPowerOff)); - } -} -``` - -Alternatively, if the duration is a more complex calculation (but still constant!) you can use a static method with no arguments: - -```java -@FixedDuration -public static Duration totalDuration() { - return ...; // some calculation -} - -// in the effect model -final Duration durationPowerOn = totalDuration().minus(durationPowerOff); -``` - -## Uncontrollable Duration - -There is no annotation to indicate "uncontrollable"; just don't apply any annotation if none fit your activity. In this case, you can assume the scheduler will invoke at least one extra simulation for any goal that creates the activity; sometimes the scheduler might invoke a simulation *per uncontrollable duration activity* that it places. In the worst case, when your scheduling goal places an uncontrollable activity such that it ends at a specific time, the scheduler might do *many* simulations per activity. For heavyweight mission models this can become unusable very quickly. - -## Caveat - -All of these annotations are essentially non-binding promises. If the actual duration does not match the promised duration, the scheduler will _probably_ notice and cause the goal to fail, but a) this will not happen if `SimulateAfter` is not selected in the scheduling UI, so you shouldn't depend on it, and b) it will not be noticed until after the produced activities have been simulated, which might be expensive. Because of this, it is recommended to use the promised duration directly in the effect model as shown in the above examples, just to be safe. diff --git a/docs/mission-modeling/activity-types/effect-model.md b/docs/mission-modeling/activity-types/effect-model.md deleted file mode 100644 index b0eea174..00000000 --- a/docs/mission-modeling/activity-types/effect-model.md +++ /dev/null @@ -1,114 +0,0 @@ -# Effect Model - -Every activity type has an associated "effect model" that describes how that activity impacts mission resources. An effect model is a method on the activity type class annotated with `@EffectModel`; there can only be one such method per activity type. - -By convention the decorated effect model function is named `run`, and it accepts the top-level `Mission` model as a parameter. This method is invoked when the activity begins, paused when the activity waits a period of time, and resumes when that period of time passes. - -The activity’s computed duration will be measured from the instant this method is entered, and extends either until the method returns or until all spawned children of the activity have completed – whichever is longer. - -## Exceptions - -If an uncaught exception is thrown from an activity’s effect model, the simulation will halt. No later activities will be performed, and simulation time will not proceed beyond the instant at which the exception occurred. As such, uncaught exceptions are essentially treated as fatal errors. - -We advise mission models to employ uncaught exceptions sparingly, as it deprives planners of information about the behavior of the spacecraft after the fault. An uncaught exception will cause the simulation to abort without producing any results – not even up to the point of failure. On the other hand, uncaught exceptions may be useful to identify bugs in the mission model or situations that the mission model is not intended to simulate. - -## Actions - -An activity’s effect model may wait for time to pass, spawn other activities, and affect spacecraft state. Spacecraft state is affected via the `Mission` model parameter, which depends on the details of the modeled mission systems. - -Actions related to the passage of simulation time are provided as static methods on the `ModelActions` class: - -1. `delay(duration)` - Delay the currently-running activity for the given duration. On resumption, it will observe effects caused by other activities over the intervening timespan. - -1. `waitUntil(condition)` - Delay the currently-running activity until the provided `Condition` becomes true. On resumption, it will observe effects caused by other activities over the intervening timespan. - -Actions related to spawning other activities are provided by the generated `ActivityActions` class, usually found under the generated package within your codebase: - -1. `spawn(mission, activity)` - Spawn a new activity as a child of the currently-running activity at the current point in time. The child will initially see any effects caused by its parent up to this point. The parent will continue execution uninterrupted, and will not initially see any effects caused by its child. - -1. `call(mission, activity)` - Spawn a new activity as a child of the currently-running activity at the current point in time. The child will initially see any effects caused by its parent up to this point. The parent will halt execution until the child activity has completed. - -For example, consider a simple activity for running the on-board heaters called `RunHeater`: - -```java -@ActivityType("RunHeater") -public final class RunHeater { - private static final int energyConsumptionRate = 1000; - - @Parameter - public long durationInSeconds = 0; - - @EffectModel - public void run(final Mission mission) { - spawn(mission, new PowerOnHeater()); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - - delay(durationInSeconds, Duration.SECONDS); - - call(mission, new PowerOffHeater()); - } -} -``` - -This activity first spawns a `PowerOnHeater` activity, which then continues concurrently with the current `RunHeater` activity. Next, the total energy to be used by the heater is subtracted from the remaining battery capacity (see the [Resources and Models documentation](../../resources-and-models)). The energy used depends on the duration parameter of the activity, allowing the activity’s effect to be tuned by the planner. Next, the activity waits for the desired heater runtime to elapse, then spawns and waits for a `PowerOffHeater` activity. The activity completes after both children have completed. - -## Activity Decomposition - -In Aerie mission models, decomposition of an activity is not an independent method, rather it is defined within the effect model by means of invoking child activities. These activities can be invoked using the `call()` method, where the rest of the effect model waits for the child activity to complete; or using the `spawn()` method, where the effect model continues to execute without waiting for the child activity to complete. This method allows any arbitrary serial and parallel arrangement of child activities. This approach replaces duration estimate based wait calls with event based waits. Hence, this allows for not keeping track of estimated durations of activities, while also improving the readability of the activity procedure as a linear sequence of events. - -## Computed Attributes - -Upon the termination of an Activity, the effect model can optionally log some information that will be included with the simulation results. This information has no effect on the current simulation, but can be used by downstream tools that process the simulation results. - -To specify the information that will be logged, the mission modeler must change the return type of the method marked with the `@EffectModel` annotation. - -For the `RunHeater` example from above the effect model return type is `void`, which means no useful information will be logged. Let’s define a record type to house our computed attributes, and update the effect model to return it: - -```java -@AutoValueMapper.Record -record ComputedAttributes( - long durationInSeconds, - int energyConsumptionRate -) {} - -@EffectModel -public ComputedAttributes run(final Mission mission) { // Notice the new return type. - spawn(mission, new PowerOnHeater()); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - - delay(durationInSeconds, Duration.SECONDS); - - call(mission, new PowerOffHeater()); - - return new ComputedAttributes(durationInSeconds, energyConsumptionRate); -} -``` - -The `@AutoValueMapper.Record` annotation exists to make this pattern more convenient - it only applies to records returned from effect models. - -Computed attributes are not limited to records - they can be any type that Aerie knows how to serialize. Here is an example where the effect model returns a list of strings, instead of a record: - -```java -@EffectModel -public List run(final Mission mission) { - final var logs = new ArrayList(); - - spawn(mission, new PowerOnHeater()); - logs.add("Spawned PowerOnHeater"); - - final double totalEnergyUsed = durationInSeconds * energyConsumptionRate; - mission.batteryCapacity.set(totalEnergyUsed); - logs.add("Total energy used: " + totalEnergyUsed); - - logs.add("Delaying for " + durationInSeconds + " seconds"); - delay(durationInSeconds, Duration.SECONDS); - - call(mission, new PowerOffHeater()); - - return logs; -} -``` diff --git a/docs/mission-modeling/activity-types/introduction.mdx b/docs/mission-modeling/activity-types/introduction.mdx deleted file mode 100644 index 7dab9610..00000000 --- a/docs/mission-modeling/activity-types/introduction.mdx +++ /dev/null @@ -1,43 +0,0 @@ -import RemoteCodeBlock from '@site/src/components/RemoteCodeBlock'; - -# Activity Types - -An **activity type** defines a simulated behavior that may be invoked by a planner, separate from the autonomous behavior of the mission model itself. -Activity types may define **parameters**, which are filled with **arguments** by a planner and provided to the activity upon execution. -Activity types may also define **validations** for the purpose of informing a planner when the parameters they have provided may be problematic. - -For example, here is a simple activity type from the examples in the template mission model: - - - -Aerie automatically generates parameter serialization boilerplate for every activity type defined in the mission model's [package-info.java](../introduction.mdx#the-package-infojava-file). -Moreover, the generated Model base class provides helper methods for spawning each type of activity as children from other activities. - -## Activity Type Annotations - -In order for Aerie to detect an activity type, its class must be annotated with the `@ActivityType` tag. An activity type is declared with its name using the following annotation: - -```java -@ActivityType("CollectData") -``` - -By doing so, the Aerie annotation processor can discover all activity types declared in the mission model, and validate that activity type names are unique. - -## Activity Type Metadata - -Metadata of activities are structured such that the Aerie annotation processor can extract this metadata given particular keywords. Currently, the annotation processor recognizes the following tags: `contact`, `subsystem`, `brief_description`, and `verbose_description`. -These metadata tags are placed in a [Javadoc](https://en.wikipedia.org/wiki/Javadoc) style comment block above the activity type to which they refer. For example: - -```java -/** - * @subsystem Data - * @contact camargo - * @brief_description A data management activity that deletes old files - */ - @ActivityType("DeleteOldFiles") -``` diff --git a/docs/mission-modeling/activity-types/parameters.md b/docs/mission-modeling/activity-types/parameters.md deleted file mode 100644 index 9d1a4bbc..00000000 --- a/docs/mission-modeling/activity-types/parameters.md +++ /dev/null @@ -1,59 +0,0 @@ -# Parameters - -Activity parameters provide the ability to modulate the behavior of an activity's effect model. -Aside from determining the effects of the activity, these parameters can be used to determine its duration, decomposition into children activities and expansion into commands. - -The Aerie annotation processor is used to extract and generate serialization code for parameters of activity types. -The annotation processor also allows authors of a mission model to create mission-specific parameter types, ensuring that they will be recognized by the Aerie framework. - -## Validations - -A mission model configuration or activity can be validated by providing one or more methods annotated by `@Validation` and `@Validation.Subject`. -The `@Validation` annotation message specifies the message to present to a planner when the validation fails. -The `@Validation.Subject` annotation specifies which parameter(s) the validation is associated with. -These associated parameters are reported from Aerie when querying for activity argument validations to couple validations with parameters. - -For example the following function validates that a parameter called `instrumentPower_W` is between `0.0` and `1000.0` inclusive: - -```java -@Validation("Instrument power W must be between 0.0 and 1000.0 inclusive") -@Validation.Subject("instrumentPower_W") -public boolean validateInstrumentPower() { - return (instrumentPower_W >= 0.0) && (instrumentPower_W <= 1000.0); -} -``` - -To associate a validation function with multiple parameters, the `{"...", "...", ...}` syntax may be used within the `@Validation.Subject` annotation. For example the following function validates that two parameters called `instrumentPower_W` and `instrumentPower_Y` are both between `0.0` and `1000.0` inclusive: - -```java -@Validation("Instrument powers W and Y must be between 0.0 and 1000.0 inclusive") -@Validation.Subject({"instrumentPower_W", "instrumentPower_Y"}) -public boolean validateInstrumentPowers() { - return (instrumentPower_W >= 0.0) && (instrumentPower_W <= 1000.0) && (instrumentPower_Y >= 0.0) && (instrumentPower_Y <= 1000.0); -} -``` - -The annotation processor identifies these methods and arranges for them to be invoked whenever a planner instantiates an instance of the class. A message will be provided to the planner for each failing validation, so the order of validation methods does not matter. - -Validations can also be more dynamic if failure reasons are needed for specific cases, rather than using the annotation above the following is supported. The first parameter is the validation result, the second is the subject of the validation, and the third is the message. - -If returning a `true` result the subject and message can be `null`. - -```java -@Validation -public ValidationResult validateInstrumentPowers() { - if (instrumentPower_W < 0) { - return new ValidationResult(false, "instrumentPower_W", "Instrument W's power is below " + 0); - } else if (instrumentPower_W > 1000.0) { - return new ValidationResult(false, "instrumentPower_W", "Instrument W's power is above " + 1000.0); - } - - if (instrumentPower_Y < 0) { - return new ValidationResult(false, "instrumentPower_Y", "Instrument Y's power is below " + 0); - } else if (instrumentPower_Y > 1000.0) { - return new ValidationResult(false, "instrumentPower_Y", "Instrument Y's power is above " + 1000.0); - } - - return new ValidationResult(true, null, null); -} -``` diff --git a/docs/mission-modeling/advanced-incons.mdx b/docs/mission-modeling/advanced-incons.mdx deleted file mode 100644 index 8bec1cfd..00000000 --- a/docs/mission-modeling/advanced-incons.mdx +++ /dev/null @@ -1,169 +0,0 @@ -# Advanced - Incons - -It is possible to initialize the internal state of your model via an initial conditions (incons) file. -This page will go over one way to do so, using a simplified version of [the Banananation model](https://github.com/NASA-AMMOS/aerie/tree/develop/examples/banananation) as a reference. -Note that a different implementation may suit your model better. - -## Configuration - -Before we can use an incons file in our Mission Model's constructor, we need to be able to specify what that file is in the Configuration. - -### Fields - -We first add a parameter, `inconsFile`, to specify the path to the file to be used. -Additionally, since this model will still allow `initialPlantCount` and `initialProducer` to possibly be specified by a Planner rather than `inconsFile`, -we include a boolean, `useInconsFile`, to determine whether we should use the default initialization or initialize from the incons file. - -```java -public record Configuration(int initialPlantCount, String initialProducer, boolean useInconsFile, String inconsFile) -``` - -### Validation - -It is recommended to include a validation of the path to the incons file, should one be in use. - -```java -@Export.Validation("incons file must exist") -public boolean validateInconsFilePath() { - if(useInconsFile) - return Path.of(inconsFile.trim()).toFile().exists(); - return true; -} -``` - -### Default - -#### With @Template - -```java -public static @Template Configuration defaultConfiguration() { - return new Configuration(DEFAULT_PLANT_COUNT, DEFAULT_PRODUCER, false, ""); -} -``` - -#### With @WithDefaults - -```java -@WithDefaults -public static final class Defaults { - public static Boolean useInconsFile = false; - public static String inconsFile = ""; -} -``` - -## Changes in the Mission Model Class - -Now that we are able to specify an incons file to use, we need to load it. - -### Parsing Incons - -In this example, we expect the incons file to be a .JSON file of the format described below, although additional fields on the resource objects are acceptable. - -```json -[ - { - "dynamics": 12, - "name": "/resource_1" - }, - { - "dynamics": { - "doubleField": 1.1, - "stringField": "JPL" - }, - "name": "/resource_2" - } -] -``` - -Below is a parser for Banananation: - -```java -private void parseInconsFile(Configuration config){ - try(final JsonReader reader = Json.createReader(new FileReader(config.inconsFile().trim()))) { - JsonArray resources = reader.readArray(); - for(int i = 0; i < resources.size(); i++){ - final JsonObject resource = resources.getJsonObject(i); - switch (resource.getString("name")) { - case "/fruit" -> { - final JsonObject initialFruit = resource.getJsonObject("dynamics"); - final double initialValue = initialFruit.getJsonNumber("initial").doubleValue(); - final double rate = initialFruit.getJsonNumber("rate").doubleValue(); - this.fruit = new Accumulator(initialValue, rate); - } - case "/flag" -> { - final String initialFlag = resource.getString("dynamics"); - this.flag = Register.forImmutable(Flag.valueOf(initialFlag)); - this.flag.isConflicted(); - } - case "/producer" -> { - final String initialProducer = resource.getString("dynamics"); - this.producer = Register.forImmutable(initialProducer); - } - case "/peel" -> { - final double initialPeel = resource.getJsonNumber("dynamics").doubleValue(); - this.peel = AdditiveRegister.create(initialPeel); - } - case "/plant" -> { - final int initialPlantCount = resource.getInt("dynamics"); - this.plant = Counter.ofInteger(initialPlantCount); - } - } - } - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } -} -``` - -:::tip - -You may want to handle a missing incons file in a more graceful way, such as loading a default file or falling back on the default initialization. - -::: - -### Default Initialization - -In your Mission Model's constructor, extract your current initialization into a method like so: - -```java -private void defaultInitialization(Configuration config){ - this.fruit = new Accumulator(4.0, 0.0); - this.peel = AdditiveRegister.create(4.0); - this.flag = Register.forImmutable(Flag.A); - this.lineCount = Register.forImmutable(0); - this.plant = Counter.ofInteger(config.initialPlantCount()); - this.producer = Register.forImmutable(config.initialProducer()); -} -``` - -### Updating the Mission Model Constructor - -Replace that initialization was with the following if statement: - -```java -if(config.useInconsFile()){ - parseInconsFile(config); -} else { - defaultInitialization(config); -} -``` - -This gives a final constructor of: - -```java -public Mission(final Registrar registrar, final Configuration config) { - if(config.useInconsFile()){ - parseInconsFile(config); - } else { - defaultInitialization(config); - } - - registrar.discrete("/flag", this.flag, new EnumValueMapper<>(Flag.class)); - registrar.discrete("/flag/conflicted", this.flag::isConflicted, new BooleanValueMapper()); - registrar.discrete("/peel", this.peel, new DoubleValueMapper()); - registrar.real("/fruit", this.fruit); - registrar.discrete("/plant", this.plant, new IntegerValueMapper()); - registrar.discrete("/producer", this.producer, new StringValueMapper()); - registrar.topic("/producer", this.producer.ref, new StringValueMapper()); -} -``` diff --git a/docs/mission-modeling/advanced-the-merlin-interface.mdx b/docs/mission-modeling/advanced-the-merlin-interface.mdx deleted file mode 100644 index 7f5c91da..00000000 --- a/docs/mission-modeling/advanced-the-merlin-interface.mdx +++ /dev/null @@ -1,177 +0,0 @@ -# Advanced - The Merlin Interface - -Aerie is a software framework for modeling spacecraft. Within Aerie is **Merlin**, a mission modeling environment and hybrid system simulator based on the [Java programming language](). - -Because mission models are expressed in Java, rather than a custom DSL, Merlin has little to no ability to see the actual Java code comprising a mission model. -Merlin must instead make inferences about the mission model based on its observable behavior. - -Merlin is a spiritual successor to the -[Blackbird](https://trs.jpl.nasa.gov/handle/2014/52245) planning -system, which similarly uses Java for activity and resource modeling. -Blackbird's design shed light on the myriad choices made in designing -Merlin. - -Predecessors of Merlin and Blackbird, such as [APGen](https://trs.jpl.nasa.gov/handle/2014/45571) and [SEQGen](https://trs.jpl.nasa.gov/handle/2014/45455), provided a domain-specific language for mission modeling, allowing them to obtain deep, fine-grained information about the composition of a mission model before performing any simulation. -In some ways, this provides enhanced ergonomics, as a mission modeler can focus on expressing their model directly in the modeling language, without being concerned with the needs of the system that will be interpreting that model. -The language itself captures all interesting aspects of the model. - -Unlike the DSLs of APGen and SEQGen, Java is a general-purpose language with no explicit provisions for mission modeling. -To serve mission modeling, these facilities must instead be built on top of Java, forming a bridge between the mission model and the simulation system. -A mission model must explicitly use this bridge to expose modeling knowledge to the system interpreting their model. -It is this modeling interface, not the authoring language, that must express all interesting aspects of the model. - -:::note - -A language can be, and often is, construed as an interface in its own right. -However, these linguistic interfaces are often so rich and complex that a difference in degree becomes a difference in kind. -Most mainstream statically-typed languages, including Java, cannot faithfully embed linguistic interfaces in their type systems; any attempt quickly blows through the degree of expressivity provided by the type system. - -Languages like [Haskell](https://www.haskell.org/) and [Scala](https://www.scala-lang.org/) provide more expressive type systems, and dependently-typed languages like [Idris](https://www.idris-lang.org/) are more expressive still. -These programming languages allow a more faithful embedding of linguistic interfaces, so they are often used to support [EDSLs](https://wiki.haskell.org/Embedded_domain_specific_language) (embedded domain-specific languages) in research and industry. - -::: - -The dichotomy between the modeling interface and the authoring language bounds the design of the Merlin modeling experience between two extremes. - -- At one extreme, the modeling interface dominates the experience of modeling, to the point that almost any authoring language could have been used as long as the interface could be embedded into it. - This design is characterized by the intrusive presence of elements of the interface throughout the mission model, and is not much different from hand-writing the abstract syntax tree of a program in some domain-specific language. - -- At the other extreme, the authoring language dominates the experience of modeling, and the interface avoids repeating capabilities that are already possessed by the authoring language. - The interface is purely relegated to the role of "bridge", binding the relevant native entities to the intended domain concepts. - -Merlin briefly explored the first extreme at its inception, with pervasive use of the [Builder pattern](https://en.wikipedia.org/wiki/Builder_pattern) to describe elements of the model. -It quickly became apparent that this avoided most of the benefits of Java: common development tools like autocompletion could not be used to guide mission modelers, and the modeling experience was very unlike Java development in general. -The early development of Merlin was characterized by a gradual shift away from this end of the design space. - -Merlin has chosen to pursue the second extreme on principle, utilizing the authoring language to its greatest extent while augmenting it with domain-specific semantics where necessary. -Among other reasons, Java was originally chosen as a modeling language because it would serve as a "transferable skill" for those both entering and exiting the "mission modeler" role. -As a general-purpose language, Java provides a solid baseline for building a modular, maintainable system of any variety. -High-performance Java runtimes already exist, and the oft-forgotten debugging experience is present out of the box. -Merlin intends that mission modeling "taste" like development in Java more broadly, with the mission model "flavors" carefully integrated into that experience. - -## So what is the interface? - -The Merlin interface must be minimal, to allow the authoring language to take center stage, while complete, to allow modeling knowledge to be transferred out of the mission model. -A minimal interface can always be built upon the authoring language to provide more natural ergonomics. -To that end, there are three primary landmarks in the Merlin mission modeling interface. - -- **Cells** allow a mission model to express **time-dependent state** in a way that can be tracked and managed by the host system -- **Tasks** allow a mission model to describe **time-dependent processes** that affect mission state -- **Directives** specify the external stimuli which may be posed against a model (i.e. spawning tasks) -- **Resources** allow a mission model to express the **time-dependent evolution** of quantities of interest to the mission - -As a common theme, the interface augments Java with time-dependence, allowing the flow of simulation time to be decoupled from (and queried independently of) the flow of real time. -As a rule, we are not interested only in the state a model finds itself in at the end of a period of time, but rather the succession of all states it transitions through over time. - -### Cells - -In Java, every object begins in some state (upon construction); can be transitioned into another state by sending messages to it (via methods); and can be interrogated for information based on its current state (also methods). -Even primitives in Java fit this mold: the value (state) of a primitive field may be replaced or retrieved wholesale. -(Structures that behave like a primitive field are sometimes called "atomic registers".) - -However, normal Java objects are not aware of the distinction between simulation time and real time. -It is not possible to ask an arbitrary object about a state it previously inhabited, and it is even less possible to put an object into two states simultaneously, as occurs when simulation time splits and rejoins for concurrently-executing tasks. - -**Cells** are a time-dependent generalization of mutable objects in Java supporting concurrent use across simultaneously-acting tasks, and retaining historical knowledge about its state at any simulation time. -**All mutable state accessible during simulation must be manipulated through a containing cell**. - -Like an object, a cell possesses an internal state and a set of operations to transition between states. -These operations are called "effects", and are logged alongside the cell. -The state of a cell at any simulation time is solely determined by its initial state and the effects upon it prior to that time. - -Unlike an object, a cell possesses a simulation-aware semantics for combining sequential and concurrent effects and for explicitly transitioning the cell between states. -**The state of a cell must not be affected except by applying effects to its containing cell**. - -Through cells, a mission model may manage mutable state much as though it were a Java object, with behavior appropriate to the order of operations in simulation time, rather than the less predictable order of operations in real time. -The concurrent semantics of Merlin simulation is confined to the internal behavior of cells, allowing for tight control of custom semantics while isolating the rest of the mission model from these concerns. - -Through cells, the Merlin simulation system may observe when and which elements of simulation state are queried or affected as the simulation proceeds. -This constitutes the single most powerful tool at Merlin's disposal to obtain insight into a mission model, as it allows Merlin to collect two different sets of knowledge over the course of simulation: - -- A **simulation timeline** captures all effects on all cells in the order they occur, even accounting for concurrent effects between two simultaneous tasks. - This structure completely captures the sense of the term "simulation time": the state of the mission model at any time of interest is fully determined by an index into this structure. - -- A **dependency graph** captures all causal dependencies between cells, tasks, and resources. - This allows the runtime system to optimize system execution in multiple ways, and it may also enable mission planners to, for instance, better understand how an earlier activity influences a later one. - -A mission model may create a cell by providing an initial state and an effect semantics through the Merlin interface. -It receives a handle to the cell in the shape of a Java object, and may then use it idiomatically like any other Java object. - -### Tasks - -In Java, an object transitions between states when a method is invoked on it. -The methods of one object may recursively invoke the methods of other objects, causing an entire graph of objects to transition between states. - -Methods in Java are not normally explicitly aware of the passage of time. -They may ask the host system what time it is, but the amount of time that passes is not a functional element of the system - rather, it is an incidental effect of the hardware and other software executing on the same host. -Moreover, multiple methods in Java cannot proceed concurrently. -At most one method is ever in progress, and it must complete before its caller - and _only_ its caller - may proceed. - -**Tasks** are a time-dependent generalization of methods in Java. -A task may transition the model between states by performing effects upon its cells. -A task may spawn other tasks, then proceed concurrently with its children - concurrent effects are resolved by the cells to which they are posed. -Tasks may explicitly await the passage of simulation time before continuing, or may await the completion of another task or the transition of the model into a particular state. - -Like methods, tasks possess their own internal state, representing the work left to be completed by the task. -Progress through a Java method is implicitly managed by the Java runtime (via the call stack); in order to use methods as a foundation for tasks, we must supplement this implicit state rather than replacing it. - -The Merlin interface treats tasks in terms of steps. -Every time a task is stepped forward, it updates its internal state, performs some effects, spawns some children, and then reports a status describing when to step the task again. -The task's internal state is managed entirely on one side of the interface, and so does not need to be transmitted. -In other words, a task is fundamentally treated as an opaque state machine. - -However, _specifying_ tasks as state machines requires interleaving modeling logic with tedious bookkeeping. -This avoids many of the benefits of Java methods, and it isn't possible to pause a task while invoking other methods unless they are specified in the same way. -To that end, we provide task specification platforms that centralize the tedious bookkeeping to a single context object against which task may invoke methods to spawn, delay, and perform effects. -A task can be specified as a regular Java method that happens to have access to this context. - -Note that these specification platforms are not part of the modeling interface itself; they merely adapt the state machine-oriented interface to the ergonomic expectations of users. - -### Directives - -Mission models are used by interacting with them in some way, and observing the resulting impact on the model state over time. -While tasks specify how the model state itself is changed, **directives** specify the external stimuli which may be posed against a model. - -The concept of directives has a different name depending on how the model is being used. -When used in an activity planning workflow, directives represent the **activities** performed by the mission system. -When used in a sequencing workflow, directives represent the **sequences** and **commands** dispatched by the ground station. -In all cases, they cause the mission system to respond in some way - in other words, to spawn a task. - -The Merlin interface allows a mission model to register the directives it supports, along with a task to be spawned when a directive is received. -Directives also specify a set of **parameters**, allowing the behavior of a directive to be modulated. -The arguments for a specific directive are provided directly to the task it spawns. - -As with most concepts in Java, method arguments are themselves named objects of some type. -However, the arguments to a directive are specified by a mission planner, typically via a UI rather than Java source code. -Thus, directive arguments must be serializable and deserializable to a model-agnostic form that can be presented ergonomically to a planner. -The modeling interface provides all arguments in this form, and does not hard-code support for arbitrary Java types. - -Working with this constrained data type poses a burden on mission modelers, who would need to interleave processing of these argument representations with their modeling logic. -Moreover, planners would generally like to know ahead of time whether the arguments they've provided are valid for a given directive, rather than waiting until simulation time to observe a failure. -To that end, mission modelers may separately provide - and reuse existing definitions of - dedicated value mappers, converting between values of the general-purpose interchange type and values of the desired modeling type. - -Finally, when executing in the context of a directive, the mission model may spawn other directives. -These directives are executed just like any other spawned task, but they can also be reported to the modeler as a product of simulation. -This process, called **decomposition**, allows planners to better understand how a single directive breaks down into distinct behaviors, and the overall decomposition hierarchy is a primary input into the sequence generation workflow, which realizes an activity plan in a form suitable for execution by a physical system. - -### Resources - -In Java, the state of an object is "encapsulated", meaning it cannot be observed directly from the outside. -Instead, the object exposes methods that return different information depending on its current state. -Moreover, an object may itself reference other objects, so the state of an object may depend on the state of many others. -A method may depend upon these references by recursively invoking other methods. - -In Merlin, a modeled system transitions between states by reacting to discrete stimuli at instantaneous times. -However, even a system in a fixed state can continuously affect its environment: a rocket that imparts a constant force over time will see its position and velocity change over time. -Thus, we need the ability to ask about the behavior of the system not just at discrete times, but over continuous regions of time. - -A **resource** is a time-dependent generalization of getter methods in Java. -Resources provide information describing the steady-state behavior of some quantity over time, starting at the time at which it is queried and continuing indefinitely. -This information is called a **dynamics**, as it describes the autonomous dynamical behavior of the resource. - -Merlin currently supports two kinds of resource: discrete and real resources. -The behavior of a discrete resource is given by a single fixed value: a discrete resource does not change autonomously. -The behavior of a real resource is given by an initial value and slope, i.e. a line: a real resource accrues over time. (We hope to support general polynomial resources in the future.) - -Although a resource dynamics describes an autonomous behavior, that behavior may change when the mission system transitions between states. -Merlin simply re-queries any resources affected by the state transition to obtain their new autonomous dynamics. diff --git a/docs/mission-modeling/assets/remote-debug-config.png b/docs/mission-modeling/assets/remote-debug-config.png deleted file mode 100644 index 8ca13e2e..00000000 --- a/docs/mission-modeling/assets/remote-debug-config.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a2dcc0baae7a030e720bf957ba5f290b79046cd7d2e12caf5c9418dd4a54d3c -size 91302 diff --git a/docs/mission-modeling/configuration.md b/docs/mission-modeling/configuration.md deleted file mode 100644 index e3751975..00000000 --- a/docs/mission-modeling/configuration.md +++ /dev/null @@ -1,73 +0,0 @@ -# Configuration - -A **mission model configuration** enables mission modelers to set initial mission model values when running a simulation. Configurations are tied to a plan, therefore each plan is able to define its own set of configuration parameters - -## Declaration - -To use a mission model configuration the `@WithConfiguration` annotation must be used within the mission model's [package-info.java](./introduction.mdx#the-package-infojava-file) to register the configuration with Aerie. - -For example, the Aerie mission model template [package-info.java](https://github.com/NASA-AMMOS/aerie-mission-model-template/blob/main/src/main/java/missionmodel/package-info.java) makes use of this annotation: - -```java -@MissionModel(model = Mission.class) -@WithConfiguration(Configuration.class) -package missionmodel; - -import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel; -import gov.nasa.jpl.aerie.merlin.framework.annotations.MissionModel.WithConfiguration; -``` - -In this example `Configuration` is the class class containing all mission model configuration data. When the `@WithConfiguration` annotation is used, the model – defined within the `@MissionModel` annotation – must accept the configuration as the last constructor parameter. See [Mission.java](https://github.com/NASA-AMMOS/aerie-mission-model-template/blob/main/src/main/java/missionmodel/Mission.java): - -```java -public Mission(final Registrar registrar, final Configuration config) { - // Initialize Mission with 'registrar' and 'config' here. -} -``` - -If the second argument is an `Instant` denoting plan start time the configuration parameter must still be provided as the last argument. For example: - -```java -public Mission(final Registrar registrar, final Instant planStart, final Configuration config) { - // Initialize Mission with 'registrar', 'planStart', and 'config' here. -} -``` - -## Parameters - -A configuration class should be defined with the same parameter annotations as activities. See [Parameters](../parameters) for a thorough explanation of all possible styles of `@Export` parameter declarations. - -Similarly to activities, the annotation processor will take care of all serialization/deserialization of the configuration object. The annotation processor will generate a configuration mapper for the configuration defined within `@WithConfiguration()`. - -## Examples - -A `Configuration` can be a simple data class. For example: - -```java -package missionmodel; - -import gov.nasa.jpl.aerie.merlin.framework.annotations.Export; -import java.nio.file.Path; -import static gov.nasa.jpl.aerie.merlin.framework.annotations.Export.Template; - -public record Configuration(Path initialDataPath) { - public static final Path DEFAULT_DATA_PATH = Path.of("/etc/os-release"); - - public static @Template Configuration defaultConfiguration() { - return new Configuration(DEFAULT_DATA_PATH); - } -} -``` - -See the Aerie [mission model examples directory](https://github.com/NASA-AMMOS/aerie/tree/develop/examples) for a demonstration of each possible style of configuration definitions: - -1. [foo-missionmodel](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/foo-missionmodel/src/main/java/gov/nasa/jpl/aerie/foomissionmodel/Configuration.java) - Uses standard `@Parameter` configuration annotations -1. [banananation](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/banananation/src/main/java/gov/nasa/jpl/aerie/banananation/Configuration.java) - Uses the `@Template` annotation to define a default Configuration object (shown above) -1. [config-with-defaults](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/config-with-defaults/src/main/java/gov/nasa/jpl/aerie/configwithdefaults/Configuration.java) - Uses `@WithDefaults` to define a default for each parameter -1. [config-without-defaults](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/config-without-defaults/src/main/java/gov/nasa/jpl/aerie/configwithoutdefaults/Configuration.java) - Defined with no default arguments, requires all arguments to be supplied by the planner - -The mission model may use a configuration to set initial values of resources, for example: - -```java -this.sink = new Accumulator(0.0, config.sinkRate); -``` diff --git a/docs/mission-modeling/introduction.mdx b/docs/mission-modeling/introduction.mdx deleted file mode 100644 index 882503fd..00000000 --- a/docs/mission-modeling/introduction.mdx +++ /dev/null @@ -1,48 +0,0 @@ -import RemoteCodeBlock from '@site/src/components/RemoteCodeBlock'; - -# Mission Modeling - -In Aerie, a mission model serves activity planning needs in two ways. First, it describes how various mission resources behave autonomously over time. Second, it defines how activities perturb these resources at discrete time points, causing them to change their behavior. This information enables Aerie to provide scheduling, constraint validation, and resource plotting capabilities on top of a mission model. - -## Creating a Mission Model - -If you want to learn how to develop a mission model, head to our [mission modeling tutorial](../../tutorials/mission-modeling/introduction/), -or check out our [GitHub template mission model repository](https://github.com/NASA-AMMOS/aerie-mission-model-template) for an empty template. -You will need a public [GitHub.com](https://github.com/) account to download the Aerie Maven packages. - -If you do not want to develop a mission model and just want to try an example, you can download the [missionmodel.jar](https://github.com/NASA-AMMOS/aerie-modeling-tutorial/blob/main/missionmodel.jar) from the [Aerie modeling tutorial repository](https://github.com/NASA-AMMOS/aerie-modeling-tutorial). -Make sure you note where you downloaded the `.jar` file as you will need to browse to that location in the UI. - -## The package-info.java File - -A mission model must contain a [package-info.java](https://www.baeldung.com/java-package-info) file containing annotations that describe the highest-level features of the mission model. For example: - - - -This `package-info.java` identifies the top-level class representing the mission model, and registers activity types that may interact with the mission model. Aerie processes these annotations at compile-time, generating a set of boilerplate classes which take care of interacting with the Aerie system. - -The `@WithMappers` annotation informs the annotation processor of a set of serialization rules for activity parameters of various types; the [BasicValueMappers](https://github.com/NASA-AMMOS/aerie/blob/develop/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/serialization/rulesets/BasicValueMappers.java) ruleset covers most primitive Java types. Mission modelers may also create their own rulesets, specifying rules for mapping custom value types. -If multiple mapper classes are included via the `@WithMappers` annotations, and multiple mappers declare a mapping rule to the same data type, the rule found in the earlier declared mapper will take precedence. - -## The Mission Model class - -The top-level mission model is responsible for defining all of the mission resources and their behavior when affected by activities. -Of course, the top-level model may delegate to smaller, more focused models based on the needs of the mission. -The top-level model is received by activities, however, so it must make accessible any resources or methods to be used therein. For example: - - - -Mission resources are declared using `Registrar#discrete` or `Registrar#real` functions. - -A model may also express autonomous behaviors, where a discrete change occurs in the system outside of an activity's effects. -A **daemon task** can be used to model these behaviors. Daemons are spawned at the beginning of any simulation, and may perform the same effects as an activity. Daemons are prepared using the `spawn` method. diff --git a/docs/mission-modeling/parameters.md b/docs/mission-modeling/parameters.md deleted file mode 100644 index c3c991f9..00000000 --- a/docs/mission-modeling/parameters.md +++ /dev/null @@ -1,153 +0,0 @@ -# Parameters - -The Aerie interface offers a variety of ways to define **parameters** for mission model configurations and activities. - -Parameters offer a concise way to export information across the mission-agnostic Aerie interface – namely a parameter's type to support serialization and a parameter's "required" status to ensure that parameters without mission-model-defined defaults have an argument supplied by the planner. - -In this guide **parent class** refers to the Java class that encapsulates parameters. This class may take the form of either a mission model [configuration](../mission-modeling/configuration.md) or [activity](../mission-modeling/activity-types/introduction.mdx). - -Both configurations and activities make use of the same Java annotations for declaring parameters within a parent class. The `@Export` annotation interface serves as the common qualifier for exporting information across the mission-agnostic Aerie interface. The following parameter annotations serve to assist with parameter declaration and validation: - -1. `@Export.Parameter` -1. `@Export.Template` -1. `@Export.WithDefaults` -1. `@Export.Validation` and `@Export.Validation.Subject` - -The following sections delve into each of these annotations along with examples and suggested use cases for each. - -## Without Export Annotations - -The first – and perhaps less obvious option – is to not use any parameter annotations. If a parent class contains no `@Export.Parameter`, `@Export.Template`, or `@Export.WithDefaults` annotation it is assumed that every class member variable is a parameter to export to Aerie. - -Defining a parent class becomes as simple as `public record Configuration(Integer a, Integer b, Integer c) { }`. However, it is not possible to declare a member variable that is not an exported parameter with this approach. - -### Example - -In the following example `a`, `b`, and `c` are all parameters that require planner-provided arguments at planning time. - -```java -@ActivityType("Simple") -public record SimpleActivity(Integer a, Integer b, Integer c) { - @EffectModel - public void run(final Mission mission) { - mission.count.set(a); - delay(1, SECOND); - mission.count.set(b); - delay(1, SECOND); - mission.count.set(c); - } -} -``` - -### Recommendation - -Avoid `@Export.Parameter`, `@Export.Template`, or `@Export.WithDefaults` when every member variable for a parent class should be an exported parameter without a default value. - -This approach is great for defining a simple record type parent class without defaults. If defaults are desired then `@Template` or `@WithDefaults` can be used without changing a record type class definition. - -### See Also - -Aerie's [config-without-defaults](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/config-without-defaults/src/main/java/gov/nasa/jpl/aerie/configwithoutdefaults/Configuration.java) example mission model makes use of this succinct style for declaring mission model configuration parameters. See the [Java documentation on Record classes](https://docs.oracle.com/en/java/javase/19/language/records.html) for more detailed information about the language feature. - -## Export Parameter - -The `@Parameter` annotation is the most explicit way to define a parameter and its defaults. Explicitly declaring each parameter within a parent class with or without a default value gives the mission modeler the freedom to decide which member variables are parameters and which parameters are required by the planner. - -### Example - -In the example below the parent class is a mission model configuration. Here’s a close look at each member variable: - -1. `a` - A traditional member variable. Not explicitly declared as a parameter and therefore is not considered to be a parameter. -1. `b` - Explicitly declared as a parameter without a default value. A value will be required by the planner. -1. `c` - Explicitly declared as a parameter with a default value. A value will not be required by the planner. - -```java -public final class Configuration { - public Integer a; - - @Export.Parameter - public Integer b; - - @Export.Parameter - public Integer c = 3; - - public Configuration(final Integer a, final Integer b, final Integer c) { - this.a = a; - this.b = b; - this.c = c; - } -} -``` - -### Recommendation - -Declare each parameter with a `@Parameter` when a non-record type parent class is desired. - -Some mission modelers may prefer the explicitness provided by individual `@Parameter` annotations. However, this opens the door to subtle mistakes such as an unintentionally absent `@Parameter` annotation or an unintentionally absent default assignment. Those who prefer a more data-oriented approach may also find this style to not be as ergonomic as using a simple record type. - -### See Also - -Aerie's [foo-missionmodel](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/foo-missionmodel/src/main/java/gov/nasa/jpl/aerie/foomissionmodel/Configuration.java) example mission model makes use of this style when declaring mission model configuration parameters. - -## Export Template - -The `@Template` annotation decouples parameter definitions and default values, allowing record types to be used as parent classes. When the `@Template` annotation is used every parent class member variable is interpreted as a parameter to export to Aerie. This annotation must be attached to a public static constructor method. - -### Example - -In the example below `SimpleActivity` is a record type with one constructor parameter called `speed`. - -```java -@ActivityType("Simple") -public record SimpleActivity(double speed) { - @Template - public static SimpleActivity defaults() { - return new SimpleActivity(1.0); - } - - @EffectModel - public void run(final Mission mission) { - // Add effect model here. Not relevant to this example. - } -} -``` - -### Recommendation - -Use `@Template` when every member variable for a parent class should be an exported parameter with a default value. - -### See Also - -Aerie's [banananation](https://github.com/NASA-AMMOS/aerie/tree/develop/examples/banananation) example mission model uses this style within the [GrowBananaActivity](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/banananation/src/main/java/gov/nasa/jpl/aerie/banananation/activities/GrowBananaActivity.java) and [ThrowBananaActivity](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/banananation/src/main/java/gov/nasa/jpl/aerie/banananation/activities/ThrowBananaActivity.java). - -## Export With Defaults - -Similarly to `@Template`, the `@WithDefaults` annotation also decouples parameter definitions and default values, allowing record types to be used as parent classes. When the `@WithDefaults` annotation is used every parent class member variable is interpreted as a parameter to export to Aerie. Unlike `@Template`, a sparse set of default values may be supplied. - -This annotation must be attached to a nested public static class within the parent class. Each member variable of this nested class must have the same name as a parent class’s member variable. Not every parent class member variable is required to have an associated member variable within the nested class. This allows the mission modeler to selectively choose which parameters must be supplied by the planner. - -### Example - -In the example below the parent class is a mission model configuration. Here's a close look at each member variable: - -1. `a` - A parameter with an associated default value. -1. `b` - A parameter without a default value. A value will be required by the planner. -1. `c` - A parameter with an associated default value. - -```java -public record Configuration(Integer a, Double b, String c) { - @WithDefaults - public static final class Defaults { - public static Integer a = 42; - public static String c = "JPL"; - } -} -``` - -### Recommendation - -Use `@WithDefaults` when every member variable for a parent class should be an exported parameter with an optionally provided default value. - -### See Also - -Aerie's [config-with-defaults](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/config-with-defaults/src/main/java/gov/nasa/jpl/aerie/configwithdefaults/Configuration.java) example mission model makes use of this style within its mission model configuration. The [banananation](https://github.com/NASA-AMMOS/aerie/tree/develop/examples/banananation) example mission model uses this style in the [BakeBananaBreadActivity](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/banananation/src/main/java/gov/nasa/jpl/aerie/banananation/activities/BakeBananaBreadActivity.java). diff --git a/docs/mission-modeling/resources-and-models.md b/docs/mission-modeling/resources-and-models.md deleted file mode 100644 index 7e336195..00000000 --- a/docs/mission-modeling/resources-and-models.md +++ /dev/null @@ -1,47 +0,0 @@ -# Resources and Models - -In Aerie a resource is any measurable quantity whose behavior is to be tracked over the course of a simulation. Resources are general-purpose, and can model quantities such as finite resources, geometric attributes, ground and flight events, and more. Aerie provides basic models for three types of quantity: - -1. A discrete quantity that can be set (Register) -1. A continuous quantity that can be added to (Counter) -1. A continuous quantity that grows autonomously over time (Accumulator) - -A common example of a Register would be a spacecraft or instrument mode, while common Accumulators might be battery capacity or data volume. - -Defining a resource is as simple as constructing a model of the appropriate type. The model will automatically register its resources for use from the Aerie UI or Aerie API. Alternatively, a resource may be **derived** or **sampled** from an existing resource. - -## Derived Resources - -A derived resource is constructed from an existing resource given a mapping transformation. For example, the [Imager class](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/foo-missionmodel/src/main/java/gov/nasa/jpl/aerie/foomissionmodel/models/Imager.java) sample model defines an "imaging in progress" resource with: - -```java -this.imagingInProgress = this.imagerMode.map($ -> $ != ImagerMode.OFF); -``` - -In this example, `imagingInProgress` is a full-fledged discrete resource and will depend only on the imager’s on/off state. - -A derived resource may also be constructed from a real resource. For example, given Accumulators `instrumentA` and `instrumentB`, a resource that maintains the current sum of both volumes may be constructed with: - -```java -var sumResource = instrumentA.volume.resource.plus(instrumentB.volume.resource); -``` - -## Sampled Resources - -A sampled resource allows for a new resource to be constructed from arbitrarily many existing resources/values and to be sampled once per second. This differs from a derived resource which provides a continuous mapping transformation from a single existing resource. - -For example, the [Mission class](https://github.com/NASA-AMMOS/aerie/blob/develop/examples/foo-missionmodel/src/main/java/gov/nasa/jpl/aerie/foomissionmodel/Mission.java) sample model defines a "battery state of charge" resource with: - -```java -this.batterySoC = new SampledResource<>(() -> this.source.volume.get() - this.sink.volume.get()); -``` - -In this example, `batterySoC` will be updated once per second to with the current difference between the "source" volume and "sink" volume. - -## Custom Models - -Often, the semantics of the pre-existing models are not exactly what you need in your mission model. Perhaps you’d like to prevent activities from changing the rate of an Accumulator, or you’d like to have some helper methods for interrogating one or more resources. In these cases, a custom model may be a good solution. - -A custom model is a regular Java class, extending the Model class generated for your mission model by Merlin (or the base class provided by the framework, if it’s mission-agnostic). It may implement any helper methods you’d like, and may contain any sub-models that contribute to its purpose. The only restriction is that it must not contain any mutable state of its own - all mutable state must be held by one of the basic models, or one of the internal state-management entities they use, known as "cells". - -The `contrib` package is a rich source of example models. See [the repository](https://github.com/NASA-AMMOS/aerie/tree/develop/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/models) for more details. diff --git a/docs/mission-modeling/testing-and-debugging.mdx b/docs/mission-modeling/testing-and-debugging.mdx deleted file mode 100644 index f2c0914a..00000000 --- a/docs/mission-modeling/testing-and-debugging.mdx +++ /dev/null @@ -1,64 +0,0 @@ -# Testing and Debugging - -While developing your mission model, you'll likely need to do some testing and debugging to ensure your model is behaving as expected. In addition to standard unit testing, you may want to run an "end to end" test by testing your full model against a plan and simulating it. - -There are two recommended methods you can use in order to perform such tests: - -1. Create standalone tests in Java, and then run those tests locally in your IDE. This option does not require you to stand up Aerie containers and interact with an Aerie deployment. -2. Perform remote debugging against a deployed version of Aerie. In this option, you can attach your IDE with your model code to Aerie and then debug just like you would with a unit test. - -Below are instructions on how to setup each option. Note that these instructions assume the use of junit as the testing framework and IntelliJ as the IDE. Instructions would likely look similar for other testing frameworks and IDEs. - -### Standalone Testing Procedures - -Once you have developed your model, create a standalone test in java with a similar structure to [this example](https://github.com/NASA-AMMOS/aerie-modeling-tutorial/blob/main/missionmodel/src/test/java/missionmodel/ModelSimulationTests.java) found in the mission modeling tutorial. - -:::note - -The example test use some utilities from java packages that your build may not depend on yet. Take a look at the [`build.gradle`](https://github.com/NASA-AMMOS/aerie-modeling-tutorial/blob/main/missionmodel/build.gradle) file in the `missionmodel` directory of the [mission modeling tutorial](https://github.com/NASA-AMMOS/aerie-modeling-tutorial) to see what dependencies exist. - -::: - -Looking at the example test, you'll notice a `beforeAll()` method that instantiates a mission model with a particular simulation configuration and instantiates a simulation utility to assist allow plan simulation within individual tests. - -There is a `beforeEach()` method that creates an empty plan for individual tests to begin with. This plan is editable so that you can build up an example plan using methods like `plan.create()` - -The creation of activities within the empty plan occurs in the method/s annotated with `@Test`, which is usually followed by some assertions checking the plan results against an expected value. Plans are simulated at any time during a test with a `plan.simulate()`. - -Once you have built your test, you can debug your procedure using an IDE just like you would with any other code in a standard java application. - -### Remotely Debugging Procedures - -Prior to starting up Aerie, make the following modifications to your [`docker-compose.yml`](/deployment/introduction/#docker-composeyml) file: - -- Find any containers with the name `aerie_merlin_worker_*` (for a basic deployment, you might just have one of these) and add the following line under the `JAVA_OPTS:` sub-section: - -`-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005` - -- Add an additional port entry to the `ports:` sub-section ending in `:5005` (same port number as the line above) while ensuring the first port number remains unique between containers. For example: - -`ports: ["27189:8080", "5006:5005"]` - -where `5006:5005` would be `5007:5005` for a second `aerie_merlin_worker_` container and so on - -:::info - -Below is a breakdown of the line above in case you are interested: - -- `-agentlib:jdwp=...`: tells the JVM to load the JDWP (Java Debug Wire Protocol) agent library, which enables debugging. -- `transport=dt_socket`: use a socket transport for debugging (as opposed to shared memory). -- `server=y`: tells the JVM to act as the debug server — it will listen for debugger connections. -- `suspend=n`: tells the JVM not to suspend on start-up so the app (Aerie) will start running immediately. -- `address=*:5005`: Listen on port 5005 on all interfaces (including inside a container), which allows external debuggers to connect. - -::: - -Next, if you haven't already, open your project with your model in it in IntelliJ and select Run -> Edit Configurations from the menu bar. In the Run/Debug Configurations dialog that comes up, click the plus sign on the toolbar. The list shows the run/debug configuration templates. Select "Remote JVM Debug" to create a new debug configuration. Give your configuration a name such a "AerieModelDebugger" and ensure your settings look like the screenshot below (note you may have to change your host if you are not deploying locally). - -![Remote Debug Configuration](assets/remote-debug-config.png) - -Verify that the line under "Command line arguments for remote JVM:" matches the line you put in the `docker_compose.yml` earlier. Click "OK" to complete your new configuration. You should notice that your configuration appears near the top right corner of the window along with any other run/debug configurations you may have. - -Once you are ready, startup your Aerie instance and go to the plan you'd like to debug your model against. Before simulating in the Aerie instance, go to your IDE and start your remote debugger by hitting the bug icon next to your configuration in the right corner of the window. Put any breakpoints that you would like within the model code itself. - -Finally, run a simulation via the Aerie UI (or a third party client). If everything is working correctly, the breakpoints in your IDE should catch the execution of the model and you can debug like normal. diff --git a/docs/mission-modeling/value-schemas.md b/docs/mission-modeling/value-schemas.md deleted file mode 100644 index e65a4bb0..00000000 --- a/docs/mission-modeling/value-schemas.md +++ /dev/null @@ -1,230 +0,0 @@ -# Value Schemas - -As a Mission Modeler sooner or later you will want to define a parameter type, or resource type, that is specifically tailored to your needs. This section describes how to declare the structure of that type so that it can be understood by downstream client tools. - -## Value Schema Basics - -A value schema is a description of the structure of some value. Using value schemas users can tell Aerie how to work with arbitrarily complex types of values, so long as they can be described using the value schema constructs provided by Aerie. - -### Elementary Value Schemas - -At a fundamental level a value schema is no more than a combination of elementary value schemas. Aerie defines the elementary value schemas as follows: - -- `BOOLEAN` - A boolean value -- `DURATION` - A duration value -- `INT` - An integer -- `PATH` - A file path -- `REAL` - A real number -- `STRING` - A string of characters -- `VARIANT` - A string value constrained to a set of acceptable values - -If you're trying to write a value schema for an integer value, all you have to do is use the `INT` value schema. But of course values can quickly take on more complex structures, and for that we must examine the remaining value schema constructs. - -Note the `VARIANT` value schema is a little unique among the elementary value schemas in that it requires input, the set of acceptable values. The way to provide input depends on the context in which you are creating a value schema and will be addressed in the corresponding sections below. - -### Combinatory Value Schemas - -In order to combine elementary value schemas we provide two main constructs: - -- `SERIES` - Denotes a list of values of a single type -- `STRUCT` - Denotes a structure of independent values of varying types - -The `SERIES` schema allows a straightforward declaration of a list of values that fall under the same schema, while the `STRUCT` schema opens things up, allowing you to create any combination of different values, each labeled by some string name. - -Now that you’ve seen the basics, let’s talk about the two different ways to create value schemas – in Java code, and in JSON (serialized value schemas). - -## Value Schemas in Java - -Creating a value schema in Java is straightforward as each of the elementary value schemas is accessible via the `ValueSchema` class. For example, a `REAL` is given by `ValueSchema.REAL`. The one exception is that to create a `VARIANT` type value schema you need to call `ValueSchema.ofVariant(Class enum)`, providing an enum to specify the acceptable variants. - -Like the `VARIANT` element, the `SERIES` and `STRUCT` constructs are created by calling their corresponding methods `ValueSchema.ofSeries(ValueSchema value)` and `ValueSchema.ofStruct(Map map)`. - -### Examples - -Below are a few examples of how to create a `ValueSchema`. In each, a Java type and its corresponding `ValueSchema` are compared. - -- `Integer` is described by `ValueSchema.INT` -- `List` is described by `ValueSchema.ofSeries(ValueSchema.REAL)` -- `Float[]` is described by `ValueSchema.ofSeries(ValueSchema.REAL)` - -Note that the second and third examples are entirely different Java types, but are represented by the same `ValueSchema`. It is also important to take a look at a `Map` type, as it can be confusing at first how to represent its structure: - -- `Map` is described by: - - ```java - ValueSchema.ofStruct( - Map.of( - "keys": ValueSchema.ofSeries(ValueSchema.STRING), - "values": ValueSchema.ofSeries(ValueSchema.INT) - ) - ); - ``` - -Here we are taking note of the fact that a `Map` is really just a list of keys and a list of values. As a final example, consider the custom type below: - -```java -public class CustomType { - public int foo; - public boolean bar; - public List baz -} -``` - -A variable of type `CustomType` has structure described by: - -```java -ValueSchema.ofStruct( - Map.of( - "foo": ValueSchema.INT, - "bar": ValueSchema.BOOLEAN, - "baz": ValueSchema.ofSeries(ValueSchema.STRING) - ) -); -``` - -## Value Schemas in JSON - -Creating value schemas in [JSON](https://www.json.org/json-en.html) is a little less straightforward since your IDE won’t be able to help you with hinting or autocompletion. A value schema is created by declaring an object with a `type` property that tells which type of schema is being created. The values allowed in this property are: - -- "boolean" corresponds to `BOOLEAN` -- "duration" corresponds to `DURATION` -- "int" corresponds to `INT` -- "path" corresponds to `PATH` -- "real" corresponds to `REAL` -- "series" corresponds to `SERIES` -- "string" corresponds to `STRING` -- "struct" corresponds to `STRUCT` -- "variant" corresponds to `VARIANT` - -### Simple Examples - -#### Boolean - -```json -{ - "type": "boolean" -} -``` - -#### Duration - -```json -{ - "type": "duration" -} -``` - -#### Int - -```json -{ - "type": "int" -} -``` - -#### Path - -```json -{ - "type": "path" -} -``` - -#### Real - -```json -{ - "type": "real" -} -``` - -#### Series - -For the "series" type, a second property called `items` must be included as well that provides the value schema for the items in the series. For example here is a value schema for a list (series) of integers: - -```json -{ - "type": "series", - "items": { - "type": "int" - } -} -``` - -#### String - -```json -{ - "type": "string" -} -``` - -#### Struct - -For the "struct" type, a second property called `items` must be included that provides the actual structure of the struct, mapping string keys to their corresponding value schema. For example here is a value schema for a struct with a string-valued `label` property, real-valued `position` property, and boolean-valued `on` property: - -```json -{ - "type": "struct", - "items": { - "label": { "type": "string" }, - "position": { "type": "real" }, - "on": { "type": "boolean" } - } -} -``` - -#### Variant - -For the "variant" type, you’ll need to include a second property called `variants` whose value is a list of objects specifying the string-valued `key` and `label` properties of each variant like this: - -```json -{ - "type": "variant", - "variants": [ - { - "key": "ON", - "label": "ON" - }, - { - "key": "OFF", - "label": "OFF" - } - ] -} -``` - -### Complex Examples - -#### Series of Series of Booleans - -```json -{ - "type": "series", - "items": { - "type": "series", - "items": { - "type": "boolean" - } - } -} -``` - -#### Struct with Series - -This is a value schema for a structure containing a list of integers labeled `lints`, and a boolean labeled `active`: - -```json -{ - "type": "struct", - "items": { - "lints": { - "type": "series", - "items": { "type": "int" } - }, - "active": { - "type": "boolean" - } - } -} -``` diff --git a/docs/overview/assets/activity-type.png b/docs/overview/assets/activity-type.png deleted file mode 100644 index 297f04bc..00000000 --- a/docs/overview/assets/activity-type.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:829e4fca85ee0bd886908302359726a6e5c6595991b18b17f8a04ee340d7d12a -size 38421 diff --git a/docs/overview/assets/aerie-capabilities.png b/docs/overview/assets/aerie-capabilities.png deleted file mode 100644 index 01a5c0e2..00000000 --- a/docs/overview/assets/aerie-capabilities.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ed9ac5971cb9826b2e5f92b10a432c99837ec8cc3b4bf744b19ff23629c37c5 -size 250970 diff --git a/docs/overview/assets/mission-model.png b/docs/overview/assets/mission-model.png deleted file mode 100644 index 7058860c..00000000 --- a/docs/overview/assets/mission-model.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c0caf915b6b4c9a222f5ab7e42e94af9b64eb06ae4f8edf824f2783980f89f7 -size 146233 diff --git a/docs/overview/assets/starting-with-aerie.png b/docs/overview/assets/starting-with-aerie.png deleted file mode 100644 index fcf7ff66..00000000 --- a/docs/overview/assets/starting-with-aerie.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d9a8e13a342b47a11261af55891bf9e8811f10efdfe26bca45b2ae6fae356d5 -size 214967 diff --git a/docs/overview/concept-of-operations.mdx b/docs/overview/concept-of-operations.mdx deleted file mode 100644 index d68315a5..00000000 --- a/docs/overview/concept-of-operations.mdx +++ /dev/null @@ -1,177 +0,0 @@ -# Concept of Operations - -## Introduction - -Planning, scheduling and sequencing are common needs across many space missions, yet a common solution does not yet exist. Many space missions clone and own legacy solutions inherited from a similar, previous mission. AMMOS is dedicated to providing solutions shared across missions, liberating them from re-inventing the wheel and major cost of maintenance. Aerie is the new generation AMMOS product, not only providing a powerful simulation framework like its predecessors, it also addresses the crucial need of enabling less expert users to input their planning requests, trigger simulations, automate scheduling and similar through a collaborative web application. - -Aerie is designed to address the following capability gaps: - -- Simplify mission modeling experience by adopting a common programming language instead of a domain specific language (DSL) -- Simplify mission modeling experience by reducing interdependencies in the mission model code base -- Allow missions to make use of existing libraries for modeling, and write custom code in their mission model as they see fit -- Allow missions to provide a web deployment of a planning tool such that operators with limited programming skills can create valid activity plans and run simulations -- Support real time collaboration and and parallel hypothesis testing such that many more iterations of an activity plan can be tested within the same time frame -- Allow automation and manual modifications to coexist throughout the planning process -- Provide a flexible constraint checking mechanism to validate simulation outputs which can be largely automated -- Provide a flexible scheduling mechanism that can scaffold parts of or generate complete activity plans according to goal snippets -- Support an easy-to-use and verified translation from activities to sequences of commands that are recognized by the flight system - -Note that none of the capabilities above are completely new. Other software solutions have offered pieces of the listed capabilities in different or more limited flavors. Aerie's mission is to cover as many key steps in the whole activity planning and sequencing workflow in one deployed tool with dedicated components. - -Although Aerie does not aim to be a complete solution for every mission, it has a strong emphasis on extensibility, customization and 3rd party tool integrations. - -## Design Tenets of Aerie - -### Enhance automation - -Mission operations may involve labour intensive, error prone tasks such as authoring sequences that implement activities or creating a valid activity plan that does not violate flight rules. Aerie supports automation for key areas including scheduling, constraint checking and translation into commands, which we refer to as command expansion. Aerie is designed to simplify these automations and improve their accessibility and usability for mission operators with diverse skill sets. - -### Design for customization and configurability - -Aerie tools and services allow systems and services to be customized to meet different mission and user needs. For instance, Aerie simulations can be configured to run at different fidelity levels depending on the planners' need at different steps of the planning process. Or an Aerie deployment can be customized to accommodate many parallel simulation and scheduling requests scaled for usage. Similarly Aerie UI can be customized by the mission system or by the users interactively to display data relevant for different contexts. - -### Design for usability - -An integral design guideline for Aerie is to improve user experience for all users. Note that the experience of mission modelers creating a mission model with Aerie framework is highly different than the experience of scientists performing activity planning through a web application, or the experience of an engineer deploying Aerie for all mission users. The Aerie mission modeling framework is striking a tough balance between expressibility and ease of learning. One of the key advantages of using Aerie for modeling is the discrete event simulation engine that takes care of integrating resources for the modelers, handling concurrency of events. This allows mission modelers to model unit behaviors independent from one another. Similarly Aerie web application enables scientists and engineers to trigger simulation with a no-code approach, and perform automated scheduling and simulation analysis with a low-code approach. - -### Design for many contexts - -Aerie can be used in many different contexts other than mission operations where planning and scheduling generally follows strict processes. Aerie can be used to model and simulate any discrete events that affect shared resources. These resources can be on board the spacecraft or on the ground. Aerie can be effectively used in mission planning to generate and evaluate long-term schedules for the purposes of validating high-level mission requirements, and to derive low-level requirements. Similarly Aerie can be used in even earlier phases to develop mission concepts. By design Aerie does not implement mission business logic or dictate workflows. - -### Eliminate duality in planning and sequencing - -Traditionally planning and sequencing are independent, serial processes where operators are responsible for accurately translating activities into spacecraft commands. This duality reduces efficiency, leads to redundant validations at the activity planning and sequencing steps, and can be error prone. Aerie aims to eliminate this duality by using the same simulation engine for activity and command simulations, which can provide an option for missions to do one simulation where abstracted activities decompose into actual commands. - -### Design for customization - -Aerie tools and services should be extendable and customizable to answer the needs of missions with different levels of complexity. In the following sections, we describe how Aerie can be used at its full capacity by a highly complex and automated mission. On the other end of the spectrum, a mission may choose manual activity planning, basically not utilizing modeling, simulation or scheduling capabilities, while leveraging command expansion to automate sequence generation based on planning inputs. The mission can utilize its flight software simulator to simulate commands, upload resulting resource profiles back to Aerie for visualization and analysis. We provide a few examples of such different mission operations in Section 4.14. - - - -## Scope - -Aerie is an extensible framework designed to cover as many key steps in the planning and sequencing process of ground operations all the way up to the sequence products generation. The long-term goal for Aerie is to cover more steps in the planning and sequencing ground operations especially in the areas of i) command level simulations that mimic the onboard command execution behavior more closely, ii) downlink data (telemetry) analysis along with simulation data to close the loop between uplink and downlink, often referred to as predicts versus actuals. The current focus of the product extends until sequence generation. The diagram below summarizes current Aerie capabilities. - -import aerieCapabilities from './assets/aerie-capabilities.png'; - -
- Current Aerie Capabilities -
Aerie offers a wide range of capabilities
-
- -While Aerie does not support observation (science) planning it can be easily integrated with tools that provide specialized features for target selection or surface coverage analysis through its powerful API. Such tools can import a scaffolded schedule, modify observation activities and feed them back to Aerie. - -Similarly Aerie is not a workflow manager, but its API allows missions to orchestrate events and requests across multiple tools that they utilize during their ground operations. You can read more about subscriptions at the API documentation page to learn more about integrating Aerie in complex workflows. - -## Conceptual View - -Aerie is a planning and simulation tool powered by a discrete event simulation engine. Running your first simulation in Aerie requires having a mission model that defines activity types, which are units for planning, and resources that will be tracked over the course of the simulation. A mission model simply defines how activities will affect these resources as they execute. Modelers can define additional dynamic behavior for these activities, expose configuration parameters to modulate simulation behavior and utilize any library or custom code during these calculations. - -import missionModel from './assets/mission-model.png'; - -
- Aerie mission model -
Aerie mission model
-
- -An activity type is a planning unit comprised of parameters that modulate its behavior and an effect model that can query and affect resources. A simple example of an activity is taking an image with an instrument, where the activity increases data volume on board, and the amount of data it will generate depends on the resolution and image size arguments. Similarly, required power can be modeled based on resources like spacecraft orientation. Activities can invoke other activities in their effect model. Aerie allows arbitrary, dynamic hierarchies which we refer to as decomposition. Finally, activities can return values computed during their execution which we refer to as computed attributes. - -import activityType from './assets/activity-type.png'; - -
- Aerie activity type -
Aerie activity type
-
- -Once a mission model is available, creating plans and running simulations can be accessible to a larger operations team through the Aerie UI. The diagram below summarizes steps to run your first simulation through Aerie. The moment that an Aerie installation and a mission model jar is accessible to the operators, they can upload the model to Aerie, create and validate a plan, configure and run simulations and view the results all through the Aerie UI in a no-code manner. - -import startingWithAerie from './assets/starting-with-aerie.png'; - -
- Aerie steps to simulation -
Steps required to run your first simulation in Aerie
-
- -Note that a single Aerie instance can work with multiple models, such that planners can make use of different activity sets in different plans. This allows testing and iterating models and activity type definitions much faster in early mission phases. It is also possible to restrict one or few models to one venue for more strict operation workflows. - -## Scenarios for Using Aerie - -As noted earlier, different missions can utilize Aerie to meet different needs. In this section we will provide several scenarios for using Aerie in realistic contexts. The examples will range from highly complex missions leveraging all Aerie capabilities, to missions interested in a narrower set of capabilities potentially in different phases. - -### Complex mission ground operations with large teams and high automation - -Below is an operations scenario for a complex deep space mission operations team that needs to create a long-term activity plan for the next planing period. The planning period can range from a week to 6 months. - -- The first step is to create a scaffold plan built around a notional DSN schedule. To do so, a DSN schedule can be input to an Aerie simulation, translated into a resource. Various scheduling goals can be written to place telecom activities into the viewing periods that satisfy the selection criteria. Engineering calibration and turns can be placed relative to these telecom activities via scheduling goals, to ensure correct spacecraft orientation. - -- Second, science team members get together to formulate a skeleton science plan. Here team members collectively modify the same plan while discussing their options. At this stage they manually add placeholder activities to the plan that simply allocate resources rather than representing spacecraft behavior more closely. The resulting plan is shared with all subsystem operators in a read-only mode. - -- At this stage each subsystem creates a branch of the skeleton plan. They replace placeholder activities with actuals, refine parameters and run higher fidelity simulations. When ready, subsystems can submit their merge requests (change requests) to the main plan. The main plan owner can review these modifications and approve or reject the requests. - -- Meanwhile an instrument team is utilizing a dedicated observation planning software to optimize surface coverage for the instrument. They can do so by querying the current state of the main Aerie plan and get observation windows that are represented by placeholder activities. The software generates specific observation activities, and replaces the placeholder ones. All such activities are then submitted to the Aerie plan through the API. Here there are options to submit the changes to the main plan or a branch for a more controlled modification workflow. The instrument team also wants to share the surface coverage that they computed in their tool with the rest of the operations team. To do so, they upload the surface coverage over time as an external resource profile, such that other planners can view this information along with the other Aerie simulated resources. - -- As higher fidelity inputs are received, the Aerie main plan gets simulated occasionally, and constraints are checked. When constraint violations are caught, collaborators are asked to modify their activities either directly on the main plan, or following the branch/merge processes. - As planning inputs get more refined, planners can increase fidelity of the Aerie simulations that they run through the simulation configuration mechanism. Adjusting simulation fidelity allows rejecting bad plans earlier in the process and more quickly. - -- As an anomaly condition, the DSN schedule gets updated close to the uplink deadline. A script can process the modifications to determine how to shift existing telecom activities, or which ones to delete and re-create based on changes. Once these changes are done, operators check constraints to detect any violations such as an observation overlapping a telecom activity. Depending on the density of violations, operators can chose to address them by manually by modifying some activities, or deleting and rescheduling a bunch of them according to the updated telecom schedule. - -- Once all the constraint violations are resolved and a satisfactory plan is formulated, the operators can generate commands for activities by pressing a button during the operations process. Note that we are assuming that the command expansion logic for activities have been created ahead of time. The resulting sequences can be queried through Aerie API, and transferred to a mission uplink store for further processing and integration with other uplink products. - -### Mission concept design and mission planning - -In this scenario, mission planners are interested in generating very long term plans to validate high-level mission requirements against the current design. They may be interested in understanding the effects of different orbit insertion scenarios on total observation durations per instrument over the course of the mission. Mission modelers can create low-fidelity activities that capture instrument behavior, and then use Aerie API to run Monte Carlo simulations that slightly vary orbit insertion inputs as well as scheduling logic. Mission planners may only utilize the Aerie web application to share outputs in read-only mode with the rest of their team. - -### Multiple spacecraft sharing ground resources - -Now imagine a scenario where a swarm of Earth-orbiting cubesats share a ground antenna. The antenna can only support few concurrent communications, and a has limited range of view and mobility. The orbit of each cubesat is given and the antenna operations team has to schedule activities for the ground antenna to rotate it into different configurations and set communication parameters to listen to the cubesats in its view period. - -This can be achieved by importing geometric conditions for each spacecraft into Aerie, and translating them into internal resources. Next, the turn behavior of the antenna needs to be modeled, such as the constraints on range of motion, or time it takes to switch from one configuration to another. The mission model can compute optimal viewing geometry for the antenna given all of the spacecraft geometric configurations. A scheduling goal can create communication activities for the antenna, placing them in the computed view periods. The parameters for the communication activities can be set at the scheduling stage, based on specific settings of each spacecraft in view. A constraint checking mechanism can be utilized to ensure each spacecraft has allocated a minimum communication duration within the planning period. Once a satisfactory activity plan is generated for the antenna, operators can refine parameters. Finally, the resulting activity specifications can be captured through the Aerie API to generate inputs for the downstream tool or process. - -## Aerie with 3rd Party Tools - -As a multi-mission framework, Aerie is designed to be as agnostic as possible to the complex ecosystems that it can live within. There are many different data sources in a wide range of formats that are necessary inputs for planning. These can include downlink telemetry, ground antenna schedule and inputs from other simulation tools. Mission modelers can import, parse and utilize arbitrary data in their mission model, reading the data from a file or fetching it from a REST end point. This makes the Aerie framework agnostic to input data formats and date exchange mechanisms. Once such data parsing utilities are implemented by one mission, it can be shared across all Aerie customers in an inner-source or open-source manner. - -For planners to view additional data along with simulation data, Aerie provides an external resource profiles mechanism. Since the Aerie web application and constraint checking service has to recognize this input, it has to adhere to a structure dictated by Aerie. - -Aerie API is a GraphQL API, which is very powerful and expressive. GraphQL allows users to define a custom query that minimizes the over-fetching and under-fetching issues that REST APIs are prone to. Any action that can be taken in the Aerie UI can also be done through the API. This allows missions to automate Aerie actions further or replace certain capabilities with custom counterparts. For instance, it is easy to imagine a mission developing a custom scheduling algorithm that utilizes Aerie simulation data, and inserts activities that it schedules through the API. - -Finally, Aerie API provides subscriptions which allow incorporating Aerie in complex workflows. Missions need to utilize a workflow manager to orchestrate dependencies across their tool chain. For instance, operators may need to trigger a process to generate inputs for Aerie simulation, and trigger a simulation when inputs are available. A subscription can notify workflow manager when the simulation is complete, which can then trigger a process to feed the results to a post-processing stage. When that completes, results can be uploaded back to Aerie for planners to view all of the data together. Note that Aerie framework will not subscribe to any mission-specific process, nor can it be used as a workflow manager. - - diff --git a/docs/overview/design/arch-decision-records/README.md b/docs/overview/design/arch-decision-records/README.md deleted file mode 100644 index 3c3beb8d..00000000 --- a/docs/overview/design/arch-decision-records/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Architectural Decision Record (ADR) - -Write down key architecture design decisions along with the context and implications of the decision. Each decision record should describe a single decision. What makes a decision architectural and not simply detailed design is subjective, but here are some features of an architectural decision: - -- It affects another component or team -- The decision affects how the system influences on or more quality attributes, for better or worse -- The decision was precipitated by a business or technical constraint -- The decision has far-reaching, significant impact such as framework or technology choice -- The decision fundamentally changes the way the team develops or ships the system - -## Guidelines - -- Include only one decision per file -- Sequentially number ADRs and keep old records. Add references to old records when a decision is superseded or changed. -- Keep ADRs short, one or two pages at most. -- Use plain language when recording decisions. -- Put architecture decisions through the same review process as code. -- Store in version control -- Augment with other architecture documentation - -## Statuses - -Draft, Proposed, Accepted, Superseded, or Deprecated - -Additionally, Retroactive will be used for a decision that was made far in the past, and we're merely documenting it - -## Acknowledgements - -Credit to Michael Keeling for much of the text in this README diff --git a/docs/overview/design/arch-decision-records/adr-0000-adr-process.md b/docs/overview/design/arch-decision-records/adr-0000-adr-process.md deleted file mode 100644 index 0ac0f456..00000000 --- a/docs/overview/design/arch-decision-records/adr-0000-adr-process.md +++ /dev/null @@ -1,31 +0,0 @@ -# ADR-0000 - Establish ADR Process - -## Context - -Over the past year, the Aerie project team has been tasked by its primary sponsor, the NASA AMMOS program, to incorporate enhanced spacecraft sequencing capabilities into Aerie (this task has been colloquially called SEQ 2.0). The program is interested in ensuring sufficient oversight to preserve stakeholder buy-in on the task as it progresses. In addition, new organizations are contributing to the project (e.g. Goddard Spaceflight Center, Ames Research Center) that have less familiarly with the Aerie project and its history. These drivers have created an increased need to clearly communicate architectural decisions and their rationale. - -## Decision - -Create an architectural decision record and use it to track architectural decisions. This record will live alongside the rest of the project's documentation in a separate section. - -Identifiers for new decisions made after the establishment of this process will begin at 0100 and will tick upward monotonically (numbers below 0100 will be used to document historical decisions the project feels are worth communicating to future contributors). ADRs will be loosely chronological, but are not required to be. If we find the need to document a past decision, it's fine to allocate it the next available number. - -A basic template and readme will be available to create new ADRs and briefly describe the content that should be within a record. - -## Alternatives Considered - -We could use the GitHub PR and issue history to track these decisions, but it can be difficult to traverse those. GitHub Discussions are also a viable home for these records, but that precludes offline editing, and isn't particularly dense or organized. Remaining in GitHub as opposed to documenting these decisions in a different location is preferred to keep these decisions close to the code that ultimately reflects these decisions. - -## Consequences - -### Positive - -- The project will have a clear written record that explains where we are today and how we got here, which currently does not exist -- Records will be tracked in the main repository alongside the source code so that architectural decisions are transparent to the project community and can be authored, reviewed, and approved by community members -- ADR reviews can serve as an opportunity for project stakeholders and sponsors to provide feedback on the direction of the project -- There is a lower barrier of entry to get involved in the projects design process since it can be seen by all members of the community - -### Potential Negative - -- If not kept up to date, these documents may become misleading and actually be more hurtful than helpful. -- Writing and reviewing these documents will take time on an already resource constrained team diff --git a/docs/overview/design/arch-decision-records/adr-0001-java.md b/docs/overview/design/arch-decision-records/adr-0001-java.md deleted file mode 100644 index 377c8fcd..00000000 --- a/docs/overview/design/arch-decision-records/adr-0001-java.md +++ /dev/null @@ -1,33 +0,0 @@ -# ADR-0001 - Java as a mission modeling language - -## Status - -Retroactive - -## Context - -APGen, Aerie's predecessor, used a Domain-Specific Language (DSL) for defining activities and resources. - -From a paper on Blackbird (see [References](#references)), a key source of inspiration for the Aerie project: - -> Much of Blackbird’s ease of use derives directly from how adapters write code directly in an industry-standard programming language. Features such as an integrated development environment (IDE) with debugging, static code analysis, performance profiling, ease of calling external libraries, online reference resources, native test frameworks, formal dependency management, and more have all reduced development time and uncaught errors. Java has been GitHub’s second most-used language since 2013 [13] and the first most-used on the TIOBE index since 2015 [14], so new developers may already be familiar with it, and all adapters benefit from an active community and helpful resources. To create an activity in Blackbird, only a small subset of the full Java language is required, so even new adapters can make contributions quickly, then add more complex behavior as their proficiency grows. Blackbird adapters typically become proficient in about two weeks full-time. Blackbird adaptations listed in later sections all required people working only part-time for limited commitments, whereas traditionally missions need to dedicate at least one full-time person for years. If a project decides that they want to use another language to write their activities, they can seamlessly incorporate Python-like Groovy, JavaScript-like Kotlin, or Lisp-like Clojure, which can all inherit Blackbird’s Java base classes. Groovy and Kotlin proofs of concept were built which motivated multiple projects using Blackbird to consider partially switching to one of these other languages to further improve the new adapter experience. Based on experience training more than 10 people to use Blackbird, the largest impediments to ease of use are not the language syntax, but the use of an IDE for adapters without prior experience, and understanding when their adaptation code will execute as part of the simulation. One is not forced to use an IDE to develop in Blackbird, but the initial investment to learn to use one is often worth the reduced risk and improved productivity afterwards. Stepping through code in the debugger has greatly helped adapters understand when their code will be called, which will always be non-trivial for any complex system. There is a detailed adapter’s guide which walks new users through defining and deploying an adaptation. - -13. "Projects", The State of the Octoverse, [online] Available: https://octoverse.github.com/projects.html -14. TIOBE Index, [online] Available: https://www.tiobe.com/tiobe-index/. - -## Decision - -Based on much of the same rationale provided by Blackbird, we choose to use Java as the language that users will use to define mission models - -## Alternatives considered - -Python is another very popular language within the aerospace and scientific communities. When providing demonstrations of Aerie's Java mission modeling language to various organizations, there have been many requests for a Python framework in addition or in lieu of a Java on. Adding or replacing the Java mission model framework with a Python one would likely help product adoption. However, there are technical challenges to hosting Python. It has a history of breaking changes, and has no notion of an "interface". It is not designed for multi-tenancy, has no JIT-compiler, and has a largely architecture-specific ecosystem (e.g. key python libraries are often written in C to improve performance and cannot easily be ported between different compute architectures). We also felt strongly that the mission model should be written in the same language as the simulator itself, and Python was not considered an appropriate implementation language for the performance-critical or complex parts of the application. - -## Consequences - -- Missions have been able to push Java quite far, pulling in frameworks like Guava, and Dagger. -- Potentially lower adoption rates from mission users whose personnel and ground systems are primarily built on python - -## References - -- C. R. Lawler, F. L. Ridenhour, S. A. Khan, N. M. Rossomando and A. Rothstein-Dowden, "Blackbird: Object-Oriented Planning, Simulation, and Sequencing Framework Used by Multiple Missions," 2020 IEEE Aerospace Conference, Big Sky, MT, USA, 2020, pp. 1-20, doi: 10.1109/AERO47225.2020.9172680. diff --git a/docs/overview/design/arch-decision-records/adr-0002-graphql.md b/docs/overview/design/arch-decision-records/adr-0002-graphql.md deleted file mode 100644 index f69b96ff..00000000 --- a/docs/overview/design/arch-decision-records/adr-0002-graphql.md +++ /dev/null @@ -1,161 +0,0 @@ -# ADR-0002 - GraphQL instead of REST API - -NOTE: Apollo has been replaced by Hasura as of [0003-hasura-and-postgres](../adr-0003-hasura-and-postgres). - -## Status - -Retroactive - -## Context - -#### Aerie API Needs - -1. Evolve the internal APIs rapidly and the public APIs slowly. - - The development of the Aerie user interface and application - internal components proceeds in parallel. As a result, the user - interface’s data needs imposes constraints on the definition of the - system’s public API. The evolving nature of the user interface’s - development makes it difficult to carry out an API design effort, - as would be the case for a REST API architectural style. Further, - the structure of resources needed by the highly configurable - interface components within the user interface, makes defining an - efficient set of resource endpoints prohibitively difficult. Such - endpoints would require continuous editing and updating as - development progressed. - -2. Multiple clients and multiple different workflows for clients. - - There exists a category of use cases in which customers develop - custom Aerie client applications. Such customization requires the - flexibility to easily define new data projections as simple queries - constructed by a client. As a result, Aerie benefits from an API - that supports high query flexibility from both the external client - -3. Ability to dynamically reduce/transform response payloads. - - Within the planning, scheduling and sequencing domain are a number - of common list like data structures/concepts which are often quite - large in size (number of elements). It is inefficient to impose - upon any client seeking a particular view/aspect of the data - structure to request and process the entire structure. For example, - certain resources can’t be made "smaller" without compromising - their intent. A Plan can be filtered but not sensibly - partitioned. It is desirable that any client requesting such data - structures be provided with an easily accessible means to query the - data structure for the elements/projection of interest. - -4. Custom queries and batch fetching. - - The Aerie stores a number of significant mission data sources - (E.g. activity plan, simulation results, and constraint violation - results). Aerie must provide users flexible access to this data to - support an arbitrary space of use cases for reporting, auditing and - interfacing with third-party customers. - -#### Trade Study - -A trade study was conducted to evaluate available API products. A down selection of the tools left both the [Tyk API](https://tyk.io/) gateway product and [Apollo GraphQL Server](https://www.apollographql.com/) product. The primary difference between these two products are their approach to exposing data. The Tyk gateway exposes system queries as [Representational State Transfer(REST)](https://www.codecademy.com/article/what-is-rest) endpoints while the Apollo GraphQL server exposes a single GraphQL query endpoint. The REST and GraphQL architectural styles present different approaches and embody contrasting capabilities. Representational State Transfer is an architectural style for distributed hypermedia systems. GraphQL is a query language for an API, exposed as a typed schema defined by a data graph. Table 3 presents a set of desirable API properties and the manifestation of each property for REST and GraphQL driven APIs. - -Table 3 Comparison of REST and GraphQL capabilities - -| System Property | REST | GraphQL | -| -------------------------------------------------- | ------- | ------------------ | -| Modifiability | ✅ | Runtime inspection | -| Scalability | ✅ | ❌ | -| Portability | ✅ | ✅ | -| Reliability | ✅ | ✅ | -| Simplicity | ✅ | ✅ | -| Visibility | ✅ | ✅ | -| Performance | ✅ | ❌ | -| Discovery and Introspection | Limited | ✅ | -| Consistency | ❌ | ✅ | -| Ease of Server Development | ❌ | ✅ | -| Ease of Client Development | ❌ | ✅ | -| Over-fetching protection without proper API design | ❌ | ✅ | -| Active Community | ✅ | ✅ | -| Tooling Server | ✅ | ✅ | -| Tooling Client | ✅ | ✅ | -| Tooling API Management | Limited | ❌ | -| Maturity | ✅ | ❌ | -| Works with any data representation | ✅ | ❌ | -| Printed Books | ✅ | ✅ | -| Enterprise Ready | ✅ | ✅ | - -The following is a discussion of the particular API qualities which -provide for Aerie’s needs. - -- **Discovery and Introspection** - The GraphQL data graph schema - provides a contract-like mechanism where requests and replies are - inherently typed and can be directly validated and resolved based on - those types. This contract like nature completely describes all - possible requests/responses where a typed service provider won’t - compile until it fully implements its contract. A typed service - consumer will be type-checked at compile time, which helps to catch - problems before deployment. Finally, it is unreasonable to expect - that a well-performant API can be developed for every conceivable - use case. As a result the improved introspection at the per field - level in GraphQL allows for targeted optimization of common or slow - queries. - -- **Consistency** - The API schema is typed and therefore either - correct or not. As a result, there is an inherent consistency - between client and server because both must abide by the generated - schema. - -- **Ease of Server Development** - It is easier (development time, - complexity) to develop and maintain data source resolvers as part of - a GraphQL server. Well designed, true REST APIs take time and - resources and are therefore more difficult to design and - maintain. GraphQL relieves the project of that unnecessary burden. - -- **Ease of Client Development** - A client can develop against the - exposed contract. A client can develop custom queries targeted to - its own use cases to limit both over and under fetching. In many - cases this may reduce latency and increase performance by limiting - client side data manipulation/filtering. - -- **Flexibility of API Design** - User and mission needs are - constantly evolving. GraphQL decouples the API allowing the Aerie - team to make adjustments to the API according to evolving customer - needs. Additionally, the increased granularity and visibility when - auditing the frequency and combinations with which certain fields - are queried, allows for clearly validated deprecation, removal, and - changes of fields available in the API schema. - -## Decision - -Use Apollo GraphQL. (Later, [[0003-hasura-and-postgres]]) - -The Aerie GraphQL API presents a consistent application boundary to Aerie users. The API server enables the composition of multiple APIs (internal to the application) as a single API endpoint. The API component additionally provides a location in the system for the following needs: - -- Manipulation of data -- Response Caching - -## Consequences - -By adopting GraphQL we knowingly forgo certain -capabilities/constraints of a REST API. Three cases have been -identified as possible risks and sufficient mitigation options are -identified: - -1. **Tooling API Management** - GraphQL is a newer technological approach to APIs (2012). - **Mitigation:** Aerie has chosen to use Hasura, a major open source contributor to the GraphQL community. -2. **Caching** - REST over HTTP benefits from existing HTTP server caching and browsers client caching mechanisms. - **Mitigation:** Most GraphQL libraries have caching mechanisms built in. Hasura caching must be handled with annotations/directives on the graph definition. -3. **Client-API Loose Coupling** - Each new client application must make affordance at development time and hardcode custom queries and mutations as made possible by the Aerie GraphQL schema. - **Mitigation:** None. In the Aerie context this is not considered a benefit. - -## Retrospective - -Note, since the time of this original decision, the loose coupling of the client API provided by GraphQL has resulted in some challenges for missions using or considering to use Aerie: - -- While missions have full flexibility in how they build queries, they must go through the legwork of building a set a queries to perform basic functions that legacy tools provided out of the box (e.g. exporting plans and simulation results). This results in added cost and an increase to the barrier of entry for missions considering Aerie. In fact, the first user of Aerie found it necessary to build a command line tool for Aerie ([aerie-cli](https://github.com/NASA-AMMOS/aerie-cli)), which they graciously provided back to the community and is now maintained by the Aerie project. -- Similarly, since Aerie lives in the greater ground system ecosystem where tools often communicate to each via files, common file formats are especially useful. Missions often don't want to go through the work of creating their own file formats if pre-built ones are sufficient. - -In addition to the flexibility provided by GraphQL, there still seems to be a need for Aerie to offer an "out of the box" solution (file formats and standard queries) that works for most use cases and reduces the workload on missions. - -## References - -- https://tyk.io/ -- https://www.apollographql.com/ diff --git a/docs/overview/design/arch-decision-records/adr-0003-hasura-and-postgres.md b/docs/overview/design/arch-decision-records/adr-0003-hasura-and-postgres.md deleted file mode 100644 index bc3feffe..00000000 --- a/docs/overview/design/arch-decision-records/adr-0003-hasura-and-postgres.md +++ /dev/null @@ -1,53 +0,0 @@ -# ADR-0003 - Use Hasura and Postgres - -## Status - -Retroactive - -## Context - -At the time, the Aerie team was developing and maintaining an API server, using [Apollo](https://www.apollographql.com/). The team found themselves spending a lot of their time on repetitive tasks involving adding endpoints to create, read, update, or delete some domain object. - -## Decision - -Implementing an API requires developers to write a lot of repetitive code. Many API calls are simply Create, Read, Update, or Delete (CRUD) operations to the database. The translation of GraphQL queries to SQL can be automated to save developers a lot of toil. This is what [Hasura](https://hasura.io/) does. - -To add a new node to the GraphQL API graph, a developer needs to create the corresponding table in the database, and update the Hasura metadata, stored in yaml files, to communicate to Hasura that that table should be exposed via the API. - -Hasura produces rich APIs that allow sorting and filtering data out of the box. This saves developers the work of adding sorting and filtering to each node individually. - -As a result, Hasura boosts the productivity of the development team, and provides a higher quality API to users. - -```mermaid -flowchart - UI --> Hasura - Hasura --> Postgres - Hasura --> Merlin - Merlin --> Postgres -``` - -## Alternatives considered - -The status quo before this decision was made was Apollo GraphQL with a MongoDB backend. The architecture looked more like this: - -```mermaid -flowchart LR - UI --> Apollo - Apollo --> Merlin - Merlin --> MongoDB -``` - -Every new domain object would need to be added first to MongoDB, then to Merlin and exposed as an HTTP endpoint, and lastly to Apollo and exposed as a GraphQL endpoint. - -Hasura came with postgres support out of the box, and this to some extent drove the decision to switch to postgres. Additional justification came in the form that most Aerie data is well structured, and fits well into a relational schema. The need for open-ended user-defined data will be implemented using `jsonb` , which we expect to be sufficient for our purposes. - -MongoDB was originally selected because it's the backend for RAVEN, which some of the developers had worked on before. - -## Consequences - -Using Hasura allowed the Aerie team to deliver features like filtering and sorting for every query without additional development effort. - -## References - -- https://hasura.io/docs/2.0/index/ -- https://www.postgresql.org/docs/ diff --git a/docs/overview/design/arch-decision-records/adr-0004-typescript-edsls.md b/docs/overview/design/arch-decision-records/adr-0004-typescript-edsls.md deleted file mode 100644 index 394a6c68..00000000 --- a/docs/overview/design/arch-decision-records/adr-0004-typescript-edsls.md +++ /dev/null @@ -1,36 +0,0 @@ -# ADR-0004 - TypeScript as the User-Facing Language for Scheduling, Constraints, and Command Expansion - -## Status - -Retroactive, partially Superseded: - -Command Expansion is still in Typescript, and Scheduling and Constraints checking TypeScript interfaces continue to be supported. Scheduling is now also supported in java. - -## Context - -Scheduling and Command Expansion were both expected to be delivered around the same time. A user study had just concluded regarding users' preferred syntax, and the conclusion was that JavaScript and Lua were tied for first place. It was suggested that if we do JavaScript, that TypeScript would provide better - -## Decision - -Describe the decision you made. Refer to references by small number [0] and other decision records as a four-digit number [0000] - -## Consequences - -Describe how your decision will or has changed the circumstances of the system, stakeholders, and team. Both positive and negative consequences should be included. Update this section as consequences emerge and are understood. - -## Alternatives considered - -- For Scheduling: - - Lua - - JavaScript - - LISP -- For Constraints: - - JSON specification (status quo) -- For Command Expansion: - - Python -- For Sequence Authoring: - - VSCode extension with custom syntax - -## References - -- `[0]` http://example.com diff --git a/docs/overview/design/arch-decision-records/adr-0005-sequencing-typescript.md b/docs/overview/design/arch-decision-records/adr-0005-sequencing-typescript.md deleted file mode 100644 index aeab0e57..00000000 --- a/docs/overview/design/arch-decision-records/adr-0005-sequencing-typescript.md +++ /dev/null @@ -1,37 +0,0 @@ -# ADR-0005 - Typescript Sequence Editor (SUPERSEDED) - -## Status - -**Superseded** by [ADR-0006](../adr-0006-phoenix-editor) - -## Context - -In the spring of 2022, the project was under immense pressure from its primary sponsor (MGSS) and its first committed customer, Europa Clipper, to deliver an integrated sequence editing capability within Aerie. Much of the pressure was driven by Clipper's impending launch date and its upcoming verification and validation activities in preparation for launch. - -Prior to this time, a VS code extension known as the "Falcon Sequence Editor" had been developed, which allowed users to author and edit sequences in a particular language format and generate corresponding seq-json sequences that could be passed to other tools within a mission's ground system. The Falcon sequence editor was completely separate from Aerie, and thus at the time, there was no way to share information between the editor and Aerie. - -TODO: Add some additional context on why the current Falcon Sequence Editor was insufficient to meet needs... - -## Alternatives considered - -Since a VS code extension already existed, one option was to continue to build upon that extension and provide an integration mechanism between the extension and Aerie. Another option was to abandon the VS code extension and add sequencing capabilities directly into Aerie by using a web-based code editor such a [Monaco](https://github.com/microsoft/monaco-editor), the editor that powers VS Code. - -Some pros/cons of these options are shown below: - -| Editor | Pros | Cons | -| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| VS Code Editor | High-level editor API, general purpose editor, Git integration, local file-system integration, both native and browser-based solutions, rich extension library, offline usage | Browser-based solution requires management server, no sequence deep-linking, clunky user authentication, strict UI/UX dependent on extension API | -| Monaco + Aerie UI | Rich integration with Aerie, plan-sequence deep-linking, fluid authentication, custom UI/UX | Low-level editor API, no offline usage, no Git integration, no native file-system integration, no extension API | - -## Decision - -Create a sequence editor directly in the Aerie UI using [Monaco](https://github.com/microsoft/monaco-editor). Since Aerie already uses Monaco to support scheduling and constraint authoring, the number of added dependencies should be limited. As with the scheduling and constraint authoring functionality, users will author their sequences via an embedded domain specific language (eDSL) using TypeScript. Since the tech stack to support TypeScript eDSLs already exists, the project feels that this approach is the shortest path to providing an integrated sequence authoring experience. Users will have the ability to generate seq-json sequences from the TypeScript sequences, which can then be used in downstream tools. - -## Consequences - -- From research, we know that many users are used to writing sequences in bespoke language formats that don't completely align with the TypeScript grammar. For example, sequence language formats often use space-delimited arguments for commands as opposed to comma-delimited arguments as TypeScript. This may impact user's perception on readability and usability of the editor. -- Given how commonplace VS Code extensions are, it is fairly easy for mission developers to understand them and make modifications if required. With the sequence editor embedded in the UI, customizations will be more challenging unless Aerie develops a mechanism that exposes certain editor features without having to modify Aerie core. - -## References - -- https://github.com/microsoft/monaco-editor diff --git a/docs/overview/design/arch-decision-records/adr-0006-phoenix-editor.md b/docs/overview/design/arch-decision-records/adr-0006-phoenix-editor.md deleted file mode 100644 index 530845a8..00000000 --- a/docs/overview/design/arch-decision-records/adr-0006-phoenix-editor.md +++ /dev/null @@ -1,55 +0,0 @@ -# ADR-0006 - Phoenix Editor with SeqN Language - -NOTE: This ADR supersedes [ADR-0005](../adr-0005-sequencing-typescript) - -## Status - -Retroactive - -## Context - -The Aerie sequence editor currently provides users with the ability to author and edit sequences in a TypeScript embedded Domain Specific Language (eDSL). As this capability has been rolled out, the feedback from users has been far from positive. Below are some of the issues that have been identified: - -- While part of the goal of the sequence editor was to not require the user to write seq-json, users feel writing a sequence in TypeScript is just as bad as json. Note that a large portion of sequencers may not be programmers by background, so things that look like code can be scary for them. -- The sequence editor has major issues with readability and review - - The editor does not enforce one command per line, which makes it easy to miss commands during a review - - No automatic indentation or support for proper indentation - - No checking that ifs and loops are closed by the proper commands -- Setting up a new sequence is not intuitive and requires typescript structures to be defined that have no meaning to sequence developers (e.g. `export default () => Sequence.New()`) -- "User-friendly" names for telemetry channels cannot be used, so one must use IDs, which is not preferable to sequencers -- The use of sequence variables and input arguments is cumbersome and not well documented -- Autocomplete feature is nice, but does not support certain cases well (e.g. does not auto complete for arguments that have defined/enumerated values) - -The issues with the editor have been significant enough for our first committed customer, Europa Clipper, to reconsider using the editor altogether and revert back to writing sequences in COTs editors, which do not offer out of the box linting support for its sequencing formats. This obviously does not bode well for future customer adoption of the Aerie sequence editor. A major architectural change or alternate solution is needed if we want to keep Clipper as a customer of our sequencing capabilities and garner additional adoption in the future. - -Another important note is that at the time of this decision (early 2024), Europa Clipper decided to more widely embrace a sequencing format called "seqN", which was originally developed to perform sequencing in the test bed for verification and validation activities. Therefore, supporting SeqN became a requirement if we wanted wide adoption of our editor on Clipper. Also, there was significant time pressure from Clipper to deliver an alternate solution as they needed to firm up their ground system tooling in the next few months. - -## Alternatives considered - -Providing a more usable and desirable sequence editor to Clipper was paramount to reestablishing a positive reputation with the project as many members of the project already or will support other target mission customers. Therefore, delivery of an editor that worked for Clipper had to take priority at the expense of ensuring the editor was designed with full multi-mission support. - -A standalone prototype web-based editor using [CodeMirror](https://codemirror.net/) had already been developed as a proof of concept that used a custom Domain-Specific Language (DSL) that could provide analogous semantics to seq-json, but in a more compact syntax. The compact syntax was defined as a [Lezer](https://lezer.codemirror.net/) grammar, which enables quick implementation of features such as highlighting, syntax-aware selection, auto-indentation, and code folding. - -Given the time pressure to deliver something workable for Clipper, it was an obvious choice to start with the prototype, especially since many developers on the team were already familiar with CodeMirror and Lezer. Moreover, the prototype was demoed to users and feedback was very positive. - -While the prototype was standalone, there was a question as to whether it should remain standalone or be integrated into Aerie much like the current eDSL sequence editor. The primary benefit of integrating the new sequence editor into Aerie was to take advantage of the Authentication and Authorization features already available there, which would significantly reduce the time to delivery. However, tying the editor to Aerie's activity planning capabilities meant that a project could not update the editor without also taking updates to activity planning capabilities. - -## Decision - -The project will create a new sequence editor called Phoenix that is fully integrated into Aerie and will deprecate the TypeScript sequence editor. The editor will use CodeMirror and will provide a seqN grammar (defined in Lezer) out of the box that can be exported in seq-json, a more machine friendly sequencing syntax. - -Although delivering a working editor for Clipper is of highest priority, the development team will try to keep Clipper-specific functionality out of the core Aerie code by developing an adaptation interface that allows custom behavior to be injected into the editor via CodeMirror extensions. - -Upon initial evaluation, we believe the sequencing constructs defined within seqN are sufficiently generic that the language could be used to support a variety of different missions and flight softwares, so providing this language out of the box could add value for potential mission users who have not selected a sequence language to use. However, we must also ensure support for other sequencing languages since there is no standard language used by all missions. Fortunately, additional sequencing grammars can be defined and supported (even simultaneously) in CodeMirror. - -## Consequences - -- We will inevitably incur some technical debt as we rush to deliver something for Clipper, but this should in part be mitigated by establishing an adaptation interface. -- Tying Phoenix to the activity planning portion of Aerie could cause issues if a mission wants to take Phoenix updates without taking activity planning updates, especially if those updates include breaking changes. -- Adds a new dependency (CodeMirror) to Aerie so that Aerie will now depend on two code editors (Monaco for scheduling, constraint, and expansion eDSLs and CodeMirror for sequencing). This will likely increase maintenance costs. -- The activity expansion capability within Aerie will expand sequences into seq-json as opposed to seqN, so developers of expansions will have to be familiar with two separate languages instead of one. - -## References - -- https://codemirror.net/ -- https://lezer.codemirror.net/ diff --git a/docs/overview/design/arch-decision-records/adr-0100-phoenix-sde.md b/docs/overview/design/arch-decision-records/adr-0100-phoenix-sde.md deleted file mode 100644 index ec8cc3dc..00000000 --- a/docs/overview/design/arch-decision-records/adr-0100-phoenix-sde.md +++ /dev/null @@ -1,89 +0,0 @@ -# ADR-0100 - Phoenix as a Multi-Mission Sequencing IDE - -## Status - -Accepted - -## Context - -The Aerie project team was tasked by its primary sponsor, the NASA AMMOS program, to deliver enhanced multi-mission spacecraft sequencing capabilities to missions (this task has been colloquially called SEQ 2.0). - -One of the highest-priority functions to deliver as part of SEQ 2.0 as determined by user research is "sequence integrated development". This is defined as - -> The user-facing function that enables sequence users to author, edit, statically verify, simulate, and compile command sequences through UI actions. - -A number of requirements have been defined for this function. Some notable requirements are: - -- Support for a variety of sequencing languages for FCPL, VML, [cFS](https://github.com/nasa/cFS), and [FPrime](https://github.com/nasa/fprime) flight softwares -- Real-time validation against mission command dictionaries -- Interfaces to mission-defined compilation, static checking, and simulation services -- Review and edit the output of an automated sequence generation process -- Author and review reusable sequences or one-time bespoke sequences - -A decision on the best path forward for the high-level architecture and technology stack for this function must be determined as the project moves into implementation. - -## Alternatives Considered - -There have been a number of past and current efforts to develop sequence integrated development functionality. A number of alternative solutions were considered: - -1. Inherit an existing tool (e.g. Phoenix) and adapt it to be multi-mission -2. Develop a new sequence development tool from scratch -3. Develop a tool based on an existing IDE framework, similar to MPS Editor and MSLICE - -### Existing Tools - -Many tools at JPL and NASA have provided sequence development capabilities. Below is a brief assessment of each tool: - -| Tools | Description | Assessment for SEQ 2.0 adoption | -| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Phoenix Editor | A web-based sequence editing tool already incorporated into Aerie originally developed to support a time-sensitive delivery for Europa Clipper (see [ADR-0006](../adr-0006-phoenix-editor)). | - Originally designed for multi-mission use cases 
- Already relatively feature-rich and supports real-time sequence validation against command dictionary 
- Is heavily Clipper-focused, but expect that it can be expanded to support other missions 
- Can be run in the cloud 
- Can be expanded to support requirements listed in the summary above | -| MSLICE | A proprietary tool developing using the Eclipse framework for the Mars Science Laboratory (MSL) project and the Curiosity rover | - In-use by MSL, but is a complete planning and sequencing tool. Extracting just the sequence authoring function would be a signifcant effort
- The Mars 2020 (M2020) project (i.e. Perserverence) decided not to adopt this because of significant maintenance and update costs and because it is not a web application, which would prevent tight integration with other M2020 tools | -| Sequencer | A proprietary sequence authoring tool developed by M2020 for M2020 that primarily uses an open-source library called [Code Mirror](https://codemirror.net/) on the backend. | - Very powerful and feature complete, but not designed for multi-mission and is intricately connected to other elements of the M2020 sequencing system.
- Extracting just the sequence authoring functions would be more work than simply adopting the Phoenix editor
- Starting from this tool was one of the paths considered when the effort to overhaul the Phoenix editor for Clipper was begun and the Aerie team made the decision to start from scratch in part because it uses an older version of Code Mirror (r5 vs r6) | -| ASIST | A real-time command and control system that provides an environment for the development, integration, testing, and on-orbit operation of spacecraft, their subsystems and instruments, as well as external control and support equipment | - In use at NASA Goddard but en-route to deprecation. | -| MPS Editor | A multi-mission Eclipse (IDE) framework provided by AMMOS in the past, used by many missions, and is now deprecated | - MPS Editor has been deprecated by AMMOS for the following reasons:
- MPS Editor was challenging to maintain and keep up with newer versions of Eclipse (similar issue to MSLICE)
- MPS Editor began to get unstable on recent Red Hat versions and the fixes/testing to stabilize it were non-trivial
- MPS Editor is so old it had become extremely expensive to maintain. The build system was really challenging. An upgrade to a newer platform would liekly require a complete rewrite. Testing was also very expensive as the majority of testing was manual | - -Of all the existing tools, our assessment is that the Phoenix editor is the best-suited for adoption. - -### New Custom In-House Tool - -The path of developing a new, custom tool entirely from the ground up is worth considering if: - -1. Adopting and expanding an existing tool to meet requirements will take more effort than developing a new tool from scratch -2. Adopting and expanding an existing tool to meet requirements will take similar or less effort to developing a new tool from scratch, but the adopted product is ultimately more difficult to maintain or less user-friendly than a new tool would be - -At the time of this writing, our assessment is that the Phoenix sequence editor can be adopted, made multi-mission, and expanded with less effort than developing a new tool. The Phoenix editor is sufficiently user-friendly (it has received very positive feedbacl from Clipper thus far) and maintenance efforts are expected to be on par with any new tool that is developed from scratch. We have found no architectural issues with the Phoenix editor that would prevent us from adding the most critical features: - -1. Expansion to support sequencing for the Psyche mission (Psyche requires sequences to be imported/exported in legacy sequence formats like SATF and SASF), VML, cFS, and Fprime missions -2. Interfaces to simulation service, compilation service, and static checking services -3. Enhanced real-time static validation against command, parameter, and telemetry dictionaries. - -Therefore, the team's assessment is to pursue adoption of the Phoenix editor over developing a new tool. However, we reserve this option as a fall-back if an insurmountable obstacle is encountered with the Phoenix editor, or if our assessment of any of the above criteria changes. - -### Customize Existing IDE - -This approach has been tried with varying levels of success in the past and present. MSLICE and MPS Editor were both IDE frameworks. See the table below for an assessment of this option against adoption of the Phoenix editor. - -| | Phoenix Editor | Customize Existing IDE | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Usability | - Offers much more control over the user experience, because it is designed from scratch
- User experience design can be directly catered towards sequence authoring | - User experience is constrained the experience provided by the chosen IDE
- User experience will be adopted from software development/programming | -| Effort to Develop | - A Minimum Viable Product (MVP) is already in-use by Europa Clipper | - Would have to be developed from scratch, but likely much of the existing code could be ported into VSCode extensions | -| Editing Features | - Currently supports many required features for sequence authoring
- No significant hurdles currently foreseen in adding other required authoring features | - Many desired editing features are provided out of the box:
- Find/Replace
- GitHub interface
- Syntax Highlighting (although grammar would need to be defined) 
- Linting
- Other general IDE features can align well in concept with required SEQ 2.0 features: 
- Running/Debugging → simulation
- Compilation 
- However, while these features align in concept with required SEQ 2.0 features, actually implementing them may not be straightforward | -| Tech Stack | - Significant overlap in tech stack with existing Aerie functions. This overlap allows easy adoption of features such as Authentication and Authorization and file storage 
- A potential high risk is the reliance on the Code Mirror open-source library, which is currently maintained by a single developer. This risk is mitigated by the adoption of the library by large companies such as Microsoft. If the developer is not responsive or pauses developing the library, the Aerie team could take over maintenance and development or an effort could be expended to port the backend to Monaco | - No significant overlap with existing Aerie tech stack, meaning several features may have be to re-implemented, or new interfaces designed to existing features written | -| Maintainability | - Under our control when we want to update things- Parts of the app could still feel dated, but we can update at our own pace
- All the work will be on the Aerie team to maintain modern feel 
- Aerie team is already familiar with CodeMirror | - Keeping pace with industry IDE version is very challenging - updating MSLICE from 3.6 to 4.0 took over a work year. MPS editor faced similar challenging keeping up with Eclipse versions 
- Keeping up with industry version is all that's needed to maintain modern feel (performance, user-friendliness of UI) 
- Team will need to learn new extension language rather than using CodeMirror or Monaco | -| Extensibility | - Initial assessment is that extending Phoenix editor to support VML, cFS, and Fprime is doable | - Can be extended to support other languages, but level of effort is unclear | - -## Decision - -The Aerie team's assessment is that continuing to develop the Phoenix editor and extending it to meet additional requirements will require the least level of effort compared to all the other options and will result in the best end product for customers. - -## Consequences - -- The greatest risk that we foresee with adopting the Phoenix editor is that support for the Code Mirror open-source library is lost, but this risk can be mitigated by: - 1. taking ownership of Code Mirror into Aerie or - 2. porting the tool to Monaco. -- Since Clipper already relies upon Phoenix, and enhancements will have to be made to support new requirements, there is a high probabilty Clipper will have to deal with breaking changes in new Phoenix releases. The Aerie team will work to limit changes and provide a high-level of support to existing customers when breaking changes are required - -## References - -- https://github.com/nasa/fprime -- https://github.com/nasa/cFS diff --git a/docs/overview/design/arch-decision-records/adr-0101-aerie-actions.md b/docs/overview/design/arch-decision-records/adr-0101-aerie-actions.md deleted file mode 100644 index cf73f0ae..00000000 --- a/docs/overview/design/arch-decision-records/adr-0101-aerie-actions.md +++ /dev/null @@ -1,146 +0,0 @@ -# ADR-0101 - Aerie Actions - -## Status - -Proposed - -## Context - -Much like an integrated development environment (IDE) for programming, the Phoenix Sequencing Development Environment (SDE) is intended to help spacecraft operators efficiently develop and verify sequences of commands in preparation for sending those commands to a spacecraft or test bed for execution (see [adr-0100](../adr-0100-phoenix-sde)). Given the wide variety of languages used to write sequences and tools used to operate on sequences found across missions (to perform static checking, compilation, and sequence translation), Phoenix must be designed so missions can easily hook in their language and tools of choice. - -As a first step in providing mission customization of Phoenix, a sequence adaptation interface was created (see [adr-0006](../adr-0006-phoenix-editor)). This adaptation is written in javascript and can loaded at runtime into the Aerie UI to add or alter the behavior of Phoenix. Over time, the interfaces and capabilities provided by the sequence adaptation have grown. The sequence adaptation currently provides the following: - -- Customization of the text editor behavior used to write and view sequences via the use of CodeMirror extensions (there are guardrails here so only some code mirror capabilities are exposed) -- Use parsed command dictionary files (e.g. parameter and telemetry) parsed by Aerie whose contents are used to provide a better sequence authoring experience (e.g. autocompletion of user-friendly names for command arguments instead of numeric values). -- Defining global variables that can be used by multiple sequences -- Parsing and translation between different sequence formats for both import and export -- Hooking up an external static checking service (including the addition of a button in the UI) such that rules can be checked and reported back to the sequence editor. - -Since the sequence adaptation already existed, it was natural to continue to grow its capabilities rather than define alternate methods for adaptation. There was also significant pressure to deliver many of these capabilities quickly to a mission customer with tight delivery deadlines. - -Additional external services to perform sequence compilation, sequence simulation, and connect to external mission data stores will need to interface and interact with Phoenix. There is a question as to whether the current sequence adaptation design is sufficient to support these additional integrations and provide a good experience for mission adaptation developers and end users (mission operators). - -## Alternatives considered - -The main questions considered to shape this architectural tradeoff and illuminate options were: - -1. Should all customization code live in a single adaptation loaded into Aerie or multiple entities (e.g. multiple adaptations or plugins) -2. Should external service interfaces be prescriptive and well “shaped” (e.g. rigid APIs for compilation, translation, simulation) -3. Do these external service interfaces fit into the same mold as the text editor customizations or should they be a separate concept? - -### Option Identification - -- Option 1: Continue to grow the sequence adaptation and have a single, "monolith" adaptation interface (single adaptation, non-prescriptive interface, single concept between external services and editor customizations). -- Option 2: Create a modular plugin design that allows for multiple plugins to provide all of the capabilities currently in the sequence adaptation plus the additional external services required to be supported by Phoenix (multiple adaptations, non-prescriptive interface, single concept) -- Option 3: Create new interfaces outside of the sequence adaptation with well-defined interfaces specific to each external service type Phoenix must support (multiple adaptations, prescriptive interface, distinct concepts) -- Option 4: Create a new concept called "actions" for custom processes to be run based on triggers/events that is separate from the sequence adaptation (multiple adaptations, non-prescriptive interface, distinct concepts) - -### Key Quality Attributes Considered - -- Usability: has consistency and approaches to the design that allow users to anticipate and understand how to interact with the system. Our users in this context are both the developer creating the adaptation and the end user who will use the adaptation (and perhaps "install" it) -- Flexibility/Extensibility: ability to manipulate the system to be able to add/modify capabilities internal to the system and reach out externally to other tools/components. -- Maintainability: ability for capability to be updated over time without onerous or costly changes for core and adaptation developers (reduction of breaking changes) - -### Option Evaluation - -#### Option 1 - Single Sequence Adaptation - -There are some known issues with the adaptation today, which would need to be fixed even if we stick with a single adaptation. Those are: - -- Adaptation updates are error prone - - No workflow for publishing adaptation API so adaptation devs can pull in changes for testing. Because we aren't publishing the type script definition file (API), its hard to catch issues when updates occur - -##### Pros - -- The adaptation already exists and it would likely cost less time and money to deliver the remaining required service integrations through this interface. -- The adaptation is a "one stop shop" so it is fairly obvious where missions would need to inject their customizations. -- There is also only one adaptation for Phoenix core to worry about (maintainability) - -##### Cons - -- Encourages a "clone and own" approach to adaptations where missions copy previous adaptations and alter them, but cannot use them without modification. However, this could be mitigated to some extent with a build process made up of multiple files that are merged together (where individual files can be shared) and then provided to Aerie -- Increases learning curve for new developers trying to understand and modify the adaptation because it does so much and serves many purposes. - -#### Option 2 - Plugin Architecture - -##### Pros - -- Plugin architectures are a well known design pattern that both users and developers are familiar with and there are many good examples from which to gain inspiration. For example, the [Obsidian](https://docs.obsidian.md/Home) markdown editor uses a plugin architecture that also leverages CodeMirror extensions. -- Plugins are modular and can be designed to only perform a single function (separation of concerns). This makes plugins easier to understand (usability) and easier to share across missions. This should also make plugins easier to test. -- A single plugin API (as opposed to one API per expected service) gives developers the flexibility to define their plugin to best match any mission-specific external services/tools they may have. A single plugin could be built to represent static checking and simulation, for example (SeqGen is a concrete example of this) -- Plugin architecture allows behavior to be written directly into the plugin as opposed to requiring it to be an external service and communicating over a network protocol -- Many external services/tools we need to connect to Phoenix will require configurable settings. A plugin architecture will allow for a single settings interface instead of duplication of such an interface per service/tool (maintainability) - -##### Cons - -- A single general plugin interface which subsumes all operations currently handled by the adaptation, and also supports future requirements for interfacing with external services, must necessarily have a large "surface area", allowing plugins to access to many parts of the application and user data at once. - - This impacts usability, a less-strict interface per service provides lots of opportunity for plugin developers to "shoot themselves in the foot" and do things they did not intend to do. It does not provide a mental model for thinking about when the plugin code will be run. - - It is also difficult to maintain such a large API from a developer standpoint, as it will need to be validated and kept up to spec as other internal Aerie code changes. -- With multiple plugins, dependency management can become a concern if plugins need to depend on each other. Similarly, multiple plugins may operate on the same user data, potentially causing unexpected behavior. -- Plugin developers will have to do more work to set up the hooks and protocol for calling out to external services and tools (this can be mitigated to some extent by providing examples developers can start from) -- System must be less opinionated about the overall workflow through Aerie and the orchestration/order of operations between plugins/services. - -#### Option 3 - Individual Service Interfaces - -In this option, services are not bundled in the adaptation at runtime and are uploaded separately via the UI (or perhaps are hooked up during deployment/startup as opposed to dynamically uploaded). Aerie would provide a separate, specific interface for each type of operation that is supported, eg. translation, compilation, static checking, and would have rigid, specific requirements for the shape of the data structures sent to and from these services. Some parts of these services would be configurable via the UI - for example, users would provide a URL and some metadata and Aerie would define what gets sent and returned and the protocol (e.g. via gRPC or HTTP) - -##### Pros - -- Strictly shaped interfaces would make the development of interface adapters relatively straightforward -- No concern of dependency management or unexpected behavior as the inputs/outputs of each service would be well defined. - -##### Cons - -- Maintainability - This option leaves the Aerie dev team with the largest number of different APIs to maintain, since it would have specific APIs for each service type we support. Each API is less complicated, but the combined surface area is large. -- Well shaped interfaces provide the least flexibility to match preexisting tools that a mission may want to connect to Aerie (round peg/square hole problem) -- Services would have to use the protocol Aerie prescribe/chooses even if certain services would benefit from alternate protocols. - -#### Option 4 - Adaptation Plus Actions - -This option keeps the existing sequence adaptation architecture for code editor customizations, but introduces a new concept for interacting with external services, called Actions. - -Actions would be custom tasks written by developers which operate on files in the Aerie workspace, and produce predictable outputs when they are run. Aerie would provide a general Actions API which would give actions access to the workspace, without being specific or prescriptive about how they work. Internally, actions may make asynchronous calls to external services using whatever protocol(s) make sense for the application. The API for actions will also expose a way for developers to create settings for the actions that can be configured by the end user within the application for a given workspace - -In addition to the API, Aerie would provide an architecture & UI for managing, configuring and running these actions. Actions would be uploaded via the UI, and if they specify configurable settings, the UI would generate a form where the user could configure them. The Aerie UI would provide a clear way to run the actions manually, and view each run's status, inputs and outputs. Actions could also be triggered to run automatically based on events within Aerie (e.g. saving of a file, importing a file). Eventually, these tasks could be assembled together in workflows or pipelines. - -##### Pros - -- Like plugins, actions are fairly understandable by end users and have precedent in other software systems (e.g. [GitHub actions](https://docs.github.com/en/actions/about-github-actions/understanding-github-actions)) -- The other pros for plugins (option 2) also apply here with an additional pro that the term actions better matches the purpose of the external services, which are to provide translation and validation steps within a mission's uplink process. -- Distinguishing actions and the sequence adaptation helps separate concerns between different customizations you can make to the system within being too prescriptive about how each type of customization is built. Editor customizations will likely be more complex to build as they require some familiarity with CodeMirror while actions could be relatively simple to build. -- Maintainability - there is only one general Actions API to maintain, rather than many specific service APIs. - -##### Cons - -- There is some risk of proliferation of concepts within Aerie as it relates to customization. With the addition of actions, Aerie has the sequence adaption, timeline plugins, extensions, parser, and grammar plugins. -- Cons are also similar to plugins although Aerie could be more restrictive with when and how actions are run compared to a generic plugin interface. -- Maintainability - while the actions API is simpler than the other proposed APIs, there is additional complexity in the *actions management system* that Aerie will need to build and maintain - specifically, the architecture required for uploading, configuring, running, and reporting results from Actions, as well as any support for "hooks" (triggers) or pipelined actions which we may add, has the potential to be a significantly complex part of Aerie. - -### Additional Considerations - -#### Scope of the adaptation or plugin API (how much do we expose to developers?) - -No matter what option is selected, an API or set of APIs will have to be exposed to developers. There is a question as to how large and expressive to make the API. A large API provides developers opportunities for more customization of Phoenix at the cost of more complexity and potentially interdependencies. A smaller API limits flexibility, but decreases mental load for developers building adaptations/plugins. We see the smaller API is the right approach to begin with as the API can be grown over time. - -#### Execution of behavior from the backend? - -There is still an open question as to how to execute behavior embedded in adaptations, plugins, or actions from the backend, but we do not feel the answer to this question would help discriminate between these options. - -## Decision - -We will introduce a new concept to Aerie called "Actions" that is separate and distinct from the sequence adaptation. - -Actions, inspired by [GitHub actions](https://docs.github.com/en/actions/about-github-actions/understanding-github-actions), would be custom tasks that developers can write that are triggered off of events within Aerie (e.g. saving of a file, importing a file) or triggered manually in the UI (exactly how and where they are manually triggered in the UI is still design work to go). Eventually, these tasks could be assembled together in workflows. The API for actions will expose a way for developers to create settings for the actions that can be configured by the end user within the application for a given workspace. We will keep the action API restrictive to try to keep actions constrained to run at predictable times and with predictable inputs/outputs to ensure end user usability and reduce complexity for action developers. Actions give a unified API for external services to work with while keeping them separate from other application customization. - -The sequence adaptation will continue to exist and focus on text editor modifications by exposing parts of the CodeMirror extension API (the whole API will not be exposed to limit adaptation complexity). The adaptation will not expose settings to the end user that they can configure at runtime. The workflow for publishing the adaptation API will be improved to improve the adaptation development experience and testability. - -We also recognize that with the additional customization interface, it will be imperative to have sufficient error reporting to make end users aware when actions do not run as expected. - -## Consequences - -Many of the consequences of this decision are noted in the "Cons" section of Option 4 above. - -## References - -- https://docs.obsidian.md/Home -- https://www.devleader.ca/2023/9/7/plugin-architecture-design-pattern-a-beginners-guide-to-modularity diff --git a/docs/overview/design/arch-decision-records/list.sh b/docs/overview/design/arch-decision-records/list.sh deleted file mode 100755 index 615f6348..00000000 --- a/docs/overview/design/arch-decision-records/list.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# List the title line for each -grep '# \[' `find . -regex "\./[0-9][0-9][0-9][0-9].*\.md"` | awk -F ':# ' '{print $2}' | sort -n diff --git a/docs/overview/design/arch-decision-records/new.sh b/docs/overview/design/arch-decision-records/new.sh deleted file mode 100755 index 11402222..00000000 --- a/docs/overview/design/arch-decision-records/new.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# Get next ADR number - -filename=`find . -regex "\./[0-9][0-9][0-9][0-9].*\.md" | xargs basename | sort -n | tail -n 1` -filename="${filename%.*}" -printf -v num "%04d" "$((10#$filename + 1))" -newfile="$num.md" -cp template.md $newfile -sed -i '' -e "s//$num/g" $newfile -echo "Created $newfile" \ No newline at end of file diff --git a/docs/overview/design/arch-decision-records/template.md b/docs/overview/design/arch-decision-records/template.md deleted file mode 100644 index 1dccd696..00000000 --- a/docs/overview/design/arch-decision-records/template.md +++ /dev/null @@ -1,19 +0,0 @@ -# ADR-XXXX [TITLE] [DRAFT] - -## Context - -Explain the circumstances under which the decision was made. This should be a series of simple, factual statements. Describe relevant architecture influencers such as technology, skills, previous decisions, and the business or political climate among others. - -## Decision - -Describe the decision you made. Refer to references by small number [0] and other decision records as a four-digit number [0000] - -## Consequences - -Describe how your decision will or has changed the circumstances of the system, stakeholders, and team. Both positive and negative consequences should be included. Update this section as consequences emerge and are understood. - -## Alternatives considered - -## References - -- `[0]` http://example.com diff --git a/docs/overview/design/arch-design-overview.md b/docs/overview/design/arch-design-overview.md deleted file mode 100644 index d095ffbc..00000000 --- a/docs/overview/design/arch-design-overview.md +++ /dev/null @@ -1,17 +0,0 @@ -# Software Architecture and Design - -Below are brief descriptions of the various documents the project provides to learn about Aerie's software architecture and design - -## Architectural Decision Records (ADRs) - -ADRs are a growing list of key architectural decisions made throughout the project. Architectural decisions are those that have far-reaching, significant impact on the design and have a strong influence on quality attributes of the project. The primary purpose of these records are to capture the rationale behind decisions made over the course of the project. If a decision is modified later, one should create a new record that supersedes the previous record, but should not remove the old record in order to preserve the decision history over time. - -Records will be configuration managed along with the source code so that architectural decisions are transparent to the project community and can be authored, reviewed, and approved by community members. This practice helps democratize the product design to the whole community, especially developers, as they can participate in the design process and be aware of design decisions that may affect their work. - -In the [Divio](https://docs.divio.com/documentation-system/) documentation model, this documentation would be considered "understanding-oriented" or explanatory documentation intended to clarify and illuminate decisions made by the project. - -## Software Design Description (SDD) - -The SDD is a document of moderate length, but sufficiently detailed to capture the main aspects of the software design. The document intends to capture a rough overview of the landscape in which the product is being built and the main goals/objectives of the software. However, this document does not detail out all product requirements (linking to a requirements document is sufficient). After an initial description of context and objectives, the document breaks down the product into components and discuss these components and their interfaces in detail. - -This document is a descriptive, [information-oriented](https://docs.divio.com/documentation-system/reference/) reference for those interested in how the software product is put together. Decisions made to come to the design described in this document should be captured in ADRs and can be linked to the design document for those interested in more in-depth explanations of design tradeoffs and consequences of design choices. diff --git a/docs/overview/design/assets/c4-aerie-container.jpg b/docs/overview/design/assets/c4-aerie-container.jpg deleted file mode 100644 index 33c669c6..00000000 Binary files a/docs/overview/design/assets/c4-aerie-container.jpg and /dev/null differ diff --git a/docs/overview/design/assets/c4-aerie-container.png b/docs/overview/design/assets/c4-aerie-container.png deleted file mode 100644 index 163f6c89..00000000 --- a/docs/overview/design/assets/c4-aerie-container.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a79c7b2d793c1984de189bd44d8469b7babe63ce1ee78698c02edcdb1ccb6584 -size 474159 diff --git a/docs/overview/design/assets/c4-aerie-container.puml b/docs/overview/design/assets/c4-aerie-container.puml deleted file mode 100644 index 73d11783..00000000 --- a/docs/overview/design/assets/c4-aerie-container.puml +++ /dev/null @@ -1,55 +0,0 @@ -@startuml -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml -Person(planner, Planner, "A mission/instrument team planner") - -System_Boundary(c1, "Aerie") { - System_Ext(cam, "CAM") - Container_Boundary(ui_boundary, "UI") { - Container(web_app, "Aerie UI", "Typescript, Svelte", "Provides all planning, simulation, scheduling, and command expansion capabilities via a browser") - Container(ui_server, "UI Server", "svelte backend", "Serves the Aerie UI, performs server-side rendering") - ContainerDb(ui_db, "UI Config Database", "PostgreSQL", "Stores mission custom UI view configurations") - } - Container_Boundary(merlin_boundary, "Merlin") { - Container(merlin, "Merlin", "Javalin and Java", "Provides mission model simulation and plan management") - ContainerDb(merlin_db, "Merlin Database", "PostgreSQL", "Stores records of mission models and plans") - } - Container(hasura, "Hasura", "Third party, yaml config", "Receives and resolves requests against the Aerie GraphQL schema") - Container_Boundary(scheduler_boundary, "Scheduler") { - Container(scheduler, "Scheduler", "Javalin and java", "Performs automated scheduling for activity planning") - ContainerDb(scheduler_db, "Scheduler Database", "PostgreSQL", "Stores records of scheduling goals and specifications") - } - Container_Boundary(sequencing_boundary, "Sequencing") { - Container(sequencing_server, "Sequencing Server", "Typescript, Express", "Provides command expansion and sequence output capability") - ContainerDb(sequencing_db, "Sequencing Database", "PostgreSQL", "Stores records of expansion logic, and expanded sequences") - } - Container(gateway, "Gateway", "Typescript, Express", "Provides file upload and authentication capabilities") - ContainerDb(file_store, "Aerie File Store", "File system", "Stores uploaded mission models and other files") -} - -Rel_R(planner, web_app, "Uses", "HTTPS") - -Rel_U(ui_server, web_app, "Delivers, and authenticates") -Rel_U(ui_server, gateway, "Auth") -Rel(merlin, merlin_db, "Reads from and writes to", "SQL") -Rel(scheduler, scheduler_db, "Reads from and writes to", "SQL") -Rel(sequencing_server, sequencing_db, "Reads from and writes to", "SQL") - -Rel(hasura, merlin, "Proxies actions", "JSON/HTTPS") -BiRel(hasura, scheduler, "Incoming actions and outgoing GraphQL queries", "JSON/HTTPS") -BiRel(hasura, sequencing_server, "Incoming actions and outgoing GraphQL queries", "JSON/HTTPS") - -Rel(hasura, ui_db, "CRUD", "SQL") -Rel(hasura, merlin_db, "CRUD", "SQL") -Rel(hasura, scheduler_db, "CRUD", "SQL") -Rel(hasura, sequencing_db, "CRUD", "SQL") -Rel(web_app, hasura, "API queries and mutations", "GraphQL") - -Rel_L(web_app, gateway, "file uploads", "HTTPS") -Rel(gateway, file_store, "Save files", "") -Rel_D(gateway, cam, "Authenticates against") - -Rel(scheduler, file_store, "Loads mission model jar", "") -Rel(merlin, file_store, "Loads mission model jar", "") -Rel(gateway, merlin_db, "Writes file metadata", "") -@enduml - diff --git a/docs/overview/design/assets/c4-aerie-context.jpg b/docs/overview/design/assets/c4-aerie-context.jpg deleted file mode 100644 index ecdbcd7a..00000000 Binary files a/docs/overview/design/assets/c4-aerie-context.jpg and /dev/null differ diff --git a/docs/overview/design/assets/c4-aerie-context.png b/docs/overview/design/assets/c4-aerie-context.png deleted file mode 100644 index b742f978..00000000 --- a/docs/overview/design/assets/c4-aerie-context.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:214df51f0a5c877782a2befc54249c1c85ef9e2bd08ad5c95d05b6f2502db7f2 -size 160801 diff --git a/docs/overview/design/assets/c4-aerie-context.puml b/docs/overview/design/assets/c4-aerie-context.puml deleted file mode 100644 index b81a5a3a..00000000 --- a/docs/overview/design/assets/c4-aerie-context.puml +++ /dev/null @@ -1,19 +0,0 @@ -@startuml -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml -title System Context diagram for Aerie - -System_Ext(missionTool, "Mission Tool") -Person(planner, "Planner", "A mission/instrument team planner") -System(missionScheduler, "Mission Scheduler", "The mission scheduling logic system") - -System(aerie, "Aerie", "Provides mission activity and sequence simulation and resource analysis") - -System_Ext(cam, "Common Access Manager", "Central authentication and authorization provider") - -Rel_D(missionTool, aerie, "Query data") -Rel_D(planner, aerie, "Views and edits plans, resource profiles, constraints") -Rel_D(missionScheduler, aerie, "Query resource profiles and constraints, mutate plans") - -Rel_D(aerie, cam, "Get authentication/authorization") - -@enduml diff --git a/docs/overview/design/assets/c4-merlin-server-component.jpg b/docs/overview/design/assets/c4-merlin-server-component.jpg deleted file mode 100644 index 57d0ecc2..00000000 Binary files a/docs/overview/design/assets/c4-merlin-server-component.jpg and /dev/null differ diff --git a/docs/overview/design/assets/c4-merlin-server-component.png b/docs/overview/design/assets/c4-merlin-server-component.png deleted file mode 100644 index bc6e5b22..00000000 --- a/docs/overview/design/assets/c4-merlin-server-component.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20bc42ca444002f32426558c564c7970bfd359c3c14b571b5119b1d407089d43 -size 95141 diff --git a/docs/overview/design/assets/c4-merlin-server-component.puml b/docs/overview/design/assets/c4-merlin-server-component.puml deleted file mode 100644 index 6de3c5c8..00000000 --- a/docs/overview/design/assets/c4-merlin-server-component.puml +++ /dev/null @@ -1,24 +0,0 @@ -@startuml -!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml -Container_Ext(hasura, "Hasura", "Proxies GraphQL requests") -Container_Boundary(merlin, "Merlin") { - Component(app, "App", "Java", "Defines handlers for HTTP queries") - Component(plan_logic, "Plan Logic", "Java", "Implements business logic specific to activity plans") - Component(mission_model_logic, "Mission Model Logic", "Java", "Implements business logic specific to mission modles and simulation results") - Component(sim_driver, "Simulation Driver", "Java", "Accepts a schedule, builds an initial task queue, runs a simulation, colelcts resource profiles and constraint windows") - Component(sim_engine, "Simulation Engine", "Java", "Manages job queues and conditions") -} - -ContainerDb(merlin_db, "Merlin Database", "PostgreSQL", "Stores plans and mission model metadata") -ContainerDb(file_store, "Aerie File Store", "File System", "Stores mission model JAR files") - -Rel_D(hasura, app, "Makes internal API calls", "JSON/HTTP") -Rel_D(app, plan_logic, "Makes calls to") -Rel_D(app, mission_model_logic, "Makes calls to") -Rel_R(mission_model_logic, sim_driver, "Uses") -Rel_U(sim_driver, sim_engine, "Uses") - -Rel_D(plan_logic, merlin_db, "Reads from and writes to") -Rel_D(mission_model_logic, merlin_db, "Reads from and writes to") -Rel_D(mission_model_logic, file_store, "Reads from and writes to") -@enduml diff --git a/docs/overview/design/assets/c4-scheduling-component.png b/docs/overview/design/assets/c4-scheduling-component.png deleted file mode 100644 index b2a8ecae..00000000 --- a/docs/overview/design/assets/c4-scheduling-component.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:af6f7df4ba3681ef1562484a801ad24cc16de62a06852e6918f51f9a0076fbf8 -size 192786 diff --git a/docs/overview/design/software-design-document.mdx b/docs/overview/design/software-design-document.mdx deleted file mode 100644 index b2ecdc28..00000000 --- a/docs/overview/design/software-design-document.mdx +++ /dev/null @@ -1,1375 +0,0 @@ -# Software Design Description - -## Purpose - -This document details the current state of the design of the Aerie system. -The document defines the components which make up the Aerie system and how these relate to the higher level system architecture. -Additionally, design rationale and trade studies are described to provide guidance and support for design -decisions. Where available, the many Aerie component interface -definitions and relevant published software interface specifications will be provided inline or referenced. - -## Overview - -Aerie is an open-source extensible software system originally built for planning, scheduling, and commanding space missions. -Initially developed by NASA's [Advanced Multi-Mission Operations System (AMMOS)](https://ammos.nasa.gov/), Aerie provides modeling and simulation capabilities that can be used for mission planning and analysis during project formulation all the way through operations, where it can be used to manage and validate spacecraft activity plans. - -Aerie provides low-code solutions for authoring scheduling rules to autogenerate plans, authoring and evaluating constraints to assist with plan validation, and authoring logic to expand activities into sequences of commands for execution. Sequences can also be authored and edited independently following an open source sequencing specification, seq-json, that includes a variety of commanding styles (e.g. absolute, relative, command completion). - -As a multi-tenant system, Aerie allows multiple distributed users to collaborate in real-time on a single plan or concurrently work on multiple plans for multiple missions. Additionally, Aerie's service-based architecture allows for efficient system deployment and scalability on the cloud. - -## References - -If you are interested in learning how to use Aerie, a great place to start is our [Fast Track](https://nasa-ammos.github.io/aerie-docs/introduction/#fast-track). More detailed information on [planning](https://nasa-ammos.github.io/aerie-docs/category/planning/), [scheduling](https://nasa-ammos.github.io/aerie-docs/scheduling/introduction/), [sequencing](https://nasa-ammos.github.io/aerie-docs/category/sequencing/), and [command expansion](https://nasa-ammos.github.io/aerie-docs/command-expansion/introduction/) is available throughout our online documentation. - -If you are interested in learning how to build and configure models to use with Aerie, please refer to our [Mission Modeling](https://nasa-ammos.github.io/aerie-docs/mission-modeling/introduction/) documentation. - -Aerie's design requirements are currently documented in the Aerie Software Requirements Document (SRD) delivered to AMMOS (DOC-002388 Rev. B). If you are interested in receiving a copy of this document, please contact aerie-support@googlegroups.com. - -Detailed code documentation is also available in our [Java Docs](https://nasa-ammos.github.io/aerie-docs/java-docs/introduction/) section. - -## Terminology and Notation - -- **Container** – in this document the term container primarily refers to the Container level of architecture as defined in the [C4 Model](https://c4model.com/) approach to describing software architecture. Other types of container, such as a Docker container, will be denoted or made clear by context. - -- **Mission model** - The term "adaptation" has become significantly - overloaded at JPL. The word is used in many different contexts to - convey or denote a range of different and often unrelated entities - or concepts. Often, the ability of one to understand the entity or - concept which term denotes relies on one’s ability to understand - the wider context in which it being used. This requires a - burdensome amount of understanding of the domain before the term - becomes specific and useful. Additionally, the boundary or extents - of what the term denotes is unclear. Take for example the commonly - termed mission planning adaptation. On first mention, it is not - clear here if such usage indicates the specific APGen code - written, or wider, the APGen code plus any modeling integrations, - or wider still, the integrated system which performs a MP - simulation. Finally, this term is very JPL centric and does not - comport with other language in the domain of modeling, simulation, - discrete event simulation etc. - - As a result Aerie has chosen to use the term mission model to denote - the modeling code (and integrations e.g. FMU) written, defined in a - [.JAR](https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jarGuide.html), - which run as an Aerie simulation. It's specific as to the purpose of - the code written, unambiguous (not confused with other terms in - use), and better comports to the domain of simulation and modeling - in general. - -- **Parameter** and **Argument** - in order to distinguish between the - declared name and type of a function parameter, and a particular - value that is provided to it, we use the word **parameter** to - mean a name paired with a type, and we use the word **argument** - to mean a value that is provided to a parameter. - -## Aerie System Design - -### System Architecture - -This section describes the Aerie system architecture, the guiding -component-based design philosophy, and an overview of the key -components. - -The architectural diagramming approach taken in this document follows -the C4 Model for visualizing and discussing software -architecture. This approach abstracts a software system into roughly -four levels, Context, Containers, Components, and Code. A brief -description of each level of abstraction is provided in [Appendix -A](#a---the-c4-model). - -import c4AerieContext from './assets/c4-aerie-context.png'; - -
- Aerie Context -
Figure 1: Aerie Context
-
- -Aerie is designed to be incrementally evolvable - so that it can more -easily be adapted to emergent user needs. The component based system -design provides the benefits of system decomposition - giving the -ability to expand the system without invasively changing the other -components - and allowing the person working with the system to think -about a small number of components at a time and not need to hold the -whole system in their head at once. In the case where application need -to accommodate growing or new capabilities, where those new -capabilities are satisfied by separating a component into its own -process, this can be done. A well designed monolith in many ways -should resemble a distributed system. This means one should avoid -shared state, strict definition and adherence to interfaces a service -interface is equal to a component’s exposed interface. A message -passing reactive system can exist whether the components are in the -same process, in separate processes, or even separate infrastructure. - -For example the -[merlin-worker](https://github.com/NASA-AMMOS/aerie/tree/develop/merlin-worker) -is a part of the Merlin bounded context. Much of Aerie has been -designed with the [DDD principle of bounded -contexts](https://martinfowler.com/bliki/BoundedContext.html). Among -the many implications of this is that the data within each bounded -context shall only be manipulated by the components explicitly -contained within the defined context. It is important not to mistake -the fact that the merlin-worker is a separate container image as -indicating that it is part of a separate bounded context. Rather, it -is simply a separate container so that the merlin-server can account -for the aforementioned process separation concerns. Therefore, each -merlin-worker has a direct connection to the merlin database. - -### Container Level Component Overview - -import c4AerieContainer from './assets/c4-aerie-container.png'; - -
- Aerie Container -
Figure 2: Aerie Composition at the Container Level
-
- -- **Merlin** - Capabilities include plan management, managing the - simulation and configuration of a mission model, managing simulation - results, external dataset, and constraints management and checking. - -- **GraphQL API** - The GraphQL gateway parses requests and - resolves those requests with application internal components. The - gateway is implemented via [Hasura](https://hasura.io/). - -- **Aerie UI** - The server provides a user interface which supports - the myriad management and analysis processes performed during - mission planning and mission operations. The server manages data - regarding an individual user’s view layouts and selected user - interface preferences. - -- **Common Access Manager (CAM)** - Aerie delegates authentication and - authorization capabilities to an MGSS Common Access Manager - server. Customers deploy their and configure a CAM to provide - authentication and authorization of mission personnel. - -- **Automated Scheduler** - An automated scheduler operates external - to the Aerie deployment and carries out computer aided mission - activity scheduling by issuing queries and mutations against the - Aerie GraphQL API. As a component external to the Aerie deployment, - an automated scheduler can be developed by a customer in their - preferred programming language and executed on infrastructure most - suited to the computational needs of their scheduler’s algorithmic - approach. - -### API - -The Aerie GraphQL API presents a consistent application boundary to -Aerie users. The API server enables the composition of multiple APIs -(internal to the application) as a single API endpoint. The API -component additionally provides a location in the system for the -following needs: - -- Manipulation of data -- Response Caching - -#### Aerie API Needs - -1. Evolve the internal APIs rapidly and the public APIs slowly. - - The development of the Aerie user interface and application - internal components proceeds in parallel. As a result, the user - interface’s data needs imposes constraints on the definition of the - system’s public API. The evolving nature of the user interface’s - development makes it difficult to carry out an API design effort, - as would be the case for a REST API architectural style. Further, - the structure of resources needed by the highly configurable - interface components within the user interface, makes defining an - efficient set of resource endpoints prohibitively difficult. Such - endpoints would require continuous editing and updating as - development progressed. - -1. Multiple clients and multiple different workflows for clients. - - There exists a category of use cases in which customers develop - custom Aerie client applications. Such customization requires the - flexibility to easily define new data projections as simple queries - constructed by a client. As a result, Aerie benefits from an API - that supports high query flexibility from both the external client - and internal services perspective. - -1. Ability to dynamically reduce/transform response payloads. - - Within the planning, scheduling and sequencing domain are a number - of common list like data structures/concepts which are often quite - large in size (number of elements). It is inefficient to impose - upon any client seeking a particular view/aspect of the data - structure to request and process the entire structure. For example, - certain resources can’t be made "smaller" without compromising - their intent. A Plan can be filtered but not sensibly - partitioned. It is desirable that any client requesting such data - structures be provided with an easily accessible means to query the - data structure for the elements/projection of interest. - -1. Custom queries and batch fetching. - - The Aerie stores a number of significant mission data sources - (E.g. activity plan, simulation results, and constraint violation - results). Aerie must provide users flexible access to this data to - support an arbitrary space of use cases for reporting, auditing and - interfacing with third-party customers. - -#### Trade Study - -A trade study was conducted to evaluate available API products. A down -selection of the tools left both the [Tyk API](https://tyk.io/) -gateway product and [Apollo GraphQL -Server](https://www.apollographql.com/) product. The primary -difference between these two products are their approach to exposing -data. The Tyk gateway exposes system queries as [Representational -State Transfer -(REST)](https://www.codecademy.com/article/what-is-rest) endpoints -while the Apollo GraphQL server exposes a single GraphQL query -endpoint. The REST and GraphQL architectural styles present different -approaches and embody contrasting capabilities. Representational State -Transfer is an architectural style for distributed hypermedia -systems. GraphQL is a query language for an API, exposed as a typed -schema defined by a data graph. Table 3 presents a set of desirable -API properties and the manifestation of each property for REST and -GraphQL driven APIs. - -Table 3 Comparison of REST and GraphQL capabilities - -| System Property | REST | GraphQL | -| -------------------------------------------------- | ------- | ------------------ | -| Modifiability | ✅ | Runtime inspection | -| Scalability | ✅ | ❌ | -| Portability | ✅ | ✅ | -| Reliability | ✅ | ✅ | -| Simplicity | ✅ | ✅ | -| Visibility | ✅ | ✅ | -| Performance | ✅ | ❌ | -| Discovery and Introspection | Limited | ✅ | -| Consistency | ❌ | ✅ | -| Ease of Server Development | ❌ | ✅ | -| Ease of Client Development | ❌ | ✅ | -| Over-fetching protection without proper API design | ❌ | ✅ | -| Active Community | ✅ | ✅ | -| Tooling Server | ✅ | ✅ | -| Tooling Client | ✅ | ✅ | -| Tooling API Management | Limited | ❌ | -| Maturity | ✅ | ❌ | -| Works with any data representation | ✅ | ❌ | -| Printed Books | ✅ | ✅ | -| Enterprise Ready | ✅ | ✅ | - -The following is a discussion of the particular API qualities which -provide for Aerie’s needs. - -- **Discovery and Introspection** - The GraphQL data graph schema - provides a contract-like mechanism where requests and replies are - inherently typed and can be directly validated and resolved based on - those types. This contract like nature completely describes all - possible requests/responses where a typed service provider won’t - compile until it fully implements its contract. A typed service - consumer will be type-checked at compile time, which helps to catch - problems before deployment. Finally, it is unreasonable to expect - that a well-performant API can be developed for every conceivable - use case. As a result the improved introspection at the per field - level in GraphQL allows for targeted optimization of common or slow - queries. - -- **Consistency** - The API schema is typed and therefore either - correct or not. As a result, there is an inherent consistency - between client and server because both must abide by the generated - schema. - -- **Ease of Server Development** - It is easier (development time, - complexity) to develop and maintain data source resolvers as part of - a GraphQL server. Well designed, true REST APIs take time and - resources and are therefore more difficult to design and - maintain. GraphQL relieves the project of that unnecessary burden. - -- **Ease of Client Development** - A client can develop against the - exposed contract. A client can develop custom queries targeted to - its own use cases to limit both over and under fetching. In many - cases this may reduce latency and increase performance by limiting - client side data manipulation/filtering. - -- **Flexibility of API Design** - User and mission needs are - constantly evolving. GraphQL decouples the API allowing the Aerie - team to make adjustments to the API according to evolving customer - needs. Additionally, the increased granularity and visibility when - auditing the frequency and combinations with which certain fields - are queried, allows for clearly validated deprecation, removal, and - changes of fields available in the API schema. - -By adopting GraphQL we knowingly forgo certain -capabilities/constraints of a REST API. Three cases have been -identified as possible risks and sufficient mitigation options are -identified: - -1. **Tooling API Management** - GraphQL is a newer technological - approach to APIs (2012).
**Mitigation:** Aerie has chosen to - use Hasura, a major open source contributor to the GraphQL - community. - -1. **Caching** - REST over HTTP benefits from existing HTTP server - caching and browsers client caching mechanisms.
- **Mitigation:** Most GraphQL libraries have caching mechanisms - built in. Hasura caching must be handled with - annotations/directives on the graph definition. - -1. **Client-API Loose Coupling** - Each new client application must - make affordance at development time and hardcode custom queries and - mutations as made possible by the Aerie GraphQL schema.
- **Mitigation:** None. In the Aerie context this is not considered a - benefit. - -#### Hasura - -Implementing an API requires developers to write a lot of repetitive -code. Many API calls are simply Create, Read, Update, or Delete (CRUD) -operations to the database. The translation of GraphQL queries to SQL -can be automated to save developers a lot of toil. This is what -[Hasura](https://hasura.io/) does. - -To add a new node to the GraphQL API graph, a developer needs to -create the corresponding table in the database, and update the Hasura -metadata, stored in yaml files, to communicate to Hasura that that -table should be exposed via the API. - -Hasura produces rich APIs that allow sorting and filtering data out of -the box. This saves developers the work of adding sorting and -filtering to each node individually. - -As a result, Hasura boosts the productivity of the development team, -and provides a higher quality API to users. - -### Merlin Server - -The Merlin server supports the following capability areas: - -1. Mission model management -1. Plan management -1. Simulation execution and results management -1. Constraints management and evaluation - -There are two driving user stories which help define the core business -logic of the Merlin server. The two user stories are: - -1. A user wants to create and edit an activity plan. -1. A user wants to view the mission resource profiles of a simulated - activity plan. - -By providing for these two user stories the Merlin server is able to -support many other more complex user stories. - -The Merlin server component diagram is show in Figure 3. There exist -strong data coupling between the data contained in a Plan and the data -defined in a mission model. To reduce the potential for significant -data model duplication and intra container communication the server is -designed as a single entity. - -The Merlin server is implemented as a Javalin application. Javalin is -a lightweight web framework implemented as a layer atop Jetty. The -underlying Jetty server is fully configurable, providing SSL and HTTP2 -capabilities. Javalin was chosen because it focuses only on providing -a web application framework, it's simple to integrated, and -demonstrates satisfactory performance (performance of raw Jetty -code). Compared to the myriad other web application frameworks, -Javalin is extremely simple in its implementation and use. The Aerie -project has chosen not to use frameworks such as Spring, due to their -steep learning curves, lack of focused capabilities, and burdensome -dependencies and size. - -The App component defines the configuration of the Javalin server and -resolves calls to the Merlin server HTTP API to response handlers -provided in the Plan Logic and Mission Model Logic components. The -Plan Logic component encapsulates the business logic for managing and -manipulating activity plans. The Mission Model Logic component manages -mission models, simulation of those models, and the queries regarding -results (resource profiles and constraints). The Mission Model Facade -defines a set of queries that can be made of a mission model. These -queries pertain to both, data defined by the mission model (activity -types and resources), and data generated by simulating the mission -model (resource profiles and constraint windows). The facade -orchestrates the loading of a mission model, and makes transparent to -the Mission Model Logic, the execution of a simulation by the -Simulation Driver. - -Components of merlin include: - -- The Merlin App, which is responsible for providing an HTTP interface to Hasura -- The Simulation Driver, which is responsible for running a simulation -- The Simulation Engine, which is responsible for keeping track of - what should happen next in a simulation -- Mission model logic, which is responsible for loading mission models - from files, and interrogating them about their contents -- The Merlin Database is responsible for persisting and indexing - planning and simulation data. The database includes stored - procedures to provide certain functionality without needing to move - data out of the database. - -All of the components of Merlin read from and write to a single -PostgreSQL database. The database maintains separate tables for each -of the components. The Mission Model Logic stores the JAR files -defining mission models in a file system. - -import c4MerlinServerComponent from './assets/c4-merlin-server-component.png'; - -
- Merlin Server Component -
Figure 3: The Component Diagram for Merlin Server
-
- -### Merlin Workers - -The merlin worker component introduces a multi-tenancy capability for -Aerie simulation. The driving use cases for the merlin worker -architecture are: - -1. Executing multiple concurrent simulations - - Aerie is a multi-user application and needs to be able to execute - multiple concurrent simulation requests. -1. Separate process spaces to provide - - Protection against SPICE's non-thread safe implementation. - - The ability to reduce simulation time by executing simulations on - computationally performant hardware, while running other Aerie - components on more modest infrastructure. - -Firstly, it is clear that threading concurrent simulation requests -could have been a viable first step on the path if the SPICE library -was thread safe. SPICE is not thread safe and therefore each library -loaded in a process must be accessed by only one thread. - -Secondly, simulation execution is the most computationally expensive -process among the various Aerie components. While Aerie in general can -be deployed to generally powerful compute resources, it is more cost -efficient to deploy only the compute bounded portion of the process to -dedicated compute resources. Separating out (decoupling) the -merlin-worker's responsibilities into a separate process enables Aerie -to be deployed across a number of hosts, where the merlin-worker -containers are deployed to infrastructure tuned for simulation -execution speed and the remainder of containers on modest hardware. - -The worker subsumes the responsibility of running a simulation and -writing the results to the merlin postgres database. The merlin-server -processes a simulation request, creates the initial dataset tables and -associations. The simulation_dataset entry is initially set with a -status "pending". This status indicates that the simulation run is -queued waiting for a worker to take the job. When a worker takes the -job the status is updated (by the worker) to "incomplete". - -The merlin-worker is a part of the Merlin bounded context. Much of -Aerie has been designed with the DDD principle of bounded -contexts. Among the many implications of this is that the data within -each bounded context shall only be directly mutated by the components -explicitly contained within the defined context. It is important not -to mistake the fact that the merlin-worker is a separate container -image as indicating that it is part of a separate bounded -context. Rather, it is simply a separate container so that the -merlin-server can account for the aforementioned process separation -concerns. Therefore, each merlin-worker has a direct connection to the -merlin database. - -The postgres NOTIFY/LISTEN feature is used to NOTIFY workers -LISTEN(ing) for newly created simulation jobs. The database trigger -emits a notification with a payload containing the simulation revision -data. Upon receiving a notification each worker will attempt to claim -the simulation job by changing it's simulation_dataset status field to -"incomplete". A simulation job is considered taken if this SQL -operation is successful. Then the successful worker continues with -executing the simulation. If the SQL operation is unsuccessful -(e.g. another worker was successful in claiming a job earlier) then -the worker handles the failure to claim the job and moves on to -attempt claiming other jobs. One is assured that there will be no race -conditions due to Postgres' ACID transaction processing. - -### Datastores - -Aerie's datastores include both relational databases and the file -system. A large majority of Aerie's data is considered to be -structured data with a small amount of semi/un-structured data. The -semi-structured is often in JSON form. A relational database is the -appropriate choice when dealing with structured data. Aerie also uses -the filesystem to store mission model .JAR files uploaded to Aerie. - -Additional considerations include: - -- Storing structured data directly and being able to retrieve and - filter based on that structure fast at the database layer is - important. -- Postgres has JSON and JSONB handling, making it possible to quickly - filter on unstructured data as well. Other database systems, such as - mariadb, store json as text and don't treat it specially. -- Design and selection criteria for the db - - Secure connections TLS - - Auth - - Redundancy/scalability - - Application components scalable on their own -> e.g. db scalable on - their own - - Historically missions are insufficiently equipped in both the - technical ability and resource (time/people/money) to manage large - IT infrastructures. As such, it is advantageous for Aerie to - minimize the number of database that may require maintenance - (backup, updates, provisioning hardware) and to provide - administrative infrastructure to carry out these tasks - transparently. -- Backups and Migration - - What data is backed up - - Mission model JARs are not. It is expected that the code base which - is compiled to the JAR is un configuration management and that a JAR - can be reproduced at any time by the user. - - Plans, constraints, simulation resource profiles - all data in the - PostgreSQL database should be backed up in case of system failure - and migration between Aerie versions. - -### Deployment - -Aerie deployment is designed to accommodate two user groups; the -individual installation on a local machine and the installation on -hosted infrastructure. - -Each Aerie service is deployed as a Docker container with the -deployment orchestrated by the [Docker -Compose](https://docs.docker.com/compose/) utility. The Docker Compose -utility creates up a single Docker network which enables dependent -Docker containers to exchange messages with each other. Each Docker -container joins the network and is both reachable by other containers -on that network, and discoverable by them at a hostname identical to -the container name. Aerie service containers have their IP and port -numbers statically assigned within the Docker Compose YAML -configuration file. - -### Authentication and Authorization - -Aerie provides authentication and authorization capabilities via the -Common Access Manager. Currently, authentication requests from the -Aerie UI are proxied through the aerie-ui-server. It is prudent that -Aerie not handle (proxy/store) any credential information, for any -duration of time. As a result, Aerie authentication will soon be -amended so requests for an authentication token are made directly to a -CAM server instance with the Cross-Origin Resource Sharing (CORS) -capability enabled. - -Authorization is provided by configuring CAM policies. The Aerie -GraphQL API Server manages authorization of API access and limits -access based on user roles per API field granularity. - -### Data Model - -#### Datasets - -In Aerie, a dataset is a set of data describing several associated -concepts. Each dataset can be broken down into three core pieces: - -- Profiles -- Spans -- Events - -Because a dataset conglomerates several types of data, the actual data -within a dataset is spread across several database tables. Several of -these tables that may contain many rows of data per dataset are -partitioned by dataset ID, allowing for higher efficiency in working -with those database tables. The partitions for a given dataset are -created and deleted alongside the dataset itself automatically via -postgres triggers. - -##### Profiles - -Profiles describe dynamic values whose behavior is defined in distinct -segments. Each profile has a type (real or discrete) governing the -dynamics it may exhibit in each segment, a name and an ID. This -information is stored in the `profile` table. The actual dynamics and -duration of each segment of a profile is stored in the -`profile_segment` table. Each segment's start is stored as an absolute -offset from the start of the dataset. - -##### Spans - -Spans describe windows of time via a start offset and duration. Each -span must have an assigned type as well as a set of attributes, and -may be specified as a child of another span by providing a parent ID. - -##### Events - -An event graph associated with a dataset may be stored using the -`event` and `topic` tables. - -#### Simulation Datasets - -One of the primary uses of datasets is to store simulation -results. Simulation results are stored by placing activity instances -in the `span` table, resource profiles in the `profile` and -`profile_segment` tables, and an event graph in the `event` and -`topic` tables. The `simulation_dataset` table is used to match a -simulation up with its associated dataset. - -Each row of the `simulation_dataset` table defines all the information -about what was input to a simulation resulting in the associated -dataset. This includes the versions of the mission model, plan, and -simulation input that were used for the simulation run. - -An insertion to `simulation_dataset` should only take the simulation -ID and offset from plan start from which the dataset should be -interpreted. When a `simulation_dataset` row is inserted, a trigger -runs to initialize the associated `dataset` row (filling in the -`dataset_id` field) and populate the revision columns with the current -revision of the associated `mission_model`, `plan`, `simulation` and -`simulation_template`. When one of these tables is updated, a separate -trigger will automatically mark all associated rows of -`simulation_dataset` as canceled by setting the `canceled` column to -`true`. Only the `simulation_dataset` representing the up-to-date -simulation results should not be canceled. - -#### External Datasets - -External datasets allow for users of our system to upload precomputed -profiles to be associated with a plan. The precomputed profiles are -stored in a `dataset` associated with a plan via the `plan_dataset` -table. - -Similar to inserts on `simulation_dataset`, an insert to -`plan_dataset` should only take a plan ID and an offset from the -plan's start from which the dataset is to be interpreted. A trigger -will automatically insert a row to the `dataset` table, and populate -the `dataset_id` of the inserted row, to which precomputed profiles -can be added. - -### EDSLs - -Aerie utilizes a number of EDSLs for parts of the system that require -customization of behavior that is more complex than simple option -changes and at a different cadence than mission models. We considered -using custom DSL's, but the infrastructural work to build in all the -capabilities we wanted (listed below in requirements) was prohibitive -from a time-to-implement perspective, so we settled on doing EDSLs -which use a pre-existing programming language and embeds a domain -specific language component therein. - -Currently these are Constraints, Scheduling Goals, and Command -Expansion. The first of these to come about was Command Expansion, -which laid the groundwork for a design that was easily extended to -Constraints and Scheduling Goals. This section is written to tell the -design/implementation story for all of these EDSLs. - -#### Command Expansion Requirements - -Command expansion is the process of taking activities in the plan and -generating actual spacecraft commands from them. Much of the command -expansion conceptual design was evolved from the ideas of the M2020 -Scripted Expansion and SIE Sequence Generator systems and the MSL -Master Submaster Generator before it. - -Conceptually, command expansion is a simple functional map from -activities in the map to they're implementing spacecraft -commands. Activity → Commands - -Due to the desire to abstract away the complexity of spacecraft -commands to the simplicity of conceptual spacecraft activities, it is -often more complex than a fixed set of commands or even a fixed set of -commands with arguments that come from the activity parameters. As -such, a more powerful method than simply templating out commands with -substitution is desired. - -On MSL, this was first captured in part by the Master Submaster -Generator that had the entire plan in scope and composed the top level -Master sequences and the second tier Submaster sequences. The -architecture of that software, however, led it to become overly -complex as it was using a visitor pattern over the plan to generate -the sequences procedurally, which grew difficult to maintain over -time. One of the biggest issues with this system is that documentation -was sparse and there was no readily accessible way to check semantic -correctness before execution, meaning the development cycle required -repeatedly re-deploying and re-executing just to find out that a -method being used did not exist or the data structure was different -than expected. - -On M2020, two systems were built with similar goals - the Scripted -Expansion system took user defined python scripts that defined a -single function expand that was passed a single activity and then the -user called functions (one per spacecraft command) that each inserted -a command into the output sequence, while the SIE Sequence Generator -ran CM'd TypeScript code that was functional - functions receiving -scoped parts of the plan and returning a sequence. Both systems used -slightly different API syntax, but both mirrored the resulting -commands they output - Scripted Expansions using a functional call -structure, and SIE Sequence Generator using an embedded XML syntax -that mirrored the resulting RML structure. Both were also composable, -allowing user defined sub-functions to abstract away complexity and -arbitrary programming constructs use allow the users to use the most -intuitive programming constructs for their specific expansion's -business logic. One of the huge benefits the Scripted Expansions had -over the SIE Sequence Generator was that the API was generated -automatically from the command dictionary rather than being built up -by the developers. - -The concept of the Aerie Command Expansion service took the best parts -of all these prior arts. Conceptually, we wanted it to be/use: - -- User defined expansions loaded dynamically like M2020 Scripted - Expansions -- A functional paradigm like M2020 SIE Sequence Generator -- Scoped to the appropriate activity in the plan like the M2020 Script - Expansions -- Intuitive API that mirrored the spacecraft commands it outputs, like - both M2020 Scripted Expansions and M2020 SIE Sequence Generator -- An API that was auto-generated from the command dictionary, like - M2020 scripted expansions -- Allow arbitrary programming constructs, with a heavy direction - toward functional patterns -- We also wanted the authoring of these expansions to be user friendly - to write - syntactic and semantic checking at development time and - well as API discovery and documentation embedded in the editor. - -In additional, we had the standard concerns associated with execution -of foreign code: - -- Security/Isolation such that expansions can not have access to other - expansions or the larger system except where explicitly defined -- Execution limits both on memory used and time to complete -- Protection against unexpected user code inputs/outputs -- Useful error reporting scoped to the code the users can influence - -And finally, for performance reasons, we wanted the execution of -expansions to be highly parallelizeable. - -#### Execution Architecture - -Based on these requirements we knew we needed a dynamic programming -language with strong editor support for hinting and type-checking, a -secure runtime with execution limits, and good support for -parallelization. The only well-supported scripting language that -currently meets these requirements is TypeScript running in a V8-based -runtime - of which the most supported server runtime is NodeJS (other -server runtimes are Deno which doesn't yet have the V8 Isolate APIs, -and Bun which is actually based on JSCore rather than V8 and is still -in very early development). Additionally, TypeScript allows great -customization of the exposed user API for us to carefully craft it to -a great user experience. Looking forward to needs outside of command -expansion (and in an effort to isolate implementation complexity) we -designed a generalized library that does this for any functional user -code execution. This is the Aerie TS User Code Runner that now -underpins all of our EDSLs. - -This solution architecture follows: - -- A NodeJS runtime -- Secure execution via V8 Isolates and the vm api (CloudFlare has a - great article describing how their isolated execution architecture - is built on these V8 isolates) - - These have built in execution limits for both run-time and memory - usage -- Programmatic compilation from TypeScript to JavaScript with the - TypeScript compiler API that type checks the inputs, outputs, and - contents prior to execution to prevent unexpected behavior - - Also allows type checking against our APIs in web editors (Monaco) - out of the box for our UI -- Specialized error curation that scopes any issues in type-checking - and execution to the user code (rather than referencing lines of - code in auxiliary libraries and the execution structure that the - user has no influence over) - -And specific to command expansion: - -- Light weight parallelization via Worker Threads -- A command API auto-generated from command dictionaries -- A functional API passing the plan scope to a user-defined function - and receiving back an array of commands generated from the command - API - -The selection of a TypeScript EDSL also allows us to have web editors. - -#### Command Expansion EDSL Crafting - -The specifics of the Command Expansion EDSL was crafted in an effort -to make the user code as reflective of the resulting command structure -and formats that JPL operations users are already familiar with. This -means having commands and arguments be simple function calls named as -the command they implement and with arguments that are named and -specified with types that reflect the command dictionary definitions -for those arguments. We also wanted to enable both the prevalent -argument array format as well as a new named argument format which are -more clear as to their usage - luckily TypeScript supports this dual -format natively. - -Below is the command dictionary definition for a single command, the -common human readable format used on previous missions, the command -EDSL definition for that command and how it would be used: - -```xml - - -
-
- - - - - - - - How much uplink data is coming - - - - shell_ctl - FSW - - A command to communicate uplink data size - The data size is received - - - - - - -
-``` - -Human Readable form: - -```ts -UPLINK_DATA_SIZE 256 -``` - -EDSL definition generated from the command dictionary: - -```ts -/** A command to communicate uplink data size **/ -function UPLINK_DATA_SIZE(size: U8): UPLINK_DATA_SIZE; // Positional arguments format. -function UPLINK_DATA_SIZE(args: { size: U8 }): UPLINK_DATA_SIZE; // Named arguments format. -``` - -EDSL usage: - -```ts -UPLINK_DATA_SIZE(256); // Positional arguments format. -UPLINK_DATA_SIZE({ size: 256 }); // Named arguments format. -``` - -With this base for our command EDSL, we wanted a clear way to indicate -timing information supported by sequencing for absolute timing, -relative timing, epoch-relative timing, and command-complete -timing. For this, we leverage tagged template literals where we have a -separate one for each. This looks like: - -```ts -A`2022-001T00:00:00.000`.UPLINK_DATA_SIZE(256); // Absolute timing. -R`00:00:00.000`.UPLINK_DATA_SIZE(256); // Relative timing to the previous command. -E`00:00:00.000`.UPLINK_DATA_SIZE(256); // Relative timing to a sequence EPOCH. -C.UPLINK_DATA_SIZE(256); // Command complete timing starting immediately after the prior command completes. -``` - -Putting all of this together, a user defined command expansion looks -like: - -```ts -export default function ({ activityInstance: ActivityType }): ExpansionReturn { - return [C.UPLINK_DATA_SIZE(activityInstance.attributes.arguments.dataSize)]; -} -``` - -#### Other EDSLs - -With a working EDSL for command expansion, we saw great opportunity to -do similar refactor of our scheduling and constraint structures from -JSON ASTs to more user-friendly APIs with similar UI benefits. These -cases were slightly different though as they required high volume -iterative execution where sending the full context for execution from -our main Java process to the NodeJS process the code runner executes -in would be prohibitive performance-wise. If you take that and the -pre-existing support for the JSON ASTs, we decided to go the route of -having the EDSL generate a JSON AST that can then be parsed and -iteratively executed in the Java Process. So the user code execution -is simply doing a transformation from EDSL to a JSON AST in these -cases. - -## Simulation and Modeling Design - -The Aerie approach to simulation aims to support both activity plan -simulation (APGen) and sequence simulation (SeqGen). To do this the -Aerie simulation architecture must built upon a general description of -the effects posted by either activity modeling effects or -sequences. Please see the document on [The Merlin -Interface](./../../../mission-modeling/advanced-the-merlin-interface) for -detailed information about how Aerie accomplishes this. - -### Mission Model Interface - -Because mission models are expressed in Java, rather than a custom -DSL, Merlin has little to no ability to see the actual Java code -comprising a mission model. Merlin must instead make inferences about -the mission model based on its observable behavior. - -Merlin is a spiritual successor to the -[Blackbird](https://trs.jpl.nasa.gov/handle/2014/52245) planning -system, which similarly uses Java for activity and resource modeling. -Blackbird's design shed light on the myriad choices made in designing -Merlin. - -Predecessors of Merlin and Blackbird, such as -[APGen](https://trs.jpl.nasa.gov/handle/2014/45571) and -[SEQGen](https://trs.jpl.nasa.gov/handle/2014/45455), provide a -domain-specific language for mission modeling, allowing them to obtain -deep, fine-grained information about the composition of a mission -model before performing any simulation. In some ways, this provides -enhanced ergonomics, as a mission modeler can focus on expressing -their model directly in the modeling language, without being concerned -with the needs of the system that will be interpreting that model. -The language itself captures all interesting aspects of the model. - -Unlike the DSLs of APGen and SEQGen, Java is a general-purpose -language with no explicit provisions for mission modeling. To serve -mission modeling, these facilities must instead be built on top of -Java, forming a bridge between the mission model and the simulation -system. A mission model must explicitly use this bridge to expose -modeling knowledge to the system interpreting their model. It is this -modeling interface, not the authoring language, that must express all -interesting aspects of the model. - -:::note - -A language can be, and often is, construed as an interface in its own -right. However, these linguistic interfaces are often so rich and -complex that a difference in degree becomes a difference in kind. -Most mainstream statically-typed languages, including Java, cannot -faithfully embed linguistic interfaces in their type systems; any -attempt quickly blows through the degree of expressivity provided by -the type system. - -Languages like [Haskell](https://www.haskell.org/) and -[Scala](https://www.scala-lang.org/) provide more expressive type -systems, and dependently-typed languages like -[Idris](https://www.idris-lang.org/) are more expressive still. These -programming languages allow a more faithful embedding of linguistic -interfaces, so they are often used to support -[EDSLs](https://wiki.haskell.org/Embedded_domain_specific_language) -(embedded domain-specific languages) in research and industry. - -::: - -The dichotomy between the modeling interface and the authoring -language bounds the design of the Merlin modeling experience between -two extremes. - -- At one extreme, the modeling interface dominates the experience of - modeling, to the point that almost any authoring language could have - been used as long as the interface could be embedded into it. This - design is characterized by the intrusive presence of elements of the - interface throughout the mission model, and is not much different - from hand-writing the abstract syntax tree of a program in some - domain-specific language. - -- At the other extreme, the authoring language dominates the - experience of modeling, and the interface avoids repeating - capabilities that are already possessed by the authoring language. - The interface is purely relegated to the role of "bridge", binding - the relevant native entities to the intended domain concepts. - -Merlin briefly explored the first extreme at its inception, with -pervasive use of the [Builder -pattern](https://en.wikipedia.org/wiki/Builder_pattern) to describe -elements of the model. It quickly became apparent that this avoided -most of the benefits of Java: common development tools like -autocompletion could not be used to guide mission modelers, and the -modeling experience was very unlike Java development in general. The -early development of Merlin was characterized by a gradual shift away -from this end of the design space. - -Merlin has chosen to pursue the second extreme on principle, utilizing -the authoring language to its greatest extent while augmenting it with -domain-specific semantics where necessary. Among other reasons, Java -was originally chosen as a modeling language because it would serve as -a "transferable skill" for those both entering and exiting the -"mission modeler" role. As a general-purpose language, Java provides -a solid baseline for building a modular, maintainable system of any -variety. High-performance Java runtimes already exist, and the -oft-forgotten debugging experience is present out of the box. Merlin -intends that mission modeling "taste" like development in Java more -broadly, with the mission model "flavors" carefully integrated into -that experience. - -The Merlin interface must be minimal, to allow the authoring language -to take center stage, while complete, to allow modeling knowledge to -be transferred out of the mission model. A minimal interface can -always be built upon the authoring language to provide more natural -ergonomics. To that end, there are three primary landmarks in the -Merlin mission modeling interface. - -- **Cells** allow a mission model to express **time-dependent state** in a way that can be tracked and managed by the host system -- **Tasks** allow a mission model to describe **time-dependent processes** that affect mission state -- **Directives** specify the external stimuli which may be posed against a model (i.e. spawning tasks) -- **Resources** allow a mission model to express the **time-dependent evolution** of quantities of interest to the mission - -As a common theme, the interface augments Java with time-dependence, -allowing the flow of simulation time to be decoupled from (and queried -independently of) the flow of real time. As a rule, we are not -interested only in the state a model finds itself in at the end of a -period of time, but rather the succession of all states it transitions -through over time. - -### Cells - -In Java, every object begins in some state (upon construction); can be -transitioned into another state by sending messages to it (via -methods); and can be interrogated for information based on its current -state (also methods). Even primitives in Java fit this mold: the -value (state) of a primitive field may be replaced or retrieved -wholesale. (Structures that behave like a primitive field are -sometimes called "atomic registers".) - -However, normal Java objects are not aware of the distinction between -simulation time and real time. It is not possible to ask an arbitrary -object about a state it previously inhabited, and it is even less -possible to put an object into two states simultaneously, as occurs -when simulation time splits and rejoins for concurrently-executing -tasks. - -**Cells** are a time-dependent generalization of mutable objects in -Java supporting concurrent use across simultaneously-acting tasks, -and retaining historical knowledge about its state at any simulation -time. All mutable state accessible during simulation must be -manipulated through a containing cell. - -Like an object, a cell possesses an internal state and a set of -operations to transition between states. These operations are called -"effects", and are logged alongside the cell. The state of a cell at -any simulation time is solely determined by its initial state and the -effects upon it prior to that time. - -Unlike an object, a cell possesses a simulation-aware semantics for -combining sequential and concurrent effects and for explicitly -transitioning the cell between states. **The state of a cell must not -be affected except by applying effects to its containing cell**. - -Through cells, a mission model may manage mutable state much as though -it were a Java object, with behavior appropriate to the order of -operations in simulation time, rather than the less predictable order -of operations in real time. The concurrent semantics of Merlin -simulation is confined to the internal behavior of cells, allowing for -tight control of custom semantics while isolating the rest of the -mission model from these concerns. - -Through cells, the Merlin simulation system may observe when and which -elements of simulation state are queried or affected as the simulation -proceeds. This constitutes the single most powerful tool at Merlin's -disposal to obtain insight into a mission model, as it allows Merlin -to collect two different sets of knowledge over the course of -simulation: - -- A **simulation timeline** captures all effects on all cells in the - order they occur, even accounting for concurrent effects between two - simultaneous tasks. This structure completely captures the sense of - the term "simulation time": the state of the mission model at any - time of interest is fully determined by an index into this - structure. - -- A **dependency graph** captures all causal dependencies between - cells, tasks, and resources. This allows the runtime system to - optimize system execution in multiple ways, and it may also enable - mission planners to, for instance, better understand how an earlier - activity influences a later one. - -A mission model may create a cell by providing an initial state and an -effect semantics through the Merlin interface. It receives a handle -to the cell in the shape of a Java object, and may then use it -idiomatically like any other Java object. - -### Tasks - -In Java, an object transitions between states when a method is invoked -on it. The methods of one object may recursively invoke the methods -of other objects, causing an entire graph of objects to transition -between states. - -Methods in Java are not normally explicitly aware of the passage of -time. They may ask the host system what time it is, but the amount of -time that passes is not a functional element of the system - rather, -it is an incidental effect of the hardware and other software -executing on the same host. Moreover, multiple methods in Java cannot -proceed concurrently. At most one method is ever in progress, and it -must complete before its caller - and _only_ its caller - may proceed. - -**Tasks** are a time-dependent generalization of methods in Java. A -task may transition the model between states by performing effects -upon its cells. A task may spawn other tasks, then proceed -concurrently with its children - concurrent effects are resolved by -the cells to which they are posed. Tasks may explicitly await the -passage of simulation time before continuing, or may await the -completion of another task or the transition of the model into a -particular state. - -Like methods, tasks possess their own internal state, representing the -work left to be completed by the task. Progress through a Java method -is implicitly managed by the Java runtime (via the call stack); in -order to use methods as a foundation for tasks, we must supplement -this implicit state rather than replacing it. - -The Merlin interface treats tasks in terms of steps. Every time a -task is stepped forward, it updates its internal state, performs some -effects, spawns some children, and then reports a status describing -when to step the task again. The task's internal state is managed -entirely on one side of the interface, and so does not need to be -transmitted. In other words, a task is fundamentally treated as an -opaque state machine. - -However, _specifying_ tasks as state machines requires interleaving -modeling logic with tedious bookkeeping. This avoids many of the -benefits of Java methods, and it isn't possible to pause a task while -invoking other methods unless they are specified in the same way. To -that end, we provide task specification platforms that centralize the -tedious bookkeeping to a single context object against which task may -invoke methods to spawn, delay, and perform effects. A task can be -specified as a regular Java method that happens to have access to this -context. - -Note that these specification platforms are not part of the modeling -interface itself; they merely adapt the state machine-oriented -interface to the ergonomic expectations of users. - -### Directives - -Mission models are used by interacting with them in some way, and -observing the resulting impact on the model state over time. While -tasks specify how the model state itself is changed, **directives** -specify the external stimuli which may be posed against a model. - -The concept of directives has a different name depending on how the -model is being used. When used in an activity planning workflow, -directives represent the **activities** performed by the mission -system. When used in a sequencing workflow, directives represent the -**sequences** and **commands** dispatched by the ground station. In -all cases, they cause the mission system to respond in some way - in -other words, to spawn a task. - -The Merlin interface allows a mission model to register the directives -it supports, along with a task to be spawned when a directive is -received. Directives also specify a set of **parameters**, allowing -the behavior of a directive to be modulated. The arguments for a -specific directive are provided directly to the task it spawns. - -As with most concepts in Java, method arguments are themselves named -objects of some type. However, the arguments to a directive are -specified by a mission planner, typically via a UI rather than Java -source code. Thus, directive arguments must be serializable and -deserializable to a model-agnostic form that can be presented -ergonomically to a planner. The modeling interface provides all -arguments in this form, and does not hard-code support for arbitrary -Java types. - -Working with this constrained data type poses a burden on mission -modelers, who would need to interleave processing of these argument -representations with their modeling logic. Moreover, planners would -generally like to know ahead of time whether the arguments they've -provided are valid for a given directive, rather than waiting until -simulation time to observe a failure. To that end, mission modelers -may separately provide - and reuse existing definitions of - dedicated -value mappers, converting between values of the general-purpose -interchange type and values of the desired modeling type. - -Finally, when executing in the context of a directive, the mission -model may spawn other directives. These directives are executed just -like any other spawned task, but they can also be reported to the -modeler as a product of simulation. This process, called -**decomposition**, allows planners to better understand how a single -directive breaks down into distinct behaviors, and the overall -decomposition hierarchy is a primary input into the sequence -generation workflow, which realizes an activity plan in a form -suitable for execution by a physical system. - -### Resources - -In Java, the state of an object is "encapsulated", meaning it cannot -be observed directly from the outside. Instead, the object exposes -methods that return different information depending on its current -state. Moreover, an object may itself reference other objects, so the -state of an object may depend on the state of many others. A method -may depend upon these references by recursively invoking other -methods. - -In Merlin, a modeled system transitions between states by reacting to -discrete stimuli at instantaneous times. However, even a system in a -fixed state can continuously affect its environment: a rocket that -imparts a constant force over time will see its position and velocity -change over time. Thus, we need the ability to ask about the behavior -of the system not just at discrete times, but over continuous regions -of time. - -A **resource** is a time-dependent generalization of getter methods in -Java. Resources provide information describing the steady-state -behavior of some quantity over time, starting at the time at which it -is queried and continuing indefinitely. This information is called a -**dynamics**, as it describes the autonomous dynamical behavior of the -resource. - -Merlin currently supports two kinds of resource: discrete and real -resources. The behavior of a discrete resource is given by a single -fixed value: a discrete resource does not change autonomously. The -behavior of a real resource is given by an initial value and slope, -i.e. a line: a real resource accrues over time. (We hope to support -general polynomial resources in the future.) - -Although a resource dynamics describes an autonomous behavior, that -behavior may change when the mission system transitions between -states. Merlin simply re-queries any resources affected by the state -transition to obtain their new autonomous dynamics. - -## Constraints - -When analyzing a simulation's results, it is useful to detect time -windows where certain conditions are met. An Aerie constraint is a -condition based on simulated activities, resources profiles, or -external datasets which must hold true for some period of time. If a -constraint does not hold true at any point within the specified time -period of interest, the constraint is a violation. - -A Constraint is defined as an expression (expression of expressions) -which operate on resource profiles and activity instances. Allowed -expressions are defined by the Aerie constraint grammar. This grammar -is defined in a Typescript eDSL that, when executed, produces a JSON -AST which can be interpreted by Merlin. - -In the Aerie constraint AST, the internal nodes represent the -constraint expression operators, while the leaf nodes represent the -operands. For example, operator nodes enable expressions such as 'or', -'and', 'less than', 'greater than', while the operands are simulated -resource profiles and activity instances. See our [constraints -documentation](../../../scheduling-and-constraints/declarative/constraints/examples) for complete examples. - -## Meta-Programming (Annotations Processing) - -Aerie uses a meta-programming approach to generate additional source -files and documentation. Meta-programming reduces the requirement for -a mission modeler to write a range of (de)serialization and state -injection code for various Aerie data objects. Aerie uses -meta-programming for the following purposes: - -- Generate (de)serialization classes for Activity definitions for - transportation across the application boundary. -- Facilitate dependency injection for Activity parameters. -- Expose: - - Activity type - - Activity effect model - - Activity parameter (de)serialization and value injection - - Activity parameter value validation - - Mission model entry point - - Mission model configuration - - Activity types defined within the mission model - - Activity dependency injection - initializing an activity from its - stored representation. - -For many built-in Java types, a reasonable serialization scheme can be -provided out of the box. Custom annotation derived mappers shall -remain as the recommended option for optimization purposes or for -greater control over the serialized data. - -Aerie chose to use Java's annotations processing mechanism. Unlike a -mechanism such as reflection which operates at runtime, annotations -processing automatically generates Java code which is then -compiled. This compile time generation allows for the compile time -type checking and results in code which can be debugged at runtime. - -Annotation processing is a general tool for generating additional -source files during compilation. These files can be of any type, -including Java files, documentation, and other resources. This tool -can only be used to generate new files, not to change existing ones. - -## Scheduling - -Just as a human operator places and edits activities in a mission -plan, so too can an algorithmic agent can make automated decisions -about activity plan editing. An automated scheduling agent enables -missions to make a large volume of plan mutations and perform the -complex evaluations as to how well such mutations achieve a mission's -goals. - -In Aerie the scheduling agent "Automated Scheduler" exists as a -separate entity and makes queries and mutation against the Aerie -API. As shown in the Figure below, an Aerie developed and maintained -automated scheduler is a part of the standard Aerie deployment. The -Aerie Automated Scheduler makes queries of simulation results, -constraint evaluations, and makes plan mutations via the Aerie -API. This loose coupling is the same as any third party entity which -may query the Aerie API gateway. As a result, a mission can choose to -forgo the Aerie Automated Scheduler and implement their own scheduler -and scheduling algorithm, which makes use of the same Aerie API. - -Previous activity planning and simulation systems chose to embed -scheduling logic within the mission model code base. Such tight -coupling of scheduling logic with the mission model code introduced -particularly burdensome restrictions. The loose coupling of the Aerie -design alleviates these burdens and is characterized by the following -aspects: - -- Expressivity - restrictions on expressivity (using other programming - paradigms to develop scheduling algorithms and evaluate scheduling - rules) -- Extensibility - extensibility (definition of new constraint concepts - without having to modify any part of Aerie or a mission model) -- Maintainability - disconnect the mission model and scheduling - editing CM process -- Process independence/async - run on different infrastructure - providing performs, and more dynamic execution workflows - -import c4SchedulingComponent from './assets/c4-scheduling-component.png'; - -
- Scheduling Component -
Figure 4: The Aerie Scheduling Component
-
- -When executing the Automated Scheduler is likely to make many calls to -the Aerie API. The Automated Scheduler will sensibly batch API calls -to reduce unnecessary communication round trips. A scheduler will need -to be able to make the following requests of the Aerie API: - -- Query for activity instances in a plan -- Add, edit, delete activity instance from a plan -- Simulate an activity plan for some duration from some start time -- Query simulated resource profiles -- Add/edit/delete constraints for a plan -- Query constraint windows for a plan -- Submit a constraint definition for its windows for a plan - -## Appendix - -### A - The C4 Model - -The architectural diagramming approach taken in this document follows -the [C4 Model](https://c4model.com/) for visualizing and discussing -software architecture. This approach abstracts a software system into -roughly four level, Context, Containers, Components, and Code. A brief -overview of the first three levels of abstraction is given below to -help the reader of this document better understand what is being -communicated in the various diagrams. - -1. The Context diagram shows the software system (Aerie) in its - broadest scope. - - **Scope:** A single software system.
**Primary elements:** - The software system in scope.
**Supporting elements:** People - (e.g., users, actors, roles, or personas) and software systems - (external dependencies) that are directly connected to the software - system in scope. Typically, these other software systems sit - outside the scope or boundary of your own software system, and you - don’t have responsibility or ownership of them.
**Intended - audience:** Everybody, both technical and non-technical people, - inside and outside of the software development team. - -2. The Container diagram shows the high-level shape of the software - architecture and how responsibilities are distributed across it. It - also shows the major technology choices and how the containers - communicate with one another. A container is a separately - runnable/deployable unit (e.g., a separate process space) that - executes code or stores data. - - **Scope:** A single software system.
**Primary elements:** - Containers within the software system in scope.
**Supporting - elements:** People and software systems directly connected to the - containers.
**Intended audience:** Technical people inside - and outside of the software development team; including software - architects, developers and operations/support staff. Notes: This - diagram says nothing about deployment scenarios, clustering, - replication, failover, etc. - -3. The Component diagram specifies the "components" which comprise a - Container, what each of those components are, their - responsibilities and the technology/implementation details. - - **Scope:** A single container.
**Primary elements:** - Components within the container in scope.
**Supporting - elements:** Containers (within the software system in scope) plus - people and software systems directly connected to the - components.
**Intended audience:** Software architects and - developers. diff --git a/docs/planning/activity-directive-metadata.md b/docs/planning/activity-directive-metadata.md deleted file mode 100644 index e17a07f6..00000000 --- a/docs/planning/activity-directive-metadata.md +++ /dev/null @@ -1,56 +0,0 @@ -# Activity Directive Metadata - -This document describes how to add mission-specific metadata (aka annotations) to activity directives. There are 6 different types of metadata which are summarized in the table below. - -| Type | Schema | Display | -| ------------- | -------------------------------------------------- | ------------------------- | -| `string` | `{ "type": "string" }` | Single line text field | -| `long_string` | `{ "type": "long_string" }` | Textarea | -| `boolean` | `{ "type": "boolean" }` | Checkbox | -| `number` | `{ "type": "number" }` | Single line numeric field | -| `enum` | `{ "enumerates": [], "type": "enum" }` | Single select dropdown | -| `string` | `{ "enumerates": [], "type": "enum_multiselect" }` | Multi select dropdown | - -## Adding Metadata Schemas - -To add mission-specific metadata schemas you need to use the GraphQL API. Here is a mutation that adds an example of each type. - -#### Mutation - -```graphql -mutation CreateActivityDirectiveMetadataSchemas($schemas: [activity_directive_metadata_schema_insert_input!]!) { - insert_activity_directive_metadata_schema(objects: $schemas) { - affected_rows - returning { - created_at - key - schema - updated_at - } - } -} -``` - -#### Query Variable - -```json -{ - "schemas": [ - { "key": "STRING_EXAMPLE", "schema": { "type": "string" } }, - { "key": "LONG_STRING_EXAMPLE", "schema": { "type": "long_string" } }, - { "key": "BOOLEAN_EXAMPLE", "schema": { "type": "boolean" } }, - { "key": "NUMBER_EXAMPLE", "schema": { "type": "number" } }, - { - "key": "ENUM_EXAMPLE", - "schema": { "enumerates": ["A", "B", "C"], "type": "enum" } - }, - { - "key": "ENUM_MULTISELECT_EXAMPLE", - "schema": { - "enumerates": ["D", "E", "F", "G"], - "type": "enum_multiselect" - } - } - ] -} -``` diff --git a/docs/planning/advanced-extensions.mdx b/docs/planning/advanced-extensions.mdx deleted file mode 100644 index 0352ade7..00000000 --- a/docs/planning/advanced-extensions.mdx +++ /dev/null @@ -1,60 +0,0 @@ -import extensions from './assets/extensions-navbar.png'; - -# Advanced - Extensions - -Extensions are external tools that can be invoked directly from inside of Aerie. When an Extension is called, the following values are passed along in the request body: - -- `gateway` - The URL for the current instance's Gateway server. -- `hasura` - The URL for the current instance's Hasura server. -- `planId` - The id of the plan the user is looking at. -- `selectedActivityDirectiveId` - The id of the selected activity directive (if applicable). -- `simulationDatasetId` - The id of the last run simulation (if applicable). - -```ts -{ - planId: number; - selectedActivityDirectiveId: number | null; - simulationDatasetId: number | null; - gateway: string; - hasura: string; -} -``` - -Here is an example when the user has no directives selected: - -```json -{ - "planId": 47, - "selectedActivityDirectiveId": null, - "simulationDatasetId": 124, - "gateway": "http://aerie_gateway:9000", - "hasura": "http://hasura:8080/v1/graphql" -} -``` - -Additionally, the user's current session information will be passed to the Extension via the `Authorization` and `x-hasura-role` headers. - -Extensions **must** return a response of the following form: - -- `message`: the message to be displayed as a notification inside of Aerie -- `success`: a boolean indicating whether the Extension ran successfully -- `url`: an optional link to a file or website. if provided, it will be opened in a new tab - -## Managing Extensions - -Extensions may only be managed via the [GraphQL API](/api/examples/advanced-extensions). - -## Using Extensions - -When looking at a plan in Aerie, all registered Extensions can be found under the Navbar entry between Scheduling and View. -If there are no any extensions registered, this entry will be hidden. - -Hovering over extensions will bring up the list of registered Extensions. -To run an extension, click on its entry in the list. - -
- Aerie Planning Navbar - Extensions -
Figure 1: Aerie Planning Navbar - Extensions
-
- - diff --git a/docs/planning/advanced-incons.mdx b/docs/planning/advanced-incons.mdx deleted file mode 100644 index aae32f0e..00000000 --- a/docs/planning/advanced-incons.mdx +++ /dev/null @@ -1,123 +0,0 @@ -import simulationInconsQuery from './assets/simulation_incons_query.mov'; -import gqlDropdown from './assets/graphql_dropdown.png'; -import gatewayDropdown from './assets/gateway_dropdown.png'; -import uploadIncons from './assets/upload_incons.mov'; -import simulateIncons from './assets/simulate_with_incons.mov'; - -# Advanced - Incons - -If you have previously run simulation on a plan, it is possible to use that simulation data to initialize resources in a later simulation run. -This can be useful when running [Temporal Subset Simulation](../create-plan-and-simulate#advanced-temporal-subset-simulation), -or if one plan's input is dependent on the output of another. - -In order to take advantage of this feature, the Mission Model must be written to support loading incons from a file. - -:::info Note - -The instructions on this page are designed to work with the example Mission Model changes provided [here](../../mission-modeling/advanced-incons). -They may need to be tweaked slightly depending on how your model implemented incon loading. - -::: - -### Step 1: Get the Incons from Hasura - -Navigate to your deployment's Hasura GraphQL console by selecting `GraphQL Console` in the dropdown under `Aerie`. -By default, the GraphQL console is accessible on [port 8080](http://localhost:8080). - -
- Aerie UI - Dropdown Menu Highlighting GraphQL Console Option -
Figure 1: Dropdown Menu Highlighting GraphQL Console Option
-
- -In the Hasura GraphiQL console of your deployment, enter [the following query](/api/examples/simulation/#query-for-simulation-resource-data-at-a-given-time): - -```graphql -query GetResources($dataset_id: Int!, $start_offset: interval!) { - getResourcesAtStartOffset(args: { _dataset_id: $dataset_id, _start_offset: $start_offset }) { - id - dataset_id - dynamics - is_gap - name - start_offset - type - } -} -``` - -In the `Query Variables` field below, enter a JSON that specifies the id of the simulation dataset to use, -and how far into the results you'd like to get results, in the format `hh:mm:ss`. -For example, the following sample JSON would get the values of all previously set resources four days into the thirteenth simulation: - -```json -{ - "dataset_id": 13, - "start_offset": "96:00:00" -} -``` - -The simulation dataset ID can be found in the `Simulation History` section of the Simulation pane. - -:::caution - -If the first time a resource's value is set is after `start_offset`, it will not appear in the returned list of resources. - -::: - - - -### Step 2: Export and Upload the Incons - -Take the value of `"getResourcesAtStartOffset"` from the output of the query and copy it into a JSON file. - -Next, navigate to your deployment's Gateway by selecting `Gateway` in the dropdown under `Aerie`. -By default, the Gateway is accessible on [port 9000](http://localhost:9000). - -
- Aerie UI - Dropdown Menu Highlighting Gateway Option -
Figure 2: Dropdown Menu Highlighting Gateway Option
-
- -Scroll down to `Files` and select [`POST /file`](http://localhost:9000/#/Files/post_file). -Click the `Try it out` button. -In the `x-auth-sso-token` field, enter your auth token if you have one, or, if auth is disabled, enter `unknown`. -If you do not know your auth token, you can get it by logging in using the [`POST /auth/login`](http://localhost:9000/#/Auth/post_auth_login) action. -Click the `browse` button next to file and select the JSON with the incons. - -Click the `Execute` button. In `ServerResponse`, you should see a response body that looks like: - -```json -{ - "file": { - "fieldname": "file", - "originalname": "incons.json", - "encoding": "7bit", - "mimetype": "application/json", - "destination": "files", - "filename": "incons-1682384407286-7ouftKBnfPWs93.json", - "path": "files/incons-1682384407286-7ouftKBnfPWs93.json", - "size": 2539 - }, - "id": 8 -} -``` - -The most important element of this JSON is `filename`. It contains the name of the file within Aerie. - - - -### Step 3: Use the Incons in Simulation - -Finally, open the Simulation Configuration panel and set the `inconsPath` argument to `/`, -where `` is the path to where the `aerie_file_store` volume is mounted in Merlin, -and `` is the value of `"filename"` from the earlier JSON from the Gateway. - -By default, `` is `merlin_file_store`. - - diff --git a/docs/planning/anchors.mdx b/docs/planning/anchors.mdx deleted file mode 100644 index 9541636e..00000000 --- a/docs/planning/anchors.mdx +++ /dev/null @@ -1,66 +0,0 @@ -import Link from '@docusaurus/Link'; -import invalidAnchorsList from './assets/invalid_anchors_list.png'; -import invalidAnchorTooltip from './assets/invalid_anchor_tooltip.png'; -import setAnchorsDemo from './assets/anchoring_ui_demo.mov'; -import removeAnchorsDemo from './assets/remove_anchors_demo.mov'; - -# Anchors - -It is possible to define the start time of an activity directive as relative to the start or end time of another directive. This is known as "Anchoring". - -## Anchor to Another Activity Directive - -Anchors can be added to an existing activity directive as follows: - -1. Select the directive. -2. Expand the `Anchor` dropdown in the `Definition` section of the `Selected Activity` pane. -3. In the `Relative To` entry, enter the activity directive you wish to anchor this directive to. -4. Toggle whether the directive is anchored to the start or end of the anchored directive (by default, activities are anchored to the start). -5. Set the offset to desired value. - -A video of how to follow these steps is below: - - - -To remove an anchor, clear the textbox besides the `Relative To` field or set it to `To Plan`. - - - -## Invalid Anchors - -For ease of use, Aerie allows anchors that are not able to be simulated to exist. These anchors are considered to be invalid. - -:::info Note - -A plan **cannot** be simulated or scheduled while there are invalid anchors. - -::: - -If an activity directive has an invalid anchor, the error message can be viewed by hovering over the problematic field in the `Selected Activity` pane. - -
- Aerie UI - Anchor Validation Error in a Tooltip -
Figure 1: Aerie UI - Anchor Validation Error in a Tooltip
-
- -To see a full list of the invalid anchors for a plan, select the `Anchor Validation Errors` tab from the Error Console - -
- Aerie UI - Anchor Validation Errors in the Error Console -
Figure 2: Aerie UI - Anchor Validation Errors in the Error Console
-
- -## Delete Activity Directive with Anchors - -
-

- Currently, it is impossible to delete an activity directive that has other directives anchored to it via the UI. If - you would like to delete an activity directive that has other directives anchored to it, either manually remove the - anchors from the dependent directives or use the API for more - options. -

-
diff --git a/docs/planning/assets/aasa.mov b/docs/planning/assets/aasa.mov deleted file mode 100644 index 344d2c88..00000000 --- a/docs/planning/assets/aasa.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:578c9760a08fa7c346837ae522a93df46562bf47c69ab7dff48ef220c47cd311 -size 10877346 diff --git a/docs/planning/assets/anchoring_ui_demo.mov b/docs/planning/assets/anchoring_ui_demo.mov deleted file mode 100644 index a202c2d2..00000000 --- a/docs/planning/assets/anchoring_ui_demo.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30b8f78a134e5507f93cffee4ae3a70e572f83a7a2e6652d2edcf2a00c3162bf -size 8568680 diff --git a/docs/planning/assets/create-plan.webm b/docs/planning/assets/create-plan.webm deleted file mode 100644 index c6365b69..00000000 --- a/docs/planning/assets/create-plan.webm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b76bff1327a72b19a22dd127e7431502dbab23fc227d4911c3f8c56abce2ef3 -size 666321 diff --git a/docs/planning/assets/export-plan.png b/docs/planning/assets/export-plan.png deleted file mode 100644 index b732c27f..00000000 --- a/docs/planning/assets/export-plan.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d8c91697b8aa81299a53654dd1f1f15e39d82db156c3398e509853b1e9a7af6 -size 24063 diff --git a/docs/planning/assets/extensions-navbar.png b/docs/planning/assets/extensions-navbar.png deleted file mode 100644 index 76085214..00000000 --- a/docs/planning/assets/extensions-navbar.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:000571f90fbe70cfa5382aa0ef876176363db7e27f8f2797541078d59fc7af5e -size 74767 diff --git a/docs/planning/assets/first-simulation.webm b/docs/planning/assets/first-simulation.webm deleted file mode 100644 index d078ebd4..00000000 --- a/docs/planning/assets/first-simulation.webm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32e7262bc6b2fa23bdfcc24b954125417a9236b124ee4ba70260bdfccdd03843 -size 2972660 diff --git a/docs/planning/assets/gateway_dropdown.png b/docs/planning/assets/gateway_dropdown.png deleted file mode 100644 index 2eb1fb31..00000000 --- a/docs/planning/assets/gateway_dropdown.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:17f8b6376b6efe98ad5639551d90276f71e1a4e5c683832144774aa8b3660c90 -size 112067 diff --git a/docs/planning/assets/graphql_dropdown.png b/docs/planning/assets/graphql_dropdown.png deleted file mode 100644 index c7463f1c..00000000 --- a/docs/planning/assets/graphql_dropdown.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e35c6a5868f485ea8269fa6cfc1a3ac61cc4f442f907893955aa8c0641dd5c49 -size 112213 diff --git a/docs/planning/assets/invalid_anchor_tooltip.png b/docs/planning/assets/invalid_anchor_tooltip.png deleted file mode 100644 index 80c8182c..00000000 --- a/docs/planning/assets/invalid_anchor_tooltip.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76ca666643911eebd5aa8bc838c0291b0628ec8d09f30f1ddf1129e6198c409b -size 50807 diff --git a/docs/planning/assets/invalid_anchors_list.png b/docs/planning/assets/invalid_anchors_list.png deleted file mode 100644 index adcfe441..00000000 --- a/docs/planning/assets/invalid_anchors_list.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0345eabb7e4b8a5c3f3d8d09d46b59dba145ed75425761a2e7bc4314b968a57f -size 67457 diff --git a/docs/planning/assets/remove_anchors_demo.mov b/docs/planning/assets/remove_anchors_demo.mov deleted file mode 100644 index cc7164ee..00000000 --- a/docs/planning/assets/remove_anchors_demo.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb0ef451378c9e6ee5d74d7560e66a06a14c8ba8697db8b26ba23fb0ad6cc10a -size 3125542 diff --git a/docs/planning/assets/simulate_with_incons.mov b/docs/planning/assets/simulate_with_incons.mov deleted file mode 100644 index d26f8900..00000000 --- a/docs/planning/assets/simulate_with_incons.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5f2b153fce5150dacbd4e77eefb3d4244d61e84972a85329b87274ce9b5fecd -size 9586640 diff --git a/docs/planning/assets/simulation_incons_query.mov b/docs/planning/assets/simulation_incons_query.mov deleted file mode 100644 index 7f8a6f73..00000000 --- a/docs/planning/assets/simulation_incons_query.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5792f0c1915149cda0f425f723243e0946a2f6e3d7afd736d22a8acf27c7d015 -size 15522756 diff --git a/docs/planning/assets/snapshots/planMetadataPane.png b/docs/planning/assets/snapshots/planMetadataPane.png deleted file mode 100644 index fa27d32d..00000000 --- a/docs/planning/assets/snapshots/planMetadataPane.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:211adcf2161d1dae217a58f83506d23ee4f3f4d747e6aee528f7f482ecf8eda4 -size 145986 diff --git a/docs/planning/assets/snapshots/restoreSnapshotModal.png b/docs/planning/assets/snapshots/restoreSnapshotModal.png deleted file mode 100644 index 1b1b646c..00000000 --- a/docs/planning/assets/snapshots/restoreSnapshotModal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:31e0f1861cbc6ed853906305a6ada3cfb221bd0fdcccfc374d6218795c729aac -size 41987 diff --git a/docs/planning/assets/snapshots/restoreSnapshotTakeSnapshot.png b/docs/planning/assets/snapshots/restoreSnapshotTakeSnapshot.png deleted file mode 100644 index d2a3d535..00000000 --- a/docs/planning/assets/snapshots/restoreSnapshotTakeSnapshot.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b85de39ad83e6d6dc7a503cdde49997e1fadaafad3c18f3fb5a1a5d9a8fe56cd -size 69251 diff --git a/docs/planning/assets/snapshots/takeSnapshotModal.png b/docs/planning/assets/snapshots/takeSnapshotModal.png deleted file mode 100644 index 9affed1f..00000000 --- a/docs/planning/assets/snapshots/takeSnapshotModal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8098539fb50bc5fa235bac659a173de08f58a94fc19b84bf0fb9d904500ec85c -size 71496 diff --git a/docs/planning/assets/snapshots/takeSnapshotNavbar.png b/docs/planning/assets/snapshots/takeSnapshotNavbar.png deleted file mode 100644 index da125010..00000000 --- a/docs/planning/assets/snapshots/takeSnapshotNavbar.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a5b0a80e968ad4fab457c541c8c80d6492e20acb485711a435a43a7d17326761 -size 56999 diff --git a/docs/planning/assets/snapshots/viewSnapshot.png b/docs/planning/assets/snapshots/viewSnapshot.png deleted file mode 100644 index f1a1d1bc..00000000 --- a/docs/planning/assets/snapshots/viewSnapshot.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c30847cde99938ea7b8fe3917c031fb04bf914b05a8c3f57b90420fee108a421 -size 668036 diff --git a/docs/planning/assets/snapshots/viewSnapshotNavbar.png b/docs/planning/assets/snapshots/viewSnapshotNavbar.png deleted file mode 100644 index 137da00c..00000000 --- a/docs/planning/assets/snapshots/viewSnapshotNavbar.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac2a5b018534e96ac580dcd950bdc8a9a7784aa3ce97cf1e777a0c99f5e36fca -size 56804 diff --git a/docs/planning/assets/timeline-activity-filtering-modal.png b/docs/planning/assets/timeline-activity-filtering-modal.png deleted file mode 100644 index 2d08e691..00000000 --- a/docs/planning/assets/timeline-activity-filtering-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52f516d73902f60954a300fd41fa2783c11c64aa37f9a1d0e6ae068c1a07c579 -size 162384 diff --git a/docs/planning/assets/timeline-activity-layer-adding.mov b/docs/planning/assets/timeline-activity-layer-adding.mov deleted file mode 100644 index fce5eb6c..00000000 --- a/docs/planning/assets/timeline-activity-layer-adding.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b40a3490377a77e16d9f62daf398b882983987e36bd67cf2f5bfa96cd500eafd -size 98196839 diff --git a/docs/planning/assets/timeline-activity-options-mode.png b/docs/planning/assets/timeline-activity-options-mode.png deleted file mode 100644 index 630cc590..00000000 --- a/docs/planning/assets/timeline-activity-options-mode.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:078d34b3084a1da29844d98f4a2a79184a6e983128ef8216502ed4ca15dadeba -size 152365 diff --git a/docs/planning/assets/timeline-activity-tree.png b/docs/planning/assets/timeline-activity-tree.png deleted file mode 100644 index 6f584b00..00000000 --- a/docs/planning/assets/timeline-activity-tree.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a855cf2e58938157beed66c4b030cf22938e439a0098b4215ae15c0f4aea09ba -size 69034 diff --git a/docs/planning/assets/timeline-cursor-interpolation-off.png b/docs/planning/assets/timeline-cursor-interpolation-off.png deleted file mode 100644 index 4d52dc58..00000000 --- a/docs/planning/assets/timeline-cursor-interpolation-off.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:26b0118f0e06719c9a35f9af9395fd89a07a991ff77a7c3f938a65756be18159 -size 16008 diff --git a/docs/planning/assets/timeline-cursor-interpolation-on.png b/docs/planning/assets/timeline-cursor-interpolation-on.png deleted file mode 100644 index c04b7d58..00000000 --- a/docs/planning/assets/timeline-cursor-interpolation-on.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4c569b8998834ab7d0bc8ce55291f684d518a873a9ff457017ead265c0c7211 -size 18078 diff --git a/docs/planning/assets/timeline-discrete-options-editor.png b/docs/planning/assets/timeline-discrete-options-editor.png deleted file mode 100644 index eb11761e..00000000 --- a/docs/planning/assets/timeline-discrete-options-editor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81c67534c51062c79e6000ce4ed07a738dbd73424934e3a1992d6b330e2c2d4d -size 77084 diff --git a/docs/planning/assets/timeline-editor.png b/docs/planning/assets/timeline-editor.png deleted file mode 100644 index 29fabc7d..00000000 --- a/docs/planning/assets/timeline-editor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7df375ffccdd2d343fd47de37790c3af816ad4c7445e0cc819d83a7864d928c4 -size 19998 diff --git a/docs/planning/assets/timeline-icon-tray.png b/docs/planning/assets/timeline-icon-tray.png deleted file mode 100644 index 76903e28..00000000 --- a/docs/planning/assets/timeline-icon-tray.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f9849608bfb8fe5dc47fdd11b7caade9f801fb134d767a761b12b3ad08ea3400 -size 19300 diff --git a/docs/planning/assets/timeline-line-layer-editing.mov b/docs/planning/assets/timeline-line-layer-editing.mov deleted file mode 100644 index 78999e47..00000000 --- a/docs/planning/assets/timeline-line-layer-editing.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2a12d195841ae0fd07def767d49a874416e9dc2ed2aaa62db3a942c9b9db27c6 -size 30251210 diff --git a/docs/planning/assets/timeline-row-editor.png b/docs/planning/assets/timeline-row-editor.png deleted file mode 100644 index ab3a8c9a..00000000 --- a/docs/planning/assets/timeline-row-editor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:447ea94882c2cd5ace1232756cf7c6612a86208eebe0393ca856d453bd0263d6 -size 203674 diff --git a/docs/planning/assets/timeline-y-axis-settings.png b/docs/planning/assets/timeline-y-axis-settings.png deleted file mode 100644 index 26c005dc..00000000 --- a/docs/planning/assets/timeline-y-axis-settings.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0ed19fb410d42e6ffe77f70303188f502db052a782e2eab5c8f7dcfadf54fb3 -size 45196 diff --git a/docs/planning/assets/upload-mission-model.webm b/docs/planning/assets/upload-mission-model.webm deleted file mode 100644 index 41b4fcb5..00000000 --- a/docs/planning/assets/upload-mission-model.webm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:79d9c4810dc42a7c547184cac80faca71787dcdfb2be62e9728efa344a70410f -size 776169 diff --git a/docs/planning/assets/upload_incons.mov b/docs/planning/assets/upload_incons.mov deleted file mode 100644 index 3fb9c4c9..00000000 --- a/docs/planning/assets/upload_incons.mov +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d42c04477d1c4611636939eea338d488a70890cf06f9129dcfba27d0a02036a -size 16016431 diff --git a/docs/planning/collaboration/assets/approve-merge.png b/docs/planning/collaboration/assets/approve-merge.png deleted file mode 100644 index 39e5e223..00000000 --- a/docs/planning/collaboration/assets/approve-merge.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f63d3e335c47648cecaf2c9634eadcbd908a8037c885c455a91d2e234a1760b2 -size 1000915 diff --git a/docs/planning/collaboration/assets/begin-merge-modal.png b/docs/planning/collaboration/assets/begin-merge-modal.png deleted file mode 100644 index 25a648ca..00000000 --- a/docs/planning/collaboration/assets/begin-merge-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db6efd022092bf2d0708162ad2aae4f70aa6e44c04bbb4e8dbced062a503b031 -size 38967 diff --git a/docs/planning/collaboration/assets/cancel-merge.png b/docs/planning/collaboration/assets/cancel-merge.png deleted file mode 100644 index d4dc41df..00000000 --- a/docs/planning/collaboration/assets/cancel-merge.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d08e5a591df8395b2601550d47d5c4027004205232df8748e6f6a655f5068eb3 -size 640813 diff --git a/docs/planning/collaboration/assets/create-branch-menu.png b/docs/planning/collaboration/assets/create-branch-menu.png deleted file mode 100644 index 8ea93c4b..00000000 --- a/docs/planning/collaboration/assets/create-branch-menu.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a30efe3c1c815b8de8e8aadf991d8ad7c59f1d244b7888277c164af127a397c6 -size 18467 diff --git a/docs/planning/collaboration/assets/create-branch-modal.png b/docs/planning/collaboration/assets/create-branch-modal.png deleted file mode 100644 index 7d5bf1d8..00000000 --- a/docs/planning/collaboration/assets/create-branch-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eb15beed9f5dd38808bfbc73772b2c10fcd6881328c955020b8e5cfeae0837fc -size 21837 diff --git a/docs/planning/collaboration/assets/create-merge-rq-modal.png b/docs/planning/collaboration/assets/create-merge-rq-modal.png deleted file mode 100644 index fc1c3f34..00000000 --- a/docs/planning/collaboration/assets/create-merge-rq-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:97122ebb6f30f7abd00408b16d8e5589cb5a8351ad0c546ed172003539695eb9 -size 22617 diff --git a/docs/planning/collaboration/assets/create-merge-rq.png b/docs/planning/collaboration/assets/create-merge-rq.png deleted file mode 100644 index 0366d609..00000000 --- a/docs/planning/collaboration/assets/create-merge-rq.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:83bd5d506e3b4fb808a3d7402ceaa0e5e8189b66082e3fc0ad12df007d03a5ab -size 87595 diff --git a/docs/planning/collaboration/assets/deny-merge.png b/docs/planning/collaboration/assets/deny-merge.png deleted file mode 100644 index ae391335..00000000 --- a/docs/planning/collaboration/assets/deny-merge.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72930a94f503277602bca3e9f017f99850a2ee3e9754860dd131019dee22dca4 -size 202651 diff --git a/docs/planning/collaboration/assets/incoming-merge-rqs.png b/docs/planning/collaboration/assets/incoming-merge-rqs.png deleted file mode 100644 index 0cfc8118..00000000 --- a/docs/planning/collaboration/assets/incoming-merge-rqs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9619038e4812edd012e2b4cf0de1b89b74d59a2ed8ce70d0fa8b96013834492f -size 34626 diff --git a/docs/planning/collaboration/assets/open-branch-header.png b/docs/planning/collaboration/assets/open-branch-header.png deleted file mode 100644 index 9423058d..00000000 --- a/docs/planning/collaboration/assets/open-branch-header.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfb41f07248d7eb394c7724fa19601670e5f3f598111cc6fa5abd0fcb782a6b1 -size 10409 diff --git a/docs/planning/collaboration/assets/open-branch-modal.png b/docs/planning/collaboration/assets/open-branch-modal.png deleted file mode 100644 index e1a4c2bd..00000000 --- a/docs/planning/collaboration/assets/open-branch-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e3840e22eefc42dc98bee8395baaa6017661973ed92d440c7b50268cdbcab4e -size 12829 diff --git a/docs/planning/collaboration/assets/open-parent-plan.png b/docs/planning/collaboration/assets/open-parent-plan.png deleted file mode 100644 index beec2541..00000000 --- a/docs/planning/collaboration/assets/open-parent-plan.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9389f29f9ed83c12b976ef23cac3df02507c4e1739c4e91dbd625785fcd0e00c -size 45162 diff --git a/docs/planning/collaboration/assets/outgoing-merge-rqs.png b/docs/planning/collaboration/assets/outgoing-merge-rqs.png deleted file mode 100644 index 6a683bf6..00000000 --- a/docs/planning/collaboration/assets/outgoing-merge-rqs.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:533308b0ab1f5f84af6bbccc45ffad1c9f4cd90aef40ffb72d6dfa9b7103e3a6 -size 32190 diff --git a/docs/planning/collaboration/assets/resolve-conflicts-bulk.png b/docs/planning/collaboration/assets/resolve-conflicts-bulk.png deleted file mode 100644 index bbe88cbe..00000000 --- a/docs/planning/collaboration/assets/resolve-conflicts-bulk.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:108d6fd49d25b947317f407dc2f866c619eb7b95753ed50cef75b0d1fc3a6de5 -size 85257 diff --git a/docs/planning/collaboration/assets/resolved-conflict.png b/docs/planning/collaboration/assets/resolved-conflict.png deleted file mode 100644 index 5c4cf755..00000000 --- a/docs/planning/collaboration/assets/resolved-conflict.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64a2d896ef2d617a45c2dee205ad96f392d1c3f84aa0a053dac545ea2ccb1bec -size 321972 diff --git a/docs/planning/collaboration/assets/unresolved-conflict.png b/docs/planning/collaboration/assets/unresolved-conflict.png deleted file mode 100644 index 9daeb74a..00000000 --- a/docs/planning/collaboration/assets/unresolved-conflict.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae2872651abb41984e4a4839ab7465c1e2b5741ab78a7b9746a594f2911b06c7 -size 992259 diff --git a/docs/planning/collaboration/assets/withdraw-merge-rq-modal.png b/docs/planning/collaboration/assets/withdraw-merge-rq-modal.png deleted file mode 100644 index 7baba7b1..00000000 --- a/docs/planning/collaboration/assets/withdraw-merge-rq-modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae87bd7fa0248f3978e059049c40dd198357a1c51a466f916000039009a5a916 -size 39377 diff --git a/docs/planning/collaboration/introduction.mdx b/docs/planning/collaboration/introduction.mdx deleted file mode 100644 index 378aa835..00000000 --- a/docs/planning/collaboration/introduction.mdx +++ /dev/null @@ -1,65 +0,0 @@ -import createBranchMenu from './assets/create-branch-menu.png'; -import createBranchModal from './assets/create-branch-modal.png'; -import openParentPlan from './assets/open-parent-plan.png'; -import openBranchHeader from './assets/open-branch-header.png'; -import openBranchModal from './assets/open-branch-modal.png'; - -# Collaboration - -Aerie supports two forms of collaboration during planning: - -1. **Real-time collaboration**, where multiple users can work on the same plan at the same time -1. **Branch-based collaboration**, where users can branch existing plans and later merge their changes into other related plans - -## Real-Time - -In the Aerie UI real-time collaboration occurs automatically whenever multiple users have the same plan open. - -## Branch-Based - -Branches can be made from any existing plan. -A branched plan will have the same start and end time as its parent plan, as well as a copy of all the activities in the parent at the moment it was branched. -It will not, however, contain any simulation data, scheduling goals, or constraints that were associated with the parent plan. - -For information on merging branches, see [Merging Plans](../../collaboration/merging-plans). - -:::tip - -To learn how to do plan collaboration via the Aerie API, see our [API documentation](../../../api/examples/planning/collaboration). - -::: - -### Branching a Plan - -When on a plan in the Aerie UI, select the drop-down from beside the plan's name and select "Create branch": - -
- Aerie UI - Create Plan Branch Menu -
Figure 1: Aerie UI - Create Plan Branch Menu
-
- -That will open a modal where you can enter the new branch's name. -Selecting "Create Branch" will open the new branch: - -
- Aerie UI - Create Plan Branch Modal -
Figure 2: Aerie UI - Create Plan Branch Modal
-
- -To return to the parent plan, either click the parent plan's name or select "Open parent plan" from the dropdown: - -
- Aerie UI - Open Parent Plan -
Figure 3: Aerie UI - Open Parent Plan
-
- -To return to a branch from the parent plan, click the 1 branch text from beside the name of the plan. -Then select the plan from the list of branches: - -
-
- Aerie UI - Open Branch Header -
- Aerie UI - Open Branch Modal -
Figure 4: Aerie UI - Open Branch Modal
-
diff --git a/docs/planning/collaboration/merging-plans.mdx b/docs/planning/collaboration/merging-plans.mdx deleted file mode 100644 index e143fc60..00000000 --- a/docs/planning/collaboration/merging-plans.mdx +++ /dev/null @@ -1,141 +0,0 @@ -# Merging Plans - -## Create a Merge Request - -In order to merge changes between two related plans (most commonly between a branch and its parent), -a merge request must first be created between the **source plan** (the one supplying changes) and the **target plan** (the one to receive changes). - -From the child plan in the Aerie UI, open the dropdown and select "Create merge request". -Confirm which plan you wish to be the target plan and press "Create merge request": - -import createMergeRq from './assets/create-merge-rq.png'; -import createMergeRqModal from './assets/create-merge-rq-modal.png'; - -
- Aerie UI - Create Merge Request Menu - Aerie UI - Create Merge Request -
Figure 1: Aerie UI - Create Merge Request
-
- -:::note - -Currently it is only possible to select the parent branch as the target plan via the Aerie UI. -To merge between other related plans, use the [Aerie API](../../../api/examples/planning/collaboration). - -::: - -## Withdraw a Merge Request - -You can withdraw a merge request so long as it is in the `pending` state. -A withdrawn merge request cannot be used to begin a merge. - -While on the **source plan**, select the "Merge requests" text besides the plan's name. -Then, press the "Withdraw" button next to the merge request you wish to withdraw: - -import outgoingMergeRequests from './assets/outgoing-merge-rqs.png'; -import withdrawMergeRequestModal from './assets/withdraw-merge-rq-modal.png'; - -
-
- Aerie UI - Outgoing Merge Requests -
- Aerie UI - Withdraw Merge Request -
Figure 2: Aerie UI - Withdraw Merge Request
-
- -## Begin a Merge - -You can begin a merge from a `pending` merge request so long as the plan is not currently locked. - -:::note - -Beginning a merge locks the target plan until the merge is either `cancelled`, `denied`, or `committed`. - -::: - -While on the **target plan**, select the "Merge requests" text beside the list of branches to see the incoming requests. -Then, press "Review" next to the merge request you wish to begin: - -import incomingMergeRequests from './assets/incoming-merge-rqs.png'; -import beginMergeModal from './assets/begin-merge-modal.png'; - -
-
- Aerie UI - Incoming Merge Requests -
- Aerie UI - Begin Merge -
Figure 3: Aerie UI - Begin Merge
-
- -## Cancel a Merge - -You can cancel any `in-progress` merge. From the merge review screen, press the "Cancel" button in the bottom-right corner: - -import cancelMerge from './assets/cancel-merge.png'; - -
- Aerie UI - Cancel Merge -
Figure 4: Aerie UI - Cancel Merge
-
- -## Resolving Conflicts - -Before a merge can be committed, all conflicts must be resolved to either **source** or **target**. - -First, select a conflicting activity from the list. -The version of the activity in the source and target plan will appear to the right, with the differing fields highlighted. -Then, determine which version to keep by pressing either "Keep Source Activity" or "Keep Target Activity": - -import unresolvedConflict from './assets/unresolved-conflict.png'; -import resolvedConflict from './assets/resolved-conflict.png'; - -
- Aerie UI - Unresolved Conflict -
Figure 5: Aerie UI - Unresolved Merge Conflict
-
- -
- Aerie UI - Resolved Conflict -
Figure 6: Aerie UI - Resolved Merge Conflict
-
- -You can also resolve conflicts in bulk: - -import resolveConflictsBulk from './assets/resolve-conflicts-bulk.png'; - -
- Aerie UI - Resolve Merge Conflicts in Bulk -
Figure 7: Aerie UI - Resolve Merge Conflicts in Bulk
-
- -## Deny a Merge - -It is possible to deny an `in-progress` merge, for example, if a request is outdated. -Once a merge has been denied, that request cannot be used to begin a merge. - -From the merge review screen, press the "Deny Changes" button in the bottom-right corner: - -import denyMerge from './assets/deny-merge.png'; - -
- Aerie UI - Deny Merge -
Figure 8: Aerie UI - Deny Merge
-
- -## Commit a Merge - -Once all conflicts have been resolved, you can commit a merge. -From the merge review screen, press the "Approve Changes" button in the bottom-right corner: - -import approveMerge from './assets/approve-merge.png'; - -
- Aerie UI - Approve Merge -
Figure 9: Aerie UI - Approve Merge
-
- -:::info External Events and Plan Merge - -External Events (specifically, derivation groups) are not included in plan snapshots and merges. This will be addressed in a future release. See [External Events](../../external-events/introduction) for more information. - -::: diff --git a/docs/planning/create-plan-and-simulate.mdx b/docs/planning/create-plan-and-simulate.mdx deleted file mode 100644 index 0e7b98a2..00000000 --- a/docs/planning/create-plan-and-simulate.mdx +++ /dev/null @@ -1,45 +0,0 @@ -import createPlan from './assets/create-plan.webm'; -import firstSimulation from './assets/first-simulation.webm'; - -# Create Plan and Simulate - -Here we describe how to create a new plan and simulate it using the [Aerie UI](https://github.com/NASA-AMMOS/aerie-ui). -This document assumes you already uploaded a model. If you still need to upload a model, first follow the [instructions for uploading a model](../upload-mission-model) and then come back here. - -## Instructions - -1. Navigate to the `/plans` page in the Aerie UI. If you are running Aerie locally it is at [http://localhost/plans](http://localhost/plans). - -1. Fill out the form on the `/plans` page to create a new plan. Pick a model ("FireSat" in this example), and give the plan a name ("Demo Plan" in this example). For this example we will just create a 2-week plan, and we will not include a [simulation template](../../mission-modeling/configuration). After you fill in all the fields click the 'Create' button to create the plan. Here is a video demonstration: - - - -1. Next open the new plan and add a few activities to it by dragging the activity types in the left pane over to the timeline. Select the simulation tab and click the "play" button to simulate the plan. The default timeline view for Aerie UI automatically includes all the resources in the plan-associated-model so you can see the simulation output easily. Here is a video demonstration: - - - - Notice in this demonstration we change the activity parameter of `ChangeMagMode` from "LOW_RATE" to "HIGH_RATE" which effects some resources as shown in the timeline simulation output. - -## Advanced: Temporal Subset Simulation - -:::caution - -Temporal Subset Simulation sets the value of all internal states to their initial values based on the Simulation Configuration. - -This means that running Temporal Subset Simulation on a region of a plan detached from Plan Start may produce **different** output then if you were to simulate from Plan Start. - -::: - -It is possible to simulate only a section of the plan. This can be done by setting the `Simulation Start` and `Simulation End` fields in the Simulation Configuration Panel. - -The bounds can be set to any point in the plan in one of three ways: - -1. Click the bound and select from the Date Picker -2. Right-click on the timeline and select `Set as Plan Start` or `Set as Plan End` -3. Right-click on an Activity Directive or Simulated Activity and use the `Set Simulation Start` and `Set Simulation End` options. - -To go back to simulating the entire plan, edit `Simulation Start` and `Simulation End` and select `Plan Start` and `Plan End`, respectively. diff --git a/docs/planning/external-datasets.md b/docs/planning/external-datasets.md deleted file mode 100644 index c655ac19..00000000 --- a/docs/planning/external-datasets.md +++ /dev/null @@ -1,289 +0,0 @@ -# External Datasets - -A **dataset** is a collection of Real (numeric) and Discrete (any type) profiles which all start at the same time. Most datasets are created by simulation, but sometimes it will be helpful to import data that can’t be simulated by Aerie so it can be used in planning (e.g. constraint checking or schedular evaluation). This document describes how to add external datasets to Aerie. - -External datasets contain resource profiles that are quite similar to the profiles produced by a simulation. A resource profile describes the behavior of a resource over time. Each profile has a name, a type, a start time, and a list of profile segments that conform to that type. Each profile segment defines the value of the resource over a duration of time (referred to as its "dynamics"). The first segment starts at the start time of the profile, and every subsequent segment starts at the end of the previous segment. - -External datasets come from the user rather than simulation output, and can be uploaded at any time before or after simulation. They can be viewed in the Aerie UI along with a plan. - -Ultimately the purpose of external datasets is up to the user. You might be looking to include some geometry information to assist in building a plan, or you may simply be adding power and thermal modeling results to be viewable with existing simulation results. Just be aware that at this time external profiles are not accessible to the mission model during simulation, and are simply for viewing purposes in the UI. - -## Add External Dataset Mutation - -To upload an external dataset you need to add it to a specific plan. The dataset will persist as long as the plan exists, or until it is explicitly deleted. An external dataset can be added via the GraphQL mutation `addExternalDataset`: - -```graphql -mutation AddExternalDataset( - $planId: Int!, - $simulationDatasetId: Int, - $datasetStart: String!, - $profileSet: ProfileSet!) { - addExternalDataset( - planId: $planId, - simulationDatasetId: $simulationDatasetId, - datasetStart: $datasetStart, - profileSet: $profileSet) { - datasetId - } -} -``` - -The `addExternalDataset` GraphQL mutation takes four query variables as specified below: - -| Parameter | Type | Description | -| --------------------------------- | ------- | ----------------------------------------------------------------------------------------------------- | -| `$planId` | Integer | The ID of the plan to associate the external dataset with | -| `$simulationDatasetId` | Integer | The optional ID of a simulation dataset that the external dataset will be exclusively associated with | -| `$datasetStart` | String | The DOY UTC timestamp the dataset starts from
UTC Format: `yyyy-dddThh:mm:ss` | -| `$profileSet` | Object | The set of precomputed profiles that make up the external dataset | - -If `$simulationDatasetId` is provided, the uploaded external dataset will only be associated with a single simulation dataset, instead of implicitly being associated with every simulation dataset pertaining to the `$planId`. This can be useful if, for example, external simulation tools are used alongside Aerie that calculate additional profiles based on a Merlin simulation dataset, which are then upstreamed to Aerie to use in constraint checking or visualization in the UI. Associating these external profiles with a single simulation dataset will keep them from showing up in future simulation run visualizations or constraint violations. - -The profile set to be uploaded should have one entry for each profile, indexed by a unique name mapping to an object specifying the details of the profile. - -Each profile should have a `type` field, which specifies whether the profile is real-valued or discrete-valued. It must also contain a `schema` field, which specifies the schema of the values it takes on. - -For discrete profiles, these are not limited to basic types, but can take on any complex structure made up using our `ValueSchema` construct (for more information, see our [ValueSchema documentation](../../mission-modeling/value-schemas)). - -Currently, real profiles only support linear equations with the following schema: - -```json -{ - "type": "struct", - "items": { - "rate": { - "type": "real" - }, - "initial": { - "type": "real" - } - } -} -``` - -Finally, each profile requires a list of segments that describe the actual behavior of the profile. The `segments` field is a list of segment objects, where each segment should contain the following two fields: - -| Field | Type | Description | -| --------------------- | ------------------------- | ----------------------------------------------------------------------------------------- | -| `duration` | Integer | The duration (in microseconds) the segment's dynamics hold before the next segment begins | -| `dynamics` (optional) | Dependent on profile type | The behavior of the profile over the lifetime of this segment | - -A discrete profile's dynamics should match the format specified by the `schema` field, while a real profile's dynamics should always contain an initial value and a rate of change. See our example external dataset query variables [below](#example-query-variables-for-the-addexternaldataset-mutation) to see both profile specification types. If the `dynamics` field of a segment isn't specified, the segment is called a "gap", and represents intervals when the value is unknown. - -## Extend External Dataset Mutation - -If your dataset is too big to upload with one request, you can extend a previously uploaded dataset using `extendExternalDataset`. - -```graphql -mutation ExtendExternalDataset($datasetId: Int!, $profileSet: ProfileSet!) { - extendExternalDataset(datasetId: $datasetId, profileSet: $profileSet) { - datasetId - } -} -``` - -The `extendExternalDataset` GraphQL mutation takes two query variables as specified below: - -| Parameter | Type | Description | -| ------------- | ------- | ---------------------------------------------------------------------------- | -| `datasetId` | Integer | The ID of the dataset to extend | -| `$profileSet` | Object | The set of precomputed profiles that are to be added to the external dataset | - -See [addExternalDataset mutation](#add-external-dataset-mutation) for -the structure of the profileSet. Any profiles that already exist in -the dataset will be appended to the end. Profiles that do not already -exist in the dataset will start from the beginning of the dataset. - -## Delete External Dataset Mutation - -There may be a time when you find an external dataset you've been using is no longer relevant and must be removed. You can use the following mutation: - -```graphql -mutation DeleteExternalDataset($id: Int!) { - delete_dataset_by_pk(id: $id) { - id - } -} -``` - -You can use the following query variable specifying the external dataset `id` you wish to delete: - -```json -{ - "id": 1 -} -``` - -## Example Query Variables for the AddExternalDataset Mutation - -Below shows example [query variables](https://graphql.org/learn/queries/#variables) you can use with the [addExternalDataset mutation](#add-external-dataset-mutation). - -### Create an External Dataset with Real and Discrete Profiles - -This example shows an external dataset being uploaded to the plan with ID `2` starting at `2018-331T04:00:00`. Two precomputed profiles are included in the external dataset. - -First a real profile called `batteryEnergy` starts at a value of 50 and decreases at a rate of -0.5 units per second over 30 seconds. At that point, the value is 35 and the rate is changed to -0.1 units per second for 30 more seconds. - -The second profile is a discrete profile called `awake` and contains a schema that tells us its values are boolean. The segments tell us that for the first 30 seconds the profile's dynamics are the value `true` and for the next 30 seconds the value `false`. - -```json -{ - "planId": 2, - "datasetStart": "2018-331T04:00:00", - "profileSet": { - "batteryEnergy": { - "type": "real", - "schema": { - "type": "struct", - "items": { - "rate": { "type": "real" }, - "initial": { "type": "real" } - } - }, - "segments": [ - { "duration": 30000000, "dynamics": { "initial": 50, "rate": -0.5 } }, - { "duration": 30000000, "dynamics": { "initial": 35, "rate": -0.1 } } - ] - }, - "awake": { - "type": "discrete", - "schema": { "type": "boolean" }, - "segments": [ - { "duration": 30000000, "dynamics": true }, - { "duration": 30000000, "dynamics": false } - ] - } - } -} -``` - -### Create an External Dataset with a Profile Gap - -This example adds a single precomputed profile called `orientation`. This discrete profile's schema tells us that its values are structs with real-valued `x`, `y` and `z` fields. For the first hour the profile takes a value of `x=0`, `y=0`, `z=1`. Then the profile has a gap for an hour. For the third hour thereafter the profile is valued at `x=1`, `y=1`, `z=0`. - -```json -{ - "planId": 7, - "datasetStart": "2038-192T14:00:00", - "profileSet": { - "orientation": { - "type": "discrete", - "schema": { - "type": "struct", - "items": { - "x": { "type": "real" }, - "y": { "type": "real" }, - "z": { "type": "real" } - } - }, - "segments": [ - { "duration": 3600000000, "dynamics": { "x": 0, "y": 0, "z": 1 } }, - { "duration": 3600000000 }, - { "duration": 3600000000, "dynamics": { "x": 1, "y": 1, "z": 0 } } - ] - } - } -} -``` - -### Create an External Dataset from a CSV - -This example shows how to convert a [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) into an external dataset with 3 profiles. The CSV has the following form: - -| Time (s) | TotalPower | BatteryStateOfCharge | Temperature | -| ----------- | ---------------- | -------------------- | ----------------- | -| 164937600.0 | 0.0 | 143.15 | 0.0 | -| 164937700.0 | 384.999999940483 | 1.4 | -12.0964867663028 | -| 164937800.0 | 384.999999399855 | 137.45 | -12.0974993557598 | -| 164937900.0 | 385.000010807604 | 134.85 | -12.0985125609155 | -| 164938000.0 | 381.80000002749 | 132.4 | -12.0995253838464 | - -Here `Time` is expressed in seconds and you can see in this example there are 100 second increments between each row. -`TotalPower`, `BatteryStateOfCharge`, and `Temperature` are the data that we import as profiles. - -Here is the example query variable showing the CSV converted into external dataset profiles. The external dataset is added to a plan with ID `1` and starts at `2024-001T00:00:00` UTC. - -```json -{ - "planId": 1, - "datasetStart": "2024-001T00:00:00", - "profileSet": { - "TotalPower": { - "type": "real", - "schema": { - "type": "struct", - "items": { - "rate": { "type": "real" }, - "initial": { "type": "real" } - } - }, - "segments": [ - { "duration": 100000000, "dynamics": { "initial": 0.0, "rate": 0.0 } }, - { "duration": 100000000, "dynamics": { "initial": 384.999999940483, "rate": 0.0 } }, - { "duration": 100000000, "dynamics": { "initial": 384.999999399855, "rate": 0.0 } }, - { "duration": 100000000, "dynamics": { "initial": 385.000010807604, "rate": 0.0 } }, - { "duration": 100000000, "dynamics": { "initial": 381.80000002749, "rate": 0.0 } } - ] - }, - "BatteryStateOfCharge": { - "type": "discrete", - "schema": { "type": "real" }, - "segments": [ - { "duration": 100000000, "dynamics": 143.15 }, - { "duration": 100000000, "dynamics": 1.4 }, - { "duration": 100000000, "dynamics": 137.45 }, - { "duration": 100000000, "dynamics": 134.85 }, - { "duration": 100000000, "dynamics": 132.4 } - ] - }, - "Temperature": { - "type": "discrete", - "schema": { "type": "real" }, - "segments": [ - { "duration": 100000000, "dynamics": 0.0 }, - { "duration": 100000000, "dynamics": -12.0964867663028 }, - { "duration": 100000000, "dynamics": -12.0974993557598 }, - { "duration": 100000000, "dynamics": -12.0985125609155 }, - { "duration": 100000000, "dynamics": -12.0995253838464 } - ] - } - } -} -``` - -Notice for `TotalPower` we use a `real` profile just for example completeness. Since we are only dealing with explicit data points in the CSV and not the rate at which the data points change, the dynamics rate for each `real` profile segment is `0.0`. Thus we could have equivalently encoded `TotalPower` as a `discrete` profile as we do for `BatteryStateOfCharge` and `Temperature`. - -Also notice the `duration` is simply calculated as how many microseconds pass between each data point in the CSV. - -## Usage in Constraints - -After the external dataset is uploaded, constraints and scheduling can access the included profiles just as if they were simulated profiles. -The key difference is that since external datasets are associated with plans and simulated datasets are associated with models, the constraint must be associated with the same plan to access the external profile. - -External profiles can contain gaps, and currently simulated profiles cannot. Gaps in profile transformations will be preserved; i.e. comparing the equality of two profiles with gaps will include the gaps, because the result of the operation is unknown. This means that windows can also have gaps, as windows are essentially boolean profiles. Ultimately the gaps are reflected in the constraint's violations as a warning, meaning the constraint _might_ be violated because the relevant profiles had unknown values. - -Gaps can be removed at any step in the constraint code by calling the `.assignGaps()` method, which replaces all gaps in the profile with the given value. This can be useful on the resulting windows object that gets turned in to a constraint: - -```ts -export default (): Constraint => { - let result = ; // Compute your constraint windows. - - // This says that gaps are nominal (non-violating). - return result.assignGaps(true); - - // OR - - // This says that gaps are violations. - return result.assignGaps(false); - - // OR - - // This will display gaps as warnings. - return result; -} -``` - -### Particularities of usage in Scheduling -Most of the above is valid for scheduling as well. However, there are 2 caveats: -- The presence of gaps in profiles used to compute windows for scheduling is forbidden. As for constraints, you can use the `assignGaps` method to remove gaps from profiles. -- Only external datasets that are not associated to simulations can be used for scheduling. In other words, the external dataset must be associated only with the plan. diff --git a/docs/planning/external-events/assets/create_derivation_group.png b/docs/planning/external-events/assets/create_derivation_group.png deleted file mode 100644 index 5f5dd6b2..00000000 --- a/docs/planning/external-events/assets/create_derivation_group.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5d6ea77e145a21655b9da1c66a8ce54883daf768747a5d216c253da510849ec6 -size 81090 diff --git a/docs/planning/external-events/assets/create_external_event_type.png b/docs/planning/external-events/assets/create_external_event_type.png deleted file mode 100644 index df8885f4..00000000 --- a/docs/planning/external-events/assets/create_external_event_type.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eebbfc471a799bddfbc41a67a86e36918ced6d3b57308d15a80a3b3459229a8f -size 77785 diff --git a/docs/planning/external-events/assets/create_external_source_type.png b/docs/planning/external-events/assets/create_external_source_type.png deleted file mode 100644 index 887230ac..00000000 --- a/docs/planning/external-events/assets/create_external_source_type.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:22b74e49510a117482463d520526d0f34846b495633d65f61dab0462c47eb28f -size 79420 diff --git a/docs/planning/external-events/assets/create_groups_and_types_button.png b/docs/planning/external-events/assets/create_groups_and_types_button.png deleted file mode 100644 index 67bd0267..00000000 --- a/docs/planning/external-events/assets/create_groups_and_types_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:845a86c6018a34451d8a95ff1c979b83b330cd7363e44f43ddc3bb2f3ba2c13c -size 31934 diff --git a/docs/planning/external-events/assets/derivation_example_full.png b/docs/planning/external-events/assets/derivation_example_full.png deleted file mode 100644 index edff034c..00000000 --- a/docs/planning/external-events/assets/derivation_example_full.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0a5834dbf72dff4654669bb075c7c206c29e54d48a2620f60b50bd00487beb7 -size 73067 diff --git a/docs/planning/external-events/assets/derivation_group_motivation.png b/docs/planning/external-events/assets/derivation_group_motivation.png deleted file mode 100644 index 99997890..00000000 --- a/docs/planning/external-events/assets/derivation_group_motivation.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6851b6d9998c81d80e1389514e319c818fbfdf9048665d8407c12ce5a5c45181 -size 93623 diff --git a/docs/planning/external-events/assets/derivation_group_motivation_ii.png b/docs/planning/external-events/assets/derivation_group_motivation_ii.png deleted file mode 100644 index 4a09f681..00000000 --- a/docs/planning/external-events/assets/derivation_group_motivation_ii.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f70e0beef0157651aa27e22e9392303188faa6c2107624a3854feb35e651db4 -size 95461 diff --git a/docs/planning/external-events/assets/derivation_motivation.png b/docs/planning/external-events/assets/derivation_motivation.png deleted file mode 100644 index 9bbdca2b..00000000 --- a/docs/planning/external-events/assets/derivation_motivation.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41caf9b3c55ae06b29f2542fef610f07a955984238a77b1b00a48cf2c54f7bfe -size 57748 diff --git a/docs/planning/external-events/assets/external_event_options.png b/docs/planning/external-events/assets/external_event_options.png deleted file mode 100644 index 7311deb1..00000000 --- a/docs/planning/external-events/assets/external_event_options.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a1af0d147f9ae92021219944b124bb76f9d989f346b6ea1504487790cc7c41e -size 38875 diff --git a/docs/planning/external-events/assets/external_event_table.png b/docs/planning/external-events/assets/external_event_table.png deleted file mode 100644 index 5a3ba090..00000000 --- a/docs/planning/external-events/assets/external_event_table.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:24fe6ba30cc1eb74e75956cefff26e823a539c5de35b1a10a02eb9c38469cdab -size 77953 diff --git a/docs/planning/external-events/assets/external_events_before.png b/docs/planning/external-events/assets/external_events_before.png deleted file mode 100644 index 803cb691..00000000 --- a/docs/planning/external-events/assets/external_events_before.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b9c51e5c540458deb7339bf6580f8c70584179b83a6bcda317b66331e4f0e663 -size 492133 diff --git a/docs/planning/external-events/assets/external_events_derivation_group_disabled.png b/docs/planning/external-events/assets/external_events_derivation_group_disabled.png deleted file mode 100644 index 57fb8fc1..00000000 --- a/docs/planning/external-events/assets/external_events_derivation_group_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d2d38842d59ea33d97443a272c2721fbaad55d6cd5b7b42619930b55c10c550 -size 138905 diff --git a/docs/planning/external-events/assets/external_events_derivation_group_enabled.png b/docs/planning/external-events/assets/external_events_derivation_group_enabled.png deleted file mode 100644 index 5e8858ba..00000000 --- a/docs/planning/external-events/assets/external_events_derivation_group_enabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:151be35418e2b9ff5d7ff315fed6e6b2a6ab1db2f7bceb698f54b5cfb141f9c0 -size 157973 diff --git a/docs/planning/external-events/assets/external_events_group_by_event_type.png b/docs/planning/external-events/assets/external_events_group_by_event_type.png deleted file mode 100644 index 431d6bb2..00000000 --- a/docs/planning/external-events/assets/external_events_group_by_event_type.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fca17e0e2bc361b6fe194e46ce9fd894891f4cacba03583e091518cca1f27ec6 -size 25703 diff --git a/docs/planning/external-events/assets/external_events_group_by_source.png b/docs/planning/external-events/assets/external_events_group_by_source.png deleted file mode 100644 index 00cd4435..00000000 --- a/docs/planning/external-events/assets/external_events_group_by_source.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c58cc153f9593b69b50922b81c962ebcdd6e3907e877549996b35a7d07b3fa94 -size 33543 diff --git a/docs/planning/external-events/assets/external_events_on_timeline.png b/docs/planning/external-events/assets/external_events_on_timeline.png deleted file mode 100644 index afaa7a74..00000000 --- a/docs/planning/external-events/assets/external_events_on_timeline.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a024e493216bf639bc7cdc523b6de1b0e266928f42035e247296dd03664c5d63 -size 31104 diff --git a/docs/planning/external-events/assets/external_events_options.png b/docs/planning/external-events/assets/external_events_options.png deleted file mode 100644 index 74ff6105..00000000 --- a/docs/planning/external-events/assets/external_events_options.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ff0e768e2e34e34555339bf06391e9ff3d4c0949c83dff4bdf32a76e8078b0b -size 93867 diff --git a/docs/planning/external-events/assets/external_events_table.png b/docs/planning/external-events/assets/external_events_table.png deleted file mode 100644 index 1a006a88..00000000 --- a/docs/planning/external-events/assets/external_events_table.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33d1663c1f55a272a520b29c8466f3a978034d950eccabe6d2f9cead4769aaa1 -size 153962 diff --git a/docs/planning/external-events/assets/external_source_manager_intro.png b/docs/planning/external-events/assets/external_source_manager_intro.png deleted file mode 100644 index 1323b7c0..00000000 --- a/docs/planning/external-events/assets/external_source_manager_intro.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:409f16965dd7a275b1308e6bdc60d6472f8223cad65ee6996adbfcaf67968360 -size 552123 diff --git a/docs/planning/external-events/assets/external_source_manager_types_expanded.png b/docs/planning/external-events/assets/external_source_manager_types_expanded.png deleted file mode 100644 index c96793d8..00000000 --- a/docs/planning/external-events/assets/external_source_manager_types_expanded.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:270d7a1e85b8aacc267c4b285e57eb6c6073c028825ba2f179bacd42027cd576 -size 68862 diff --git a/docs/planning/external-events/assets/external_source_manager_types_page.png b/docs/planning/external-events/assets/external_source_manager_types_page.png deleted file mode 100644 index be8642e2..00000000 --- a/docs/planning/external-events/assets/external_source_manager_types_page.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6163f952148c14bae8c9a1597dcc4d0f834db3f7a21e311ea2e4b1e6a6eeaaca -size 345628 diff --git a/docs/planning/external-events/assets/external_source_manager_upload.png b/docs/planning/external-events/assets/external_source_manager_upload.png deleted file mode 100644 index 579db703..00000000 --- a/docs/planning/external-events/assets/external_source_manager_upload.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4085c3e17cd021c3ce70b103d0b28fe204250c92ccd369e0e9d5280fb669738e -size 46869 diff --git a/docs/planning/external-events/assets/external_sources_table.png b/docs/planning/external-events/assets/external_sources_table.png deleted file mode 100644 index 9d4a9abb..00000000 --- a/docs/planning/external-events/assets/external_sources_table.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:76d9f374e24d8c092bd5c5730cf8440d940a3215fb5fba05a20ef96bc5c76e09 -size 368479 diff --git a/docs/planning/external-events/assets/inspected_source.png b/docs/planning/external-events/assets/inspected_source.png deleted file mode 100644 index a1b90b45..00000000 --- a/docs/planning/external-events/assets/inspected_source.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e44e8110b1b0dd6c0a5415e74d89ce3a15ed0ea6f656858146b19ada80e9b01c -size 52203 diff --git a/docs/planning/external-events/assets/manage_derivation_group_modal.png b/docs/planning/external-events/assets/manage_derivation_group_modal.png deleted file mode 100644 index f0957ee2..00000000 --- a/docs/planning/external-events/assets/manage_derivation_group_modal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b3b14aa3a58ce96a316624700ce5ac56fd4f1de66a1aad4646e29e0f2c33666 -size 242640 diff --git a/docs/planning/external-events/assets/ordering_external_sources.png b/docs/planning/external-events/assets/ordering_external_sources.png deleted file mode 100644 index e8c968c5..00000000 --- a/docs/planning/external-events/assets/ordering_external_sources.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb46ab0d859a310728494a8d9993efdba8fa097b08e3c14745cfd7aef3a6aeb9 -size 79791 diff --git a/docs/planning/external-events/assets/plan_external_source_full_view.png b/docs/planning/external-events/assets/plan_external_source_full_view.png deleted file mode 100644 index 0ac39ef4..00000000 --- a/docs/planning/external-events/assets/plan_external_source_full_view.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4bef05de154c3d0cf1fb4f477ce12bd2b99325c40164ffd040072695ef48b22b -size 669650 diff --git a/docs/planning/external-events/assets/plan_external_sources_panel.png b/docs/planning/external-events/assets/plan_external_sources_panel.png deleted file mode 100644 index c20d7a5a..00000000 --- a/docs/planning/external-events/assets/plan_external_sources_panel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecda120f619abab45f0f212e2e148891971049b295dab81e96fce99ccb30e18a -size 132005 diff --git a/docs/planning/external-events/assets/plan_selected_external_event.png b/docs/planning/external-events/assets/plan_selected_external_event.png deleted file mode 100644 index f2075518..00000000 --- a/docs/planning/external-events/assets/plan_selected_external_event.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e317cb1529b7b2b14a3e5fd75e3ab72fef84748e6caeaaba2a2a8a28829a0989 -size 350228 diff --git a/docs/planning/external-events/assets/timeline_and_editor_full.png b/docs/planning/external-events/assets/timeline_and_editor_full.png deleted file mode 100644 index c2c0385f..00000000 --- a/docs/planning/external-events/assets/timeline_and_editor_full.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:685011885b47ab742e2e5c62f359c683e69564a084047b13777960b42f38903e -size 391106 diff --git a/docs/planning/external-events/external-events-attributes.md b/docs/planning/external-events/external-events-attributes.md deleted file mode 100644 index fa7b4c99..00000000 --- a/docs/planning/external-events/external-events-attributes.md +++ /dev/null @@ -1,163 +0,0 @@ -# External Event and Source Attributes - -This page describes the format for files that define External Source Types and External Event Types. These files detail the attributes that External Sources and External Events corresponding to those types are required or allowed to have. Any additional attributes not specified in these files will be rejected. - -Both External Source Types and External Event Types can be defined in the same file, which has the following general outline: - -```json -{ - "event_types": { - "EventTypeName": { - ... - }, - "SecondEventTypeName": { - ... - }, - ... - }, - "source_types": { - "SourceTypeName": { - ... - }, - "AdditionalSourceTypeName": { - ... - }, - ... - } -} -``` - -Any number of source and event types can be provided in a given file. Note that any source can contain events of any type. - -## External Types -The full structure of External Source and Event types is detailed below. These are defined as nested JSON Schemas. Refer to JSON Schema docs [here](https://json-schema.org/learn/getting-started-step-by-step). - -This is an example of an **External Source Type** definition, nested within `source_types`: - -```json -... -"Weather": { - "type": "object", - "required": [ - "location", - "version", - "changelog" - ], - "properties": { - "location": { - "type": "string" - }, - "version": { - "type": "number" - }, - "changelog": { - "type": "string" - } - } -} -``` - - In this file, following JSON Schema syntax, the attributes of a `Weather` Source are described - it must be an object, with 3 required fields: `location` (a string), `version` (a number), and `changelog` (a string). No other attributes will be allowed. Additional JSON Schema functionality can be added to these properties as well, like a `pattern` specification, which restricts strings for a given field to follow a regex. - -**External Event Type** attribute specifications function similarly: - -```json -"WeatherReport": { - "type": "object", - "required": ["daily_high", "daily_low", "precipitation_lik"], - "properties": { - "daily_high": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "daily_low": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "precipitation_lik": { - "type": "number" - } - } -} -``` - -A complete file looks like the following: - -```json -{ - "event_types": { - "WeatherReport": { - "type": "object", - "required": ["daily_high", "daily_low", "precipitation_lik"], - "properties": { - "daily_high": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "daily_low": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "precipitation_lik": { - "type": "number" - } - } - } - }, - "source_types": { - "Weather": { - "type": "object", - "required": [ - "location", - "version", - "changelog" - ], - "properties": { - "location": { - "type": "string" - }, - "version": { - "type": "number" - }, - "changelog": { - "type": "string" - } - } - } - } -} -``` - -Note that this structure provides the full expressiveness of JSON Schema syntax, allowing users to define attributes as simple primitives or complex nested structures. The full meta-schema used to validate schema files can be found [here](https://github.com/NASA-AMMOS/aerie-gateway/blob/develop/src/schemas/external-event-validation-schemata.ts). \ No newline at end of file diff --git a/docs/planning/external-events/introduction.mdx b/docs/planning/external-events/introduction.mdx deleted file mode 100644 index 5d27821d..00000000 --- a/docs/planning/external-events/introduction.mdx +++ /dev/null @@ -1,721 +0,0 @@ -import derivationExampleFull from './assets/derivation_example_full.png'; -import derivationMotivation from './assets/derivation_motivation.png'; -import derivationGroupMotivation from './assets/derivation_group_motivation.png'; -import derivationGroupMotivation2 from './assets/derivation_group_motivation_ii.png'; -import externalEventsOnTimeline from './assets/external_events_on_timeline.png'; -import externalEventsTable from './assets/external_events_table.png'; -import externalEventsWithResourcesAndActivities from './assets/external_events_before.png'; -import externalEventLayerOptions from './assets/external_event_options.png'; -import externalSourceOrdering from './assets/ordering_external_sources.png'; -import externalSourceManager from './assets/external_source_manager_intro.png'; -import externalSourceManagerTypesPage from './assets/external_source_manager_types_page.png'; -import externalSourceManagerTypesExpanded from './assets/external_source_manager_types_expanded.png'; -import externalSourceManagerUpload from './assets/external_source_manager_upload.png'; -import externalSourceTable from './assets/external_sources_table.png'; -import externalSourceInspected from './assets/inspected_source.png'; -import planExternalSourceView from './assets/plan_external_source_full_view.png'; -import planExternalSourcesPanel from './assets/plan_external_sources_panel.png'; -import planDerivationGroupModal from './assets/manage_derivation_group_modal.png'; -import planSelectedExternalEvent from './assets/plan_selected_external_event.png'; -import timelineEditorFull from './assets/timeline_and_editor_full.png'; - -# External Events - - -An **External Event** is temporal data used to inform a mission planner's decision making process. By nature, these events are *external* to Aerie itself and represent temporal occurrences that are immutable in nature. - -Examples of External Events include: -* Ground station contacts -* Orbital events -* Weather forecasts - -In this document, we will discuss what External Events are, what their containing entities (External Sources) are, their grouping mechanisms (Derivation Groups), as well as how they manifest within Aerie. - -## Concepts - -### Data Constructs -With External Events (and External Sources) come several constructs. - -#### External Events -External Events represent temporal occurrences outside of the locus of control of the modelled spacecraft(s). See [External Events](#external-events-2) below for more information. - -#### External Sources -External Sources are containers for External Events and the primary way that users interact with External Events. To get External Events into Aerie, an External Source must be uploaded that contains the event definitions inside. See [External Sources](#external-sources-1) below for more information. - -#### External Event Types -External Event Types define the *typing* of each External Event. This typing groups External Events on the basis of the data they represent, and details the allowed attributes that a given External Event is allowed to have. For example, an External Event may be of the type `Ground Station Contact`, `Solar Flare`, or `Weather Forecast`. Each of these types contains a schema for a set of allowed/defined attributes which are then implemented by the events using this External Event Type to display additional data, such as `station`, `flux level`, or `precipitation`. - -#### External Source Types -External Source Types define the *typing* of each External Source. This typing groups External Sources on the basis of the data they contain, and details the allowed attributes that a given External Source can have to describe the data contained within it. For example, you may have an External Source Type of `Weather Report` to represent all your weather reports, and inside each of the `Weather Report`-typed sources you can expect to find External Events that share a common External Event Type that appears in all `Weather Report`-typed sources (ex: Temperature). Identical to [External Event Types](#external-event-types), External Source Types also contain attribute schemas that are then implemented by the External Sources using the External Source Type. - -#### Derivation Groups -Derivation Groups are collections of External Sources that can be associated with plans. Within a Derivation Group, the collective External Events from its member External Sources are used to create a *derived* set of events for the Derivation Group. See [Derivation](#derivation) below for more information. - -#### Derivation Group & Plan Associations -Derivation Groups can be *linked* to a plan (or plans) which allows their derived events to be utilized within the plan for visualization, planning, and scheduling. See [UI Usage](#ui-usage) below for more information on use within the UI. - -### External Events -An **External Event** is a construct that represents the occurrence of something outside of what is modeled in the mission model. They could be events that are close to the spacecraft and directly affect behavior on it, like a Ground Contact event that might affect when communications happen, or they could be much more remote, like a constellation event. The main concept with External Events is that they are immutable events that occur at some point in time (either instantaneously, or for some set duration) and can/will impact mission planning. Functionally, these are at the interstice between external datasets and activities, in that they manifest on the timeline as activities (discrete blocks of time), but they themselves do not affect the mission model or invoke any actions similar to external datasets. - -
- Figure 1: External Events - Timeline view -
Figure 1: External Events - Timeline view
-
- -#### Why? -The need for these became apparent after an exploration of External Events implemented as activities and as resources. To mimic the same construct as a resource, we would need to parse the external data and then place it on the timeline as a discrete resource, which given the fact that there are gaps between events and there can also be simultaneous events proved extremely unwieldy and awkward. While activities were a more natural representation, we still required an intermediary of a resource to schedule these activities based off of an ingested file, as it isn't possible to schedule activities exclusively following information in a file. - -
- Figure 2: External Events - External Events with resources and activities -
Figure 2: External Events - External Events with resources and activities
-
- -:::info Note - -Currently, External Events are *not* accessible to the procedural constraints engine. However, this is in the planned scope for External Events, and future releases will add this capability - -::: - -### External Sources -**External Sources** represent a collection of External Events. When we upload External Events, we can only do so by uploading them in an External Source. - -These sources are defined using [JSON Schema](https://json-schema.org/) format with a [meta-schema](https://github.com/NASA-AMMOS/aerie-gateway/blob/develop/src/schemas/external-event-validation-schemata.ts) enforced to ensure certain fields such as `key` and `start_time` are present and in the correct format. - - -#### Schema -There are two parts to each External Source: 1) a `source` header and 2) a collection of `events`: -```json -{ - "source": { - // fields - }, - "events": [ - // events - ] -} -``` - -##### Source - -The `source` field includes the following fields: - -* `key` (required) - * Unique identifier for the External Source - * Uniqueness must hold across the **Derivation Group** that it is a a part of - * Typically a filename or combination of details about the source itself (i.e., an external version number, spacecraft identifier, etc.) - * **Example**: `ExampleExternalSource:example-external-source.json` - -* `source_type_name` (required) - * Used to classify the External Source, categorize for grouping/filtering within the UI & Derivation Groups (see [External Source Types](#external-source-types) for more information), and applying the schema defined for the type to the attributes of the External Source being uploaded (see [Attributes](../external-events-attributes/) for more information) - * **Example**: `ExampleSourceType` - -* `derivation_group_name` (optional) - * Defines the Derivation Group that this External Source should become a part of once uploaded. See [Derivation](#derivation) for more information. - * If `derivation_group_name` is omitted in the source file, it will need to be specified on upload. - * **Example**: `ExampleSourceType Default` - -* `valid_at` (required) - * Similar to a version number, though it describes at exactly what real-world time a file becomes *valid* - * Can be sorted and ordered like a version number, but additionally provides extreme detail about when it can/should be utilized which plays a key role in [derivation](#derivation) - * **Example**: `2022-001T00:00:00Z` - -* `period` (required) - * Specifies the exact range of simulation/plan time over which this external source applies. - * Multiple External Sources of the same External Source Type may cover the same `period`, which is where `valid_at` comes into play - see [derivation](#derivation) for more information - * Composed of a combination of `start_time` and either `end_time` *or* `duration` - * **Example** - ```json - ..., - "period": { - "start_time": "2022-001T00:00:00Z", - "end_time": "2022-008T00:00:00Z" - }, - ... - ``` - -* `attributes` (optional) - * An object that includes additional data relevant to the External Source in question. - * In order for an External Source to have attributes, its source type must define a schema for those attributes. - * For a Weather Forecast file, this may include details about the location, which version this file is, and notes about what changed between this version and the last. - * The `attributes` key can be omitted for sources whose type has no attributes. - * ***Example*** - ```json - ..., - "attributes": { - "location": "Greenbelt, MD", - "version": 2, - "changelog": "Updated precipitation likelihood for 2024-007T00:00:00 from 20% to 35%." - }, - ... - ``` - -##### Events - -Events are represented as a set of objects contained in a list, and each event has the following fields: - -* `key` (required) - * Unique identifier for the External Event - * Uniqueness is required within the External Source, but **not** across different External Sources. For more information, see [derivation](#derivation) - * **Example**: `ExampleEvent:1/sc/sc1:1:X/1` - -* `event_type_name` (required) - * Used to classify the External Event, and categorize for grouping/filtering within the UI & Derivation Groups (see [External Event Types](#external-event-types) for more information) - * **Example**: `GroundContact` - -* `start_time` (required) - * Specifies the start time of the External Event within simulation/plan time - * **Example**: `2022-001T00:00:00Z` - -* `duration` (required) - * Specifies how long the External Event lasts in simulation/plan time (represented in `HH:MM:SS`) - * This value may be `00:00:00` to represent an instantaneous event (ex: time of sunrise/sunset) - * **Example**: `00:30:00` - -* `attributes` (optional) - * Similar to attributes for External Sources, this property lists various characteristics of a given event not directly implied by the above fields' values. - * Must follow the schema as defined by the External Event Type - * If the External Event Type does not define an attributes schema, this key may be omitted. - * For a Weather Forecast file, a given event may require properties detailing the likelihood of precipitation, or the wind speed. - * **Example:** - ```json - ..., - "attributes": { - "precipitation_lik": 0.35, - "wind_speed (mph)": 3 - }, - ... - ``` - -##### Full External Source & Event Types Example - -The following is a full example of a valid, `JSON` representation of an External Source Type and External Event Type. The type definitions must be uploaded to Aerie prior to attempting to use them in an External Source and/or External Event(s). - -The example represents the definitions for a weather forecast source & event. - -```json -{ - "event_types": { - "WeatherReport": { - "type": "object", - "required": ["daily_high", "daily_low", "precipitation_lik"], - "properties": { - "daily_high": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "daily_low": { - "type": "object", - "properties": { - "value": { - "type": "number" - }, - "units": { - "type": "string" - } - }, - "required": ["units", "value"] - }, - "precipitation_lik": { - "type": "number" - } - } - } - }, - "source_types": { - "Weather": { - "type": "object", - "required": [ - "location", - "version", - "changelog" - ], - "properties": { - "location": { - "type": "string" - }, - "version": { - "type": "number" - }, - "changelog": { - "type": "string" - } - } - } - } -} -``` - -:::info Note - -For more information on External Source & Type definitions & attribute schemas, see [External Events and Source Attributes](../external-events-attributes) - -::: - -##### Full External Source Example - -The following is a full example of a valid, `JSON` representation of an External Source that can be ingested by Aerie. - -The example represents a week-long weather forecast where each event is the report for a specific day. - -```json -{ - "source": { - "key": "WEATHER_2024001_2024008_IDEAL.V00.json", - "source_type_name": "Weather", - "derivation_group_name": "Weather Default", - "valid_at": "2024-001T00:00:00Z", - "period": { - "start_time": "2024-001T00:00:00Z", - "end_time": "2024-008T00:00:00Z" - }, - "attributes": { - "location": "Greenbelt, MD", - "version": 2, - "changelog": "Updated precipitation_lik for 2024-007T00:00:00 from 20% to 35%." - } - }, - "events": [ - { - "key": "W/Day/001", - "event_type_name": "WeatherReport", - "start_time": "2024-001T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 43.2, - "units": "F" - }, - "daily_low": { - "value": 32.9, - "units": "F" - }, - "precipitation_lik": 0.7 - } - }, - { - "key": "W/Day/002", - "event_type_name": "WeatherReport", - "start_time": "2024-002T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 40.5, - "units": "F" - }, - "daily_low": { - "value": 29.2, - "units": "F" - }, - "precipitation_lik": 0.6 - } - }, - { - "key": "W/Day/003", - "event_type_name": "WeatherReport", - "start_time": "2024-003T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 42.1, - "units": "F" - }, - "daily_low": { - "value": 28.4, - "units": "F" - }, - "precipitation_lik": 0.5 - } - }, - { - "key": "W/Day/004", - "event_type_name": "WeatherReport", - "start_time": "2024-004T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 50.0, - "units": "F" - }, - "daily_low": { - "value": 32.1, - "units": "F" - }, - "precipitation_lik": 0.4 - } - }, - { - "key": "W/Day/005", - "event_type_name": "WeatherReport", - "start_time": "2024-005T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 46.3, - "units": "F" - }, - "daily_low": { - "value": 21.0, - "units": "F" - }, - "precipitation_lik": 0.3 - } - }, - { - "key": "W/Day/006", - "event_type_name": "WeatherReport", - "start_time": "2024-006T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 34.3, - "units": "F" - }, - "daily_low": { - "value": 22.5, - "units": "F" - }, - "precipitation_lik": 0.2 - } - }, - { - "key": "W/Day/007", - "event_type_name": "WeatherReport", - "start_time": "2024-007T00:00:00Z", - "duration": "24:00:00", - "attributes": { - "daily_high": { - "value": 39.0, - "units": "F" - }, - "daily_low": { - "value": 35.3, - "units": "F" - }, - "precipitation_lik": 0.35 - } - } - ] -} -``` -:::info Note - -External Sources expect a `start_time` and an `end_time`, whereas External Events expect a `start_time` and a `duration`. - -::: - -### Derivation - -Derivation was an operation conceived for the reconciliation of overlapping External Sources. The problem at hand was that it is entirely possible for sources to be generated at later points in time, that cover slightly different windows of plan time than their predecessors. - -
- Figure 3: A diagram illustrating a motivating case for derivation -
- Figure 3: A diagram illustrating a motivating case for derivation. Which events should be present? -
-
- -If this is the case, some reconciliation scheme is needed to determine which External Events from these External Sources are valid and should be visible on the timeline. - -#### Reconciliation Mechanism - -To begin this discussion, we should first talk about when External Sources apply. - -External Sources, as discussed earlier, have within them a `valid_at` field, which was described as a more particular form of a version number. With this, we know we can impose an ordering of External Sources. Given that ordering, figuring out when each External Source actually applies is as simple as determining, for a given time (or slot of time), what the most recently valid External Source is. For example, the following set of 4 External Sources would be applicable in the following order. Note that one of the External Sources, `B`, is valid twice, as it is the most recently valid External Source for a given slot of time: - -
- Figure 4: A diagram illustrating how External Sources are reconciled against each other in terms of the time slots they occupy -
- Figure 4: A diagram illustrating how External Sources are reconciled against each other in terms of the time slots they occupy -
-
- -Given this, we can now discuss how to derive External Events from a Derivation Group of External Sources. There are 4 rules (though the first two are effectively cases of each other): - -1. _An External Event superseded by nothing will be present in the final, derived result._ - - The logic behind this is that this External Event has no future data that might challenge its validity, so it reasonably would trickle down as an event in a final result. - -2. _An External Event partially superseded by a later External Source, but whose start time occurs before the start of said External Source(s), will be present in the final, derived result._ - - The logic behind this is similar. This External Event, at its start, has no future data that might challenge its validity. Because its start is still considered valid, we accept the event in its entirety to the final, derived result, as no future External Source that could overlap with this External Event could place an External Event that applies to the time that this External Event starts at. If it did, we have a case of rule 3. - -3. _An External Event whose start is superseded by another External Source, even if its end occurs after the end of said External Source, will be replaced by the contents of that External Source (whether they are blank spaces, or other events)._ - - The logic here is simply that we now have more recent data, so we can safely do a complete replacement. Should an External Event be replaced despite ending after the original source, we can still justify this as the updated External Source - if it had need for such an External Event - would have included it. - -4. _An External Event that shares an ID with an External Event in a later External Source will always be replaced._ - - By introducing naming, we can allow replacement. - -#### Derivation Example -A diagram illustrating each of the derivation cases follows: - -
- Figure 5: An example illustrating each derivation case -
- Figure 5: An example illustrating each derivation case -
-
- -The following explains what is happening to each External Event in the example: - -* **External Source A** - - * **2:D** - * Status: _Excluded_ - * Rules - * **3**: Even though its end is after end of External Source B, the start is subsumed. In any case, it is wholly subsumed by External Source C. - * **4**: Replaced by key in External Source C (regardless of the fact that the new event has a different type). - - * **7:C** - * Status: _Excluded_ - * Rules - * **3**: Even though its end is after end of External Source B, the start is subsumed. - - * **8:B** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - -* **External Source B** - - * **1:A** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - - * **2:A** - * Status: _Excluded_ - * Rules - * **3**: Completely subsumed by External Source D. - * **4**: replaced by key in External Source C (regardless of the fact that the new event has a different type). - - * **3:B** - * Status: **Included** - * Rules - * **2**: Starts before any other External Source, despite end being subsumed. - - * **4:B** - * Status: _Excluded_ - * Rules - * **3**: Completely subsumed by later External Source. - -* **External Source C** - - * **5:C** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - - * **6:C** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - - * **2:B** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - * **4**: Also most recent occurrence by key name, so this is the only one that persists - -* **External Source D** - - * **9:C** - * Status: **Included** - * Rules - * **1**: This External Event is subsumed by nothing. - -#### Derivation Groups - -The final question remaining is: **what set of external sources** will this derivation operation be run against? One possible candidate for this could be the External Source Type. External Events that share an External Source Type include External Events of the same types, and will likely overlap in key namespace as well. This is a nearly ideal solution except in the case of contingencies. - -Imagine we have 4 sources of the same source type. Two of these relate to an ideal contingency, and two of these relate to a degenerate contingency: - -1) **Ideal** - - * `DSN_Contact_Ideal_2026001007.json` - * `DSN_Contact_Ideal_2026003010.json` - -2) **Degenerate** - - * `DSN_Contact_Degen_2026001007.json` - * `DSN_Contact_Degen_202603010.json` - -If we were to derive solely based on External Source Type, we would effectively be stacking **all** of these sources against each other to produce **one** result: - -
- Figure 6: A diagram illustrating the unwanted conflation of two sets of sources -
Figure 6: A diagram illustrating the unwanted conflation of two sets of sources
-
- -Given that these are different cases (despite sharing a source type) it would be useful to have different groups under which derivation is possible. This way, we don't conflate sources just for sharing a source type: - -
- Figure 7: A diagram illustrating the prevented conflation of two sets of sources -
Figure 7: A diagram illustrating the prevented conflation of two sets of sources
-
- -As such, we now define the notion of a **Derivation Group**. A Derivation Group defines a subgroup within an External Source Type, of External Sources to derive against each other. Derivation Groups are what we associate with plans (as opposed to individual, un-derived External Sources, or entire External Source Types). - -## UI Usage -External Events show up in the UI in two main places - an External Source Manager, and in plans (both in panels and timelines). Our discussion of UI features will therefore be divided along which page the features appear on. - -:::info Note - -There is also a [tutorial section](../../../tutorials/external-events/introduction) that walks through the content of this section in a step-by-step fashion, albeit with less detail. - -::: - -### External Source Manager -The External Source Manager page handles the uploading & inspection of External Sources, as well as the creation and management of Derivation Groups. - -
- Figure 8: External Source Manager -
Figure 8: External Source Manager
-
- -#### Defining External Source Types and External Event Types -In order to use attributes in External Sources or Events, the corresponding Source and Event Type schemas must first be defined in a schema file. You can find more about the structure of schema files [here](../external-events-attributes). - -To upload a schema file, navigate to the `Types` tab in the External Source Manager. - -
- Figure 9: External Source and Event Types Page -
Figure 9: External Source and Event Types Page
-
- -Use the left pane to upload a new schema file. - -This page displays all of the types that have been defined and the attributes they have. Here, we see the `Weather` source type has three attributes: `version` (a number), `location` (a string), and `changelog` (a string). - -
- Figure 10: Detail on a given External Source Type -
Figure 10: Detail on a given External Source Type
-
- -#### Uploading an External Source -The main view allows users to upload External Sources. Most of these fields autofill and are immutable once parsed - except the Derivation Group which will be autofilled in the style of `external_source_type Default`, but can be edited by the user before upload. - -
- Figure 11: Uploading an External Source -
Figure 11: Uploading an External Source
-
- -#### Inspecting an External Source -After uploading an External Source, it will show up in the table on the upper-right pane. This table can be filtered by External Source Type as well as sorted by each of the columns - note the default multi-sorting on the columns `Source Type`, `Derivation Group`, and `Valid At`, which is meant to present the sources in the easiest-to-read manner (grouping by their common types & Derivation Groups, then ordering by their `Valid At` times). - -
- Figure 12: External Source Table -
Figure 12: External Source Table
-
- -Selecting a source allows for the inspection of the source, which is essentially an in-Aerie rendering of what's included under `source` in the uploaded `JSON` (as discussed earlier). It is here, as well as in the table, that deletion can be performed. Note that for deletion to work, an External Source must not be associated with any existing plan (that is the Derivation Group it is a part of must be dissociated from all plans before deletions become legal). Aside from that, there is currently no system of ownership, meaning admins and users alike, as long as External Sources satisfy the aforementioned condition, can delete at will. Viewers, however, cannot. - -It should also be noted that all this inspection takes place outside the context of a plan, so that External Sources & their events can be viewed without having to open and associate the External Source(s) to a plan. - -
- Figure 13: Inspected External Source -
Figure 13: Inspected External Source
-
- -Finally, it is also possible to inspect each source's contained events as a table. This is useful, as Derivation Groups on plan timelines only show what has been derived, meaning anything that has been selected against won't appear. This page is therefore the absolute truth to see everything that is included, derived-out or not, with a given External Source. - -
- Figure 14: External Events Table -
Figure 14: External Events Table
-
- -### Plan -Once an External Source has been uploaded, it can be viewed and interacted with in the context of a plan. - -
- Figure 15: Overview of the plan view containing External Event information -
Figure 15: Overview of the plan view containing External Event information
-
- -To begin working with external sources in the plan, the left side panel should be set to the "External Sources" option. This panel is where users (administrators or plan collaborators) can associate different Derivation Groups with the plan. - -#### Derivation Group Management - -There are currently two levels to linking a Derivation Group to a plan: **association** and **enabling**. Association is handled here, via the _Manage Derivation Groups_ button. This presents a modal where users can expand different Derivation Groups to view the associated sources and select whether or not they would like to associate said Derivation Group with their plan. - -
- Figure 16: Derivation Group Modal -
Figure 16: Derivation Group Modal
-
- -**Association** simply means that the plan is *aware* of the existence of the Derivation Group, and is the gatekeeper for whether it is visible in the External Sources panel. In this panel lies the second level to linking, which is enabling. **Enabling** simply dictates whether a Derivation Group (even if the filters in the *Timeline Editor* select for it) is *visible* on the timeline. Disabling hides all events associated with the group completely, whereas enabling does the opposite. It does not, however, dissociate the plan and Derivation Group. This is simply a visibility attribute and is actually persisted as part of the view (and even persists after Derivation Group dissociation/reassociation, meaning if a Derivation Group is dissociated and then re-associated, its "enabled" setting stays the same). - -Once a Derivation Group has been associated with the plan, new external event sources may be added to it (or deleted from it), causing the derivation process to automatically re-run, and affecting the events which are shown on the plan timeline. For this reason, this panel also provides users with a notification of recently added and deleted sources which affect this plan, and their respective External Source Type and Derivation Group. This notification persists until acknowledged with the `Dismiss` button. - -Enabled|Disabled -:-------------------------:|:-------------------------: -![Enabled.](./assets/external_events_derivation_group_enabled.png) | ![Disabled.](./assets/external_events_derivation_group_disabled.png) - -
- Figure 17: External Sources Panel -
Figure 17: External Sources Panel
-
- -#### Timeline -The timeline shows any events from Derivation Groups that are associated with the plan, enabled in the current view, and are filtered for in the timeline editor. Having covered the first two, we can now discuss the editor and its effects on the timeline. - -
- Figure 18: Timeline & Layer Editor -
Figure 18: Timeline & Layer Editor
-
- -The layer of the timeline is modelled after the activity layer, as it provides an easily readable representation of discrete-time occurrences. Layers share the layer options also provided to activity layers, as well as implementing a 'Group By' option specifically for external events. The 'Group By' mechanic allows the user to have external events either grouped by their external source (within the derivation group), or their event types. - -'Group By' External Source|'Group By' External Event Type -:-------------------------:|:-------------------------: -![External Source](./assets/external_events_group_by_source.png) | ![External Event Type](./assets/external_events_group_by_event_type.png) - -
- Figure 19: External Event Layer Options -
Figure 19: External Event Layer Options
-
- -#### Tables and Inspection -The screenshot below shows a brief overview of the remaining panels that have been added to plans. It is possible to either mouse over External Events for additional detail or to select them in the timeline, which selects them in a table view beneath the timeline as well as details them in the right pane. This looks as follows: - -
- Figure 20: The selected External Event view and a table of External Events, working in tandem -
Figure 20: The selected External Event view and a table of External Events, working in tandem
-
- -#### A Note on Views -As a final note on the frontend, it is worth briefly detailing what parts of the plan page additionally get persisted when a view is saved: -- _Derivation Group enablement_ - whether a Derivation Group is enabled or not in the External Sources panel, -- _External Event Type filters_ - the External Event Type filters selected in the timeline editor, -- _External Event options_ - the options in the timeline editor filter. - -## Scheduling Activities from Events -External events associated with a plan are accessible from the [Procedural Scheduling](/scheduling-and-constraints/procedural/introduction/) API, just like resources and activities. This allows users to write scheduling goals which create activities based on the presence (or absence) of external events - for example, "create a downlink activity 5 minutes after the start of every 'DSN Contact' type event". - -To access events in a procedural goal, you must create an External Events timeline object - see [Timelines: External Events](/scheduling-and-constraints/procedural/timelines/basics/external-events/). A full example of a scheduling goal using events can be found on the [Procedural Scheduling Examples](/scheduling-and-constraints/procedural/scheduling/examples/#scheduling-based-off-of-external-events) page. - -Currently, external events are **not accessible from the [Declarative Scheduling EDSL](/scheduling-and-constraints/declarative/scheduling/introduction/)** - but we may implement support for this in the future. - -## What Remains -Here, we discuss briefly everything that is not currently implemented but that we do plan to in the future. - -### Ownership/Roles - -Our current solution to a lack of clear ownership/roles around External Sources is as follows: -- any user and any administrator can add External Sources, -- as there is no ownership yet, any user and any administrator can delete External Sources as long as the Derivation Group they are a part of is not associated with any plans, and -- associations between plans and Derivation Groups can be handled by any administrator or a user that is collaborating on that plan. - -It might make sense to add ownership, i.e. a given user owns an External Source and therefore has privileges to delete it (much like how plans are implemented, except External Sources cannot be edited), but as it would primarily be used for deletion functionality but would introduce a lot of complexity for this simple problem, it was sidelined entirely. - -That being said, as we move forward with more features relating to External Events, more nuance may come to the problem of ownership and roles. Once plans start actually depending on these plans, the rules surrounding ownership and permissions may need to change or be entirely rethought. Ultimately, we need something that justifies the implementation of such a scheme in that it provides more than the status quo. - diff --git a/docs/planning/plan-import-export.mdx b/docs/planning/plan-import-export.mdx deleted file mode 100644 index faff62d7..00000000 --- a/docs/planning/plan-import-export.mdx +++ /dev/null @@ -1,41 +0,0 @@ -import exportPlanScreenshot from './assets/export-plan.png'; - -# Plan Import/Export - -Aerie has the ability to export plans into a JSON-based file format, and import plans from files using the same format. -This allows you to save plans you've created in Aerie for backup or archival purposes, transfer plans between different Aerie -deployments, or interface with other tools which may ingest or modify the plan file. - -Aerie plan files are mainly a way to persist **activities, tags, anchors, and arguments** from a plan. -Note that the following data is not included in plan files: -- Mission models - when importing, you will need to choose an existing mission model to associate with your plan -- Views - saved separately from plans, you can use the Download View button to save your views -- Resource profiles and other simulation results -- Plan specifications for Constraints, Scheduling Goals and Conditions -- Simulation Template - you can optionally associate a template when importing - -The format of these files is described by this Typescript type in the Aerie repository: - -https://github.com/NASA-AMMOS/aerie-gateway/pull/97/files#diff-aadcd2e92cdf1b17122b6fc65396f3e5603e14064d730a020b231cd30134704e - -Future releases of Aerie will include a JSON schema fully describing the format. - -## Exporting a plan - -To export an Aerie plan, first open it in Aerie, then open the "Plan Metadata" panel. Use the export button in the top -right of the panel to export a `plan.json` file. - -
- Aerie UI - Export Plan from Plan Metadata panel -
- -## Importing a plan - -On the Plans page, you can select an Aerie plan file to upload when creating a new plan. This will pre-populate -the other fields from the plan file, but you will still need to select a mission model file to associate with your plan. - -There is currently no validation to check that the associated mission model is the same as the one used to create the -plan originally. This allows you to test the same plans with different models, but you may experience errors during -simulation or scheduling if the activities in the plan file don't match those in the model. - - diff --git a/docs/planning/plugins/introduction.mdx b/docs/planning/plugins/introduction.mdx deleted file mode 100644 index c168448f..00000000 --- a/docs/planning/plugins/introduction.mdx +++ /dev/null @@ -1,21 +0,0 @@ -# Advanced - UI Plugins - -The Aerie UI provides an client-side API for customizing various aspects of the UI. This customization is accomplished by supplying the UI with specifically named javascript files in the `static/resources` directory of the application and enabling the plugins using runtime environment variables. These plugins are run in the Aerie UI browser context. The relevant portions of the API specification can be found on specific plugin pages below or for the entire specification please reference the Aerie UI [plugin.ts](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/src/types/plugin.ts) type file. Plugins may also load additional supporting files from the `static/resources` directory or from external sources. Please note that additional resource requests to `static/resources/*` should be referenced as `/resources/*` given the client side routing setup. - -Plugins must export an asynchronous function named `getPlugin` that returns a subset of the Plugin API. - -```javascript -// Empty plugin example (.js) -export async function getPlugin() { - console.log('Plugin loaded'); - return { - /* Plugin contents */ - }; -} -``` - -## Supported Plugins - -| Plugin | Description | -| ------------------------------ | --------------------------------------------------- | -| [Time](/planning/plugins/time) | Customize how time is presented and input in the UI | diff --git a/docs/planning/plugins/time.mdx b/docs/planning/plugins/time.mdx deleted file mode 100644 index 91e24665..00000000 --- a/docs/planning/plugins/time.mdx +++ /dev/null @@ -1,66 +0,0 @@ -# Time Plugin - -Time input and presentation in the Aerie UI can be customized by implementing a time plugin. Currently the time plugin is only used on the plans and individual plan page. This time plugin does not affect how time is stored in the database and instead converts times from the database format to the plugin format on the fly in the UI. - -Example use cases for the Time Plugin: - -- Displaying times in a particular timezone -- Displaying and inputting times in a custom time such as LMST, SCLK, etc - -## Configuration - -- The Time Plugin must be named `time-plugin.js` and must be placed inside of the `static/resources` directory of the Aerie UI. -- Set `PUBLIC_TIME_PLUGIN_ENABLED=true` in your runtime environment. - -## Time Plugin API - -The Time Plugin must return a `time` object from a `getPlugin` function with the following optional properties: - -| Property | Type | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `primary` | PluginTime | See below for a definition of the `PluginTime` type.
Default: UTC DOY parsing and input | -| `additional` | Optional[] | Additional time formats that must supply at least the `format` and a `parse` functions. These additional time formats are used in the plan timeline secondary time displays as well as other secondary time displays in the plan page.
Default: User's local timezone | -| `enableDatePicker` | boolean | If true, show a UTC date picker. Disable this when the plugin does not use a primary time of UTC.
Default: `true` | -| `getDefaultPlanEndDate` | (start: Date) => Date \| null | Compute a plan end date given a plan start date
Default: Add one Earth day to the start date | -| `ticks` | Object | See below for the properties of `ticks` | -| `ticks.getTicks` | (start: Date, stop: Date, count: number) => Date[] | Return an array of `Date` objects given a start `Date`, a stop `Date`, and a target count of ticks. This function allows for cleanly lining up ticks with the plugin's primary time system.
Default: UTC based time ticks | -| `ticks.maxLabelWidth` | number | The maximum width in pixels of any tick label. Used internally by the Aerie UI timeline to compute the number of ticks that should be displayed in the timeline.
Default: 130 | - -
- -The `PluginTime` type is defined by the following optional properties: - -| Property | Type | Description | -| -------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `format` | (date: Date) => string \| null | Convert a `Date` to a human readable `string`.
Default: UTC DOY `YYYY-DDDThh:mm:ss.SSS` | -| `formatShort` | (date: Date) => string \| null | Convert a `Date` to a short human readable `string`.
Default: UTC DOY `YYYY-DDD` | -| `formatString` | string | Format that users should adhere to for string date entry.
Default: `YYYY-DDDThh:mm:ss` | -| `formatTick` | (date: Date, durationMs: number, tickCount: number) => string \| null | Format a timeline tick given a `Date`, tick window duration, and number of ticks.
Default: Dynamic UTC date formatting | -| `label` | string | Label for the time format.
Default: UTC | -| `parse` | (dateString: string) => Date \| null | Convert a string representation of a date to a `Date` object.
Default: Native Javascript `Date` parsing | -| `validate` | (dateString: string) => string \| null | Return an error string if the given date string is invalid, otherwise return null.
Default: UTC date string validation | - -## Error handling - -The Time Plugin is responsible for gracefully failing when exceptions are encountered within the plugin. When an operation such as `parse` or `format` fails, the plugin should return `null` to indicate to the Aerie UI that an error occurred and that the date is invalid. - -## Example Plugins - -Below is a basic example that sets the additional time formats to a single format that displays the current UTC year. For additional plugin examples, please refer to the [aerie-ui-plugin-examples](https://github.com/NASA-AMMOS/aerie-ui-plugin-examples) repository. - -```javascript -// Time plugin example (time-plugin.js) -export async function getPlugin() { - console.log('Plugin loaded'); - return { - time: { - additional: [ - { - format: (date: Date) => date.getUTCFullYear().toString(), - parse: (dateString: string) => new Date(string), - }, - ], - }, - }; -} -``` diff --git a/docs/planning/snapshots.mdx b/docs/planning/snapshots.mdx deleted file mode 100644 index f5218693..00000000 --- a/docs/planning/snapshots.mdx +++ /dev/null @@ -1,81 +0,0 @@ -import takeSnapshotNavbar from './assets/snapshots/takeSnapshotNavbar.png'; -import takeSnapshotModal from './assets/snapshots/takeSnapshotModal.png'; - -import viewSnapshotNavbar from './assets/snapshots/viewSnapshotNavbar.png'; -import planMetadataPane from './assets/snapshots/planMetadataPane.png'; -import viewSnapshot from './assets/snapshots/viewSnapshot.png'; - -import restoreSnapshotModal from './assets/snapshots/restoreSnapshotModal.png'; -import restoreAndTakeSnapshot from './assets/snapshots/restoreSnapshotTakeSnapshot.png'; - -# Snapshots - -A snapshot of a plan is a named record of the state of all activities in a plan at a certain time. -Snapshots are useful for recording and returning to known "good" states of a plan. - - - -## Taking a Snapshot - -Snapshots can via one of two methods: -1. Clicking on the Plan's name in the navbar, then clicking "Take Snapshot" -2. Clicking on the "Take Snapshot" button in the Plan Metadata Pane - -Either option will then present you with a modal where you can name the snapshot and optionally provide a description and tags. -Note that snapshot names must be unique per plan. - -
-
- Aerie UI - Expanded drop down under Plan in the Navbar, the option 'Take Snapshot' is highlighted -
- Aerie UI - Take Snapshot Modal -
Figure 1: Aerie UI - How to Take a Plan Snapshot
-
- - -## Viewing a Snapshot - -You can view the snapshots taken for a specific plan by clicking on the Plan's name in the navbar, then clicking "View Snapshot History". -Doing so will open the Plan Metadata Pane, where a list of all snapshots taken will be displayed. -If a snapshot has been simulated, an icon will be present indicating the results of the most recent simulation for that snapshot. - -You can filter this list to only display snapshots for the currently selected simulation dataset by selecting the "Snapshot" badge to the left of the "Take Snapshot" button. - -
-
- Aerie UI - Expanded drop down under Plan in the Navbar, the option 'View Snapshot' is highlighted -
- Aerie UI - Plan Metadata Pane -
Figure 2: Aerie UI - How to View a Plan Snapshot
-
- -Clicking on a snapshot will open a preview of its contents. From here, you can examine activities and related simulation datasets. - -
- Aerie UI - Take Snapshot Modal -
Figure 3: Aerie UI - Viewing a Plan Snapshot
-
- -## Restoring a Snapshot - -If you would like to update a plan to match the state of a snapshot, you can do so by clicking on the "Restore Snapshot" button seen while previewing a snapshot. - -When restoring a snapshot, you can optionally choose to take a snapshot of the current state of the plan prior to restoring. - -
-
- Aerie UI - Restore Snapshot Modal -
- Aerie UI - Restore and Take Snapshot Modal - Simple -
Figure 4: Aerie UI - How to Restore a Plan Snapshot
-
- -The snapshot you restored will remain in the list in case you need to restore to it again in the future. diff --git a/docs/planning/timeline-controls.mdx b/docs/planning/timeline-controls.mdx deleted file mode 100644 index 40944c2f..00000000 --- a/docs/planning/timeline-controls.mdx +++ /dev/null @@ -1,49 +0,0 @@ -import Link from '@docusaurus/Link'; -import timelineIconTray from './assets/timeline-icon-tray.png'; -import timelineInterpolationOff from './assets/timeline-cursor-interpolation-off.png'; -import timelineInterpolationOn from './assets/timeline-cursor-interpolation-on.png'; - -# Timeline Controls - -Here we describe the timeline rendering and interaction options that are not part of the Aerie UI View. Above the timeline is a tray of icon buttons that can be used to control how data is rendered and interacted with on the timeline. - -
- Aerie UI - Timeline Icon Tray -
Figure 1: Aerie UI - Timeline Icon Tray
-
- -The following controls are available: - -1. `Auto Scroll to Offscreen Selections` - Automatically scrolls the timline to the selected activity directive or span. This can be helpful when selecting directives or spans from their associated tables. -2. `Decimation` - Perform on the fly min-max decimation of line layer points for improved timeline rendering performance. [Learn more about decimation below](#line-layer-decimation). -3. `Cursor Value Interpolation` - Show interpolated value when hovering over resources. [Learn more about interpolation below](#cursor-value-interpolation). -4. `Limit Tooltip to Line` - When enabled, the timeline tooltip will only appear when hovering near a resource line segment. When disabled, the tooltip will appear when hovering over any part of a resource layer. -5. `Timeline Tooltip Visibility` - Toggle timeline tooltip visibility. -6. `Timeline Modes` - Toggle between `Pan & Zoom` and `Interaction` modes. [Learn more about timeline modes below](#timeline-modes). -7. `Time Step` - Slide the time view window to forwards and backwards. -8. `Zoom` - Zoom in and out of the plan time window. -9. `Copy URL` - Copy URL including plan, visible time window, selection, and simulation dataset to clipboard. -10. `Drag Activities` - Enables dragging of activity directives in the timeline. -11. `Timeline Options Menu` - Displays timeline controls with additional information. - -## Line Layer Decimation - -Decimation can be enabled on the Aerie timeline in order to more peformantly render line plots. This option can be useful when loading large quantities of profile segments (tens of thousands) and when visualizing a large number of moderately dense resource plots. Decimation of resources is performed on the fly using the min-max decimation algorithm. Min-max decimation reduces the number of points drawn on screen while accurately maintaining the shape of the data. This is achieved by binning the points by the number of available pixels and for each bin plotting the first point, the min, the max, and the last point. If decimation is enabled, line segments will always be rendered and individual points will only be rendered if the data no longer requires decimation (i.e. the number of pixels available is greater than the number of original points). - -## Cursor Value Interpolation - -Aerie will display a tooltip with resource values when you hover over a line plot. By default, Aerie will select the nearest point to your cursor position as shown in Figure 2. If Cursor Value Interpolation is enabled, Aerie will display a linearly interpolated value in the tooltip based on the two points neigboring your cursor's time position, as shown in Figure 3. The neighboring points used for the interpolation will be indicated by ghosted red triangles. - -
- Aerie UI - Timeline Cursor Value Interpolation Off -
Figure 2: Aerie UI - Timeline Cursor Value Interpolation Off
-
- -
- Aerie UI - Timeline Cursor Value Interpolation On -
Figure 3: Aerie UI - Timeline Cursor Value Interpolation On
-
- -## Timeline Modes - -The default mode of the Aerie timeline is `Interaction` mode which enables you to click on activities and spans. Switching to `Pan and Zoom` mode will disable clicking on activities and spans and instead let you click and drag anywhere on the timeline to pan and scroll using the trackpad or mousewheel to zoom. `Pan and Zoom` mode can also be temporarily entered by holding down the `Command` key on Mac and `Control` key on Windows and then clicking, dragging, and zooming. Finally, while in `Interaction` mode, you can hold down the middle mouse button and drag (if you have one available) to pan. diff --git a/docs/planning/timeline-editing.mdx b/docs/planning/timeline-editing.mdx deleted file mode 100644 index 2e6805cb..00000000 --- a/docs/planning/timeline-editing.mdx +++ /dev/null @@ -1,237 +0,0 @@ -import timelineEditor from './assets/timeline-editor.png'; -import timelineRowEditor from './assets/timeline-row-editor.png'; -import timelineDiscreteOptionsEditor from './assets/timeline-discrete-options-editor.png'; -import timelineActivityOptionsMode from './assets/timeline-activity-options-mode.png'; -import timelineActivityFilteringModal from './assets/timeline-activity-filtering-modal.png'; -import timelineYAxisSettings from './assets/timeline-y-axis-settings.png'; -import timelineActivityTree from './assets/timeline-activity-tree.png'; -import timelineActivityEditingDemo from './assets/timeline-activity-layer-adding.mov'; -import timelineLineLayerEditingDemo from './assets/timeline-line-layer-editing.mov'; - -# Timeline Editing - -Here we describe how to edit the timeline using the [Aerie UI](https://github.com/NASA-AMMOS/aerie-ui). For more details on the full timeline definition please refer to the [UI Views](./ui-views.md#timeline) documentation. - -## Edit Timeline Properties - -Edit general timeline properties from within the timeline editor panel. If multiple timelines exist you can use the top dropdown to edit different timelines. - -:::info Note -Changes made in the Timeline Editor are not automatically saved and must be saved to the UI view. -::: - -
- Aerie UI - Timeline Editor -
Figure 1: Aerie UI Timeline Editor - Manage Timeline properties, Vertical Guides, and Rows
-
- -### Edit Timeline Margins - -Edit the left and right margins of the timeline. This can be useful when you have a row with many resource layer axis labels. - -### Manage Vertical Guides - -Click on the plus button to add a new vertical guide to the timeline. By default vertical guides will appear in the center of the timeline view. vertical guides that you have created directly on the timeline will also appear here. Edit the properties of your vertical guides directly in the list of vertical guides. Click on the trashcan icon to delete a vertical guide. - -### Manage Rows - -Click on the plus button to add a new row to the timeline. A row will be added below the other rows. By default rows will have no layers within them. Click and drag on a row to re-order the row. Click on the trashcan icon to delete a row. - -## Edit a Row - -Edit row properties from within the timeline row editor. If multiple rows exist you can use the top dropdown to edit different rows. A blue highlight will appear around the row in the timeline that you are currently editing. You can also edit a row by clicking on the pencil icon in the header of a row in the timeline. - -
- Aerie UI - Timeline Row Editor -
- Figure 2: Aerie UI Timeline Row Editor - Manage Row properties, Horizontal Guides, Y Axes, and Layers -
-
- -### Add an Activity Layer - -To visualize activities on the timeline, add a new activity layer using the plus button in the Activity Layers section. The default Aerie UI view creates a row with a single activity layer with all activities grouped by type. Manage which activities are visualized in an activity layer by clicking on the Activity Layer filter button within the layer. Activity filtering will be explained in the next section. You can also manage the color of the activities rendered by this layer using the color picker. All activity layers within a single row are drawn on the same canvas so that all of the activities are grouped and packed together correctly. Below is a demonstration of this workflow. - - - -### Filter Activities - -Each activity layer can be configured with a custom filter on activity types and directive/span instance properties. Clicking on the default Activity Layer button on a layer will open the Activity Filtering overlay. This overlay can be repositioned by dragging on the top bar of the overlay. The overlay can also be resized using the resize handle in the bottom right of the overlay. With this overlay you can: - -- Manually specify activity types to include in the layer -- Dynamically specify activity types to include in the layer by type name and/or subsystem -- Apply other filters on top of the manual and dynamic type filters that target specific directives/spans. This includes - filtering by tags, activity name, scheduling goal id, and activity parameter values. These filters can also be applied to specific activity types. - -
- Aerie UI - Activity Filtering -
Figure 3: Aerie UI Activity Filtering
-
- -#### 1. Layer Name - -Change the activity layer name. It is recommended that you name your activity layers when you have multiple activity layers within a row in order to better keep track of what your layers are filtering on. No name is automatically generated from your filters. - -#### 2. Manually Select Types - -Manually select specific activity types to render in the activity layer. This is useful when you want tighter control over a small set of activity types. - -#### 3. Dynamically Select Types - -Dynamically select activity types by type name and/or subsystem. Click on the filter with the plus button to add a new clause. For example, you could create a filter such as `Type includes "banana"` to match all activity types that include the string banana. This matching is case insensitive. Each clause is ANDed together, so if you add another clause such as `Type does not include "grow"` the final statement would be `Type includes "banana" AND does not include "grow"`. Dynamic type filters are stored as clauses within the UI View so that they can be dynamically applied to any model. - -:::info Note -The resulting activity types for a filter are determined using the union of the manual and dynamic types. If no manual or dynamic type filters are added, all activity types are by default included. -::: - -#### 4. Other Activity filters - -Filter activity directive and span instances by tags, activity name, scheduling goal id, and activity parameter values. These filters apply to the set of manually and dynamically selected types (or all types if none specified). These filters behave similarly to dynamic types – each clause is ANDed together. For example, you could create a filter such as `Tag includes (FOO or "Bar") AND Parameter "biteSize" equals 1`. The list of parameters is filtered by the list of resulting activity types. Additionally, parameters are uniquely identified in the filter by the combination of parameter name and parameter type. Parameter filtering works across activity types when the parameter name + type is shared - a common use case when multiple spacecraft are being modeled that share certain parameters. - -##### 5. Resulting Types - -This section shows the list of types resulting from the manually and dynamically selected types. If no manual or dynamic filters have been specified, all types in the model will be included. The indicator to the right of the Resulting Types text shows the number of resulting types followed by the number of "instances" which is the number of directives and spans (where each span does not have a direct parent directive) that match your filters. - -##### 6. Resulting Type Filter - -Add other filters (see 4) to a specific activity type in order to further filter instances of that type. - -### Manage how Activities and Events are Rendered - -A Layer Options section will appear in the row editor when the row has at least one activity or event layer. Layer Options affect how activities and events are rendered for this row. - -
- Aerie UI - Timeline Discrete Options -
Figure 4: Aerie UI Timeline Discrete Options Editor - Manage Activity and Event Rendering
-
- -#### 1. Height - -Sets the height of activities and events in the row. - -#### 2. Display Mode - -Activities and events can be rendered in two ways: - -- `Grouped Mode`: Activities are recursively grouped by type. In this mode, activity groups appear in the left side of the row and can be expanded to reveal the directives and spans within each type group. If a directive/span has descendant, the directive/span can be expanded to reveal its grouped descendants. This behavior continues down the decomposition tree until the resulting directive/span has no descendants. This mode is useful for understanding larger sets of activities in a broad sense across an entire plan, especially if the plan is long or contains a significant number of activities that cannot easily be visualized in a small area. Events render in a similar manner to activities. -- `Compact Mode`: Activities from all of the layers are packed together in a space efficient waterfall manner. This mode is useful for visualizing smaller sets of activities in a more traditional timeline view. In this mode the row will not autosize and must be manually adjusted to accommodate the current activity packing solution. This allows for a more precise adjustment of the density in the timeline. Events render in a similar manner to activities. - -
- Aerie UI - Timeline Activity Display Mode -
Figure 5: Aerie UI Timeline Layer Options Display Mode
-
- -#### 3. Label Visibility - -There are several options to control how activity and event labels appear in the timeline: - -- `On`: Always show labels regardless of collision. -- `Off`: Never show labels. -- `Auto`: Show labels when they do not collide with other labels or rectangles. - -A Layer Options section will appear in the row editor when the row has at least one activity or event layer. Layer Options affect how activities and events are rendered for this row. - -### Activity Specific Options - -#### 4. Directive and Span Visibility - -By default, both directives and spans are shown in a row. Alternatively you may choose to show only directive or only spans. - -#### 5. Hierarchy Root - -_This option is only available in `Grouped` mode._ - -Affects which activities appear in the initial type groups. By default, `Flat` is selected. - -- `Flat`: Both activity directives and spans will appear in the top most type groups. This is useful when examining all activities resulting from a simulation without needing to consider activity decomposition. -- `By Directive`: Only activity directives will appear in the top most type groups, effectively hiding any child spans until further down in the trees. This is useful when examining the timeline from a decomposition perspective. - -### Event Specific Options - -#### 6. Directive and Span Visibility - -_This option is only available in `Grouped` mode._ - -Group events either by event source or by event type. - -### Understanding the Grouped Activity Tree - -
- Aerie UI - Timeline Activity Tree -
Figure 6: Aerie UI Timeline Activity Tree
-
- -In `Grouped` mode the activity tree presented in the row header has several important indicators and actions: - -1. `Filter Activities by Time Window` - Automatically filter nodes in the tree when you zoom and pan around in the plan. -2. This folder represents a group of activities whose type is `BananaNap`. Groups can contain both directives and spans. -3. This icon represents a directive without a span. -4. This icon represents a directive and its root span. The `parent` span has children spans which can be expanded as demonstrated here. -5. Click on this button while hovering over an expandable folder/element in the tree to select that directive/span. In this case, the `parent` row is blue since it has been selected. Note that when selecting a combined directive and span node, the span details will be preferred over the directive details. You can jump between span and directive using a button in the top right of the `Selected Activity` panel. -6. This icon represents a span. In this case, `child` is a span that also has children which are expanded below and grouped appropriately. -7. This node is a span that does not have any children. The node can be selected by clicking directly on the node. Double click to open the `Selected Activity` panel if not already open. - -### Add a Resource Line Layer - -To show resources on the timeline in a line plot, add a new resource layer using the plus button in the Resource Layers section. If the row did not already contain a Y axis, a new one will automatically be created and assigned to the line layer. To select a resource to plot on this layer, click on the input on the layer and select a resource. - -:::info Note -Y axes are independent of layers. When you create a new line or x-range layer the layer will automatically be assigned to a Y axis. To modify which Y axis a layer is associated with, click on the settings icon for the layer and select a Y axis from the Y Axis dropdown. -::: - -Below is a demonstration of this workflow. - - - -### Add a Resource X-Range Layer - -To show resources on the timeline in a line plot, add a new resource layer using the plus button in the Resource Layers section and click on the x-range icon next to the line icon. If the row did not already contain a Y axis, a new one will automatically be created and assigned to the x-range layer. A Y Axis is only used to provide a label to the x-range plot. To select a resource to plot on this layer, click on the input on the layer and select one or more resources. - -:::info Showing X-Range as Line Plot -X-Range plots can be shown as line plots through the corresponding option in the layer settings menu. Use this option when you would like to visualize a discrete resource (Banananation's `producer` resource for example) as a line plot. -::: - -### Manage Y Axis Settings - -Y Axes have additional options available in the settings menu popover. By default, horizontal ticks are shown and Y Axis domain fitting is set to `Autofit Time Window`. The domain fitting option controls how the Y Axis domain behaves in response to your plan and current time window and has three modes: - -1. `Autofit Plan`: Automatically fit the domain to the bounds of all resources associated with the axis across the entire plan. -2. `Autofit Time Window`: Automatically fit the domain to the bounds of all resources associated with the axis, filtered to the current time window. As you zoom and pan through the plan, the bounds will automatically update and the resources will rescale on the canvas. -3. `Manual`: Manually enter a static min and max for the axis. - -
- Aerie UI - Y Axis Settings -
Figure 7: Aerie UI Timeline Y Axis Settings
-
- -### Manage Line and X-Range Names - -By default, line and x-range layers will use the name of the resource associated with them when displayed in the row legend on the left and in the tooltip when hovering over a resource. When available, units will be appended to the resource name. To override this resource name, enter a name for the resource within the layer settings menu. Units will still be appended to your custom name. - -### Manage Horizontal Guides - -Horizontal guides can be added to a row when a row has at least one line or x-range layer. Add a horizontal guide using the plus button in the Horizontal Guides section. Horizontal guides are associated with a single Y axis and will use the first available Y axis by default. You can edit the name, Y value, associated Y axis, and color of each horizontal guide from within this section. - -### Layer Order - -Layers have a fixed draw order in a row. X-range layers are on the bottom, activity layers are in the middle, and line layers are on top. diff --git a/docs/planning/ui-views.md b/docs/planning/ui-views.md deleted file mode 100644 index da5fedc2..00000000 --- a/docs/planning/ui-views.md +++ /dev/null @@ -1,465 +0,0 @@ -# UI Views - -Users can create custom planning views for different sub-systems (e.g. science, engineering, thermal, etc.), where only data (e.g. activities and resources) for those sub-systems are visualized. This is done through custom JSON configuration files (or directly via the UI). The format of a UI View is the subject of this document. - -See the [UI view JSON schema specification](https://github.com/NASA-AMMOS/aerie-ui/blob/develop/src/schemas/ui-view-schema.json) for the complete set of view object properties and types. - -## View Schema - -This is the main type interface for the planning UI view: - -```ts -type ViewActivityTable = { - columnDefs: ColDef[]; - columnStates: ColumnState[]; - id: number; -}; - -type ViewIFrame = { - id: number; - src: string; - title: string; -}; - -type ViewDefinition = { - plan: { - activityTables: ViewActivityTable[]; - iFrames: ViewIFrame[]; - layout: Grid; - timelines: Timeline[]; - }; -}; -``` - -For example, here is a JSON object that implements an empty `ViewDefinition` interface: - -```json -{ - "plan": { - "activityTables": [], - "iFrames": [], - "layout": {}, - "timelines": [] - } -} -``` - -### Layout (Grid) - -A planning UI view consists of a layout which describes the different visible components and how they are arranged in re-sizeable rows and columns. The layout follows the following `Grid` type definitions: - -```ts -type GridComponent = { - activityTableId?: number; - componentName: string; - gridName?: string; - iFrameId?: number; - id: number; - props?: any; - timelineId?: number; - type: 'component'; -}; - -type GridColumns = { - columnSizes: string; - columns: Grid[]; - gridName?: string; - id: number; - type: 'columns'; -}; - -type GridGutter = { - gridName?: string; - id: number; - track: number; - type: 'gutter'; -}; - -type GridRows = { - gridName?: string; - id: number; - rowSizes: string; - rows: Grid[]; - type: 'rows'; -}; - -type Grid = GridColumns | GridComponent | GridGutter | GridRows; -``` - -For example, here is a grid layout definition in JSON: - -```json -{ - "columnSizes": "1fr 3px 2fr 3px 1fr", - "columns": [ - { "componentName": "ActivityFormPanel", "id": 1, "type": "component" }, - { "id": 2, "track": 1, "type": "gutter" }, - { - "id": 3, - "rowSizes": "70% 3px 1fr", - "rows": [ - { - "componentName": "TimelinePanel", - "id": 4, - "timelineId": 0, - "type": "component" - }, - { "id": 5, "track": 1, "type": "gutter" }, - { - "activityTableId": 0, - "componentName": "ActivityTablePanel", - "id": 6, - "type": "component" - } - ], - "type": "rows" - }, - { "id": 7, "track": 3, "type": "gutter" }, - { "componentName": "ActivityTypesPanel", "id": 8, "type": "component" } - ], - "gridName": "Activities", - "id": 0, - "type": "columns" -} -``` - -### Timeline - -The `timelines` section allows you to specify a list of `timeline` visualizations which display time-ordered data (i.e. activities or resources). Here is the interface of a timeline: - -```ts -interface Timeline { - id: number; - marginLeft: number; - marginRight: number; - rows: Row[]; - verticalGuides: VerticalGuide[]; -} -``` - -To visualize data in a timeline you need to add row objects to the `rows` array. A row is a layered visualization of time-ordered data. Each layer of a row is specified as an object of the `layers` array. The types for a `Row`, `Layer`, and `DiscreteOptions` are as follows: - -```ts -type Row = { - autoAdjustHeight: boolean; - discreteOptions: DiscreteOptions; - expanded: boolean; - height: number; - horizontalGuides: HorizontalGuide[]; - id: number; - layers: Layer[]; - name: string; - yAxes: Axis[]; -}; - -type Layer { - chartType: 'activity' | 'line' | 'x-range' | 'external-event'; - filter: { - activity?: ActivityLayerFilter; - externalEvent?: ExternalEventLayerFilter; - resource?: ResourceLayerFilter; - }; - id: number; - name: string; - yAxisId: number | null; -} - -type DiscreteOptions = { - // Activity-Layer-specific Options - activityOptions?: { - // Whether or not to display only directives, only spans, or both in the row - composition: 'directives' | 'spans' | 'both'; - - // If 'directive' the activities are grouped starting with directive types, if 'flat' activities are grouped by type regardless of hierarchy - hierarchyMode: 'directive' | 'flat'; - }; - - // Describes the primary method in which external events are visualized within this row - displayMode: 'grouped' | 'compact'; - - // External-Event-Layer-specific Options - externalEventOptions?: { - // Determines whether to group the External Events by their event type, or their external source - groupBy: 'event_type_name' | 'source_key'; - }; - - // Height of subrows - height: number; - - // Item text label behavior - labelVisibility: 'on' | 'off' | 'auto'; -}; -``` - -Here is a JSON object that creates a single row with one activity layer that renders all activity types. Activity layer filtering and customization is discussed in depth on the [Timeline Editing](./timeline-editing.mdx) page. - -```json -{ - "autoAdjustHeight": true, - "height": 200, - "horizontalGuides": [], - "id": 0, - "layers": [ - { - "activityColor": "#283593", - "chartType": "activity", - "id": 0, - "yAxisId": null - } - ], - "yAxes": [] -} -``` - -For data that has y-values (for example resource data), you can specify a y-axis and link a layer to it by ID. Here are the interfaces for `Axis` and `Label`: - -```ts -interface Axis { - color: string; - id: number; - label: Label; - scaleDomain: (number | null)[]; - tickCount: number | null; -} - -interface Label { - color?: string; - text: string; -} -``` - -Y-axes are specified in the row separately from layers so we can specify multi-way relationships between axes and layers. For example you could have many layers corresponding to a single row axis. - -Here is the JSON for creating a row with two overlaid `resource` layers. The first layer shows only resources with the name `peel`, and uses the y-axis with ID `1`. The second layer shows only resources with the name `fruit`, and uses the y-axis with the ID `2`. - -```json -{ - "autoAdjustHeight": false, - "height": 100, - "horizontalGuides": [], - "id": 1, - "layers": [ - { - "chartType": "line", - "filter": { "resource": { "names": ["peel"] } }, - "id": 1, - "lineColor": "#283593", - "lineWidth": 1, - "pointRadius": 2, - "yAxisId": 1 - }, - { - "chartType": "line", - "filter": { "resource": { "names": ["fruit"] } }, - "id": 2, - "lineColor": "#ffcd69", - "lineWidth": 1, - "pointRadius": 2, - "yAxisId": 2 - } - ], - "yAxes": [ - { - "color": "#000000", - "id": 1, - "label": { "text": "peel" }, - "scaleDomain": [0, 4], - "tickCount": 5 - }, - { - "color": "#000000", - "id": 2, - "label": { "text": "fruit" }, - "scaleDomain": [-10, 4], - "tickCount": 5 - } - ] -} -``` - -### Activity Tables - -Here is an example `activityTables` view JSON definition. The `columnDefs` and `columnStates` follow the schemas of the [ag-grid](https://www.ag-grid.com/) [ColDef](https://github.com/ag-grid/ag-grid/blob/c602622913d6e8600f01f7634d29b2a80a637205/community-modules/core/src/ts/entities/colDef.ts#L112) and [ColumnState](https://github.com/ag-grid/ag-grid/blob/c602622913d6e8600f01f7634d29b2a80a637205/community-modules/core/src/ts/columns/columnModel.ts#L88) respectively. - -```json -{ - "activityTables": [ - { - "columnDefs": [ - { - "field": "id", - "filter": "agTextColumnFilter", - "headerName": "ID", - "sortable": true, - "resizable": true - }, - { - "field": "type", - "filter": "agTextColumnFilter", - "headerName": "Type", - "sortable": true, - "resizable": true - }, - { - "field": "start_time", - "filter": "agTextColumnFilter", - "headerName": "Start Time", - "sortable": true, - "resizable": true - }, - { - "field": "duration", - "filter": "agTextColumnFilter", - "headerName": "Duration", - "sortable": true, - "resizable": true - } - ], - "columnStates": [], - "id": 0 - } - ] -} -``` - -To use the `ActivityTablePanel` you need to add an `ActivityTablePanel` component to the grid layout and connect it via the `activityTableId`. For example: - -```json -{ - "activityTableId": 0, - "componentName": "ActivityTablePanel", - "id": 2, - "type": "component" -} -``` - -Notice how we connect the grid component `activityTableId` with the `id` of the definition in the `activityTables` array. - -### IFrames - -An IFrame component allows you to embed another application in the Aerie UI. Here is an example `iFrames` view JSON definition: - -```json -{ - "iFrames": [ - { - "id": 0, - "src": "https://eyes.nasa.gov/apps/solar-system", - "title": "NASA-Eyes-Solar-System" - } - ] -} -``` - -To use the `IFrame` you need to add an `IFramePanel` component to the grid layout and connect it via the `iFrameId`. For example: - -```json -{ "componentName": "IFramePanel", "iFrameId": 0, "id": 1, "type": "component" } -``` - -Notice how we connect the grid component `iFrameId` with the `id` of the definition in the `iFrames` array. - -## GraphQL Queries - -The following GraphQL queries can be used to programmatically operate on UI views. - -### Create Single View - -```graphql -mutation { - insert_view_one( - object: { - definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } } - name: "My First View" - owner: "system" - } - ) { - id - } -} -``` - -### Create Multiple Views - -```graphql -mutation { - insert_view( - objects: [ - { - definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } } - name: "First View" - owner: "system" - } - { - definition: { plan: { activityTables: [], iFrames: [], layout: {}, timelines: [] } } - name: "Second View" - owner: "system" - } - ] - ) { - returning { - id - } - } -} -``` - -### Get All Views - -```graphql -query { - view { - created_at - definition - id - name - owner - updated_at - } -} -``` - -### Get Single View by ID - -```graphql -query { - view_by_pk(id: 1) { - created_at - definition - id - name - owner - updated_at - } -} -``` - -### Delete Single View by ID - -```graphql -mutation { - delete_view_by_pk(id: 1) { - id - } -} -``` - -### Delete All Views in the Database - -```graphql -mutation { - delete_view(where: {}) { - affected_rows - } -} -``` - -### Update Definition and Name of a Single View by ID - -```graphql -mutation { - update_view_by_pk(pk_columns: { id: 1 }, _set: { definition: { plan: {} }, name: "New Name" }) { - id - } -} -``` diff --git a/docs/planning/upload-mission-model.mdx b/docs/planning/upload-mission-model.mdx deleted file mode 100644 index 8b98228b..00000000 --- a/docs/planning/upload-mission-model.mdx +++ /dev/null @@ -1,19 +0,0 @@ -import uploadMissionModel from './assets/upload-mission-model.webm'; - -# Upload Mission Model - -Here we describe how to upload a mission model to Aerie using the [Aerie UI](https://github.com/NASA-AMMOS/aerie-ui). -This document assumes you already have the Aerie services running somewhere (either locally or remotely). -To get the Aerie services running locally first follow the [fast track instructions](../../introduction#fast-track) and then come back here. - -## Instructions - -1. First develop your mission model using the [mission modeling documentation](../../mission-modeling/introduction). If you want to learn how to develop a mission model, head to our [mission modeling tutorial](../../tutorials/mission-modeling/introduction). If you do not want to develop a mission model and just want to try an example, you can download the [missionmodel.jar](https://github.com/NASA-AMMOS/aerie-modeling-tutorial/blob/main/missionmodel.jar) from the [Aerie modeling tutorial repository](https://github.com/NASA-AMMOS/aerie-modeling-tutorial). Make sure you note where you downloaded the `.jar` file as you will need to browse to that location in the UI. - -1. Navigate to the `/models` page in the Aerie UI. If you are running Aerie locally it is at [http://localhost/models](http://localhost/models). - -1. Use the form on the `/models` page to upload the mission model `.jar` file. Give the mission model a name ("MissionModel" in this example). The version can be any string (preferably a semantic version string like `1.0.0`). After you fill in all the fields click the 'Create' button to upload the model. Here is a video demonstration: - - diff --git a/docs/scheduling-and-constraints/assets/remote-debug-config.png b/docs/scheduling-and-constraints/assets/remote-debug-config.png deleted file mode 100644 index 6e0b1875..00000000 --- a/docs/scheduling-and-constraints/assets/remote-debug-config.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0bcb45792383c3f836e0ad89e2de36197277597e3ba1e2ec19ac08bcd43d6f19 -size 93597 diff --git a/docs/scheduling-and-constraints/assets/run-scheduling-analysis.png b/docs/scheduling-and-constraints/assets/run-scheduling-analysis.png deleted file mode 100644 index 70e786ae..00000000 --- a/docs/scheduling-and-constraints/assets/run-scheduling-analysis.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53c3154f7a4b362badec7b35b510a2e9b21404e00a82c354075caceed1db5f91 -size 9769 diff --git a/docs/scheduling-and-constraints/assets/run-scheduling.png b/docs/scheduling-and-constraints/assets/run-scheduling.png deleted file mode 100644 index ca026457..00000000 --- a/docs/scheduling-and-constraints/assets/run-scheduling.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad5213a8be1c7f901b5105262f39953ef5ddf72d1dc00a983c98733a744dc3af -size 9761 diff --git a/docs/scheduling-and-constraints/assets/scheduling-error.png b/docs/scheduling-and-constraints/assets/scheduling-error.png deleted file mode 100644 index a64bb910..00000000 --- a/docs/scheduling-and-constraints/assets/scheduling-error.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:215608e4c1006e0426b61c224c27098ded8971611f6cfdab084b0d8a42a2c2ee -size 50840 diff --git a/docs/scheduling-and-constraints/assets/scheduling-failed.png b/docs/scheduling-and-constraints/assets/scheduling-failed.png deleted file mode 100644 index 1a0511cf..00000000 --- a/docs/scheduling-and-constraints/assets/scheduling-failed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b4441df094392acf0d75a6c214ef1b9dac8e5d523ebbd8e4dadd47879611fc8 -size 13990 diff --git a/docs/scheduling-and-constraints/assets/scheduling-success.png b/docs/scheduling-and-constraints/assets/scheduling-success.png deleted file mode 100644 index 83929f07..00000000 --- a/docs/scheduling-and-constraints/assets/scheduling-success.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77a90d11627bd8f6e80bc1d58c7e124dcf7f913ca12cdd94335e53c4bcedfa6e -size 4757 diff --git a/docs/scheduling-and-constraints/declarative/constraints/concepts.md b/docs/scheduling-and-constraints/declarative/constraints/concepts.md deleted file mode 100644 index 5011fafe..00000000 --- a/docs/scheduling-and-constraints/declarative/constraints/concepts.md +++ /dev/null @@ -1,35 +0,0 @@ -# Concepts - -The goal of most constraints is to produce a Windows object through operations on profiles and activity instances. This is a conceptual guide to what they represent. - -## Profiles - -Profiles represent functions over time to a specific type, and usually come from mission model resources. For example, a "mission phase" resource might be simulated by the mission model and exposed to the constraints with intervals of time labelled as `cruise`, `edl`, and `surface`; a "battery charge" resource could be represented as a real number between `0` and `1` that varies with activities; etc. These resources can be referenced by calling `Discrete.Resource("mission phase")` or `Real.Resource("battery charge")` in the constraint, allowing you to transform them and do comparisons. - -Real profiles are for integers and floating point numbers, and provide methods for some basic math operations like addition and derivatives. Discrete profiles are for everything else, like strings or objects. - -Profiles can have _gaps_, or intervals where the value is unknown. This comes up most often when dealing with [external datasets](../../../../planning/external-datasets). In most cases it is best to apply a default value to a profile's gaps ASAP using the `profile.assignGaps(defaultValue)` method. - -## Windows - -Windows are like a boolean profile, augmented with some extra functionality. For many constraints, the final result is a `Windows` object, which tells Aerie what times are violations of the constraint. `true` means the state is nominal and `false` means the state is a violation. This means that you should describe the conditions you _want_ to happen, not the conditions you don’t want to happen. - -There are a few ways to calculate a `Windows` object from profiles, and many operations that can be done on them; including the traditional boolean `and`, `or`, and `not`. These are all in the API documentation. Like all profiles, `Windows` can have gaps too. However, some operations (such as converting to Spans or splitting segments) are not possible on gaps, so you'll be required to apply a default value using `windows.assignGaps(boolean)`. - -You can directly return your `Windows` object from the constraint function, and it will be automatically converted in to a `Constraint` object. - -## Spans - -`Spans` are designed to work around a limitation of `Windows`: if two `true` segments of a `Windows` object are transformed so that they touch, they are combined or "coalesced" into a single segment, and the knowledge that they were originally separate is lost. In situations where this is not acceptable (such as when working with activities that can overlap), `Spans` can be used. `Spans` are not a type of profile at all; instead they’re just a collection of intervals on the timeline. They share some operations available for Windows, but not all are valid (such as not). - -## Activity Instances - -Activity instances are accessible through two functions: `Constraint.ForEachActivity` and `Spans.ForEachActivity`. These functions provide you with a reference to each activity of a given type, and allow you to access the activity’s location in time as a `Windows` or `Spans` object, and exposes its parameters as profiles. - -## Other Constraint Types - -Not all constraints are based solely off of a `Windows` object. The main exceptions are constraints that deal with individual activity instances, because these will use the `Constraint.ForEachActivity(…)` function instead of returning a basic `Windows`. This will evaluate your `Windows` expression once for each instance of the given activity type, and associate any violations in those expressions with the activity instance it was evaluated on, which can be helpful in the UI to figure out which activity caused the violation. - -## Mental Model for Evaluation - -A constraint doesn't directly query simulation data, or directly return violations. Instead, your constraint code defines an expression to be interpreted by Aerie. The exact implementation details don’t matter for constraint authors, but for this reason you cannot directly inspect a profile's values or a plan's activities. This is also why there are no plans to support querying external profiles directly from a web request or filesystem access inside the constraint code. For that, see the [external dataset documentation](../../../../planning/external-datasets). diff --git a/docs/scheduling-and-constraints/declarative/constraints/examples.md b/docs/scheduling-and-constraints/declarative/constraints/examples.md deleted file mode 100644 index dcbc8038..00000000 --- a/docs/scheduling-and-constraints/declarative/constraints/examples.md +++ /dev/null @@ -1,91 +0,0 @@ -# Examples - -To define a constraint, you will need to build it up by constructing and transforming objects in the constraint API. To help get you started this document contains a few examples. - -## Accessing Resource Profiles - -Let's start off with a basic constraint that a resource, let's call it `BatteryTemperature`, doesn't exceed some threshold, say `340`. We do so by using `Real.Resource(...)` to get the `BatteryTemperature` resource, and `Real.Value(...)` to get a real number we can compare a real resource profile to: - -```ts -export default (): Constraint => - Real.Resource('BatteryTemperature') // This references a real profile - .lessThanOrEqual(Real.Value(340)); // This transforms it into Windows -``` - -The `Real.Resource(...)` function creates an object that refers to the `BatteryTemperature` real resource profile. The `.lessThanOrEqual(...)` method then expects either another real profile or a number literal as argument. In the above example, we passed it a real profile which has the value 340 for all time. We could instead omit `Real.Value(...)`, and `.lessThanOrEqual(...)` will automatically wrap the 340 literal in `Real.Value(...)` for us: - -```ts -// This is identical to the previous example -export default (): Constraint => Real.Resource('BatteryTemperature').lessThanOrEqual(340); -``` - -The result of `.lessThanOrEqual(...)` is a `Windows` object, representing the time windows when the condition is `true`. - -## Manipulating Windows - -Now we examine a more complex constraint. Let's imagine a solar panel that rotates the panels to a certain angle. Suppose the panels are able to rotate as fast as 5 degrees per second, but are not allowed to go more than 3 degrees per second unless the spacecraft is operating in IDLE mode. For this we will use a real resource, `PanelAngle`, and a discrete resource, `OpMode`. - -Note that this breaks down to two conditions, either of which must be true the entire simulation. This constraint should be satisfied as as either: - -1. The `OpMode` is `"IDLE"` -1. The rate of the `PanelAngle` is no more than 3 degrees per second - -```ts -export default (): Constraint => - Windows.Or( - // This "or"s together any number of Windows objects - Discrete.Resource('OpMode').equal('IDLE'), - Real.Resource('PanelAngle').rate().lessThan(3), - ); -``` - -The API keeps track of the type schemas of all your Discrete and Real value profiles. Real profiles are easy; they are always numbers. The structures of Discrete profiles are defined by the simulation developer. For example, the `OpMode` resource might be defined as an enum of either `"IDLE"` or `"ACTIVE"`. If you tried to use a different value, like `Discrete.Resource("OpMode").equal("BOOGIE")`, it would throw a compile-time type error. - -Much like the previous example, the `.equal("IDLE")` method could instead be `.equal(Discrete.Value("IDLE"))`. The equal method and all such comparison operators operate on Discrete and Real profiles of the same type. If you provide a literal instead of a profile, it will be automatically wrapped in `Discrete.Value(...)` if it is the correct type. - -We also provide a helper function if, which is used when a condition only needs to apply at certain times. `checkTheseWindows.if(onlyTheseWindowsAreTrue)` translates to `Windows.Or(checkTheseWindows, onlyTheseWindowsAreTrue.not())`. So the above example could be rewritten as: - -```ts -export default (): Constraint => - Real.Resource('PanelAngle').rate.lessThan(3).if(Discrete.Resource('OpMode').notEqual('IDLE')); -``` - -## Accessing Activities - -This example of an activity constraint says that whenever an instance of `ActivityTypeA` occurs, the value of `ResourceX` must be less than 10.0. This will be evaluated on every instance of an `ActivityTypeA` activity: - -```ts -export default (): Constraint => - Constraint.ForEachActivity(ActivityType.ActivityTypeA, instance => - Real.Resource('ResourceX').lessThan(10).if(instance.window()), - ); -``` - -For those unfamiliar with Typescript, the `instance => ...` syntax defines an [anonymous function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) which the `Constraint.ForEachActivity(...)` function calls. `instance` is of the type `ActivityInstance`, and can be used to access the instance’s window, start time, end time, and parameters. Unfortunately, in order for `ForEachActivity` to behave correctly in more complex cases, it needs to re-evaluate the condition on the whole plan for each instance separately. This means that we need to manually trim the violation down to the extent of the activity with `.if(instance.window())`. - -## Violations - -Constraint violations contain two sets of information describing where constraints are violated. First, a list of associated activity instance IDs representing the activity instances in violation (this will be an empty list for constraints that don't involve activities). Second, the list of violation windows themselves tells when during the simulation violations occur. - -Constraint violations are reported per activity instance, so it is entirely possible for multiple violations to be produced by a single constraint. This unambiguous representation clearly indicates activity instances that violate a constraint despite the constraint being defined at the type-level. - -Below is a violation with a single activity instance with ID 2 that is in violation from the start of the plan for one hour. Durations are in microseconds: - -```json -{ - "activityInstanceIds": [2], - "windows": [[0, 3600000000]] -} -``` - -Next a constraint is violated for the first and fourth hours of the plan. No activities are involved in this violation: - -```json -{ - "activityInstanceIds": [], - "windows": [ - [0, 3600000000], - [14400000000, 18000000000] - ] -} -``` diff --git a/docs/scheduling-and-constraints/declarative/constraints/introduction.mdx b/docs/scheduling-and-constraints/declarative/constraints/introduction.mdx deleted file mode 100644 index ed1567dd..00000000 --- a/docs/scheduling-and-constraints/declarative/constraints/introduction.mdx +++ /dev/null @@ -1,18 +0,0 @@ -# Constraints - -When analyzing simulation results, it may be useful to detect windows where certain conditions are met. -Constraints are the Aerie tool for fulfilling that role. -A constraint is a condition on activities and resources that must hold through an entire simulation. -If a constraint does not hold true at any point in a simulation, this is considered a violation. - -## Managing - -All constraints are associated with either a mission model or a specific plan. -If associated with a model, a constraint will be applied to all plans made with that model. -If associated with a plan, it will only applied to the plan, and it will have access to any [external datasets](../../../../planning/external-datasets) associated with the plan as well. - -:::tip - -To learn how to add constraints via the Aerie API, see our [API documentation](../../../../api/examples/constraints). - -::: diff --git a/docs/scheduling-and-constraints/declarative/introduction.mdx b/docs/scheduling-and-constraints/declarative/introduction.mdx deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-context-menu.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-context-menu.png deleted file mode 100644 index 75e2c84f..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-context-menu.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:105b0812d65b172f0777d74d0fd3e7d05cf93864d5c0b174fb9aaf59aed9cdcc -size 42720 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-edit.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-edit.png deleted file mode 100644 index 6369d75b..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-edit.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:689eeca9651c3fceedd44fa295d62a631cbe43cc8f5461434faf579cd539e944 -size 75245 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-new.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-new.png deleted file mode 100644 index 6a9d3200..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/goal-editor-new.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3cafd87266edfe7bb94e3e6332e450bbc33d1cac700a5b85f01c0c845fef92b7 -size 33746 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/invalid-recurrence-goal.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/invalid-recurrence-goal.png deleted file mode 100644 index 8b3d4ad7..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/invalid-recurrence-goal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80b0aee306cb36e3accaa9ad967d64efea90c6638a5dc957299fa13d905aa94a -size 59670 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-panel.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-panel.png deleted file mode 100644 index c47b2285..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-panel.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:682e7bfa49770ed44fa16b9ea37250e0cef4b854df5472e297461a044107d47c -size 9538 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-specification.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-specification.png deleted file mode 100644 index b14bbdc8..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/scheduling-specification.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d6d766be18c0efda7ec43868fa806cb3dafeb6fe7ba8e27c5bc58f136047a64c -size 13501 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xbeforey.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xbeforey.png deleted file mode 100644 index 64baf7ea..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xbeforey.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f17617375d9690b1cef2844dbcecfc3c654d322b74dda61927b3e171b9f968bf -size 1884 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xcontainsy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xcontainsy.png deleted file mode 100644 index ed232416..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xcontainsy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f88657bf53ebac1ffcbeeacb95df6465b0eb082b7a704bcfc24fc175c9db6bfd -size 2511 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xequalsy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xequalsy.png deleted file mode 100644 index 75f75262..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xequalsy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abdf8708f9a8dcfcbc3b11729392f4816d589d26ec089baafbc396ea8be37f46 -size 1089 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xfinishesy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xfinishesy.png deleted file mode 100644 index db5007ae..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xfinishesy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:22b4d5c78994e077ee7075bfb4bed7a895dd2973b748ca9e077c6ef5308f01ab -size 1671 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xmeetsy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xmeetsy.png deleted file mode 100644 index 03a455cd..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xmeetsy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d7d097e5cde9330b71ffc8eaef4743471e54930ab988034ef4bf101256f0d359 -size 1124 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xoverlapsy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xoverlapsy.png deleted file mode 100644 index 69c8d35c..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xoverlapsy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e3df2b231324df0c87c5169e87a73b076ac19d2428b36c38b034ec3e93e3ee6 -size 1963 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/assets/xstartsy.png b/docs/scheduling-and-constraints/declarative/scheduling/assets/xstartsy.png deleted file mode 100644 index b7f01110..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/assets/xstartsy.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:efdb5b796127b19bf04ee0a23c7ade25362ebf83f9aab5869624efd54855d85e -size 1771 diff --git a/docs/scheduling-and-constraints/declarative/scheduling/global-conditions.mdx b/docs/scheduling-and-constraints/declarative/scheduling/global-conditions.mdx deleted file mode 100644 index a285f93b..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/global-conditions.mdx +++ /dev/null @@ -1,72 +0,0 @@ -# Global Conditions - -It is possible to restrict the scheduler from placing activities when certain conditions are not met. -A global scheduling conditions is defined in the TypeScript EDSL (just like a Scheduling Goal), but the return type is expected to be of type `GlobalSchedulingCondition`. -These conditions, when activated, apply for **all goals**, hence the global qualification. There are several types of global scheduling conditions available described in the sections below. - -## Global Condition Types - -### Schedule Activities Only When - -This global condition restricts when _any_ activity type can be scheduled. -This condition takes an expression of type `Windows` and prevents the scheduler from inserting any activity outside the time intervals produced by the expression when evaluated. - -#### Examples - -In this example, activities of any type can only be placed whenever `/plant` is lower than `10.0`: - -```ts -export default function scheduleActivitiesOnlyWhenExample(): GlobalSchedulingCondition { - return GlobalSchedulingCondition.scheduleActivitiesOnlyWhen(Real.Resource('/plant').lessThan(10.0)); -} -``` - -### Schedule Only When - -This global condition restricts when _some_ activity types can be scheduled. -This condition takes a list of activity types and an expression of type `Windows`. -It prevents the scheduler from inserting activity of the given activity types outside the time intervals produced by the expression when evaluated. - -#### Examples - -In this example, activities of type `BananaNap` and `BiteBanana` can be placed only when `/plant` is greater than `10.0`: - -```ts -export default function scheduleOnlyWhenExample(): GlobalSchedulingCondition { - return GlobalSchedulingCondition.scheduleOnlyWhen( - [ActivityType.BananaNap, ActivityType.BiteBanana], - Real.Resource('/plant').greaterThan(10.0), - ); -} -``` - -### Mutex - -This global condition prevents activities from overlapping ([mutual exclusion](https://en.wikipedia.org/wiki/Mutual_exclusivity)). -This condition takes two lists of activity types as arguments. -It prevents the scheduler from inserting activities of the types from the first list to overlap with activities of type from the second list (and vice versa). - -#### Examples - -In this example, activities of types `BananaNap` and `BiteBanana` will not be allowed to overlap with activities of types `ChangeProduced` and `GrowBanana`. -But two activities of type `GrowBanana` can still overlap. And a `BananaNap` can still overlap with a `BiteBanana`. - -```ts -export default function firstMutexExample(): GlobalSchedulingCondition { - return GlobalSchedulingCondition.mutex( - [ActivityType.BananaNap, ActivityType.BiteBanana], - [ActivityType.ChangeProducer, ActivityType.GrowBanana], - ); -} -``` - -By default, two activities from the same type can overlap. -To restrict activities of a given type from overlapping with each other, it is necessary to explicitly write a mutex condition such as the following one: - -```ts -export default function secondMutexExample(): GlobalSchedulingCondition { - return GlobalSchedulingCondition.mutex([ActivityType.BiteBanana], [ActivityType.BiteBanana]); -} -``` - -This condition will prevent `BiteBanana` from overlapping with each other. diff --git a/docs/scheduling-and-constraints/declarative/scheduling/goals.mdx b/docs/scheduling-and-constraints/declarative/scheduling/goals.mdx deleted file mode 100644 index a32bcefe..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/goals.mdx +++ /dev/null @@ -1,651 +0,0 @@ -# Goals - -This document describes different scheduling goals available in Aerie and how to author them using the TypeScript EDSL. - -:::caution - -Activities with [uncontrollable durations](../../../../mission-modeling/activity-types/parameters#duration-types) have been found to behave somewhat unpredictably in terms of when they are placed. -This has to do with how temporal constraints interact with the unpredictability of the durations. -Finding when an activity will start while subject to temporal constraint involves search. - -::: - -## ActivityTemplate and ActivityExpression - -An `ActivityExpression` allows to search for activities in the plan. An activity expression must have an activity type and may have a subset or all of the parameter of the activity type. - -```ts -ActivityExpression.ofType(ActivityTypes.ParamActivity); -``` - -In this case, all the activities of type `ParamActivity` will be matched (disregarding its parameters) and potentially used for satisfying the goal. - -```ts -ActivityExpression.build(ActivityTypes.ParamActivity, { param: 1 }); -``` - -In this case, all the activities of type `ParamActivity` with parameter `param` equal to 1 will be matched and used for satisfying the goal. To do this, you must use the `.build` -function instead of `.ofType`. - -An optional `ActivityExpression` can be passed with the `activityFinder` parameter of goals. See below for examples of its use in goals. - -An `ActivityTemplate` specifies the type of an activity, as well as the arguments it should be given. -Activity templates are generated for each mission model. -You can get the full list of activity templates by typing `ActivityTemplates.` (note the period) into the scheduling goal editor, and viewing the auto-complete options. - -If an `activityFinder` is not passed to a goal, the mandatory activity template passed with a goal (as the `activityTemplate` parameter) will be used for searching activities in the plan that satisfies the template. The template will also be used for creating activities to satisfy this goal. - -If the activity has parameters, pass them into the constructor in a dictionary as key-value pairs. For example: - -```ts -ActivityTemplate.ParamActivity({ param: 1 }); -``` - -If the activity has no parameters, do not pass a dictionary. For example: - -```ts -ActivityTemplate.ParameterlessActivity(); -``` - -The value of a parameter can also be a profile (see [windows](../../constraints/concepts)). This capability is currently restricted to the use of a Coexistence Goal, see [Coexistence Goal](#coexistence-goal) for more details on how to write activity templates in this case. - -## Goal Types - -### Activity Recurrence Goal - -The Activity Recurrence Goal (sometimes referred to as a "frequency goal") specifies that a certain activity should occur repeatedly throughout the plan at some given interval. - -#### Inputs - -- `activityTemplate` - The description of the activity whose recurrence we're interested in -- `interval` - A [Temporal.Duration](https://tc39.es/proposal-temporal/docs/duration.html) of time specifying how often this activity must occur -- `activityFinder` - an optional activity expression. If present, it will be used as replacement of `activityTemplate` to match against existing activities in the plan. - -#### Behavior - -The `interval` parameter is treated as a lower bound - so if the activity occurs more frequently, that is not considered a failure. -The scheduler will find places in the plan where the given activity has not occurred within the given interval, and it will place an instance of that activity there. - -:::note - -The interval is measured between the start times of two activity instances. Neither the duration, nor the end time of the activity are examined by this goal. - -::: - -#### Examples - -The following goal will place a `GrowBanana` activity in every 2-hour period of time that does not already contain one with the exact same parameters. - -```ts -export default function recurrenceGoalExample() { - return Goal.ActivityRecurrenceGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - growingDuration: Temporal.Duration.from({ hours: 1 }), - quantity: 1, - }), - interval: Temporal.Duration.from({ hours: 2 }), - }); -} -``` - -In the following goal, an activity finder is used to match against existing activities in the plan that would satisfy the goal. Here, any GrowBanana activity with a `growingDuration` parameter equal to `Temporal.Duration.from({hours : 1})` would match, disregarding the value of the other parameter `quantity`. - -#### With an activityFinder - -```ts -export default function myGoal() { - return Goal.ActivityRecurrenceGoal({ - activityFinder: ActivityExpression.build(ActivityTypes.GrowBanana, { - growingDuration: Temporal.Duration.from({ hours: 1 }), - }), - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ hours: 1 }), - }), - interval: Temporal.Duration.from({ hours: 2 }), - }); -} -``` - -### Coexistence Goal - -The Coexistence Goal specifies that a certain activity should occur once **for each** occurrence of some condition. - -#### Inputs - -- `forEach` - A set of time `Windows`, `Intervals` or `Instants`, or a set of activities (`ActivityExpression`) -- `activityTemplate` - The description of the activity to insert after each activity identified by `forEach`. This can be an `ActivityTemplate` object or an `ActivityTemplate` factory function with one argument of either `ActivityInstance` or `Interval`, depending on if `forEach` was an `ActivityExpression` or `Windows`, respectively. This allows to define the content of the `ActivityTemplate` with components of the anchor activity or window. -- `activityFinder` - an optional activity expression. If present, it will be used as replacement of `activityTemplate` to match against existing activities in the plan. -- `startsAt` - Optionally specify a specific time when the activity should start relative to the window -- `startsWithin` - Optionally specify a range when the activity should start relative to the window -- `endsAt` - Optionally specify a specific time when the activity should end relative to the window -- `endsWithin` - Optionally specify a range when the activity should end relative to the window - -:::note - -Either the start or end of the activity must be constrained. -This means that at least 1 of the 4 properties `startsAt`, `startsWithin`, `endsAt`, `endsWithin` must be given. - -::: - -#### Behavior - -The scheduler will find places in the plan where the `forEach` condition is true, and if not, it will insert a new instance using the given `activityTemplate` and temporal constraints. - -#### Examples - -The following example specifies a `CoexistenceGoal` where for each activity "A" of type `GrowBanana` present in the plan, place an activity of type `PeelBanana` starting exactly at the end of "A" + 5 minutes: - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - startsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -This next example specifies a `CoexistenceGoal` where for each activity "A" of type `GrowBanana` present in the plan, place an activity of type `PeelBanana` starting in the interval [end of "A", end of "A" + 5 minutes] and ending in the interval [end of "A", end of "A" + 6 minutes]: - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - startsWithin: TimingConstraint.range(WindowProperty.END, Operator.PLUS, Temporal.Duration.from({ minutes: 5 })), - endsWithin: TimingConstraint.range(WindowProperty.END, Operator.PLUS, Temporal.Duration.from({ minutes: 6 })), - }); -``` - -This example specifies a `CoexistenceGoal` where for each continuous period of time during which the `/fruit` resource is equal to `4`, place an activity of type `PeelBanana` ending exactly at the end of "A" + 6 minutes: - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: Real.Resource('/fruit').equal(4.0), - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - endsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -Note that the scheduler will allow a default timing error of 500 milliseconds for temporal constraints. -This parameter will be configurable in an upcoming release. - -In this example, we use an activity template factory to use the value of one of the parameter of the anchor activity inside the activity template of the activity to create. -Let's imagine that the base plan would contain A and B of type `GrowBanana` and that `A.quantity = 3` and `B.quantity = 4`. After applying this goal, two new activities of type `PickBanana`, `C` (with `A` for anchor) and `D` (with `B` for anchor) would be created -and their parameters would be `C.quantity = 3` and `D.quantity = 4`. - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: growBananaActivity => - ActivityTemplates.PickBanana({ quantity: growBananaActivity.parameters.quantity }), - startsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -If the `forEach` field is a `Windows` object, we can also make a factory that references each individual `Interval` in the `Windows`: - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: Real.Resource('/fruit').equal(4.0), - activityTemplate: interval => - ActivityTemplates.GrowBanana({ - growingDuration: interval.duration(), - quantity: 1, - }), - startsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -You may want to access the value of a resource at a defined timepoint in the activity template. For that, you can use the `valueAt` operator on `Real` and `Discrete` classes. For now, this operation takes a `Spans` object as argument (see [concepts](../../constraints/concepts). This span should be reduced to a single timepoint for this to work. This is only possible -by referencing the span of an activity such as in the following example : - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: growBananaActivity => - ActivityTemplates.ChangeProducer({ - producer: Discrete.Resource('/producer').valueAt(growBananaActivity.span().starts()), - }), - startsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -In this example the value of the `/producer` resource is taken at the beginning of the anchor activity and assigned to the `producer` parameter. - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: Real.Resource('/fruit').equal(4.0), - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - activityFinder: ActivityExpression.ofType(ActivityTypes.PeelBanana), - endsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }); -``` - -In these final two examples, the `forEach` condition defines a time `Instant` and `Interval` respectively at which the goal has to create an instance of the activity template. - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: Temporal.Instant.from('2021-01-01T05:00:00.000Z'), - activityTemplate: span => ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - startsAt: TimingConstraint.singleton(WindowProperty.START), - }); -``` - -```ts -export default () => - Goal.CoexistenceGoal({ - forEach: Interval.Between( - Temporal.Instant.from('2021-01-01T05:00:00.000Z'), - Temporal.Instant.from('2021-01-01T10:00:00.000Z'), - Inclusivity.Inclusive, - Inclusivity.Exclusive, - ), - activityTemplate: span => ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - startsAt: TimingConstraint.singleton(WindowProperty.START), - }); -``` - -Behavior: The activity finder is being used to match against any existing PeelBanana activity in the plan, disregarding the value of its parameters. - -:::caution - -If the end is unconstrained while the activity has an [uncontrollable duration](../../../../mission-modeling/activity-types/parameters#duration-types), the scheduler may fail to place the activity. -To work around this, add an `endsWithin` constraint that encompasses your expectation for the duration of the activity - this will help the scheduler narrow the search space. - -::: - -### Cardinality Goal - -The Cardinality Goal specifies that a certain activity should occur in the plan either a certain number of times, or for a certain total duration. - -#### Inputs - -- `activityTemplate` - The description of the activity whose recurrence we're interested in -- `specification` - An object with either an occurrence field, a duration field, or both (see examples below) -- `activityFinder` - an optional activity expression. If present, it will be used as replacement of `activityTemplate` to match against existing activities in the plan. - -#### Behavior - -The duration and occurrence are treated as lower bounds - so if the activity occurs more times, or for a longer duration, that is not considered a failure, and the scheduler will not add any more activities. - -The scheduler will identify whether or not the plan has enough occurrences, or total duration of the given activity template. -If not, it will add activities until satisfaction. - -#### Examples - -The following example is a `CardinalityGoal` that sets a lower bound on the total duration: - -```ts -export default function cardinalityGoalExample() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ seconds: 1 }), - }), - specification: { duration: Temporal.Duration.from({ seconds: 10 }) }, - }); -} -``` - -This next example specifies a `CardinalityGoal` that sets the lower bound on the number of occurrences: - -```ts -export default function cardinalityGoalExample() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ seconds: 1 }), - }), - specification: { occurrence: 10 }, - }); -} -``` - -Finally we combine the previous two examples: - -```ts -export default function cardinalityGoalExample() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ seconds: 1 }), - }), - specification: { occurrence: 10, duration: Temporal.Duration.from({ seconds: 10 }) }, - }); -} -``` - -With an activity finder: - -```ts -export default function myGoal() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ seconds: 1 }), - }), - activityFinder: ActivityExpression.build(ActivityTypes.GrowBanana, { quantity: 1 }), - specification: { occurrence: 10, duration: Temporal.Duration.from({ seconds: 10 }) }, - }); -} -``` - -In this last example, an activity finder is used to match against existing activities in the plan that would satisfy the goal. Here, any GrowBanana activity with a `quantity` parameter equal to 1 would match, disregarding the value of the other parameter `growingDuration`. - -:::note - -Make sure to specify the proper mutual exclusion constraint as [global scheduling conditions](../global-conditions) - namely that new activities will not be allowed to overlap with existing activities. Otherwise, the cardinality goal may stack activities at one spot in the plan. There is no default constraint in place. - -::: - -### OR Goal - Disjunction of Goals - -The OR Goal aggregates several goals together and specifies that at least one of them must be satisfied. - -#### Inputs - -- `goals` - A list of goals (here below referenced as the sub-goals) - -#### Behavior - -The scheduler will try to satisfy each sub-goal in the list until one is satisfied. -If a sub-goal is only partially satisfied, the scheduler will not backtrack and will let the inserted activities in the plan. - -#### Examples - -The following example shows how to use the `.or` operator on a pair of goals. -If the plan has a 24-hour planning horizon, the `OR` goal below will try placing activities of the `GrowBanana` type. -The first sub-goal will try placing 10 1-hour occurrences. -If it fails to do so, because the planning horizon is too short, it will then try to schedule 1 activity every 2 hours for the duration of the planning horizon. - -It may fail to achieve both sub-goals but as the scheduler does not backtrack for now, activities inserted by any of the sub-goals are kept in the plan. - -```ts -export default function orGoalExample() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ hours: 1 }), - }), - specification: { occurrence: 10 }, - }).or( - Goal.ActivityRecurrenceGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ hours: 1 }), - }), - interval: Temporal.Duration.from({ hours: 2 }), - }), - ); -} -``` - -### AND Goal - Conjunction of Goals - -The AND Goal aggregates several goals together and specifies that all of them must be satisfied. - -#### Inputs - -- `goals` - A list of goals (here below referenced as the sub-goals) - -#### Behavior - -The scheduler will try to satisfy each sub-goal in the list. -If a sub-goal is only partially satisfied, the scheduler will not backtrack and will let the inserted activities in the plan. -If all the sub-goals are satisfied, the AND goal will appear satisfied. -If one or several sub-goals have not been satisfied, the AND goal will appear unsatisfied. - -#### Examples - -The AND goal below has two sub-goals. -The `CoexistenceGoal` will place activities of type `PeelBanana` every time the `/fruit` resource is equal to 4. -The second `CardinalityGoal` will place 10 occurrences of the `PeelBanana` activity. - -The first sub-goal will be evaluated first and will place a certain number of `PeelBanana` activities in the plan. -When the second goal is evaluated, it will count already present `PeelBanana` activities and insert the missing number. - -Imagine the first goals leads to inserting 2 activities. -The second goal will then have to place 8 activities to be satisfied. - -```ts -export default function andGoalExample() { - return Goal.CoexistenceGoal({ - forEach: Real.Resource('/fruit').equal(4.0), - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - endsAt: TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes: 5 })), - }).and( - Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.PeelBanana({ peelDirection: 'fromStem' }), - specification: { occurrence: 10 }, - }), - ); -} -``` - -## Accessing Activity Presets - -When creating an `ActivityTemplate`, you can access all the activity presets that planners can, on the `ActivityPresets` object. For example, -if you have a preset of the `BiteBanana` activity called "big bite", you can access it like this: - -```ts -export default (): Goal => { - return Goal.ActivityRecurrenceGoal({ - activityTemplate: ActivityTemplates.BiteBanana(ActivityPresets.BiteBanana["big bite"]), - interval: ... - }); -} -``` - -`ActivityPresets.BiteBanana["big bite"]` is a concrete object containing the actual values of the preset, not a lazily-evaluated expression. This means it is easy for you to access and override specific values like this: - -```ts -let biteArgs = ActivityPresets.BiteBanana['large bite']; -biteArgs.biteSize += 10; // 10 bigger than the preset - -// use ActivityTemplates.BiteBanana(biteArgs) -``` - -## Restricting When a Goal is Applied - -By default, a goal applies on the whole planning horizon. -The Aerie scheduler provides support for restricting when a goal applies with the `.applyWhen()` method in the `Goal` class. -This node allows users to provide a set of `Windows` which could be a time-based, or a resource-based window. - -The `.applyWhen()` method, takes one argument: the windows (in the form of an expression) that the goal should apply over. -Below is an example that applies a daily recurrence goal only when a given resource is greater than 2. -If the resource is less than two, then the goal is no longer applied. - -```ts -export default function applyWhenExample() { - return Goal.ActivityRecurrenceGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ hours: 1 }), - }), - interval: Temporal.Duration.from({ hours: 2 }), - }).applyWhen(Real.Resource('/fruit').greaterThan(2)); -} -``` - -Note that when using this feature with a `CardinalityGoal`, the counters for duration and numbers of occurrence will reset for each window of the set (as `Windows` is a set of non-overlapping intervals). In other words, the goal will be applied multiple times, one for each window. - -## Goal Boundary Exclusivity - -Note if you are trying to schedule an activity, or a recurrence within a window but that window cuts off either the activity or the recurrence interval (depending on the goal type), it will not be scheduled. -For example, if you had a recurrence interval of 3 seconds, scheduling a 2 second activity each recurrence, and had the following window, you'd get the following: - -```txt -Recurrence Interval: [++-++-++-] -Goal Window: [+++++----] -Result: [++-------] -``` - -That, is, the second activity won't be scheduled as the goal window cuts off its recurrence interval. -Scheduling is _local_, not global. This means for every window that is matched (as it is possible to have disjoint windows, imagine a resource that fluctuates upward and downward but only applying that goal when the resource is over a certain value), the goal is applied individually. -So for that same recurrence interval setup as before, we could have: - -```txt -Recurrence Interval: [++-++-++-++-] -Goal Window: [+++++--+++--] -Result: [++-----++---] // (the second one is applied independently of the first!) -``` - -When mapping out a temporal window to apply a goal over, keep in mind that the ending boundary of the goal is exclusive, i.e. if I want to apply a goal in the window of 10-12 seconds, it will apply only on seconds 10 and 11. -This is in line with the [fencepost problem](https://en.wikipedia.org/wiki/Off-by-one_error#Fencepost_error). - -## Satisfaction of a Goal - Backtracking - -The scheduler will take each goal and try to satisfy it. In terms of satisfaction, the two extreme cases are - -- A goal cannot be satisfied at all, no activity could be found or could be inserted. -- A goal is totally satisfied. - -But sometimes, a goal is only partially satisfied. In other words, only a subset of the activities that would have been necessary for total satisfaction could have been inserted in the plan. In this case, there are two strategies that the scheduler can take: - -- Either adopt a best-effort approach: mark the goal unsatisfied but let activities partially satisfying it in the plan. These activities consume resources and time but as the goals are evaluated in a decreasing priority manner, the completion of the current goal is more important than completion of the goals that have not been evaluated yet. -- Or adopt a all-or-nothing approach: mark the goal unsatisfied and remove activities inserted in the plan specifically for satisfying the goal. - -In both cases, the goal is marked as unsatisfied. - -You can control this behavior with a setter function of the `Goal` class, the `backtrackIfUnsatisfied(boolean)`. If the argument of this function is set to `false`, the scheduler will adopt the best-effort approach for this goal. -If the argument of this function is set to `true`, the scheduler will adopt the all-or-nothing approach for this goal. The best-effort behavior is the default for all goals. - -Let's take one of the cardinality goal example and add this: - -```ts -export default function cardinalityGoalExample() { - return Goal.CardinalityGoal({ - activityTemplate: ActivityTemplates.GrowBanana({ - quantity: 1, - growingDuration: Temporal.Duration.from({ seconds: 1 }), - }), - specification: { occurrence: 10 }, - }).backtrackIfUnsatisfied(true); -} -``` - -## Creating a New Goal - -This section describes how to create a new scheduling goal via the Aerie UI. In the UI, open the scheduling pane by clicking on the top-right bar. - -import schedulingPanel from './assets/scheduling-panel.png'; - -
- Aerie UI - Scheduling Panel -
Figure 1: Aerie UI Scheduling Panel
-
- -Next click "New" to create a new scheduling goal in the specification for the plan you are viewing. This will open the goal editor. - -import goalEditorNew from './assets/goal-editor-new.png'; - -
- Aerie UI - Scheduling Goal Editor - New Goal -
Figure 2: Scheduling Goal Editor - New Goal
-
- -The default new goal shown in the editor is the following: - -```ts -export default (): Goal => { - // Your code here... -}; -``` - -This is a [TypeScript](https://www.typescriptlang.org/) function that takes no arguments and returns a Goal. To unpack all of the parts: - -- `export default` signals to Aerie that this is the function that defines the Goal. -- `() => {}` in TypeScript is called an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). -- The parenthesis `()` represent the parameters that the function takes. Scheduling goals cannot take any parameters, so these parenthesis must be empty. -- The curly braces `{}` represent the definition of the goal. The return statement for the function must go inside the braces. -- The `: Goal` part signifies that this function returns a Goal. TypeScript will check that the function does indeed return a Goal - if it does not, it will underline your code in red. - -In the initial code provided in the new goal editor the function does not yet return anything, so you should see the word Goal underlined in red (see Figure 2 above). - -Mousing over the word Goal, you should see something akin to the following message: - -```text -A function whose declared type is neither 'void' nor 'any' must return a value. -``` - -This message means that the function has promised to return a value, but it currently lacks a return statement. Between the curly braces, add the following code: - -```ts -export default (): Goal => { - return Goal.ActivityRecurrenceGoal(); -}; -``` - -The editor should tell you that `ActivityRecurrenceGoal()` takes one argument: - -import invalidRecurrenceGoal from './assets/invalid-recurrence-goal.png'; - -
- Aerie UI - Invalid Recurrence Goal -
Figure 3: Invalid Recurrence Goal
-
- -The argument that we’re missing is the "options" object. Objects in typescript are defined using curly braces `{}` with key-value pairs, like so: - -```ts -{ - key: value; -} -``` - -If we pass an empty object `{}` to `ActivityRecurrenceGoal`: - -```ts -export default (): Goal => { - return Goal.ActivityRecurrenceGoal({}); // The empty object is written as {} -}; -``` - -We get an error message that tells us what keys our object needs: - -```text -Argument of type '{}' is not assignable to parameter of type '{ activityTemplate: ActivityTemplate; interval: number; }'. -Type '{}' is missing the following properties from type '{ activityTemplate: ActivityTemplate; interval: number;}': activityTemplate, interval -``` - -This error message tells us that our object is missing two keys: `activityTemplate`, and `interval`. If we look up the definition of [ActivityRecurrenceGoal](#activity-recurrence-goal), we see that it does indeed need an activity template and an interval. Let’s add those: - -```ts -export default (): Goal => { - return Goal.ActivityRecurrenceGoal({ - activityTemplate: null, - interval: Temporal.Duration.from({ hours: 24 }), - }); -}; -``` - -Now, we just need to finish specifying the `activityTemplate`. Start by typing `ActivityTemplates.` (note the period), and select an activity type. Provide your activity an object with the arguments that that activity takes. Once the editor is no longer underlining your code, save your goal by clicking on "Save". - -## Deleting or Editing a Goal - -To delete or edit a scheduling goal via the UI, open the scheduling pane and right-click on the goal you want to delete or edit: - -import goalContextMenu from './assets/goal-context-menu.png'; - -
- Aerie UI - Goal Context Menu -
Figure 4: Goal Context Menu
-
- -Click on "Delete Goal" to delete the goal. If you click on "Edit Goal" it will open a new tab with an editor: - -import goalEditorEdit from './assets/goal-editor-edit.png'; - -
- Aerie UI - Scheduling Goal Editor - Edit Goal -
Figure 5: Scheduling Goal Editor - Edit Goal
-
- -When you are done with editing the goal, click on "Save". Your goal is saved, and you can close the tab or continue working. diff --git a/docs/scheduling-and-constraints/declarative/scheduling/introduction.mdx b/docs/scheduling-and-constraints/declarative/scheduling/introduction.mdx deleted file mode 100644 index 70a7e1f8..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/introduction.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# Scheduling - -This guide explains how to use the declarative scheduling service with Aerie. -The scheduling service allows you to add activities to a plan based on goals that you define (this is something called "goal based scheduling"). -Declarative Goals are defined in [TypeScript](https://www.typescriptlang.org/) using an embedded domain specific language (EDSL) provided by Aerie. - -:::note - -This guide assumes some very basic familiarity with programming (terminology like functions, arguments, return values, types, and objects, will not be explained), but it does not assume the reader has seen TypeScript or JavaScript before. - -::: diff --git a/docs/scheduling-and-constraints/declarative/scheduling/modelling-temporal-relations.mdx b/docs/scheduling-and-constraints/declarative/scheduling/modelling-temporal-relations.mdx deleted file mode 100644 index 4475ae1e..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/modelling-temporal-relations.mdx +++ /dev/null @@ -1,140 +0,0 @@ -import xbeforey from './assets/xbeforey.png'; -import xequalsy from './assets/xequalsy.png'; -import xmeetsy from './assets/xmeetsy.png'; -import xoverlapsy from './assets/xoverlapsy.png'; -import xcontainsy from './assets/xcontainsy.png'; -import xstartsy from './assets/xstartsy.png'; - -# Modelling Temporal Relations with Coexistence Goal -In mission modeling and planning for space systems, having the capability to establish temporal dependencies between events or activities is crucial. -Aerie includes the capability to model flexible temporal relations through the use of `Coexistence Goals`. - -A Coexistence Goal can be used to define a causality relation between two activities: the first activity is a directive that should be already included in the plan, -while the second is an activity type to be added to the plan under certain conditions. Both activities can be instantaneous or have a duration, and can be related temporally -in several ways. The [Allen’s Temporal Relations](https://en.wikipedia.org/wiki/Allen%27s_interval_algebra) describe 14 different ways in which two time intervals relate to each other. -Many of these relations can be easily generalized to relations between timepoints for instantaneous activities. - -The following sections introduce the syntax to represent temporal relations in Aerie and provide a number of examples. - -## Flexible Time Intervals in eDSL -Consider a time interval as a time range defined by two timepoints that specify the lower and upper bounds of the interval. For example, `[lb, ub]` represents a closed time interval bounded by the timepoints `lb` and `ub`. -We define a flexible interval as a time range where its timepoints are specified relative to another timepoint. For example, considering `ae` as the timepoint at which an activity `a` ends, we can define a time interval relative to it -as follows: `[ae + x, ae + y]` where `x` and `y` represents the delta lower and upper bounds with respect to`a`'s end. - -Flexible Time Intervals are represented in Aerie by means of `TimingConstraint.bounds`, which are composed of two `TimingConstraint.singletons` to model the lower and upper bound. -For example, -```ts -TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 5})), TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 10}))) -``` -represents a time interval that ranges between 5 and 10 units of time after the start timepoint of a `Window` (`WindowProperty.START`). - -`bounds` represent the core expression to model Allen relations. - -## Allen's Temporal Relations -The following paragraphs provide examples for 7 Allen relations, while the other 7 are the opposite relation (e.g. Starts vs Started_by). -The examples are based on the Banananation scenario. GrowBanana and DurationParameterActivity are used as examples of durative activities while peelBanana is used whenever instantaneous activities are applicable. - -### Before -The relation `x BEFORE[lb, ub] y` indicates that y must start between `[lb, ub]` units of time after x completes. Both activities can be instantaneous or durative. -In the example below, peelBanana starts between `[5, 10]` units of time after GrowBanana finishes. - -
- Allen Relation BEFORE -
Figure 1: Relation x BEFORE [lb, ub] y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), - startsWithin: TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes : 5})), TimingConstraint.singleton(WindowProperty.END).plus(Temporal.Duration.from({ minutes : 10}))) - }) -``` - -### Equals -The relation `x EQUALS y` indicates that y starts and finishes at the same time as x. Both activities must be durative. The timepont variant would need to consider the start value. -In the example below, DurationParameterActivity starts and finishes at the same time as GrowBanana. - -
- Allen Relation EQUALS -
Figure 2: Relation x EQUALS y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ hours : 1})}), - startsAt: TimingConstraint.singleton(WindowProperty.START), - endsAt: TimingConstraint.singleton(WindowProperty.END) - }) -``` - -### Meets -The relation `x MEETS y` indicates that y starts right after the time x finishes. x must be a durative activity while y could be durative or instantaneous. -In the example below, peelBanana takes place at the time when GrowBanana finishes. - -
- Allen Relation MEETS -
Figure 3: Relation x MEETS y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), - startsAt: TimingConstraint.singleton(WindowProperty.END) - }) -``` - -### Overlaps -The relation `x OVERLAPS[lb,ub] y` indicates that y start time overlaps x end time by an amount of time between `lb` and `ub`. Both activities must be durative. -In the example below, DurationParameterActivity starts between [5,10] units of time before GrowBanana finishes. - -
- Allen Relation OVERLAPS -
Figure 4: Relation x OVERLAPS y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ hours : 1})}), - startsWithin: TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.END).minus(Temporal.Duration.from({ minutes : 10})), TimingConstraint.singleton(WindowProperty.END).minus(Temporal.Duration.from({minutes : 5}))) - }) -``` - -### Contains -The relation `x CONTAINS[lb1,ub1] [lb2, ub2] y` indicates that y starts between `[lb1,ub1]` units of time after x does, and it ends between `[lb2,ub2]` before x does. Both activities must be durative. -In the example below, DurationParameterActivity starts between `[5,10]` units of time after GrowBanana starts and finishes between `[5,10]` units of time before GrowBanana does. - - -
- Allen Relation CONTAINS -
Figure 5: Relation x CONTAINS y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.DurationParameterActivity({duration: Temporal.Duration.from({ minutes : 50})}), - startsWithin: TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 5})), TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({minutes : 10}))), - endsWithin: TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.END).minus(Temporal.Duration.from({ minutes : 10})), TimingConstraint.singleton(WindowProperty.END).minus(Temporal.Duration.from({ minutes : 5}))) - }) -``` - -### Starts -The relation `x STARTS[lb,ub] y` indicates that y starts between `[lb,ub]` after x does. x must be durative. -In the example below, DurationParameterActivity starts between `[5,10]` units of time after GrowBanana does. - -
- Allen Relation STARTS -
Figure 5: Relation x STARTS y
-
- -```ts - export default () => Goal.CoexistenceGoal({ - forEach: ActivityExpression.ofType(ActivityTypes.GrowBanana), - activityTemplate: (span) => ActivityTemplates.PeelBanana({peelDirection: "fromStem"}), - startsWithin: TimingConstraint.bounds(TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 5})), TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 10}))) - }) -``` diff --git a/docs/scheduling-and-constraints/declarative/scheduling/temporal-subset.mdx b/docs/scheduling-and-constraints/declarative/scheduling/temporal-subset.mdx deleted file mode 100644 index b8ffbdfd..00000000 --- a/docs/scheduling-and-constraints/declarative/scheduling/temporal-subset.mdx +++ /dev/null @@ -1,25 +0,0 @@ -# Temporal Subset - -Sometimes, it may be desirable to limit the action of the scheduler to a certain time range. -This can be accomplished using [Global Conditions](../global-conditions). - -First, you’ll need to define a numeric resource in your mission model that represents time - for example, you could use a [Clock](https://github.com/NASA-AMMOS/aerie/blob/develop/contrib/src/main/java/gov/nasa/jpl/aerie/contrib/models/Clock.java). - -```java -Mission(Registrar registrar, Instant planStart) { - final var clock = new Clock(planStart); - registrar.realResource("/clock", clock.ticks); -} -``` - -Then you can write a global scheduling condition based on that resource. -This example says that the scheduler may only place activities between 8 and 12 days after the start of the plan: - -```ts -export default (): GlobalSchedulingCondition => - GlobalSchedulingCondition.scheduleActivitiesOnlyWhen( - Real.Resource('/clock') - .greaterThan(Temporal.Duration.from({ days: 8 }).total('milliseconds')) - .and(Real.Resource('/clock').lessThan(Temporal.Duration.from({ days: 12 }).total('milliseconds'))), - ); -``` diff --git a/docs/scheduling-and-constraints/execution.mdx b/docs/scheduling-and-constraints/execution.mdx deleted file mode 100644 index 9e30d0b7..00000000 --- a/docs/scheduling-and-constraints/execution.mdx +++ /dev/null @@ -1,61 +0,0 @@ -# Execution - -## Constraints - -Constraints can be checked inside the main plan view by opening the `Constraints` pane and clicking the checklist icon -in the top right of the pane. Alternatively, you can hover over `Constraints` in the top bar and click "Check Constraints". - -## Scheduling - -To run the scheduler, click on the play button: - -import runScheduling from './assets/run-scheduling.png'; - -
- Aerie UI - Run Scheduling -
Figure 2: Aerie UI Run Scheduling
-
- -If the scheduler fails, a failure icon will appear next to the buttons: - -import schedulingFailed from './assets/scheduling-failed.png'; - -
- Aerie UI - Scheduling Failed -
Figure 3: Aerie UI Scheduling Failed
-
- -An error message will also appear in the bottom error panel. For example: - -import schedulingError from './assets/scheduling-error.png'; - -
- Aerie UI - Scheduling Error -
Figure 4: Aerie UI Scheduling Error
-
- -If scheduling succeeded, some information about the satisfaction of the goal appears next to each goal. For example: - -import schedulingSuccess from './assets/scheduling-success.png'; - -
- Aerie UI - Scheduling Success -
Figure 5: Aerie UI Scheduling Success
-
- -- The round blue checkmark means that the goal is completely satisfied -- `174` means that there are `174` activity directives that contribute to the satisfaction of the goal -- `+3` means that 3 new activities have been inserted in the plan to satisfied the goal during the last scheduling run - -### Running a Scheduling Analysis - -The scheduler has an analysis mode that will evaluate the satisfaction of goals but will not place any new activities. To run the scheduler in analysis mode, click on the "analysis" button: - -import runSchedulingAnalysis from './assets/run-scheduling-analysis.png'; - -
- Aerie UI - Scheduling Analysis -
Figure 6: Aerie UI Run Scheduling Analysis
-
- -Information about the satisfaction of the goals will be the same as when the scheduler is run normally. diff --git a/docs/scheduling-and-constraints/introduction.mdx b/docs/scheduling-and-constraints/introduction.mdx deleted file mode 100644 index b5472de3..00000000 --- a/docs/scheduling-and-constraints/introduction.mdx +++ /dev/null @@ -1,43 +0,0 @@ -# Scheduling & Constraints - -Aerie provides related frameworks for defining constraints and scheduling new activities in the plan, and two implementations -of those frameworks: one for arbitrary procedures that run on the JVM, and a legacy system based on a declarative Typescript eDSL -(embedded Domain-Specific Language). Both frameworks are documented here, but new users are encouraged to focus on -creating JVM procedures. The declarative eDSL is significantly less capable, and the difference in capabilities is only expected to -grow. - -## Constraints - -Constraints represent what is nominal for a plan or mission model, and when executed, the UI will display "violations" -whenever the plan or model is off-nominal. They don't alter the behavior of the simulation engine or scheduler; they -just serve as a warning, indicating that some requirement - perhaps a flight rule - was broken. - -## Scheduling - -The scheduler allows users to automate the creation of new activities, to remove some cognitive load from planners. A -scheduling specification contains a list of goals and rules with a priority order; during a scheduling run, they are -executed one at a time, starting from a priority of 0 and increasing from there. - -### Procedural Goals - -Procedural goals directly edit the plan, creating new activities at definite (grounded) times. They -can simulate potential changes to the plan, but aren't required to. In fact, a scheduling specification composed entirely -of procedures might run in its entirety without performing any simulations, potentially at the cost of optimality or even -soundness. - -### eDSL Goals - -eDSL goals are more declarative, in that they don't allow you to directly create grounded activities; instead they allow -you to describe a pattern of activities that should be present in the plan. If the pattern isn't found, the goal tries to -create it for you. Currently eDSL goals are simpler to write than procedural goals, for patterns that they can represent. -Many goals are more complex than can be represented in the eDSL, and will have to be written as a procedure. - -### Global Conditions - -Global scheduling conditions (or sometimes just "conditions") are supplemental pieces of code that define when scheduling -goals can and cannot place activities. They are incorporated into the solver when attempting to resolve conflicts as -a substitute for constraints. This is because it is too difficult to respect constraints during scheduling; constraints -only indicate that something went wrong, not what caused it or how to fix it. So in cases when the scheduler keeps violating -constraints, users can create a condition as a heuristic to help it satisfy the constraint. - -Conditions will be accessible to scheduling rules, but will be non-binding. diff --git a/docs/scheduling-and-constraints/management.mdx b/docs/scheduling-and-constraints/management.mdx deleted file mode 100644 index 55ea9ba7..00000000 --- a/docs/scheduling-and-constraints/management.mdx +++ /dev/null @@ -1,37 +0,0 @@ -# Management - -## Creating and Updating - -The recommended setup is to store goals/rules/conditions/constraints (hereafter called "peripheral code") -in one or more repositories outside Aerie, and uploading -them through the UI. - -For example, after creating a new goal as described in the following pages, you can upload it with the UI by selecting -`New` on the `Scheduling` option in main top-left dropdown. Or, you can upload it with an automatic association with your -plan by opening the `Scheduling Goals` pane in your plan, and navigating to `Manage Goals -> New`. Here you have the -option to either upload a JAR file, or copy-paste a typescript eDSL file. - -## Model and Plan Association - -In Aerie, peripherals live independently of plans and models, and can be associated with -any number of plans and models, or none at all. Each model and plan has a scheduling specification and a constraints -specification, which is simply a list of peripherals to run during the scheduling or constraints actions, respectively. - -Model specifications are never run directly, and instead populate the default spec for any plans created from that model. -So if a particular constraint is widely applicable to all plans made from a particular model, you can associate it with -the model by navigating to `Models ->