Skip to main content

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:

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:

ParameterTypeDescription
$planIdIntegerThe ID of the plan to associate the external dataset with
$simulationDatasetIdIntegerThe optional ID of a simulation dataset that the external dataset will be exclusively associated with
$datasetStartStringThe DOY UTC timestamp the dataset starts from
UTC Format: yyyy-dddThh:mm:ss
$profileSetObjectThe 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).

Currently, real profiles only support linear equations with the following schema:

{
"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:

FieldTypeDescription
durationIntegerThe duration (in microseconds) the segment's dynamics hold before the next segment begins
dynamics (optional)Dependent on profile typeThe 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 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.

mutation ExtendExternalDataset($datasetId: Int!, $profileSet: ProfileSet!) {
extendExternalDataset(datasetId: $datasetId, profileSet: $profileSet) {
datasetId
}
}

The extendExternalDataset GraphQL mutation takes two query variables as specified below:

ParameterTypeDescription
datasetIdIntegerThe ID of the dataset to extend
$profileSetObjectThe set of precomputed profiles that are to be added to the external dataset

See addExternalDataset 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:

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:

{
"id": 1
}

Example Query Variables for the AddExternalDataset Mutation

Below shows example query variables you can use with the addExternalDataset 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.

{
"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.

{
"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 into an external dataset with 3 profiles. The CSV has the following form:

Time (s)TotalPowerBatteryStateOfChargeTemperature
164937600.00.0143.150.0
164937700.0384.9999999404831.4-12.0964867663028
164937800.0384.999999399855137.45-12.0974993557598
164937900.0385.000010807604134.85-12.0985125609155
164938000.0381.80000002749132.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.

{
"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(<value>) 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:

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.