Skip to content

Input Files

Note

This page provides examples for how to write FDS files. However, all file listed under the xfds.render.files in the config file will have access to the template syntax described below.

Example

All the input and output files on this page can be found in the xFDS Examples directory.

xFDS allows you to add more features to your FDS input files using the Jinja template syntax. While this page covers a high level overview of the Jinja syntax, the user is encouraged to read the Jinja documentation for more information.

xFDS uses Jinja's default delimeters. They are:

  • {{ ... }} indicates an expression. This can be a variable, calculation, or a function call.
  • {% ... %} indicates a statement such as if/else conditionals and loops.
  • {# ... #} indicates a comment and the contents of the block will be removed from the final fds file.

Normal FDS syntax is always valid, but you can use Jinja to help build your FDS lines.

Defining Variables

Jinja Docs

See the Jinja documentation for more information on variables

The ability to set and use variables is what makes xFDS so powerful. There are two ways to define variables for your model.

Using Jinja's Assignments

Jinja allows you to assign values in the middle of your template. This can be useful when you want to use a value several times without having to type the value out each time. Variables also make it easy to keep things consistent. The variable may also be dynamic based on other inputs.

The following example automatically calculates the HRRPUA parameter and XB bounds for a 1000 kW fire on a 1.5 m2 burner. Note how:

  • Line 9: top is set so that the &VENT always sits on the top of the &OBST. Updating the variable will set the zmin and zmax for the &VENT as well as the zmax for the &OBST.
  • Line 10: area is defined in at the top, but the length of a side (for a square burner) is calculated in the template.
  • Line 11: r (radius) is defined to be half the length of a side. This helps define the XB so the burner is perfectly centered regardless of the area.
  • Line 12: HRRPUA is calculated based on the hrr and area variables defined. This way HRRPUA is always correct if either hrr or area are updated.
  • Lines 13-14: The XB parameters use r, top, and depth to ensure the burner is centered and that the &VENT always aligns with &OBST.
examples/hrrpua/hrrpua.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{% set hrr = 1000 -%}
{% set area = 1.5 -%}
{% set depth = 0.2 -%}

&MESH XB=-2, 2, -2, 2, 0, 4, IJK=40, 40, 40/
&TIME T_END=30/
&REAC FUEL='PROPANE'/

{% set top = 0.2 %}
{% set side = area ** 0.5 %}
{% set r = (-side / 2)|round(4) %}
&SURF ID='BURNER', COLOR='RED', HRRPUA={{ (hrr / area)|round(2) }}/
&OBST XB={{ (-r, r, -r, r, top - depth, top)|xb }}/
&VENT XB={{ (-r, r, -r, r, top, top)|xb }}, SURF_ID='BURNER'/
examples/hrrpua/output/hrrpua/hrrpua.fds
1
2
3
4
5
6
7
&MESH XB=-2, 2, -2, 2, 0, 4, IJK=40, 40, 40/
&TIME T_END=30/
&REAC FUEL='PROPANE'/

&SURF ID='BURNER', COLOR='RED', HRRPUA=666.67/
&OBST XB=  0.612, -0.612,  0.612, -0.612,  0.000,  0.200/
&VENT XB=  0.612, -0.612,  0.612, -0.612,  0.200,  0.200, SURF_ID='BURNER'/

Variables can be set multiple times

If a variable needs to take on different values for different parts of the model, the variable can be assigned a new value. Note how top is redefined on line 16 and lines 17-18 are the same as lines 13-14. This will change the zmin and zmax values for the second burner.

examples/hrrpua2/hrrpua2.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{% set hrr = 1000 -%}
{% set area = 1.5 -%}
{% set depth = 0.2 -%}

&MESH XB=-2, 2, -2, 2, 0, 4, IJK=40, 40, 40/
&TIME T_END=30/
&REAC FUEL='PROPANE'/

{% set top = 0.2 %}
{% set side = area ** 0.5 %}
{% set r = (-side / 2)|round(4) %}
&SURF ID='BURNER', COLOR='RED', HRRPUA={{ (hrr / area)|round(2) }}/
&OBST XB={{ (-r, r, -r, r, top - depth, top)|xb }}/
&VENT XB={{ (-r, r, -r, r, top, top)|xb }}, SURF_ID='BURNER'/

{% set top = 1.2 %}
&OBST XB={{ (-r, r, -r, r, top - depth, top)|xb }}/
&VENT XB={{ (-r, r, -r, r, top, top)|xb }}, SURF_ID='BURNER'/
examples/hrrpua2/output/hrrpua2/hrrpua2.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
&MESH XB=-2, 2, -2, 2, 0, 4, IJK=40, 40, 40/
&TIME T_END=30/
&REAC FUEL='PROPANE'/

&SURF ID='BURNER', COLOR='RED', HRRPUA=666.67/
&OBST XB=  0.612, -0.612,  0.612, -0.612,  0.000,  0.200/
&VENT XB=  0.612, -0.612,  0.612, -0.612,  0.200,  0.200, SURF_ID='BURNER'/

&OBST XB=  0.612, -0.612,  0.612, -0.612,  1.000,  1.200/
&VENT XB=  0.612, -0.612,  0.612, -0.612,  1.200,  1.200, SURF_ID='BURNER'/

In the Configuration File

When generating multiple output files from a single fds input file, a configuration file should be used. See Configuration for more details.

Control Structures

If Statements

Jinja Docs

See the Jinja documentation for more information on if statements.

To make FDS records optional, use an if statement. The expression following the if keyword must evaluate to True or False. See this Real Python article for more information on how Python evaluates "truthiness".

In the example below, a varibale is defined for opening the mesh boundaries vs leaving them closed (default in FDS). Note how the only difference between the two files are the value of bounds on line 1. When the bounds are closed, the &VENT lines are omitted from the output file.

Open Bounds

examples/bounds_open/bounds_open.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{% set bounds = "open" -%}

&MESH XB=0, 1, 0, 1, 0, 1, IJK=10, 10, 10/

{% if bounds == "open" %}
&VENT MB='XMIN', SURF_ID='OPEN'/
&VENT MB='XMAX', SURF_ID='OPEN'/
&VENT MB='YMIN', SURF_ID='OPEN'/
&VENT MB='YMAX', SURF_ID='OPEN'/
{% endif %}
examples/bounds_open/output/bounds_open/bounds_open.fds
1
2
3
4
5
6
&MESH XB=0, 1, 0, 1, 0, 1, IJK=10, 10, 10/

&VENT MB='XMIN', SURF_ID='OPEN'/
&VENT MB='XMAX', SURF_ID='OPEN'/
&VENT MB='YMIN', SURF_ID='OPEN'/
&VENT MB='YMAX', SURF_ID='OPEN'/
Closed Bounds
examples/bounds_closed/bounds_closed.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{% set bounds = closed -%}

&MESH XB=0, 1, 0, 1, 0, 1, IJK=10, 10, 10/

{% if bounds == "open" %}
&VENT MB='XMIN', SURF_ID='OPEN'/
&VENT MB='XMAX', SURF_ID='OPEN'/
&VENT MB='YMIN', SURF_ID='OPEN'/
&VENT MB='YMAX', SURF_ID='OPEN'/
{% endif %}
examples/bounds_closed/output/bounds_closed/bounds_closed.fds
1
&MESH XB=0, 1, 0, 1, 0, 1, IJK=10, 10, 10/

If the model requires mutally exclusive options, if/elif/else blocks can be used.

{% if ventillation == "natural" %}
    # open doors and windows
{% elif ventillation == "mechanical" %}
    # insert fans
{% else %}
    # room is sealed
{% endif %}

For Loops

Jinja Docs

See the Jinja documentation for more information on for loops

Sometimes it is beneficial to iterate through a list or quickly generate an array of items. While some FDS records can be duplicated with a &MULT record, this is not always the case.

Imagine needing to quickly layout a grid of sprinklers, and the sprinklers need to have unique names (e.g. for developing &CTRL records). Devices do not support &MULT records. For loops can assist in generating the sprinkler grid while calculating the position for each sprinkler individually.

By defining the number of sprinklers in each direction (nx, ny) and the sprinkler spacing, the location of the sprinklers are determined. The linspace filter will create nx sprinklers evenly spaced between -dx / 2 and dx / 2 and simlar for the y direction.

examples/sprinkler_loop/sprinkler_loop.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{% set nx = 6 %}
{% set ny = 4 %}
{% set offset = 0.3048 %}
{% set spacing = 2.438 %}
{% set dx = spacing * (nx - 1) %}
{% set dy = spacing * (ny - 1) %}
{% set zmax = 3 %}
{% set res = 0.2 %}
{% set mesh = -dx / 2, dx / 2, -dy / 2, dy / 2, 0, zmax -%}

&MESH XB={{ mesh|xb }}, IJK={{ mesh|ijk(res) }}

&PROP ID='Link',
      QUANTITY='SPRINKLER LINK TEMPERATURE',
      ACTIVATION_TEMPERATURE=74/

{% for x in nx|linspace(-dx / 2, dx / 2) %}
{% set i = loop.index %}
{% for y in ny|linspace(-dy / 2, dy / 2) %}
{% set j = loop.index %}
&DEVC ID='SPR_{{i}}_{{j}}', XYZ={{ (x, y, zmax - offset)|xyz }}, PROP_ID='Link'/
{% endfor %}
{% endfor %}

&OBST XB={{ (-0.5, 0.5, -0.5, 0.5, 0.0, 0.1)| xb }}, COLOR='RED'/

Tip

When looping, it might be useful to know what loop iteration is being processed. The loop index can be accessed by {{ loop.index }}. This is used to set i and j for the device naming scheme above.

Note how each sprinkler has a unique ID and the &MESH will adjust based on the number of sprinklers. Additionally, the &OBST representing the burner is perfectly centered between the 4 central sprinklers!

examples/sprinkler_loop/output/sprinkler_loop/sprinkler_loop.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
&MESH XB= -6.095,  6.095, -3.657,  3.657,  0.000,  3.000, IJK=61,37,15

&PROP ID='Link',
      QUANTITY='SPRINKLER LINK TEMPERATURE',
      ACTIVATION_TEMPERATURE=74/

&DEVC ID='SPR_1_1', XYZ= -6.095, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_1_2', XYZ= -6.095, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_1_3', XYZ= -6.095,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_1_4', XYZ= -6.095,  3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_2_1', XYZ= -3.657, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_2_2', XYZ= -3.657, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_2_3', XYZ= -3.657,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_2_4', XYZ= -3.657,  3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_3_1', XYZ= -1.219, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_3_2', XYZ= -1.219, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_3_3', XYZ= -1.219,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_3_4', XYZ= -1.219,  3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_4_1', XYZ=  1.219, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_4_2', XYZ=  1.219, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_4_3', XYZ=  1.219,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_4_4', XYZ=  1.219,  3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_5_1', XYZ=  3.657, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_5_2', XYZ=  3.657, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_5_3', XYZ=  3.657,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_5_4', XYZ=  3.657,  3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_6_1', XYZ=  6.095, -3.657,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_6_2', XYZ=  6.095, -1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_6_3', XYZ=  6.095,  1.219,  2.695, PROP_ID='Link'/
&DEVC ID='SPR_6_4', XYZ=  6.095,  3.657,  2.695, PROP_ID='Link'/

&OBST XB= -0.500,  0.500, -0.500,  0.500,  0.000,  0.100, COLOR='RED'/

Macros

Jinja Docs

See the Jinja documentation for more information on macros

Macros are useful when defining complex elements that require multiple lines of FDS code or simple elements that are reused multiple times.

For example, defining a simple leakage path through a door requires at least three different FDS records, two &VENTs and one &HVAC. It may be beneficial to include an &OBST record to ensure the &VENT records are applied to a solid surface.

The example below creates doors along a wall in a corridor. A macro is defined on line 11 that takes in four parameters: the x position of the door, width and height of the door, and the leakage area. (The y position is fixed along the wall). The macro will determine the extends of the XB parameters and use a consistent naming scheme to tie the elements together.

Tip

Macros can be called by passing in a list of values, or by specifying the parameter and value. Both options work the same, but specifying the parameter and value will make the file more readable.

38
{{ door(1.0, 1.0, 2.0, 0.3) }}
41
{{ door(x=3.0, width=2.0, height=2.0, leak_area=0.6) }}

Additionally, tenability devices are placed at 1 meter intervals along the corridor. A macro is defined on line 19 to ensure all the tenability criteria are defined at each location (visibility, temperature, O2, CO2, CO). The macro takes the x and y positions and will place all five devices at that location (the z position is hard coded in this example). A for loop (line 44) is used to set the 1 meter spacing starting at x = 1 m until the end of the corridor is reached.

examples/leaks/leaks.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% set x0 = 0 -%}
{% set x1 = 6 -%}
{% set y0 = 0 -%}
{% set y1 = 3 -%}
{% set z0 = 0 -%}
{% set z1 = 3 -%}
{% set res = 0.1 -%}

