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
scale, as well as a shape and visualization scale.
These parameters already have the correct names to be passed to instances of
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
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.
inherit directive can be used in any Pogona configuration file to include other configuration files with a path relative to the inheriting file.
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
seedspecifies 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_limitsays that our simulation should end after 15 s.
base_delta_timedefines the step size. Every 5 ms, all
Sensorinstances will have a chance to react to all particles. If we do not use adaptive time stepping,
base_delta_timealso corresponds to the step size with which the positions of particles will be updated.
use_adaptive_time_stepping: Truespecifies that we do want to use adaptive time stepping in this simulation.
adaptive_time_max_error_thresholdis the maximum integration error we want to tolerate when using adaptive time stepping. The step size will be adjusted accordingly.
movement_predictor contains the settings for the ‘kernel component’
All existing kernel components are listed in the documentation of the
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
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
Like the configuration of the
SimulationKernel, each component can also be configured using the names of
AbstractProperty-based class attributes of each
You can find an example of a complete configuration at the bottom of this page.
This component does not require any additional configuration. By default, any particle entering its region will be removed from the simulation.
The injector needs a number of particles to inject and a reference to the
Objectthat newly spawned particles will be associated with:
injector: type: Injector injection_amount: 5 # particles in each base time step (cp. `modulation`) attached_object: tube0
SensorEmpiricalneeds 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
scaleparameter for vector fields such as our
tube1, because vector fields typically already have the correct scale. However, the
ObjectTubeclass 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 a
ModulationOOKinstance, to transmit a bit sequence from a
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 leave
tube0, we need to make sure that their respective association is handed over to
tube1. This is done with a
SensorTeleporting, that checks if a particle is within the
ObjectTube's outlet_zone. Furthermore, each
Objectdefines 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 in
tube0and if this change should propagate to
tube1. 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
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.