Skip to content

Commit c2a653d

Browse files
authored
Merge pull request #204 from pathsim/feature/xprt-ir-blocks
Add missing XPRT IR blocks: logic, Atan2, Rescale, Alias, discrete Delay
2 parents c579dd7 + f02e3cb commit c2a653d

9 files changed

Lines changed: 1452 additions & 30 deletions

File tree

src/pathsim/blocks/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from .noise import *
2222
from .table import *
2323
from .relay import *
24+
from .logic import *
2425
from .math import *
2526
from .ctrl import *
2627
from .lti import *

src/pathsim/blocks/delay.py

Lines changed: 95 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#########################################################################################
22
##
3-
## TIME DOMAIN DELAY BLOCK
3+
## TIME DOMAIN DELAY BLOCK
44
## (blocks/delay.py)
55
##
66
#########################################################################################
@@ -9,70 +9,120 @@
99

1010
import numpy as np
1111

12+
from collections import deque
13+
1214
from ._block import Block
1315

1416
from ..utils.adaptivebuffer import AdaptiveBuffer
17+
from ..events.schedule import Schedule
1518
from ..utils.mutable import mutable
1619

1720

1821
# BLOCKS ================================================================================
1922

2023
@mutable
2124
class 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

Comments
 (0)