{% set ns = namespace(door_id=0, devc_loc=0) -%}

{% macro door(x, width, height, leak_area) -%}
{% set ns.door_id = ns.door_id + 1 %}
&OBST ID='Door_{{ ns.door_id }}',   XB={{ (x, x + width, 2.0, 2.2, 0.0, height)|xb }}/
&VENT ID='Door_{{ ns.door_id }}_i', XB={{ (x, x + width, 2.0, 2.0, 0.0, height)|xb }}, SURF_ID='LEAK'/
&VENT ID='Door_{{ ns.door_id }}_o', XB={{ (x, x + width, 2.2, 2.2, 0.0, height)|xb }}, SURF_ID='LEAK'/
&HVAC ID='Door_{{ ns.door_id }}_l', VENT_ID='Door_{{ ns.door_id }}_i', VENT2_ID='Door_{{ ns.door_id }}_o', AREA={{ leak_area }}, TYPE_ID='LEAK'/
{%- endmacro -%}

{% macro tenability(x, y) -%}
{% set ns.devc_loc = ns.devc_loc + 1 %}
&DEVC ID='VIS_{{ ns.devc_loc }}', XYZ={{ (x, y, 1.8)|xyz }}, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_{{ ns.devc_loc }}', XYZ={{ (x, y, 1.8)|xyz }}, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_{{ ns.devc_loc }}', XYZ={{ (x, y, 1.8)|xyz }}, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_{{ ns.devc_loc }}', XYZ={{ (x, y, 1.8)|xyz }}, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_{{ ns.devc_loc }}', XYZ={{ (x, y, 1.8)|xyz }}, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/
{%- endmacro -%}

