FMU Models
CosimGym runs Functional Mock-up Units (FMUs) as ordinary models through a single wrapper class, BaseFMUModel (src/models/base_FMU_model.py). It supports FMI 2.0 and 3.0 co-simulation (with partial FMI 1.0 support) via fmpy, and reads the FMU's input/output variable names directly from the FMU's modelDescription.xml.
You do not write any Python to use an FMU — you register a catalog entry pointing class_name at BaseFMUModel and declare where the FMU file lives.
1. Catalog entry
models:
my_building_fmu:
class_name: BaseFMUModel
module_path: models.base_FMU_model
version: 1.0.0
description: My building FMU.
category: physical_model
time_step: 600 # must match the FMU's fixed communication step (see below)
max_time_step: 600
min_time_step: 600
user_defined:
fmu_source:
type: local # local | minio | http
path: /abs/path/to/MyBuilding.fmu
inputs: # names MUST match the FMU's input variables
ZoneSetPoint:
type: float
default_value: 20.0
unit: degC
outputs: # names MUST match the FMU's output variables
TBuilding:
type: float
default_value: 0.0
unit: degC
The
inputs/outputskeys are not free-form: they must equal the variable names declared inside the FMU.BaseFMUModellooks each scenario input/output up by name and sets/gets it on the FMU via its value reference.
FMU sources
fmu_source.type selects how the binary is resolved. Downloaded FMUs are cached under ~/.cosimgym/fmu_cache/<model_name>/<version>/ and reused.
# Local file
fmu_source: {type: local, path: /abs/path/to/Model.fmu}
# MinIO / S3 (the docker-compose stack ships a MinIO service)
fmu_source:
type: minio
endpoint: http://localhost:9000
bucket: fmus
object_key: building/MyBuilding.fmu
# access_key/secret_key optional — fall back to MINIO_ACCESS_KEY/MINIO_SECRET_KEY env vars
# HTTP download
fmu_source: {type: http, url: https://example.com/MyBuilding.fmu}
2. Communication step must match the FMU
Many co-sim FMUs declare canHandleVariableCommunicationStepSize="false", meaning the communication step is fixed. You must set the federate's real_period (and the catalog time_step) to that fixed step. For an EnergyPlus FMU with Timestep = 6 per hour, the step is 3600 / 6 = 600 seconds.
timing_configs:
real_period: 600 # match the FMU's required communication step
3. EnergyPlus FMUs: defined stop time
FMUs generated from EnergyPlus IDF files (idf-to-fmu-export-prep) require a defined stop time. BaseFMUModel derives it automatically from the scenario start_time/end_time and passes it to setupExperiment (FMI 2.0) / enterInitializationMode (FMI 3.0). If the stop time is left undefined, EnergyPlus clamps it to 0 and the second doStep fails with fmi2Error.
EnergyPlus FMUs also create runtime working directories named Output_EPExport_<federate>.<n>/. These are git-ignored — do not commit them.
4. Worked example — BUI0 building FMU
The repository ships a complete two-federate example wiring a Python schedule feeder into an EnergyPlus building FMU.
- Scenario:
src/scenarios/bui0_fmu_test.yaml - FMU:
src/models/model_catalog/physical_models/resources/BUI0.fmu(FMI 2.0) - Feeder model:
bui0_input_feeder— generates six schedule signals (PeopleNumber,LightsWatt,EEquipWatt,OthEquRadWatt,OthEquFCWatt,ZoneSetPoint) from an hour-of-day occupancy profile. - FMU outputs:
TBuilding(zone air temperature),HeatingLoadTarget.
Data flow:
feeder_federate (bui0_input_feeder)
──6 schedule signals──▶ building_federate (bui0_building_fmu, BaseFMUModel)
──▶ TBuilding, HeatingLoadTarget
Both federates use real_period: 600; one simulated day = 144 steps. Run it:
# src/test_script.py
main('bui0_fmu_test')
docker compose -f src/docker-compose.yaml up -d
python src/models/model_catalog/catalog_loader.py
conda activate cosim_gym
python src/test_script.py
Inspect results/bui0_fmu_test/<sim_id>/federation_1/ — TBuilding and HeatingLoadTarget should be populated after the first step.