Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GMLC-TDC/HELICS/llms.txt

Use this file to discover all available pages before exploring further.

This quickstart walks through setting up and running a two-federate co-simulation using Python and HELICS. The example models a simple interaction between a battery federate and a charger federate — the same scenario used throughout the HELICS user guide fundamental examples. By the end, you will have a working federation that exchanges data between two independent Python processes coordinated by a HELICS broker. The full example files are available in the HELICS-Examples repository on GitHub.
1

Install HELICS

Install HELICS and the CLI runner using pip. The [cli] extra is recommended because it provides the helics run command for launching a complete federation from a single JSON file.
pip install 'helics[cli]'
Verify the installation:
helics_broker --version
You should see a version string such as 3.x.x (20XX-XX-XX). If the command is not found, make sure your Python environment’s bin directory is on your PATH.
2

Create federate configuration files

Each federate is configured with a JSON file that declares its name, timing parameters, core type, and the interfaces it will use to exchange data. Create two configuration files.BatteryConfig.json — the battery federate subscribes to voltage from the charger and publishes current back:
{
  "name": "Battery",
  "log_level": 1,
  "core_type": "zmq",
  "period": 60,
  "uninterruptible": false,
  "terminate_on_error": true,
  "wait_for_current_time_update": true,
  "publications": [
    {
      "key": "Battery/EV1_current",
      "type": "double",
      "unit": "A",
      "global": true
    }
  ],
  "subscriptions": [
    {
      "key": "Charger/EV1_voltage",
      "type": "double",
      "unit": "V",
      "global": true
    }
  ]
}
ChargerConfig.json — the charger federate publishes voltage and subscribes to current from the battery:
{
  "name": "Charger",
  "log_level": 1,
  "core_type": "zmq",
  "period": 60,
  "uninterruptible": false,
  "terminate_on_error": true,
  "publications": [
    {
      "key": "Charger/EV1_voltage",
      "type": "double",
      "unit": "V",
      "global": true
    }
  ],
  "subscriptions": [
    {
      "key": "Battery/EV1_current",
      "type": "double",
      "unit": "A",
      "global": true
    }
  ]
}
The key in a publication must match the key in the corresponding subscription on the other federate. The global: true flag means the key is used as-is rather than being prefixed with the federate name.
3

Write the federate Python scripts

Create two Python scripts. Each script registers a federate using its JSON config, enters execution mode, runs a time loop, and finalizes when done.Battery.py — subscribes to voltage, publishes current:
import helics as h
import logging

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)

# Register and configure from JSON
fed = h.helicsCreateValueFederateFromConfig("BatteryConfig.json")
federate_name = h.helicsFederateGetName(fed)
logger.info(f"Created federate {federate_name}")

# Get handles for the registered interfaces
subid = h.helicsFederateGetInputByIndex(fed, 0)
pubid = h.helicsFederateGetPublicationByIndex(fed, 0)

# Enter execution mode — blocks until all federates are ready
h.helicsFederateEnterExecutingMode(fed)
logger.info("Entered HELICS execution mode")

# Time loop configuration
hours = 24 * 7
total_interval = int(60 * 60 * hours)  # one week in seconds
update_interval = int(
    h.helicsFederateGetTimeProperty(fed, h.helics_property_time_period)
)
grantedtime = 0

# Simulate a fixed battery capacity for this minimal example
battery_capacity = 100.0  # kWh
soc = 0.5  # state of charge, starting at 50%

while grantedtime < total_interval:
    requested_time = grantedtime + update_interval
    grantedtime = h.helicsFederateRequestTime(fed, requested_time)

    # Receive voltage from the charger
    charging_voltage = h.helicsInputGetDouble(subid)
    logger.debug(f"Received voltage {charging_voltage:.2f} V at time {grantedtime}")

    # Compute charging current (simplified model: I = V / R, R = 10 ohms)
    if charging_voltage > 0 and soc < 1.0:
        charging_current = charging_voltage / 10.0
        soc += (charging_current * update_interval) / (3600 * battery_capacity)
        soc = min(soc, 1.0)
    else:
        charging_current = 0.0

    # Publish current back to the charger
    h.helicsPublicationPublishDouble(pubid, charging_current)
    logger.debug(f"Published current {charging_current:.2f} A, SOC={soc:.3f}")

# Finalize
h.helicsFederateFinalize(fed)
h.helicsFederateFree(fed)
h.helicsCloseLibrary()
logger.info("Battery federate finalized")
Charger.py — publishes voltage, subscribes to current:
import helics as h
import logging

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)