# Corridor with open ends and wall on one side
{% set mesh = x0, x1, y0, y1, z0, z1 %}
&MESH XB={{ mesh|xb }}, IJK={{ mesh|ijk(res) }}/
&VENT MB='XMIN', SURF_ID='OPEN'/
&VENT MB='XMAX', SURF_ID='OPEN'/
&OBST XB={{ (x0, x1, 2.0, 2.2, z0, z1)|xb }}/

&SURF ID='LEAK', COLOR='FIREBRICK'/

# Single Door
{{ door(1.0, 1.0, 2.0, 0.3) }}

# Double Door
{{ door(x=3.0, width=2.0, height=2.0, leak_area=0.6) }}

# Tenability Devices
{% for x in range(1, x1) %}
{{ tenability(x, 1.0)}}
{% endfor %}
examples/leaks/output/leaks/leaks.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Corridor with open ends and wall on one side
&MESH XB=  0.000,  6.000,  0.000,  3.000,  0.000,  3.000, IJK=60,30,30/
&VENT MB='XMIN', SURF_ID='OPEN'/
&VENT MB='XMAX', SURF_ID='OPEN'/
&OBST XB=  0.000,  6.000,  2.000,  2.200,  0.000,  3.000/

&SURF ID='LEAK', COLOR='FIREBRICK'/

