Configuration
Pogona simulations can be configured with YAML files.
You can find the available options and Component
instances in the API documentation.
Inheriting the Scene
First, let us have a look at the partial configuration file that we already generated in the previous step. The following is what the first couple of lines of may look like:
1components:
2 destructor:
3 rotation:
4 - -0.0
5 - 1.570796251296997
6 - 0.0
7 scale:
8 - 0.0015999999595806003
9 - 0.0015999999595806003
10 - 0.010000000707805157
11 shape: CYLINDER
12 translation:
13 - 0.16022001206874847
14 - 0.0
15 - 0.0
16 visualization_scale:
17 - 1.2000000476837158
18 - 1.2000000476837158
19 - 1.0
20 injector:
21 rotation:
22 - -0.0
23 - 1.570796251296997
24 - 0.0
25 scale:
26 - 0.0015200000489130616
27 - 0.0015200000489130616
At the top level, you can see that below the key components
are listed all the Pogona components we added to the Blender scene, and each component has defined a translation
, rotation
, scale
, as well as a shape and visualization scale.
These parameters already have the correct names to be passed to instances of pogona.Component
.
To configure the remainder of our simulation scenario, we would technically be able to simply edit the file linked-tubes.scene.yaml
and pass this configuration file to the Pogona simulator.
However, if we ever discover that we might want to adjust the 3D scene of this scenario, we would have to either overwrite this file using the Blender add-on or edit it manually. 1
Instead, create a new file and name it linked-tubes.config.yaml
.
Keeping the scene separate from the remaining configuration will also help us to reduce configuration clutter.
Start the new file as follows:
1inherit:
2 - linked-tubes.scene.yaml
In lines 1 and 2 we specify that we want to inherit from our scene configuration file.
The inherit
directive can be used in any Pogona configuration file to include other configuration files with a path relative to the inheriting file.
- 1
It would technically also be possible to extend the Blender add-on to merge exported configurations with an existing configuration, but since configurations are Python
dict
-based, this may change the order of items or strip the configuration of comments if not properly implemented.
Configuring the Simulation Kernel
Now extend the configuration as follows:
1inherit:
2 - linked-tubes.scene.yaml
3seed: 42
4sim_time_limit: 15
5base_delta_time: 0.005
6use_adaptive_time_stepping: True
7adaptive_time_max_error_threshold: 1e-7
8movement_predictor:
9 integration_method: RUNGE_KUTTA_FEHLBERG_45
The parameters in lines 3–7 configure the SimulationKernel
, with each key corresponding to any class attribute that is a subclass of AbstractProperty
:
seed
specifies a random seed that will be used whenever random values are needed, unless the respective component is configured to use its own random seed.sim_time_limit
says that our simulation should end after 15 s.base_delta_time
defines the step size. Every 5 ms, allSensor
instances will have a chance to react to all particles. If we do not use adaptive time stepping,base_delta_time
also corresponds to the step size with which the positions of particles will be updated.use_adaptive_time_stepping: True
specifies that we do want to use adaptive time stepping in this simulation.adaptive_time_max_error_threshold
is the maximum integration error we want to tolerate when using adaptive time stepping. The step size will be adjusted accordingly.
Finally, movement_predictor
contains the settings for the ‘kernel component’ MovementPredictor
.
All existing kernel components are listed in the documentation of the SimulationKernel
.
In this case, we need to override the default setting of the integration_method
to an Integration
method that supports adaptive time stepping.
Configuring Simulation Components
Next, we need to configure the components that so far only exist in our scene as empty husks.
To do this, copy the names of all components defined in linked-tubes.scene.yaml
into the components
block in linked-tubes.config.yaml
:
1inherit:
2 - linked-tubes.scene.yaml
3seed: 42
4sim_time_limit: 15
5base_delta_time: 0.005
6use_adaptive_time_stepping: True
7adaptive_time_max_error_threshold: 1e-7
8movement_predictor:
9 integration_method: RUNGE_KUTTA_FEHLBERG_45
10components:
11 destructor:
12 type: SensorDestructing
13 injector:
14 type: Injector
15 sensor:
16 type: SensorEmpirical
17 tube0:
18 type: ObjectTube
19 tube1:
20 type: ObjectTube
Besides the component names, you will notice that the version of the configuration file above also includes a type
for each component, which corresponds to the name of the respective Component
subclass.
Like the configuration of the SimulationKernel
, each component can also be configured using the names of AbstractProperty
-based class attributes of each Component
subclass.
You can find an example of a complete configuration at the bottom of this page.
- destructor
This component does not require any additional configuration. By default, any particle entering its region will be removed from the simulation.
- injector
The injector needs a number of particles to inject and a reference to the
Object
that newly spawned particles will be associated with:injector: type: Injector injection_amount: 5 # particles in each base time step (cp. `modulation`) attached_object: tube0
- sensor
SensorEmpirical
needs a subfolder name in the output directory where it will store its sensor logs. Additionally, we can specify a sensor model, which in this case maps the relative position of particles inside the sensor to a magnetic susceptibility response. In the scene configuration, we already made sure that the dimensions of our sensor match this sensor model choice.sensor: type: SensorEmpirical log_folder: sensor_data # relative to `--results-dir` use_known_sensor: ERLANGEN_20200310
- tube0, tube1
As explained in the chapter for the scene configuration, we cannot use the regular
scale
parameter for vector fields such as ourtube0
andtube1
, because vector fields typically already have the correct scale. However, theObjectTube
class still allows us to choose the length of the tube. Internally, the instance of this class will then load the smallest available tube vector field that our tube would fit into. Additionally, we need to define the flow rate and mesh resolution, which are used to compose the final OpenFOAM simulation results path from which the vector field will be loaded.tube0: type: ObjectTube radius: 0.00076 # in m length: 0.09 # in m flow_rate: 10 # in ml/min mesh_resolution: 11 # radius cells in OpenFOAM mesh definition tube1: type: ObjectTube radius: 0.00076 # in m length: 0.09 # in m flow_rate: 10 # in ml/min mesh_resolution: 11 # radius cells in OpenFOAM mesh definition
With all components from our 3D scene taken care of, there are a few further, dimensionless components we need to add.
We already have an
Injector
, but we have not yet specified when and how injections should take place. We can use on-off keying, using aModulationOOK
instance, to transmit a bit sequence from aBitstreamGenerator
.modulation: type: ModulationOOK pause_duration: 1.5 # in s attached_injector: injector attached_pump: pump # Inject new particles in every time step while active, # rather than all at once: use_burst: False bitstream_generator: type: BitstreamGenerator start_time: 0 # in s bit_sequence: "101001" attached_modulation: modulation
When using vector fields in a Pogona simulation, each particle will typically always be associated with exactly one
Object
. When particles leavetube0
, we need to make sure that their respective association is handed over totube1
. This is done with aSensorTeleporting
, that checks if a particle is within theObjectTube's outlet_zone
. Furthermore, eachObject
defines its own set of inlets and outlets, since some objects may have multiple of each. We have to specify the name of the inlet and outlet for the link between our two tubes. This is not critical in the present example, but it is required if we have a simulation scenario in which the flow rate might change intube0
and if this change should propagate totube1
. If we had a separate inlet for the particle injections in which we wanted to stop the flow whenever the pump is inactive, we would need another teleporter from the pump to the respective tube.teleport_tube0_to_tube1: type: SensorTeleporting source_object: tube0 target_object: tube1 source_outlet_name: outlet target_inlet_name: inlet
Lastly, we want to log all particle positions in each base time step for later visualization, and we want see some status information like the current number of particles in the terminal output:
plotter_terminal: type: PlotterTerminal plotter_csv: type: PlotterCSV folder: particle_positions # relative to --results-dir write_interval: 1 # in number of time steps
Complete Configuration
1inherit:
2 - linked-tubes.scene.yaml
3seed: 42
4sim_time_limit: 15
5base_delta_time: 0.005
6use_adaptive_time_stepping: True
7adaptive_time_max_error_threshold: 1e-7
8movement_predictor:
9 integration_method: RUNGE_KUTTA_FEHLBERG_45
10components:
11 destructor:
12 type: SensorDestructing
13 injector:
14 type: Injector
15 injection_amount: 5
16 attached_object: tube0
17 sensor:
18 type: SensorEmpirical
19 log_folder: sensor_data # relative to `--results-dir`
20 use_known_sensor: ERLANGEN_20200310
21 tube0:
22 type: ObjectTube
23 radius: 0.00076 # in m
24 length: 0.09 # in m
25 flow_rate: 10 # in ml/min
26 mesh_resolution: 11 # radius cells in OpenFOAM mesh definition
27 tube1:
28 type: ObjectTube
29 radius: 0.00076 # in m
30 length: 0.09 # in m
31 flow_rate: 10 # in ml/min
32 mesh_resolution: 11 # radius cells in OpenFOAM mesh definition
33 modulation:
34 type: ModulationOOK
35 pause_duration: 1.5 # in s
36 attached_injector: injector
37 attached_pump: pump
38 # Inject new particles in every time step while active,
39 # rather than all at once:
40 use_burst: False
41 bitstream_generator:
42 type: BitstreamGenerator
43 start_time: 0 # in s
44 bit_sequence: "101001"
45 attached_modulation: modulation
46 pump:
47 type: ObjectPumpTimed
48 flow_rate: 10
49 injection_flow_mlpmin: 10 # Don't change flow rate on injection in this scenario.
50 pump_duration_s: 0.1
51 teleport_tube0_to_tube1:
52 type: SensorTeleporting
53 source_object: tube0
54 target_object: tube1
55 source_outlet_name: outlet
56 target_inlet_name: inlet
57 plotter_terminal:
58 type: PlotterTerminal
59 plotter_csv:
60 type: PlotterCSV
61 folder: particle_positions
62 write_interval: 1 # in number of time steps
Having now configured our simulation scenario, we can go ahead and run it.