Writing YAML Configs#
The swarmsim.yaml module provides a custom YAML loader
that defines some convenience tags. This article will explain how
the YAML files are loaded and how to use the custom tags.
See also
Looking for how to load or dump YAML files with our custom tags?
See swarmsim.yaml
Or for information on how to use your custom class in a YAML file, see Config Registry.
When we set up a world configuration, swarmsim lets us either create the
configuration in Python, or define it in a YAML file. Here’s a simple example:
world.yaml#type: RectangularWorld
size: [8, 8]
time_step: !np 1 / 40
agents:
- type: MazeAgent
position: [4, 4]
controller:
type: StaticController
output: [0.1, 0.2]
This YAML file can be loaded and run like this:
from swarmsim import config_from_yaml, run_sim
world_config = config_from_yaml('world.yaml')
run_sim(world_config)
This is equivalent to the following Python code:
from swarmsim import RectangularWorldConfig, run_sim
from swarmsim.agent.MazeAgent import MazeAgentConfig
from swarmsim.agent.control.StaticController import StaticController
world_config = RectangularWorldConfig(
size=[8, 8],
time_step=1 / 40,
agents=[
MazeAgentConfig(
position=[4, 4],
controller=StaticController(output=[0.1, 0.2]),
),
],
)
run_sim(world_config)
As you can see, the YAML file is much more concise than the equivalent Python code.
The YAML Language#
YAML is a human-readable data serialization format. We use it to write configurations that describe simulations.
It’s a superset of JSON, so it’s easy to read and write.
We use the PyYAML library to load and dump YAML files.
See also
Here’s a nice and quick tutorial for YAML: Learn YAML in Y minutes
Or have a look at the YAML specification
Parametric YAML Configs via Jinja Templating#
When working with Python-based configuration files, you can create parametric configurations by passing a variable in a class or dataclass instantiation:
def world_configurator(width):
height = width / 2
RectangularWorldConfig(size=[width, height])
You can actually do this with YAML too by using Jinja templating! You can create a YAML file that acts like a function, taking in variables and returning a configuration:
world.yaml##%> set height = width / 2
type: RectangularWorld
size:
- <{ width }>
- <{ height }>
Then, you can pass in variables like this:
from swarmsim import config_from_yaml
world_config = config_from_yaml('world.yaml', width=6)
The <{ width }> and <{ height }> are Jinja blocks which are converted to
text before the YAML file is loaded. The #%> is a line statement prefix, which
allows you to set Jinja variables which can be referenced in the blocks.
You can also write statements like <% set height = width / 2 %> in the YAML file.
Note
The <% statement %>, <{ expression }>, and #%> line_statement delimiters are
slightly different from the usual Jinja syntax. This is because YAML files may also contain
specifications for modules that use Jinja templating inside them, such as the
JinjaMetric.
The different delimiters allow us to distinguish between Jinja at the YAML level and Jinja
templates and expressions that are used to specify other behavior.
See also
For information on our custom additions to the Jinja2 API, see swarmsim.util.jinja.
Jinja templating is a powerful tool for creating parametric configurations. Here’s some examples of what you can do:
Dynamic Includes
Since Jinja templates are evaluated down to pure YAML, you can dynamically import other YAML files:
world.yaml#type: RectangularWorld
agents:
- !include <{ agent_file }>.yaml
from swarmsim import config_from_yaml
world_config = config_from_yaml('world.yaml', agent_file='drone')
Default Values
You can set default values for variables in the YAML file with the default() filter:
world.yaml##%> set width = width|default(10)
type: RectangularWorld
size: [ <{ width }>, <{ width }> ]
time_step: <{ time_step|default('!np 1 / 40') }>
This will set the width to 10 if you don’t pass in a value for width.
You can also use the filter inside blocks. Note that we force it to become a string
so that YAML can parse the !np tag.
There are a lot more operators like this; see the full list of Jinja filters and Jinja tests.
Swappable Sections
You can use Jinja templating to swap out sections of a YAML file. Here’s an example where we can swap between a binary and a human controller:
world.yaml#type: RectangularWorld
agents:
- type: MazeAgent
#%> set controller_type = controller_type|default('binary')
<% if evader == 'binary' %>
sensors:
- type: BinaryFOVSensor
controller:
type: BinaryController
a: [0.2, 0.2]
b: [0.2, 0]
sensor_id: 0
<% elif evader == 'human' %>
controller:
type: HumanController
joystick: null
keys: arrowkeys
<% endif %>
Jinja2 offers a full set of control structures that haven’t been covered here. See the List of Control Structures for more information.
See also
Jinja2 is a very powerful and widely used templating engine, and we’ve only covered a small subset of its features here. For more information, see the official Template Designer Documentation guide.