# Single Door
&OBST ID='Door_1',   XB=  1.000,  2.000,  2.000,  2.200,  0.000,  2.000/
&VENT ID='Door_1_i', XB=  1.000,  2.000,  2.000,  2.000,  0.000,  2.000, SURF_ID='LEAK'/
&VENT ID='Door_1_o', XB=  1.000,  2.000,  2.200,  2.200,  0.000,  2.000, SURF_ID='LEAK'/
&HVAC ID='Door_1_l', VENT_ID='Door_1_i', VENT2_ID='Door_1_o', AREA=0.3, TYPE_ID='LEAK'/

# Double Door
&OBST ID='Door_2',   XB=  3.000,  5.000,  2.000,  2.200,  0.000,  2.000/
&VENT ID='Door_2_i', XB=  3.000,  5.000,  2.000,  2.000,  0.000,  2.000, SURF_ID='LEAK'/
&VENT ID='Door_2_o', XB=  3.000,  5.000,  2.200,  2.200,  0.000,  2.000, SURF_ID='LEAK'/
&HVAC ID='Door_2_l', VENT_ID='Door_2_i', VENT2_ID='Door_2_o', AREA=0.6, TYPE_ID='LEAK'/

# Tenability Devices
&DEVC ID='VIS_1', XYZ=  1.000,  1.000,  1.800, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_1', XYZ=  1.000,  1.000,  1.800, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_1', XYZ=  1.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_1', XYZ=  1.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_1', XYZ=  1.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/
&DEVC ID='VIS_2', XYZ=  2.000,  1.000,  1.800, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_2', XYZ=  2.000,  1.000,  1.800, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_2', XYZ=  2.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_2', XYZ=  2.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_2', XYZ=  2.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/
&DEVC ID='VIS_3', XYZ=  3.000,  1.000,  1.800, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_3', XYZ=  3.000,  1.000,  1.800, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_3', XYZ=  3.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_3', XYZ=  3.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_3', XYZ=  3.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/
&DEVC ID='VIS_4', XYZ=  4.000,  1.000,  1.800, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_4', XYZ=  4.000,  1.000,  1.800, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_4', XYZ=  4.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_4', XYZ=  4.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_4', XYZ=  4.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/
&DEVC ID='VIS_5', XYZ=  5.000,  1.000,  1.800, QUANTITY='VISIBILITY'/
&DEVC ID='TMP_5', XYZ=  5.000,  1.000,  1.800, QUANTITY='TEMPERATURE'/
&DEVC ID='OXY_5', XYZ=  5.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='OXYGEN'/
&DEVC ID='CO2_5', XYZ=  5.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON DIOXIDE'/
&DEVC ID='CMO_5', XYZ=  5.000,  1.000,  1.800, QUANTITY='VOLUME FRACTION', SPEC_ID='CARBON MONOXIDE'/

