Component variants
Most components in Trano (radiators, spaces, controls, air handling units,
boilers, valves, ...) ship with several pre-defined implementations called
variants. A variant is a concrete Modelica realisation of a component
class — for example the Radiator class has a default variant
(RadiatorEN442_2, a water radiator), an ideal variant (an idealised heat
injection block driven by its control) and an idealbus variant (the same,
driven through the data bus).
This tutorial explains:
- What variants are and where they live in the codebase.
- How to use an existing variant from your YAML model.
- How to create a new variant (built-in or in your own folder).
- How to register an external variants folder so Trano picks up your variants automatically.
- How variants interact with model validation.
- How to test custom variants.
1. What is a variant?
A variant describes how an instance of a Trano element class (e.g.
Radiator, Space, EmissionControl, AirHandlingUnit, ...) is rendered to
Modelica code.
Each variant is a YAML entry that contains:
| Field | Purpose |
|---|---|
classes |
The Trano element class(es) this variant applies to (e.g. Radiator). |
variant |
The string name used to select this variant from a YAML model. |
library |
The library (or list of libraries) this variant targets (default, ideas, buildings, FastSmoothBuildings, ...). |
template |
The Jinja2 template rendered into the generated Modelica model. |
ports |
Connection ports the component exposes (fluid, heat, data, weather). |
parameter_processing |
How the user-provided parameters are mapped to the template. |
figures |
Plots auto-generated for the reporting view (optional). |
component_template |
An optional inner Modelica model wrapping the component (optional). |
The built-in variants live under
trano/elements/library/models/<library>/<component>.yaml. Each YAML file
contains a list of variant definitions for one or several classes. A
definition whose library is default serves every library that has no
definition of its own for that class and variant.
Examples of variants currently shipped
The table below summarises the variants that already exist for the most common components, so you can pick the right one for your model. The full list can always be discovered with:
from trano.elements.library.components import get_components_registry
for c in get_components_registry().components:
print(c["classes"], c["variant"], c.get("library"))
Radiator (Radiator)
| Variant | Use case |
|---|---|
default |
Detailed Buildings.Fluid.HeatExchangers.Radiators.RadiatorEN442_2 water radiator with hydronic ports, followed by a valve. |
ideal |
Idealised heat emission driven by its emission control — no fluid loop required. |
idealbus |
Idealised heat emission driven by its control through the data bus. |
Space (Space)
| Variant | Use case |
|---|---|
default |
The library's zone model (MixedAir for Buildings, IDEAS...Zone for IDEAS, 5R1C for iso_13790, RC for reduced_order). |
infiltration |
Same zone, configured for infiltration. |
rectangular_zone |
IDEAS RectangularZoneTemplate (IDEAS only). |
Air Handling Unit (AirHandlingUnit)
| Variant | Use case |
|---|---|
default |
Detailed AHU partial model with VAV boxes, dampers and fans (needs an ahu_control). |
system_d |
System-D mechanical balanced ventilation with heat recovery. |
test |
Simplified HVAC model — no VAV boxes. |
Emission Control (EmissionControl)
| Variant | Use case |
|---|---|
default |
ASHRAE G36 thermal zone control loops with heating/cooling outputs. |
onoff |
Simple on/off thermostat with occupancy schedule. |
PController |
Proportional (P) controller. |
MonitoredSetpoint |
P controller whose setpoint is read from a data source (e.g. measured boundary data). |
Boiler (Boiler)
| Variant | Use case |
|---|---|
default |
Gas boiler with a thermal storage tank. |
without_storage |
Gas boiler without storage (direct loop). |
water_water_heat_pump |
Water/water heat pump as production system. |
air_water_heat_pump |
Air/water heat pump as production system. |
Boiler Control (BoilerControl)
| Variant | Use case |
|---|---|
default |
Default boiler control loop. |
complex |
Detailed boiler control with cascade logic. |
simple |
Simple on/off boiler control. |
VAV box (VAV)
| Variant | Use case |
|---|---|
default |
Pressure-independent damper as a single block (takes a vav_control of variant constant). |
complex |
Full VAV box partial model with reheat coil, dampers, sensors (takes a vav_control of variant default). |
VAV Control (VAVControl)
| Variant | Use case |
|---|---|
default |
ASHRAE G36 reheat-box terminal-unit controller. |
constant |
Simple constant-flow VAV controller. |
Occupancy (Occupancy)
| Variant | Use case |
|---|---|
default |
Schedule-based occupancy block. |
co2 |
Occupancy with CO2 generation. |
Floor on ground (FloorOnGround)
| Variant | Use case |
|---|---|
default |
Floor on the ground. |
outdoor_air |
Floor exposed to outdoor air. |
2. Selecting a variant from a YAML model
Use the variant: field of the component you are configuring. If a component
does not declare a variant, Trano falls back to the variant called
default.
spaces:
- id: SPACE:001
parameters:
floor_area: 80.0
average_room_height: 2.5
external_boundaries: ...
emissions:
- radiator:
id: RADIATOR:001
# Use the *ideal* radiator instead of the default detailed one
variant: ideal
parameters:
nominal_heating_power_positive_for_heating: 2500
control:
emission_control:
id: EMISSION_CONTROL:001
# Use the on/off emission control variant
variant: onoff
parameters:
schedule: 3600*{10, 20}
temperature_heating_setpoint: 295.15
temperature_heating_setback: 291.15
The variant key works the same way for any component in the YAML schema:
spaces[i].variant, boiler.variant, air_handling_unit.variant,
vav.variant, occupancy.variant, etc.
3. Creating a new variant
Creating a new variant is a small, focused YAML edit. There are two equally valid options:
| Option | When to use |
|---|---|
| A — built-in variant | The variant should ship with Trano and be available to everyone. |
| B — external variants folder | The variant is project- or vendor-specific; lives in your repo. |
In both cases the YAML schema is identical.
Step-by-step
- Pick the class you want to extend (e.g.
Radiator). - Pick a unique variant name for that class. The name + library combo
must be unique. The string is what users will write under
variant:in their YAML model. - Write the Modelica template as a Jinja2 string. Trano renders it with
the
element,package_name,library_nameandparametersvariables (see the existing templates undertrano/elements/library/models/for reference). - Declare the ports — for hydronic components typically
port_a/port_bfluid ports, plus optionalheatports (heatPortCon,heatPortRad) anddataports for control buses. The declared ports are also how Trano classifies the component when validating the model: a radiator variant with fluid ports is a water radiator (and must be followed by a valve); one without fluid ports needs no hydronic loop. - Choose a
parameter_processingfunction —default_parameters,modify_alias,exclude_parameters(seetrano/elements/library/parameters.py). - Save the file.
Below is the YAML you need to add a custom radiator that injects a fixed
heat flow, driven by its control (same shape as the built-in ideal
variant):
- classes:
- Radiator
variant: my_constant_emission
library: default
parameter_processing:
function: default_parameters
ports:
- flow: convective
medium: heat
names: [heatPortCon]
targets: [Space]
- flow: radiative
medium: heat
names: [heatPortRad]
targets: [Space]
- flow: undirected
medium: data
names: [y]
targets: [Control]
template: |2-
MyCompany.Trano.Emission.ConstantHeatRadiator
{{ element.name }}(
{{ macros.render_parameters(parameters) | safe }})
"Constant-power radiator"
figures: []
Option A — Add the variant to Trano's built-in catalogue
Drop the YAML snippet above (as a list) into the right file under
trano/elements/library/models/<library>/. For a radiator targeting every
library, append it to
trano/elements/library/models/default/radiator.yaml.
Built-in variants are also validated against the schema enums in
trano/data_models/trano.yaml (e.g. RadiatorVariant), so a new built-in
variant must be added there (and in trano_final.yaml) as well.
Option B — Keep the variant in an external folder
If your variant should not live inside Trano (proprietary code, project specific, internal Modelica library...), place it in any folder of your choice and tell Trano about it.
my_project/
├── modelica/
│ └── MyCompany/...
└── trano_variants/ ← any path, anywhere
└── radiator_constant.yaml ← contains the YAML list shown above
There are three equivalent ways to register the folder:
From the Python API
from trano.elements.library.components import register_variants_folder
from trano.main import create_model
register_variants_folder("my_project/trano_variants")
create_model("my_project/model.yaml")
register_variants_folder accepts str and pathlib.Path. The folder is
scanned recursively for *.yaml files, so you can organise variants
into sub-folders.
From the CLI
trano create-model my_project/model.yaml --variants-folder my_project/trano_variants
trano simulate-model my_project/model.yaml --variants-folder my_project/trano_variants
trano compile-model my_project/model.yaml --variants-folder my_project/trano_variants
The --variants-folder option (-V) can be passed multiple times to
register several folders.
From an environment variable
Trano reads TRANO_VARIANTS_PATH whenever the component registry is
loaded. The value is an os.pathsep-separated list of folders (: on
Linux/macOS, ; on Windows).
Using the new variant
Once registered, your variant is just like any built-in one — reference it by name:
emissions:
- radiator:
id: RADIATOR:001
variant: my_constant_emission
parameters:
nominal_heating_power_positive_for_heating: 1500
control:
emission_control:
id: EMISSION_CONTROL:001
4. Variants and model validation
Trano validates every model before generating it: against the schema (unknown fields, enums, bounds) and against topology rules (water loops, valves, controls). Externally registered variants take part in both:
- The schema's variant enums only know the built-in variants; a
variant:value that names a registered external variant of that component is accepted. - A radiator variant is classified by its declared ports: with fluid
ports it is a water radiator and the usual rule applies (it must be
followed by a valve in the
emissionsof its space); without fluid ports it is treated like the ideal variants and needs no hydronic loop.
An unknown variant (not built-in, not registered) is still rejected with the usual one-line validation error.
5. Programmatic listing and inspection
from trano.elements.library.components import (
get_components_registry,
register_variants_folder,
registered_variants_folders,
clear_variants_folders,
)
register_variants_folder("./trano_variants")
# What folders are currently registered?
print(registered_variants_folders())
# All variants known to Trano (built-in + external)
for c in get_components_registry().components:
if "Radiator" in c["classes"]:
print(c["library"], c["variant"])
# Reset to the built-in catalogue (useful in tests)
clear_variants_folders()
6. Testing your custom variant
A custom variant is just YAML, but it is still good practice to test it end-to-end. The recommended pattern:
from pathlib import Path
import pytest
from trano.elements.library.components import (
clear_variants_folders,
register_variants_folder,
)
from trano.elements.system import Radiator
@pytest.fixture(autouse=True)
def reset_variants():
clear_variants_folders()
yield
clear_variants_folders()
def test_my_variant_is_registered(tmp_path: Path):
register_variants_folder(Path(__file__).parent / "trano_variants")
radiator = Radiator(name="r1", variant="my_constant_emission")
library_data = next(ld for ld in radiator.libraries_data
if ld.variant == "my_constant_emission")
assert "ConstantHeatRadiator" in library_data.template
For an end-to-end test that builds a network and a Modelica model from a
YAML file, see
tests/test_variants.py::test_create_model_yaml_using_custom_variant.
7. Cheat sheet
| I want to ... | Do this |
|---|---|
| Use an existing variant | Set variant: <name> on the component in your YAML model. |
| Add a new variant for everyone | Edit trano/elements/library/models/<library>/<component>.yaml and the variant enums in trano/data_models/trano.yaml / trano_final.yaml. |
| Add a new variant only for my project | Put YAML files in a folder, then register_variants_folder(folder). |
| Register variants without changing my Python code | export TRANO_VARIANTS_PATH=/path/to/folder. |
| Register variants from the CLI | trano create-model model.yaml --variants-folder /path/to/folder (also on simulate-model and compile-model). |
| List registered external folders | registered_variants_folders(). |
| Reset to built-in only | clear_variants_folders(). |
That's it — variants are the central extension point of Trano. Any new modelling capability (a new emission technology, a new control law, a new zone model from your in-house Modelica library...) is just a new YAML file.