Browse Source

Added adaptive step size docs

rparedis 4 years ago
parent
commit
8f7844e583

+ 48 - 30
doc/examples/ContinuousTime.rst

@@ -25,43 +25,61 @@ results.
 .. figure:: ../_figures/stepsize/sine-1.png
    :width: 600
 
-This behaviour is the default in the simulator. To explicitly set it, make use of
-the :class:`CBD.stepsize.Fixed` class as follows (assuming :code:`sim` is the
-simulator object):
+This behaviour is the default in the simulator. This can be explicitly set as follows
+(assuming :code:`sim` is the simulator object):
 
 .. code-block:: python
 
-   sim.setStepSize(Fixed(0.5))
-   #  OR, EQUIVALENTLY
-   sim.setStepSize(0.5)
+   sim.setDeltaT(0.5)
 
-Yet, when the step size logic has been untouched since the creation of the
-simulator instance, the normal :func:`CBD.simulator.Simulator.setDeltaT` can be
-used as well.
 This was done for academic reasons, as it is much easier to explain CBDs with a
-fixed step size, as compared to varying step sizes.
-
-Euler 2-Step
-------------
-Variable step size algorithms continuously increases or decreases the :code:`dt`
-to allow for less steps to be taken. Whenever the simulation fluctuates a lot,
-:code:`dt` will be small. However, if there are only small changes, it will be
-large.
-
-The `Euler 2-Step` does this by (approximately) halving/doubling the current
-:code:`dt`, trying to get the best match for the given data. More information on
-the actual algorithm can be found in the documentation for
-:class:`CBD.stepsize.Euler2` and `professor Joel Feldman's notes on variable step
-size algorithms <http://www.math.ubc.ca/~feldman/math/vble.pdf>`_.
-It can be set on the simulator object as follows:
+fixed step size, as compared to varying step sizes. By default, the :code:`dt` is
+set to 1.
+
+Manipulating the Clock
+----------------------
+To maintain the block structure of the simulator, the simulation clock
+(see :class:`CBD.lib.std.Clock`) is implemented as an actual block. If this clock is
+not used in the model to simulate, the simulator will automatically add a fixed-rate
+clock, given the :code:`dt` information, as explained above. This can also be done
+manually via calling :func:`CBD.Core.CBD.addFixedRateClock`. The clock will actually
+be used to compute the simulation time. The :class:`CBD.lib.std.TimeBlock` outputs the
+current simulation time and can therefore be used to access the current time without
+the need for the actual clock. However, blocks that depend on the :code:`dt` value
+either need to be linked to a Clock block, or should have an input that yields the
+correct value; i.e., a :class:`CBD.lib.std.ConstantBlock`.
+
+Adaptive Step Size
+------------------
+Adaptive step size (or variable step size) is a simulation method in which the delta
+changes throughout the simulation time. The clock-as-a-block structure allows the
+variation of the :code:`dt`, as is required for adaptive step size. This can be done
+manually by computing some simulation outputs, or via RK-preprocessing.
+
+The :class:`CBD.preprocessing.rungekutta.RKPreprocessor` transforms the original CBD
+model into a new block diagram that applies the Runge-Kutta algorithm with error
+estimation. The full family of Runge-Kutta algorithms can be used as long as they are
+representable in a Butcher tableau. Take a look at
+:class:`CBD.preprocessing.butcher.ButcherTableau` to see which algorithms are automatically
+included.
+
+For instance, to apply the Runge-Kutta Fehlberg method for 4th and 5th order to ensure
+adaptive step size of a CBD model called :code:`sinGen`, the following code can be used:
 
 .. code-block:: python
 
-   sim.setStepSize(Euler2(0.1, 0.005))
+   from CBD.preprocessing.butcher import ButcherTableau as BT
+   from CBD.preprocessing.rungekutta import RKPreprocessor
 
-For the sine wave example, with any starting delta and an acceptance error epsilon
-of :code:`0.005`, the following plot is obtained. Note that a decrease of the
-epsilon will make the plot more precise.
+   tableau = BT.RKF45()
+   RKP = RKPreprocessor(tableau, atol=2e-5, hmin=0.1, safety=.84)
+   newModel = RKP.preprocess(sinGen)
 