Tip

Use namespace to set up a counter that can be incremented to ensure unique IDs. Counters are initialized on line 9 as follows:

9
{% set ns = namespace(door_id=0, devc_loc=0) -%}

The following lines are located at the top of the respective macros and will increment the number by 1 every time the macro is called.

12
{% set ns.door_id = ns.door_id + 1 %}
20
{% set ns.devc_loc = ns.devc_loc + 1 %}

Filters

Jinja Filters

Jinja Docs

See the Jinja documentation for more information on filters.

Filters can modify the value of a variable or expression. This is useful when you need to ensure values follow a certain format or if a value needs to be modified. The examples below demonstrate how to use some of the built-in filters provided by Jinja.

Absolute Value

examples/filters/abs.fds
1
{{ -42|abs }}
examples/filters/output/filters/abs.fds
1
42

Center Text

examples/filters/center.fds
1
"{{ "xFDS"|center(10) }}"
examples/filters/output/filters/center.fds
1
"   xFDS   "

Convert to Float

Tip

In addition to the float filter, this example uses python's printf-style formatting along with the format filter to control the number of decimals displayed.

%7.4f"|format(x) tells xFDS how to format the value of x.

  • %f: format as a float
  • %.3f: format as a float to 3 decimals
  • %8.3f: format as float to 3 decimals and a fixed width of 8 characters (includes decimal '.', sign '-', and extra white space at the beginning)

examples/filters/float.fds
1
2
3
4
{{ 42|float }}
{{ "%f"|format(42)}}
{{ "%.3f"|format(42)}}
"{{ "%8.3f"|format(-42)}}"
examples/filters/output/float/float.fds
1
2
3
4
42.0
42.000000
42.000
" -42.000"

Convert to integer

examples/filters/int.fds
1
2
{{ 42.0|int }}
{{ "%d"|format(42.0)}}
examples/filters/output/int/int.fds
1
2
42
42

Maximum Value

examples/filters/max.fds
1
{{ [1, 2, 3]|max }}
examples/filters/output/max/max.fds
1
3

Minimum Value

examples/filters/min.fds
1
{{ [1, 2, 3]|min }}
examples/filters/output/min/min.fds
1
1

Round Value

examples/filters/round.fds
1
{{ 3.14159|round(3)}}
examples/filters/output/round/round.fds
1
3.142

Trim Text

examples/filters/trim.fds
1
"{{ ' xFDS '|trim }}"
examples/filters/output/trim/trim.fds
1
"xFDS"

Filter Unique Values

examples/filters/unique.fds
1
2
# unique
{{ [1, 1, 2, 2]|unique|list }}
examples/filters/output/unique/unique.fds
1
2
# unique
[1, 2]

Text to Uppercase

examples/filters/upper.fds
1
{{ 'pbd tools'|upper }}
examples/filters/output/upper/upper.fds
1
PBD TOOLS

xFDS Custom Filters

In addition to the built-in filters that comes with Jinja, xFDS ships with some additional filters useful for creating FDS records.

ARange

To create evenly spaced items, Python's range() function could be used, but it requires integer values for its parameters. Numpy's arange() function allows floats to be used. xFDS defines this as a filter for convience.

examples/filters/arange.fds
1
2
3
4
5
6
7
8
{% for x in 0.25|arange(1.5, 2.5) %}
&DEVC XYZ={{ (x, 0, 0)|xyz }}, .../
{% endfor %}

