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.
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 |
---|
| &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 |
---|
| &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 |
---|
| {% 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 |
---|
| &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 |
---|
| {% 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 |
---|
| &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.
| {{ door(1.0, 1.0, 2.0, 0.3) }}
|
| {{ 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:
| {% 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.
| {% set ns.door_id = ns.door_id + 1 %}
|
| {% 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/output/filters/abs.fds |
---|
| |
Center Text
examples/filters/center.fds |
---|
| "{{ "xFDS"|center(10) }}"
|
examples/filters/output/filters/center.fds |
---|
| |
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 |
---|
| {{ 42|float }}
{{ "%f"|format(42)}}
{{ "%.3f"|format(42)}}
"{{ "%8.3f"|format(-42)}}"
|
examples/filters/output/float/float.fds |
---|
| 42.0
42.000000
42.000
" -42.000"
|
Convert to integer
examples/filters/int.fds |
---|
| {{ 42.0|int }}
{{ "%d"|format(42.0)}}
|
examples/filters/output/int/int.fds |
---|
| |
Maximum Value
examples/filters/output/max/max.fds |
---|
| |
Minimum Value
examples/filters/output/min/min.fds |
---|
| |
Round Value
examples/filters/round.fds |
---|
| |
examples/filters/output/round/round.fds |
---|
| |
Trim Text
examples/filters/trim.fds |
---|
| |
examples/filters/output/trim/trim.fds |
---|
| |
Filter Unique Values
examples/filters/unique.fds |
---|
| # unique
{{ [1, 1, 2, 2]|unique|list }}
|
examples/filters/output/unique/unique.fds |
---|
| |
Text to Uppercase
examples/filters/upper.fds |
---|
| |
examples/filters/output/upper/upper.fds |
---|
| |
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 |
---|
| {% 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 |
---|
| &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.
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 |
---|
| |
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 |
---|
| # 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 |
---|
| # 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 |
---|
| {% 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 |
---|
| &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 |
---|
| {%+ 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 |
---|
| &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 |
---|
| {% 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 |
---|
| &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 |
---|
| &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 |
---|
| &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 |
---|
| &DEVC XYZ={{ (1, 2, 3)|xyz }}, .../
&DEVC XYZ={{ (1, 2, 3)|xyz("%5.2f") }}, .../
|
examples/filters/output/xyz/xyz.fds |
---|
| &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