11#########################################################################################
22##
3- ## TIME DOMAIN DELAY BLOCK
3+ ## TIME DOMAIN DELAY BLOCK
44## (blocks/delay.py)
55##
66#########################################################################################
99
1010import numpy as np
1111
12+ from collections import deque
13+
1214from ._block import Block
1315
1416from ..utils .adaptivebuffer import AdaptiveBuffer
17+ from ..events .schedule import Schedule
1518from ..utils .mutable import mutable
1619
1720
1821# BLOCKS ================================================================================
1922
2023@mutable
2124class Delay (Block ):
22- """Delays the input signal by a time constant 'tau' in seconds.
25+ """Delays the input signal by a time constant 'tau' in seconds.
26+
27+ Supports two modes of operation:
2328
24- Mathematically this block creates a time delay of the input signal like this:
29+ **Continuous mode** (default, ``sampling_period=None``):
30+ Uses an adaptive interpolating buffer for continuous-time delay.
2531
2632 .. math::
27-
28- y(t) =
33+
34+ y(t) =
2935 \\ begin{cases}
3036 x(t - \\ tau) & , t \\ geq \\ tau \\ \\
3137 0 & , t < \\ tau
3238 \\ end{cases}
3339
40+ **Discrete mode** (``sampling_period`` provided):
41+ Uses a ring buffer with scheduled sampling events for N-sample delay,
42+ where ``N = round(tau / sampling_period)``.
43+
44+ .. math::
45+
46+ y[k] = x[k - N]
47+
3448 Note
3549 ----
36- The internal adaptive buffer uses interpolation for the evaluation. This is
37- required to be compatible with variable step solvers. It has a drawback however.
38- The order of the ode solver used will degrade when this block is used, due to
39- the interpolation.
50+ In continuous mode, the internal adaptive buffer uses interpolation for
51+ the evaluation. This is required to be compatible with variable step solvers.
52+ It has a drawback however. The order of the ode solver used will degrade
53+ when this block is used, due to the interpolation.
54+
4055
41-
4256 Note
4357 ----
44- This block supports vector input, meaning we can have multiple parallel
58+ This block supports vector input, meaning we can have multiple parallel
4559 delay paths through this block.
4660
4761
4862 Example
4963 -------
50- The block is initialized like this :
64+ Continuous-time delay :
5165
5266 .. code-block:: python
53-
67+
5468 #5 time units delay
5569 D = Delay(tau=5)
56-
70+
71+ Discrete-time N-sample delay (10 samples):
72+
73+ .. code-block:: python
74+
75+ D = Delay(tau=0.01, sampling_period=0.001)
76+
5777 Parameters
5878 ----------
5979 tau : float
60- delay time constant
80+ delay time constant in seconds
81+ sampling_period : float, None
82+ sampling period for discrete mode, default is continuous mode
6183
6284 Attributes
6385 ----------
6486 _buffer : AdaptiveBuffer
65- internal interpolatable adaptive rolling buffer
87+ internal interpolatable adaptive rolling buffer (continuous mode)
88+ _ring : deque
89+ internal ring buffer for N-sample delay (discrete mode)
6690 """
6791
68- def __init__ (self , tau = 1e-3 ):
92+ def __init__ (self , tau = 1e-3 , sampling_period = None ):
6993 super ().__init__ ()
7094
71- #time delay in seconds
95+ #time delay in seconds
7296 self .tau = tau
7397
74- #create adaptive buffer
75- self ._buffer = AdaptiveBuffer (self .tau )
98+ #params for sampling
99+ self .sampling_period = sampling_period
100+
101+ if sampling_period is None :
102+
103+ #continuous mode: adaptive buffer with interpolation
104+ self ._buffer = AdaptiveBuffer (self .tau )
105+
106+ else :
107+
108+ #discrete mode: ring buffer with N-sample delay
109+ self ._n = max (1 , round (self .tau / self .sampling_period ))
110+ self ._ring = deque ([0.0 ] * self ._n , maxlen = self ._n + 1 )
111+
112+ #flag to indicate this is a timestep to sample
113+ self ._sample_next_timestep = False
114+
115+ #internal scheduled event for periodic sampling
116+ def _sample (t ):
117+ self ._sample_next_timestep = True
118+
119+ self .events = [
120+ Schedule (
121+ t_start = 0 ,
122+ t_period = sampling_period ,
123+ func_act = _sample
124+ )
125+ ]
76126
77127
78128 def __len__ (self ):
@@ -83,27 +133,36 @@ def __len__(self):
83133 def reset (self ):
84134 super ().reset ()
85135
86- #clear the buffer
87- self ._buffer .clear ()
136+ if self .sampling_period is None :
137+ #clear the adaptive buffer
138+ self ._buffer .clear ()
139+ else :
140+ #clear the ring buffer
141+ self ._ring .clear ()
142+ self ._ring .extend ([0.0 ] * self ._n )
88143
89144
90145 def update (self , t ):
91- """Evaluation of the buffer at different times
92- via interpolation.
146+ """Evaluation of the buffer at different times
147+ via interpolation (continuous) or ring buffer lookup (discrete) .
93148
94149 Parameters
95150 ----------
96151 t : float
97152 evaluation time
98153 """
99154
100- #retrieve value from buffer
101- y = self ._buffer .get (t )
102- self .outputs .update_from_array (y )
155+ if self .sampling_period is None :
156+ #continuous mode: retrieve value from buffer
157+ y = self ._buffer .get (t )
158+ self .outputs .update_from_array (y )
159+ else :
160+ #discrete mode: output the oldest value in the ring buffer
161+ self .outputs [0 ] = self ._ring [0 ]
103162
104163
105164 def sample (self , t , dt ):
106- """Sample input values and time of sampling
165+ """Sample input values and time of sampling
107166 and add them to the buffer.
108167
109168 Parameters
@@ -114,5 +173,11 @@ def sample(self, t, dt):
114173 integration timestep
115174 """
116175
117- #add new value to buffer
118- self ._buffer .add (t , self .inputs .to_array ())
176+ if self .sampling_period is None :
177+ #continuous mode: add new value to buffer
178+ self ._buffer .add (t , self .inputs .to_array ())
179+ else :
180+ #discrete mode: only sample on scheduled events
181+ if self ._sample_next_timestep :
182+ self ._ring .append (self .inputs [0 ])
183+ self ._sample_next_timestep = False
0 commit comments