{#
    This would cause an error
    for x in range(1.5, 2.5, 0.25)
#}
examples/filters/output/arange/arange.fds
1
2
3
4
&DEVC XYZ=  1.500,  0.000,  0.000, .../
&DEVC XYZ=  1.750,  0.000,  0.000, .../
&DEVC XYZ=  2.000,  0.000,  0.000, .../
&DEVC XYZ=  2.250,  0.000,  0.000, .../

Convert

Thanks to the magic of pint, xFDS will allow you to convert between units. You can define values in the config file with the desired units (determined by you) while ensuring that the correct units are passed to FDS. The convert will return a float type which could be used in further calculations. If the conversion will be part of the final output, the formatting can be controlled by using the format and convert filters together. Alternatively, str_convert can be used to improve readability.

Info

Pint allows custom units to be defined. xFDS will detect a file called units.txt located in the same directory as the configuration file. Here is a simple definition of a smoot.

examples/filters/units.txt
1
smoot = 1.702 * meter
examples/filters/convert.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{{ 212|convert('degF', 'degC') }}
{{ 212|str_convert('degF', 'degC', '%.0f') }}
{{ '%.0f'|format(212|convert('degF', 'degC')) }}

{{ 6|convert('ft', 'm')}}
{{ 6|str_convert('ft', 'm', '%.3f')}}

{{ 100_000|convert('ft^3/min', 'm^3/s')}}
{{ 100_000|convert('cfm', 'm^3/s')}}
{{ 100_000|str_convert('cfm', 'm^3/s', '%.2f')}}

{{ 1_000|convert('BTU/s', 'kW')}}

{{ 1|convert('smoot', 'm')}}
{{ 1|convert('smoot', 'ft')}}
{{ 1|str_convert('smoot', 'ft', '%.3f')}}
examples/filters/output/convert/convert.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
100.00000000000006
100
100

1.8287999999999998
1.829

47.19474431999999
47.19474431999999
47.19

1055.056

1.702
5.583989501312336
5.584

DXB

Similar to the xb filter below, dxb takes a triplet representing the anchor point (x, y, z) and parameters to set the width, depth, and height respectfully. Specify xloc, yloc, or zloc as min, max, or mid to indicate how the anchor point should be treated. dxb also accepts a format string.

examples/filters/dxb.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Centered at (0, 0, 0) by default
&OBST XB={{ (0, 0, 0)|dxb(2, 4, 1)}}/

# Only xmax and zmin are `0`,
&OBST XB={{ (0, 0, 0)|dxb(2, 4, 1, xloc='max', yloc='mid', zloc='min')}}/

# zloc = 'max' forces the xb to be drawn below 0.0.
&VENT XB={{ (0, 0, 0)|dxb(2, 0, 10, zloc='max')}}/

# Same as above, but formatted differntly.
&VENT XB={{ (0, 0, 0)|dxb(2, 0, 10, zloc='max', fmt="%5.2f")}}/
examples/filters/output/dxb/dxb.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Centered at (0, 0, 0) by default
&OBST XB= -1.000,  1.000, -2.000,  2.000, -0.500,  0.500/

# Only xmax and zmin are `0`,
&OBST XB= -2.000,  0.000, -2.000,  2.000,  0.000,  1.000/

# zloc = 'max' forces the xb to be drawn below 0.0.
&VENT XB= -1.000,  1.000,  0.000,  0.000,-10.000,  0.000/

# Same as above, but formatted differntly.
&VENT XB=-1.00, 1.00, 0.00, 0.00,-10.00, 0.00/

Exhaust/Supply

For a &VENT, the sign of either VOLUME_FLOW or VELOCITY defined on the &SURF indicates if the vent is a supply or exhaust. These filters take the value specified and ensure the sign is correct. This makes the intent of the surface type clear to the reader.

Note

In this example, the exhaust and supply are both calculated as positive values, but the filters ensure the signs are correct for the respective types.

examples/filters/exhaust.fds
1
2
3
4
5
{% set ex = 100_000|convert('cfm', 'm^3/s') %}
{% set sup = 0.95 * ex %}

&SURF ID='EXHAUST', VOLUME_FLOW={{ ex|exhaust|round(1) }}/
&SURF ID='SUPPLY',  VOLUME_FLOW={{ sup|supply|round(1) }}/
examples/filters/output/exhaust/exhaust.fds
1
2
&SURF ID='EXHAUST', VOLUME_FLOW=47.2/
&SURF ID='SUPPLY',  VOLUME_FLOW=-44.8/

IJK

The ijk filter will take an xb sextuplet along with a resolution to calculate the IJK values for a MESH. The way ijk converts a float to an integer can be controlled by passing in a rouding parameter.

  • rounding='ceil': Round the number up to the nearest integer.
  • rounding='round': Round the number to the nearest integer (default).
  • rounding='floor': Round the number down to the nearest integer.

examples/filters/ijk.fds
1
2
3
4
{%+ set mesh = (0, 5, 0, 4, 0, 3) +%}
&MESH XB={{ mesh|xb("%5.2f") }}, IJK={{ mesh|ijk(0.3, rounding='ceil') }}/
&MESH XB={{ mesh|xb("%5.2f") }}, IJK={{ mesh|ijk(0.3) }}/
&MESH XB={{ mesh|xb("%5.2f") }}, IJK={{ mesh|ijk(0.3, rounding='floor') }}/
examples/filters/output/ijk/ijk.fds
1
2
3
&MESH XB= 0.00, 5.00, 0.00, 4.00, 0.00, 3.00, IJK=17,14,10/
&MESH XB= 0.00, 5.00, 0.00, 4.00, 0.00, 3.00, IJK=17,13,10/
&MESH XB= 0.00, 5.00, 0.00, 4.00, 0.00, 3.00, IJK=16,13,10/

IOR

For devices that measure surface properties, the user needs to tell FDS which way the device should point. The IOR property is defined as the direction from the target to the device. To ensure the direction is defined correctly, the ior filter takes the axis (x, y, or z) and either the direction from_target_to_device or from_device_to_target as + or -.

In the example below, the first device is located in the -X direction from the obstruction. Therefore, target is along the x axis and the direction can be defined either as either:

  • from_target_to_device="-" (negative x direction)
  • from_device_to_target="+" (positive x direction)

examples/filters/ior.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{% set xb = -1.5, 1.5, -1.5, 1.5, -1.5, 1.5 %}
&MESH XB={{ xb|xb }}, IJK={{ xb|ijk(0.15) }}/
&OBST XB={{ (-0.9, 0.9, -0.9, 0.9, -0.9, 0.9)|xb }}/

{% set xyz= -1,0,0 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'x'|ior(from_target_to_device="-") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'x'|ior(from_device_to_target="+") }}, QUANTITY='INCIDENT HEAT FLUX'/

