Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data Files

SpecForge supports three data formats for signal data: CSV, JSON, and JSONL (also known as NDJSON). Parameters are provided separately in a JSON file.

Signal data

Each data sample has a time field and one or more signal values. The time field is required and must be named exactly time (case-sensitive). It accepts any numeric value (integer or floating point) and samples should be in increasing time order.

CSV

Each column header maps to a signal name. Record-typed signals are flattened with a separator (default _).

time,speed,ambient_temp,gps_lat,gps_lon
0.0,0.0,25.0,34.634,135.996
1.0,30.0,26.0,34.635,135.997

Booleans in CSV can be written as true/false, True/False, or 1/0.

JSON

A JSON array of objects, each with time and value keys. Record-typed signals can use nested JSON objects:

[
  {
    "time": 0.0,
    "value": {
      "speed": 0.0,
      "ambient_temp": 25.0,
      "gps": { "lat": 34.634, "lon": 135.996 }
    }
  },
  {
    "time": 1.0,
    "value": {
      "speed": 30.0,
      "ambient_temp": 26.0,
      "gps": { "lat": 34.635, "lon": 135.997 }
    }
  }
]

They can also be flattened (see Record encoding):

[
  {
    "time": 0.0,
    "value": {
      "speed": 0.0,
      "ambient_temp": 25.0,
      "gps_lat": 34.634,
      "gps_lon": 135.996
    }
  },
  {
    "time": 1.0,
    "value": {
      "speed": 30.0,
      "ambient_temp": 26.0,
      "gps_lat": 34.635,
      "gps_lon": 135.997
    }
  }
]

JSONL

Same as JSON, but one object per line without the outer array.

{"time": 0.0, "value": {"speed": 0.0, "ambient_temp": 25.0, "gps": {"lat": 34.634, "lon": 135.996}}}
{"time": 1.0, "value": {"speed": 30.0, "ambient_temp": 26.0, "gps": {"lat": 34.635, "lon": 135.997}}}

Column names

The column names in a data file must match the signal declarations in the spec. How signal names map to column names depends on two things: the component hierarchy and the record encoding.

Note: Extra columns in data files that do not match any signal declaration are silently ignored.

Component hierarchy

Signals from system components are prefixed with the component path, separated by ::. For instance, given a Battery system:

system Battery

signal level: Float
signal voltage: Float

And a Vehicle that uses it:

system Vehicle

signal speed: Float

component battery: Battery

The data file needs columns for speed (a direct signal) and battery::level, battery::voltage (signals from the Battery component).

In CSV this looks like:

time,speed,battery::level,battery::voltage
0.0,0.0,80.0,7.4

In JSON, components can also be written as nested objects:

{
  "time": 0.0,
  "value": {
    "speed": 0.0,
    "battery": { "level": 80.0, "voltage": 7.4 }
  }
}

Record encoding

Signals with record types need their fields represented as separate columns. There are two encoding modes.

Flat (default): record fields are concatenated with a separator (default _). This applies to all formats. For example, given:

signal gps: { lat: Float, lon: Float }

The columns are gps_lat and gps_lon.

time,gps_lat,gps_lon
0.0,34.634,135.996

A custom separator can be specified. The separator cannot contain ,, ", \, or ::.

Nested: record fields are represented as nested JSON objects. This only applies to JSON and JSONL.

{
  "time": 0.0,
  "value": { "gps": { "lat": 34.634, "lon": 135.996 } }
}

Mapped signals

Component signals that have an expression body (mapped signals) are computed from other signals and do not need data. For example, given a PowerUnit with two signals:

system PowerUnit

param max_output: Float

signal output: Float
signal temperature: Float

A Vehicle can map temperature to an expression:

component power: PowerUnit {
  signal temperature = ambient_temp + speed * 0.5
}

Here power::temperature is computed, so it need not appear in the data file. Only unmapped signals like power::output require data columns.

Parameter files

The structure of a parameter file mirrors the param declarations in the spec.

The Vehicle system declares its own parameter and provides a value for PowerUnit's:

system Vehicle

param temp_threshold: Float

component power: PowerUnit {
  param max_output = 100.0
}

A corresponding parameter file:

{
  "temp_threshold": 50.0
}

Parameters with default values can be omitted. In the example above, power::max_output has a default of 100.0 and is not present in the file.

Putting it all together

Assembling the Battery and PowerUnit systems from above:

system Vehicle

signal speed: Float
signal ambient_temp: Float
signal `fuel level`: Float
signal gps: { lat: Float, lon: Float }

param temp_threshold: Float

component battery: Battery
component power: PowerUnit {
  param max_output = 100.0
  signal temperature = ambient_temp + speed * 0.5
}

The parameter file provides temp_threshold and omits power::max_output (it has a default):

{
  "temp_threshold": 50.0
}

A CSV data file for this system (flat encoding, _ separator):

time,speed,ambient_temp,fuel level,gps_lat,gps_lon,battery::level,battery::voltage,power::output
0.0,0.0,25.0,100.0,34.634,135.996,80.0,7.4,0.0
1.0,30.0,26.0,99.8,34.635,135.997,78.0,7.3,40.0

Note that `fuel level` appears as fuel level without backticks, and power::temperature is absent because it is a mapped signal.

The same data in JSON (nested encoding):

[
  {
    "time": 0.0,
    "value": {
      "speed": 0.0,
      "ambient_temp": 25.0,
      "fuel level": 100.0,
      "gps": { "lat": 34.634, "lon": 135.996 },
      "battery": { "level": 80.0, "voltage": 7.4 },
      "power": { "output": 0.0 }
    }
  },
  {
    "time": 1.0,
    "value": {
      "speed": 30.0,
      "ambient_temp": 26.0,
      "fuel level": 99.8,
      "gps": { "lat": 34.635, "lon": 135.997 },
      "battery": { "level": 78.0, "voltage": 7.3 },
      "power": { "output": 40.0 }
    }
  }
]

Notes

  • Integer values are automatically coerced to Float when the signal type is Float.
  • The data format is auto-detected from the file extension (.csv, .json, .jsonl) but can be overridden with the --file-format CLI flag.