# Register and configure from JSON
fed = h.helicsCreateValueFederateFromConfig("ChargerConfig.json")
federate_name = h.helicsFederateGetName(fed)
logger.info(f"Created federate {federate_name}")

# Get handles for the registered interfaces
subid = h.helicsFederateGetInputByIndex(fed, 0)
pubid = h.helicsFederateGetPublicationByIndex(fed, 0)

# Enter execution mode
h.helicsFederateEnterExecutingMode(fed)
logger.info("Entered HELICS execution mode")

# Time loop configuration
hours = 24 * 7
total_interval = int(60 * 60 * hours)
update_interval = int(
    h.helicsFederateGetTimeProperty(fed, h.helics_property_time_period)
)

# Publish an initial charging voltage before entering the main loop
charging_voltage = 240.0  # Level 2 charging voltage in volts
h.helicsPublicationPublishDouble(pubid, charging_voltage)

grantedtime = 0

while grantedtime < total_interval:
    requested_time = grantedtime + update_interval
    grantedtime = h.helicsFederateRequestTime(fed, requested_time)

    # Receive current from the battery
    charging_current = h.helicsInputGetDouble(subid)
    logger.debug(f"Received current {charging_current:.2f} A at time {grantedtime}")

    # If current drops to zero, battery is full — stop charging
    if charging_current <= 0.01:
        charging_voltage = 0.0

    # Publish updated voltage
    h.helicsPublicationPublishDouble(pubid, charging_voltage)
    logger.debug(f"Published voltage {charging_voltage:.2f} V at time {grantedtime}")

# Finalize
h.helicsFederateFinalize(fed)
h.helicsFederateFree(fed)
h.helicsCloseLibrary()
logger.info("Charger federate finalized")
4

Create a runner file and launch the co-simulation

The HELICS runner lets you launch the broker and all federates with a single command. Create a runner JSON file:runner.json:
{
  "name": "battery_charger_example",
  "broker": true,
  "federates": [
    {
      "directory": ".",
      "exec": "python -u Charger.py",
      "host": "localhost",
      "name": "Charger"
    },
    {
      "directory": ".",
      "exec": "python -u Battery.py",
      "host": "localhost",
      "name": "Battery"
    }
  ]
}
Setting "broker": true tells the runner to automatically launch helics_broker -f 2 --loglevel=7 before starting the federates. The -f 2 flag tells the broker to expect exactly two federates.Launch the full co-simulation:
helics run --path=runner.json
You can also launch the broker and federates manually in separate terminal windows if you prefer more control. Start the broker first, then the federates in any order.
# Terminal 1
helics_broker -f 2 --loglevel=warning

# Terminal 2
python -u Battery.py

# Terminal 3
python -u Charger.py
5

Verify the output

When the co-simulation runs successfully, you will see log output from both federates showing the time loop progressing and values being exchanged:
INFO:__main__:Created federate Battery
INFO:__main__:Entered HELICS execution mode
DEBUG:__main__:Received voltage 240.00 V at time 60.0
DEBUG:__main__:Published current 24.00 A, SOC=0.500
DEBUG:__main__:Received voltage 240.00 V at time 120.0
...
INFO:__main__:Battery federate finalized
If either federate exits immediately with an error, check that:
  1. The broker was started before the federates (or the runner launched it automatically).
  2. The publication keys in one federate’s config exactly match the subscription keys in the other.
  3. The number of federates the broker expects (-f 2) matches the number of federates that actually connect.
Both federates must start and connect to the broker within the broker’s timeout window. If one federate takes too long to start, the broker may time out and shut down the federation before both have connected.

What just happened?

The co-simulation you ran demonstrates the core HELICS workflow:
  1. A broker started and waited for two federates to connect.
  2. Each federate registered its publication and subscription interfaces.
  3. All federates entered execution mode together, synchronized by the broker.
  4. At each time step, the charger published a voltage and the battery published a current. HELICS routed each value to the federate that subscribed to it.
  5. Both federates advanced through time in lockstep until they reached the simulation end time.
  6. Each federate finalized and HELICS cleaned up.

Next steps

Federates

Learn how federates register interfaces, manage time, and exchange data in depth.

Configuration reference

Explore the full set of JSON configuration options for federates, timing, and logging.

Value federates

Understand publications, subscriptions, and data types for value-based communication.

HELICS apps

Learn about the Broker, Player, Recorder, and other built-in HELICS helper tools.

Build docs developers (and LLMs) love