{% set xyz= 1,0,0 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'x'|ior(from_target_to_device="+") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'x'|ior(from_device_to_target="-") }}, QUANTITY='INCIDENT HEAT FLUX'/

{% set xyz= 0,-1,0 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'y'|ior(from_target_to_device="-") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'y'|ior(from_device_to_target="+") }}, QUANTITY='INCIDENT HEAT FLUX'/

{% set xyz= 0,1,0 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'y'|ior(from_target_to_device="+") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'y'|ior(from_device_to_target="-") }}, QUANTITY='INCIDENT HEAT FLUX'/

{% set xyz= 0,0,-1 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'z'|ior(from_target_to_device="-") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'z'|ior(from_device_to_target="+") }}, QUANTITY='INCIDENT HEAT FLUX'/

{% set xyz= 0,0,1 %}
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'z'|ior(from_target_to_device="+") }}, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ={{xyz|xyz}}, IOR={{ 'z'|ior(from_device_to_target="-") }}, QUANTITY='INCIDENT HEAT FLUX'/
examples/filters/output/ior/ior.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
&MESH XB= -1.500,  1.500, -1.500,  1.500, -1.500,  1.500, IJK=20,20,20/
&OBST XB= -0.900,  0.900, -0.900,  0.900, -0.900,  0.900/

&DEVC XYZ= -1.000,  0.000,  0.000, IOR=-1, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ= -1.000,  0.000,  0.000, IOR=-1, QUANTITY='INCIDENT HEAT FLUX'/

&DEVC XYZ=  1.000,  0.000,  0.000, IOR=1, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ=  1.000,  0.000,  0.000, IOR=1, QUANTITY='INCIDENT HEAT FLUX'/

&DEVC XYZ=  0.000, -1.000,  0.000, IOR=-2, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ=  0.000, -1.000,  0.000, IOR=-2, QUANTITY='INCIDENT HEAT FLUX'/

&DEVC XYZ=  0.000,  1.000,  0.000, IOR=2, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ=  0.000,  1.000,  0.000, IOR=2, QUANTITY='INCIDENT HEAT FLUX'/

&DEVC XYZ=  0.000,  0.000, -1.000, IOR=-3, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ=  0.000,  0.000, -1.000, IOR=-3, QUANTITY='INCIDENT HEAT FLUX'/

&DEVC XYZ=  0.000,  0.000,  1.000, IOR=3, QUANTITY='INCIDENT HEAT FLUX'/
&DEVC XYZ=  0.000,  0.000,  1.000, IOR=3, QUANTITY='INCIDENT HEAT FLUX'/

Linspace

Numpy's linspace() function will generate evenly spaced intervals between two values. This is useful when records, such as DEVCs, need to be evenly spaced between two bounds such as tenability devices across a large space or a thermocouple tree.

examples/filters/linspace.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{% set start = 0 %}
{% set stop = 10 %}
{% for x in 5|linspace(start, stop) %}
&DEVC XYZ={{ (x, stop - x, 0)|xyz }}, .../
{% endfor %}

