Realtime Simulation
===================
Besides normal (as-fast-as-possible) simulation, it is also possible to simulate CBD models in realtime. Here, the
time (and therefore `delta t` as well) will be interpreted as seconds and the simulator will wait **non-blocking**
until the required time has passed. There are several supported backend that provide this functionality. These
backends are based on the backends provided by PyPDEVS_.
While there doesn't have to be feedback duing a simulation, the :func:`CBD.simulator.Simulator.setProgressBar`
function provides a `tqdm progress bar `_. When running long simulations, this might
be a useful feature. Note that, when combined with a termination condition, the progress bar may yield inaccurate
values.
.. note::
- When using progress bars, `tqdm `_ must be installed.
- In :doc:`LivePlot`, realtime simulation is used together with a variation of the :doc:`SinGen` example.
.. attention::
Unlike PyPDEVS_, interrupt events are not possible. However, as can be seen in the :doc:`Dashboard`
example, the :class:`CBD.lib.std.ConstantBlock` allows for altering the internal value it outputs
**during** the simulation. This mechanism may be manipulated to allow for external interrupts if
necessary.
.. _PyPDEVS: https://msdl.uantwerpen.be/documentation/PythonPDEVS/realtime.html
Example Model
-------------
To simplify the explanations of the following sections, we will be using the :doc:`SinGen` as a basis model.
To recap:
.. code-block:: python
from CBD.CBD import CBD
from CBD.simulator import Simulator
from CBD.lib.std import TimeBlock, GenericBlock
from CBD.lib.endpoints import SignalCollectorBlock
class SinGen(CBD):
def __init__(self, name="SinGen"):
CBD.__init__(self, name, input_ports=[], output_ports=[])
# Create the blocks
self.addBlock(TimeBlock("time"))
self.addBlock(GenericBlock("sin", block_operator="sin"))
self.addBlock(SignalCollectorBlock("collector"))
# Connect the blocks
self.addConnection("time", "sin")
self.addConnection("sin", "collector")
sinGen = SinGen("SinGen")
sim = Simulator(sinGen)
sim.setRealTime()
.. note::
Realtime simulation happens non-blocking. This means the :func:`CBD.simulator.Simulator.run` method will be called
asynchronously. Additionally, simulation runs as a daemon thread, so exiting the main thread will automatically
terminate the simulation. To keep the script alive until after the simulation, you can use:
.. code-block:: python
while sim.is_running():
pass
|
Python Threading Backend
------------------------
The threading (or Python) backend/platform will use the :mod:`threading` module for delaying the simulation steps.
This is the default simulation backend.
.. warning::
Python threads can sometimes have a rather low granularity in CPython 2. So while we are simulating in soft
realtime anyway, it is important to note that delays could potentially become significant.
.. code-block:: python
sim.setRealTimePlatformThreading()
sim.setDeltaT(0.3)
sim.run(100.0)
# Keep it alive
while sim.is_running(): pass
print("FINISHED!")
.. seealso::
- :func:`CBD.simulator.Simulator.setRealTimePlatform`
- :func:`CBD.simulator.Simulator.setRealTimePlatformThreading`
- :func:`CBD.simulator.Simulator.is_running`
TkInter Backend
---------------
The `TkInter `_ event loop can become quite complex, as it is
required to interface to the GUI as wel as to the simulation. Luckily, this backend will wrap all the complexity
into a white box. It is, however, required to define the GUI application and start the mainloop, but afterwards,
all will be handled for you.
.. code-block:: python
import tkinter as tk
root = tk.Tk()
sim.setRealTimePlatformTk(root)
sim.setDeltaT(0.3)
sim.run(100.0)
root.mainloop()
print("FINISHED!")
.. seealso::
- :func:`CBD.simulator.Simulator.setRealTimePlatform`
- :func:`CBD.simulator.Simulator.setRealTimePlatformTk`
GameLoop Backend
----------------
Whenever it is required to control the invocation of delays from another execution loop, like e.g. a gameloop,
it is pertinent to make use of the `GameLoop` backend. Delays won't happen internally anymore, as they should be
handled by the execution loop. By making use of the :func:`CBD.simulator.Simulator.realtime_gameloop_call`, the
simulation can advance to the next timestep.
.. code-block:: python
sim.setRealTimePlatformGameLoop()
sim.setDeltaT(0.3)
sim.run(100.0)
while sim.is_running():
# do some fancy computations
...
# do some rendering
...
# advance the model's state
sim.realtime_gameloop_call()
print("FINISHED!")
.. attention::
The simulation is still variable on the time constraints of your current system. Use the
:class:`CBD.realtime.threadingGameLoopAlt.ThreadingGameLoopAlt` instead to fully control the time yourself.
In this case, the :func:`CBD.simulator.Simulator.realtime_gameloop_call` requires the simulation time to be
passed as an argument.
While this is an option, it is highly encouraged to use the other backends instead. The alternative gameloop
runs on the bare bones of the simulator, making system invalidities possible when not fully understanding the
simulator itself. Additionally, exploiting time in a simulation in this way is heavily discouraged and is
considered to be a bad practice.
.. seealso::
- :func:`CBD.simulator.Simulator.setRealTimePlatform`
- :func:`CBD.simulator.Simulator.setRealTimePlatformGameLoop`
- :func:`CBD.simulator.Simulator.realtime_gameloop_call`
- :class:`CBD.realtime.threadingGameLoopAlt.ThreadingGameLoopAlt`