-.. figure:: ../_figures/stepsize/sine-euler2.png
-   :width: 600
+Notice how the :code:`preprocess` method returns a new model that must be used in the simulation.
+Make sure to refer to this model when reading output traces or changing constants (see
+:doc:`Dashboard`). Note that this will only work if one or more :code:`CBD.lib.std.IntegratorBlock`
+are used (otherwise, the original model will be returned). To obtain a block from the original
+model, the path :code:`RK.RK-K_0.block_name` should be used. However, it is perfectly possible there
+are multiple variations in the transformed model. Hence, it is highly encouraged to only read data
+from the output ports and not from blocks in the model itself. This is an unfortunate side-effect
+of "transforming" the model to comply to adaptive step size simulation.

+ 3 - 2
doc/examples/Dashboard.rst

@@ -126,7 +126,8 @@ time).
     sim.run()
     root.mainloop()
 
-While changing the values, a lot of noice will appear, as there is no consistency in-between datapoints. Yet,
-when not changing the values, the plot will show the requested results.
+While changing the values (especially the period), a lot of noice will appear. This is caused by the fact that
+every update to a slider alters a result from another function that may be at a completely different location.
+Lower the resolution for the scales to minimize this effect.
 
 .. figure:: ../_figures/sin-dashboard.png

+ 30 - 30
doc/examples/Fibonacci.rst

@@ -29,20 +29,20 @@ we can implement the first equation. This yields:
 .. code-block:: python
 
     class FibonacciGen(CBD):
-    def __init__(self, block_name):
-        CBD.__init__(self, block_name, input_ports=[], output_ports=['OUT1'])
+        def __init__(self, block_name):
+            CBD.__init__(self, block_name, input_ports=[], output_ports=['OUT1'])
 
-        # Create the Blocks
-        self.addBlock(DelayBlock("delay1"))
-        self.addBlock(DelayBlock("delay2"))
-        self.addBlock(AdderBlock("sum"))
+            # Create the Blocks
+            self.addBlock(DelayBlock("delay1"))
+            self.addBlock(DelayBlock("delay2"))
+            self.addBlock(AdderBlock("sum"))
 
-        # Create the Connections
-        self.addConnection("delay1", "delay2")
-        self.addConnection("delay1", "sum")
-        self.addConnection("delay2", "sum")
-        self.addConnection("sum", "delay1", input_port_name='IN1')
-        self.addConnection("sum", "OUT1")
+            # Create the Connections
+            self.addConnection("delay1", "delay2")
+            self.addConnection("delay1", "sum")
+            self.addConnection("delay2", "sum")
+            self.addConnection("sum", "delay1", input_port_name='IN1')
+            self.addConnection("sum", "OUT1")
 
 Now, at time :code:`0` **and** at time :code:`1`, we would like to output :code:`1`.
 We know:
@@ -73,24 +73,24 @@ The complete generator is therefore as follows:
     from CBD.lib.std import ConstantBlock, AdderBlock, DelayBlock
 
     class FibonacciGen(CBD):
-    def __init__(self, block_name):
-        CBD.__init__(self, block_name, input_ports=[], output_ports=['OUT1'])
-
-        # Create the Blocks
-        self.addBlock(DelayBlock("delay1"))
-        self.addBlock(DelayBlock("delay2"))
-        self.addBlock(AdderBlock("sum"))
-        self.addBlock(ConstantBlock("zero", value=0))
-        self.addBlock(ConstantBlock("one", value=1))
-
-        # Create the Connections
-        self.addConnection("delay1", "delay2")
-        self.addConnection("delay1", "sum")
-        self.addConnection("delay2", "sum")
-        self.addConnection("sum", "delay1", input_port_name='IN1')
-        self.addConnection("sum", "OUT1")
-        self.addConnection("zero", "delay1", input_port_name='IC')
-        self.addConnection("one", "delay2", input_port_name='IC')
+        def __init__(self, block_name):
+            CBD.__init__(self, block_name, input_ports=[], output_ports=['OUT1'])
+
+            # Create the Blocks
+            self.addBlock(DelayBlock("delay1"))
+            self.addBlock(DelayBlock("delay2"))
+            self.addBlock(AdderBlock("sum"))
+            self.addBlock(ConstantBlock("zero", value=0))
+            self.addBlock(ConstantBlock("one", value=1))
+
+            # Create the Connections
+            self.addConnection("delay1", "delay2")
+            self.addConnection("delay1", "sum")
+            self.addConnection("delay2", "sum")
+            self.addConnection("sum", "delay1", input_port_name='IN1')
+            self.addConnection("sum", "OUT1")
+            self.addConnection("zero", "delay1", input_port_name='IC')
+            self.addConnection("one", "delay2", input_port_name='IC')
 
 When running the simulation for 10 time-units, we obtain the first 10 values:
 