{% set floor = 0 %}
{% set ceiling = 10|convert('ft', 'm') %}
{% set buffer = 6|convert('in', 'm') %}
{% for z in 10|linspace(floor + buffer, ceiling - buffer) %}
&DEVC ID="TC_TREE_z{{ "%.1f"|format(z) }}" XYZ={{ (0, 0, z)|xyz }}, QUANTITY='THERMOCOUPLE'/
{% endfor %}
examples/filters/output/linspace/linspace.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
&DEVC XYZ=  0.000, 10.000,  0.000, .../
&DEVC XYZ=  2.500,  7.500,  0.000, .../
&DEVC XYZ=  5.000,  5.000,  0.000, .../
&DEVC XYZ=  7.500,  2.500,  0.000, .../
&DEVC XYZ= 10.000,  0.000,  0.000, .../

&DEVC ID="TC_TREE_z0.2" XYZ=  0.000,  0.000,  0.152, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z0.5" XYZ=  0.000,  0.000,  0.457, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z0.8" XYZ=  0.000,  0.000,  0.762, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z1.1" XYZ=  0.000,  0.000,  1.067, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z1.4" XYZ=  0.000,  0.000,  1.372, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z1.7" XYZ=  0.000,  0.000,  1.676, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z2.0" XYZ=  0.000,  0.000,  1.981, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z2.3" XYZ=  0.000,  0.000,  2.286, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z2.6" XYZ=  0.000,  0.000,  2.591, QUANTITY='THERMOCOUPLE'/
&DEVC ID="TC_TREE_z2.9" XYZ=  0.000,  0.000,  2.896, QUANTITY='THERMOCOUPLE'/

Node

The node filter is useful for creating PBS files. See Generating PBS Files for more information.

t2

On a SURF, the TAU_Q parameter indicates the time at which the peak heat release rate is achieved. If a fire needs to be defined in terms of a standardized growth time (time to reach 1 MW), the t2 filter will calculate TAU_Q from the peak heat release rate and characteristic growth time. Altenatively, alpha (\(\alpha\)) may be specified.

\[Q=1000*\left(\frac{t}{t_g}\right)^2=\alpha t^2\]

examples/filters/t2.fds
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{% set hrr = 1250 %}
{% set area = 1.5 %}

&SURF ID='BURNER_TG',
      HRRPUA={{ (hrr / area)|round(1) }},
      TAU_Q={{ hrr|t2(tg=300)|round(1) }}/

&SURF ID='BURNER_ALPHA',
      HRRPUA={{ (hrr / area)|round(1) }},
      TAU_Q={{ hrr|t2(alpha=0.01111)|round(1) }}/
examples/filters/output/t2/t2.fds
1
2
3
4
5
6
7
&SURF ID='BURNER_TG',
      HRRPUA=833.3,
      TAU_Q=-335.4/

&SURF ID='BURNER_ALPHA',
      HRRPUA=833.3,
      TAU_Q=-335.4/

XB

The xb filter takes a list of six numbers (x0, x1, y0, y1, z0, z1) and formats the numbers to have a consistent format. A custom format string can be provided. See Python's stringg formatting for more information.

examples/filters/xb.fds
1
2
&OBST XB={{ (0, 5, -2, 2, 0, 3)|xb }}/
&OBST XB={{ (0, 5, -2, 2, 0, 3)|xb("%5.2f") }}/
examples/filters/output/xb/xb.fds
1
2
&OBST XB=  0.000,  5.000, -2.000,  2.000,  0.000,  3.000/
&OBST XB= 0.00, 5.00,-2.00, 2.00, 0.00, 3.00/

XYZ

The xyz filter acts exactly the same as the xb filter, but takes a triplet rather than a sextuplet.

examples/filters/xyz.fds
1
2
&DEVC XYZ={{ (1, 2, 3)|xyz }}, .../
&DEVC XYZ={{ (1, 2, 3)|xyz("%5.2f") }}, .../
examples/filters/output/xyz/xyz.fds
1
2
&DEVC XYZ=  1.000,  2.000,  3.000, .../
&DEVC XYZ= 1.00, 2.00, 3.00, .../

User Defined Filters

Jinja Docs

See the Jinja documentation for more information on custom filters

Warning

Custom functions that depend on packages that do not ship with xFDS might not work. If possible, stick to packages in the Python standard library. Additionally, Numpy and Pandas are included by default.

Users who are familiar with Python may create their own custom filters as desired. If there is a file called filters.py in the same directory as the configuration file pbd.yml, xFDS will import every function in filters.py and make it available to the template in model.fds.

See the user_filters test case as an example or checkout the xFDS custom filters source file.

/path/to/project/
.
├── filters.py
├── model.fds
├── pbd.yml
└── units.txt