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.

Dates and times

Every datetime is an instant, and FDS returns it in UTC with a Z suffix: 2012-01-27T15:52:00Z. Send one with an offset, such as 2012-07-03T10:15:00+01:00, and FDS stores that instant. Send one without an offset and it is taken to be UTC, so a local time sent that way is stored an hour out during summer time. Include the offset, or convert to UTC first.

When FDS listed a record

Every Device, Shot, Dataset and Collection also carries two fields that FDS sets itself:

Field Description
created_at When FDS listed the record
updated_at When FDS last changed the record

They describe FDS's entry, not the thing it describes. Shot 28352 was fired in 2012 and listed in 2026, so its shot_at is in 2012 and its created_at in 2026. In JSON-LD they appear on the catalogue record, not on the resource.

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 an annotation (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.

Annotations

A property may carry an optional extent: a 1D range on one named axis of the data, making it an annotation. 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 annotation datasets.

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 a property parameter that filters on scientific_metadata, in one of two forms:

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

Repeat the parameter to give more than one. Whether that broadens or narrows the result depends on whether you repeat the same property:

  • The same property with different values matches a shot with any of them.
  • Different properties must all match.
GET /v1/devices/mast/shots?property=heating:SS Beam&property=heating:SW Beam

Shots heated by either beam, because both values are heating.

GET /v1/devices/mast/shots?property=disruption&property=elm

Only shots that had both, because disruption and elm are different properties.

The two combine, so this asks for shots on either beam that also had a disruption:

GET /v1/devices/mast/shots?property=heating:SS Beam&property=heating:SW Beam&property=disruption

This is what ticking two values in one filter and one in another means.

Requiring several values of one property at once, such as a shot that was in L-mode and later in H-mode, is a different question. It needs nesting that a query string cannot express.

Only the first : separates name from value, so a value may itself contain one (?property=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 ?property=disruption:true matches a stored boolean true as well as the string "true".

property_min and property_max bound a numeric value:

GET /v1/devices/mast/shots?property_min=plasma_current_max:700000
GET /v1/devices/mast/shots?property_min=plasma_current_max:700000&property_max=plasma_current_max:900000

Bounds combine with AND and with any property filter. A record whose value for that name is not a number is skipped rather than matched.

Shot lists also accept id_prefix, which keeps the shots whose ID starts with it. This finds shots 30400 to 30499:

GET /v1/devices/mast/shots?id_prefix=304

Dataset lists additionally accept name, plus shot_property, 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 /v1/devices/mastu/datasets?name=equilibrium&shot_property=elm

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

Discovering what to filter on

Annotation names are an open vocabulary, so a device's shots tell you what they can be filtered by:

GET /v1/devices/mast/shots/properties
{
  "total": 5300,
  "annotations": [
    {"name": "flat_top", "records": 5300, "distinct": 1, "values": ["true"]},
    {"name": "campaign", "records": 5300, "distinct": 6, "values": ["M5", "M6", "M7", "M8", "M9", "M9a"]},
    {"name": "plasma_current_max", "records": 5300, "distinct": 5267, "unit": "A"}
  ]
}

Each entry gives the number of shots carrying that name, the number of distinct values it takes, and its kind:

kind what it is how to filter it
term a value drawn from a vocabulary equality, with property
quantity a magnitude on a scale range, with property_min / property_max
text prose written for a human not filterable; never listed

A producer sets kind on the property to say what a value is. When they have not, FDS infers it from the values in scope: a handful of repeated values is a term whether or not those values are numbers, a number that differs on nearly every record is a quantity, and text that is near-unique per record is prose. Scientific Metadata covers what to declare and how each kind is presented.

A term's values appear only when there are at most max_values of them, 20 by default and at most 200. Above that, fetch them a page at a time:

GET /v1/devices/mast/shots/properties/objective/values?q=conditioning

That returns each matching value with the number of shots carrying it, most common first. unit appears only when every record agrees on one, and min/max are set for quantities. A property carrying an extent also reports the dimension it is localised on, which makes it an annotation: a claim about a region of the data rather than about the shot as a whole.

total counts the shots you may read, and the endpoint accepts the same property parameter as the listing, so a filtered scope can be counted exactly:

GET /v1/devices/mast/shots/properties?property=campaign:M9

Values a caller is not permitted to read never appear, so two callers can see different properties over the same device.

URL structure

GET /v1/devices/{device}/shots/{shot_id}/datasets/{name}
GET /v1/devices/{device}/shots/{shot_id}/collections/{name}
GET /v1/devices/{device}/shots/{shot_id}/datasets          # list, one shot
GET /v1/devices/{device}/datasets                          # list, whole device
GET /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)