+ 1 - 0
doc/index.rst

@@ -53,6 +53,7 @@ to model complex systems of equations.
     examples/RealTime
     examples/LivePlot
     examples/Dashboard
+    examples/ContinuousTime
 
 .. toctree::
     :maxdepth: 3

File diff suppressed because it is too large
+ 1 - 1
examples/BuiltIn.xml


+ 13 - 12
examples/SinGen/dashboard.py

@@ -11,7 +11,7 @@ import matplotlib.pyplot as plt
 import tkinter as tk
 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
 
-DELTA_T = 0.1
+DELTA_T = 0.01
 
 class SinGen(CBD):
 	def __init__(self, block_name):
@@ -20,7 +20,7 @@ class SinGen(CBD):
 		# Create the Blocks
 		self.addBlock(GenericBlock("sin", block_operator=("sin")))
 		self.addBlock(TimeBlock("time"))
-		self.addBlock(SignalCollectorBlock("plot"))
+		self.addBlock(SignalCollectorBlock("plot", int(10.0 / DELTA_T) + 10))
 		self.addBlock(ConstantBlock("A", 1.0))
 		self.addBlock(ConstantBlock("B", 1.0))
 		self.addBlock(ProductBlock("amp"))
@@ -72,17 +72,18 @@ amplitude = tk.Scale(root, label="Amplitude", length=1200, orient=tk.HORIZONTAL,
                      command=set_amplitude)
 amplitude.set(1.0)
 amplitude.grid(column=1, row=3)
-period = tk.Scale(root, label="Period", length=1200, orient=tk.HORIZONTAL, from_=0, to=5, resolution=0.1,
+period = tk.Scale(root, label="Period", length=1200, orient=tk.HORIZONTAL, from_=0, to=5, resolution=0.01,
                   command=set_period)
 period.set(1.0)
 period.grid(column=1, row=4)
 
-# Run the Simulation
-# sim_time = 100.0
-sim = Simulator(cbd)
-sim.setRealTime()
-# sim.setProgressBar()
-sim.setDeltaT(DELTA_T)
-sim.setRealTimePlatformTk(root)
-sim.run()
-root.mainloop()
+if __name__ == '__main__':
+	# Run the Simulation
+	# sim_time = 100.0
+	sim = Simulator(cbd)
+	sim.setRealTime()
+	# sim.setProgressBar()
+	sim.setDeltaT(DELTA_T)
+	sim.setRealTimePlatformTk(root)
+	sim.run()
+	root.mainloop()

File diff suppressed because it is too large
+ 8 - 8
examples/notebook/.ipynb_checkpoints/HybridTrain-checkpoint.ipynb


File diff suppressed because it is too large
+ 4 - 4
examples/notebook/HybridTrain.ipynb


+ 1 - 1
src/CBD/realtime/plotting.py

@@ -288,7 +288,7 @@ class PlotHandler:
 			This function is only used in the backend to prevent double
 			closing of the plots.
 		"""
-		print("closed")
+		# print("closed")
 		if self.__opened:
 			self.__opened = False
 			self.stop()

+ 3 - 0
whishlist.txt

@@ -31,3 +31,6 @@
 * Plotting: ggplot
 
 * Better conversion from drawio to CBD, using Andrei's and Joeri's work
+
+* Custom CBD Exception for better fine-grained error handling?
+  -> Instead of "ValueError"s