Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

2. Describe and build it

The federate from page 1 runs, but its config was written by hand and its broker started manually. oedisi build takes a “wiring diagram” JSON and generates both.

Interface

component_definition.json in our component’s directory tells OEDISI its behavior.

{
  "directory": "power_component",
  "execute_function": "python power_component.py",
  "static_inputs": [
    { "type": "", "port_id": "node_ids" },
    { "type": "", "port_id": "equipment_ids" },
    { "type": "", "port_id": "base_power" },
    { "type": "", "port_id": "multiplier" },
    { "type": "", "port_id": "number_of_timesteps" },
    { "type": "", "port_id": "step_size_seconds" },
    { "type": "", "port_id": "start_time" }
  ],
  "dynamic_inputs": [],
  "dynamic_outputs": [{ "type": "PowersReal", "port_id": "power" }],
  "capabilities": { "broker_config": true }
}

Component types

oedisi build requires a list of all components, taking the form of a components.json mapping component name to a definition file:

{
  "PowerComponent": "power_component/component_definition.json",
  "Recorder": "Components/recorder/component_definition.json"
}

Paths are relative to wherever oedisi build runs.

The published power needs somewhere to go. The shared component repository has a recorder federate that subscribes to any measurement and writes CSV and Feather, so make it reachable:

ln -s "$OEDISI_COMPONENTS" Components

Wiring diagram

The wiring diagram lists component instances and the links between them. Create system_power.json:

{
  "name": "tutorial_power",
  "components": [
    {
      "name": "power",
      "type": "PowerComponent",
      "parameters": {
        "node_ids": ["113.1", "113.2", "114.1"],
        "equipment_ids": ["Load.load1", "Load.load1", "Load.load2"],
        "base_power": [10.0, 12.5, 7.5],
        "multiplier": 1.2,
        "number_of_timesteps": 4
      }
    },
    {
      "name": "power_recorder",
      "type": "Recorder",
      "parameters": {
        "feather_filename": "power.feather",
        "csv_filename": "power.csv"
      }
    }
  ],
  "links": [
    {
      "source": "power",
      "source_port": "power",
      "target": "power_recorder",
      "target_port": "subscription"
    }
  ]
}

Here, we give each “instance” of a component “type” (PowerComponent) a unique name. parameters is combined with the HELICS information and saved to the component as static_inputs.json. A link connects an output port_id to an input port_id, so the recorder can listen to our PowerComponent.

Build

oedisi build --system system_power.json --component-dict components.json \
    --target-directory build_power

build_power/ holds one directory per component: a copy of the component’s code plus two generated files.

build_power/
├── power/
│   ├── power_component.py
│   ├── component_definition.json
│   ├── static_inputs.json      ← generated
│   └── input_mapping.json      ← generated
├── power_recorder/
│   └── ...
└── system_runner.json

static_inputs.json is the file written by hand on page 1:

{"name": "power", "node_ids": ["113.1", "113.2", "114.1"], ...}

The name came from the component’s name in the wiring diagram. Nothing inside the component sets it, which is what allows one component to appear under several names in one federation.

input_mapping.json names the subscriptions. The power component has no inputs, so its copy is {}. The recorder’s input_mapping.json is the interesting one:

{"subscription": "power/power"}

The recorder’s subscription port was linked to the power component’s power port, so the key is power/power, following the {component name}/{port id} convention from page

  1. The recorder does not know what is upstream of it. It opens whatever key this file names, so wiring something else into it changes only this file.

system_runner.json is the run plan, including a broker sized to the federation:

{
  "name": "tutorial_power",
  "federates": [
    { "directory": "power", "name": "power", "exec": "python power_component.py" },
    { "directory": "power_recorder", "name": "power_recorder",
      "exec": "python -m src.recorder.record_subscription" },
    { "directory": ".", "name": "broker", "exec": "helics_broker -f 2 --loglevel=warning" }
  ]
}

Run

oedisi run --runner build_power/system_runner.json

Each federate’s output goes to build_power/<name>.log, and the recorder wrote its files inside its own build directory:

cat build_power/power_recorder/power.csv
113.1,113.2,114.1,time
12.0,15.0,9.0,2017-01-01 00:00:00.000000
12.0,15.0,9.0,2017-01-01 00:15:00.000000
12.0,15.0,9.0,2017-01-01 00:30:00.000000
12.0,15.0,9.0,2017-01-01 00:45:00.000000

The columns are the ids from the published PowersReal, and the time column is its time field.

Next: give the component an input.