Skip to content

Data Model

FDS organises fusion data into a three-level hierarchy, with Collections as a first-class grouping mechanism at any level.

Hierarchy

Global
├── Dataset
├── Collection: (Dataset, Dataset, …)
└── Device  (e.g. MAST, MAST-Upgrade, JET)
    ├── Dataset
    ├── Collection: (Dataset, Dataset, …)
    └── Shot  (a single plasma discharge)
        ├── Dataset
        └── Collection: (Dataset, Dataset, …)
                      └── Collection: (Dataset, Dataset, …)

Datasets and Collections can exist at any scope level: Global, Device, or Shot. A Collection can contain other Collections as well as Datasets.

Scientific metadata

The scientific_metadata field on Shot, Dataset and Collection holds a structured list of experimental conditions. Each entry is a {name, value, unit, description} property:

Field Type Required Description
name string Yes Property identifier (e.g. plasma_current)
value any Yes Any JSON-compatible type: number, string, boolean, or list
unit string No Unit for physical quantities (e.g. MA, T)
description string No Free-text explanation
extent object No Optional 1D range that localises the property on one named axis, making it a feature (see below)
"scientific_metadata": [
  {"name": "plasma_current", "value": 0.8, "unit": "MA"},
  {"name": "toroidal_field", "value": -0.5, "unit": "T"},
  {"name": "confinement_mode", "value": "H-mode"},
  {"name": "heating_schemes", "value": ["NBI", "ECRH"]},
  {"name": "disrupted", "value": false}
]

In JSON-LD, these are mapped to schema:additionalProperty / schema:PropertyValue nodes, making them indexable by Google Dataset Search. See Semantic Metadata.

Feature annotation

A property may carry an optional extent: a 1D range on one named axis of the data, making it a feature. Time is the common case (an H-mode window, a disruption), but the axis can be any dimension the store has, such as frequency for an MHD mode. A property with no extent is a plain scalar property.

Field Type Required Description
dimension string Yes The axis the range is on (e.g. time, frequency, x); free text, not validated against the store
start number Yes Coordinate on that axis, in the axis's own frame (may be negative)
end number No Range end; omit for a point (a single instant or slice)
unit string No Unit of start/end on that axis (e.g. s, Hz)
"scientific_metadata": [
  {"name": "confinement_mode", "value": "H-mode", "extent": {"dimension": "time", "start": 1.0, "end": 2.0}},
  {"name": "disruption", "value": true, "extent": {"dimension": "time", "start": 3.4}},
  {"name": "mode", "value": "n=1 tearing", "extent": {"dimension": "frequency", "start": 8000, "end": 12000, "unit": "Hz"}}
]

start/end are coordinates on the named axis, in that axis's own frame, so a consumer overlays them directly on the axis it plots the signal against. Time is not privileged: aligning a shot-level time extent to a particular diagnostic's axis is the consumer's job and is not guaranteed, since processing may put that diagnostic on a different base. A Shot can declare t0_at, the wall-clock instant of its relative t=0, so a provider can communicate the offset from shot_at; FDS stores it but never uses it to convert event times or to assume two datasets share a time base.

Only 1D localisation can be stored in the metadata. Multi-dimensional regions (a mask, a 2D shape) and dense series (every ELM in a shot) are referenced as feature annotations.

A time extent projects to a W3C Time time:Interval in JSON-LD; an extent on any other axis projects to a numeric range.

Finding annotated records

Shot, Dataset and Collection list endpoints accept an annotation parameter that filters on scientific_metadata, in one of two forms:

Form Meaning Example
name The property is present, whatever its value ?annotation=disruption
name:value The property is present with this value ?annotation=confinement_mode:H-mode
GET /api/v1/devices/mast/shots?annotation=disruption
GET /api/v1/devices/mast/shots?annotation=confinement_mode:H-mode

Repeat the parameter to require several annotations at once. They combine with AND, so this returns only shots carrying both:

GET /api/v1/devices/mast/shots?annotation=disruption&annotation=elm

Only the first : separates name from value, so a value may itself contain one (?annotation=mode:n=1:tearing looks for the value n=1:tearing). A trailing separator with no value is rejected: omit it to filter on presence alone.

Values are compared against the text you supply or its natural type, so ?annotation=disruption:true matches a stored boolean true as well as the string "true".

Dataset lists additionally accept name, plus shot_annotation, which filters on an annotation carried by the dataset's parent shot rather than the dataset itself. That answers questions spanning both levels in one request:

GET /api/v1/devices/mastu/datasets?name=equilibrium&shot_annotation=elm

Datasets that belong to no shot never match a shot_annotation filter. Annotation names are not validated, so a name that nothing uses returns an empty list rather than an error.

URL structure

GET /api/v1/devices/{device}/shots/{shot_id}/datasets/{name}
GET /api/v1/devices/{device}/shots/{shot_id}/collections/{name}
GET /api/v1/devices/{device}/shots/{shot_id}/datasets          # list, one shot
GET /api/v1/devices/{device}/datasets                          # list, whole device
GET /api/v1/datasets/id/{id}                                   # stable ID-based lookup

Datasets and Collections are siblings at each scope level. A Collection does not appear in a Dataset's path.

Example: MAST shot 30421

Device: mast
└── Shot: 30421
    │   shot_at: 2008-11-18T14:32:00Z
    │   creator: "MAST Team"
    │   scientific_metadata:
    │     - {name: "plasma_current", value: 0.4, unit: "MA"}
    │     - {name: "confinement_mode", value: "L-mode"}
    ├── Dataset: equilibrium    (EFIT reconstruction)
    ├── Dataset: magnetics      (calibrated IDS)
    ├── Dataset: thomson_scattering
    ├── … (10 more IDS datasets)
    └── Collection: experiment-data
        └── (all 13 IDS datasets above)