-
-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathconnection.py
More file actions
281 lines (181 loc) · 7.66 KB
/
connection.py
File metadata and controls
281 lines (181 loc) · 7.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#########################################################################################
##
## CONNECTION CLASS
## (connection.py)
##
## This module implements the 'Connection' class that transfers
## data between the blocks and their input/output channels
##
#########################################################################################
# IMPORTS ===============================================================================
from .utils.portreference import PortReference
from .utils.deprecation import deprecated
# CLASSES ===============================================================================
class Connection:
"""Class to handle input-output relations of blocks by connecting them (directed graph)
and transfering data from the output port of the source block to the input port of
the target block.
The default ports for connection are (0) -> (0), since these are the default inputs
that are used in the SISO blocks.
Examples
--------
Lets assume we have some generic blocks
.. code-block:: python
from pathsim.blocks._block import Block
B1 = Block()
B2 = Block()
B3 = Block()
that we want to connect. We initialize a 'Connection' with the blocks directly
as the arguments if we want to connect the default ports (0) -> (0)
.. code-block:: python
from pathsim import Connection
C = Connection(B1, B2)
which is a connection from block 'B1' to 'B2'. If we want to explicitly declare
the input and output ports we can do that by utilizing the '__getitem__' method
of the blocks
.. code-block:: python
C = Connection(B1[0], B2[0])
which is exactly the default port setup. Connecting output port (1) of 'B1' to
the default input port (0) of 'B2' do
.. code-block:: python
C = Connection(B1[1], B2[0])
or just
.. code-block:: python
C = Connection(B1[1], B2).
The 'Connection' class also supports multiple targets for a single source.
This is specified by just adding more blocks with their respective ports into
the constructor like this:
.. code-block:: python
C = Connection(B1, B2[0], B2[1], B3)
The port definitions follow the same structure as for single target connections.
'self'-connections also work without a problem. This is useful for modeling direct
feedback of a block to itself.
Port definitions support slicing. This enables direct MIMO connections. For example
connecting ports 0, 1, 2 of 'B1' to ports 1, 2, 3 of 'B2' works like this:
.. code-block:: python
C = Connection(B1[0:2], B2[1:3])
Port definitions also support lists and tuples of 'int'. For example the slice
above is identical to this:
.. code-block:: python
C = Connection(B1[0, 1], B2[1, 2])
Or to be more programmatic about it, like this:
.. code-block:: python
prts_1 = [0, 1]
prts_2 = [1, 2]
C = Connection(B1[prts_1], B2[prts_2])
Another way to define the ports is by using strings. Some blocks have internal
aliases for the ports that can be used instead of the integer port indices to
define the connections (or access the port data):
.. code-block:: python
C = Connection(B1["out"], B2["in"])
Or mixed with integer port indices:
.. code-block:: python
C = Connection(B1["out"], B2["in"])
Parameters
----------
source : PortReference, Block
source block and optional source output port
targets : tuple[PortReference], tuple[Block]
target blocks and optional target input ports
Attributes
----------
_active : bool
flag to set 'Connection' as active or inactive
"""
__slots__ = ["source", "targets", "_active"]
def __init__(self, source, *targets):
#assign source block and port
self.source = source if isinstance(source, PortReference) else PortReference(source)
#assign target blocks and ports
self.targets = [trg if isinstance(trg, PortReference) else PortReference(trg) for trg in targets]
#flag to set connection active
self._active = True
#validate port aliases
self._validate_ports()
#validate port dimensions at connection creation
self._validate_dimensions()
def __str__(self):
"""String representation of the connection"""
src = f"{self.source.block}[{self.source.ports}]"
trgs = ", ".join(f"{t.block}[{t.ports}]" for t in self.targets)
return f"Connection({src} -> {trgs})"
def __len__(self):
"""Returns the number of ports that are defined in the connection"""
return len(self.source)
def __bool__(self):
return self._active
def __contains__(self, other):
"""Check if block is part of connection
Paramters
---------
other : Block
block to check if its part of the connection
Returns
-------
bool
is other part of connecion?
"""
if isinstance(other, Block):
return other in self.get_blocks()
return False
def _validate_dimensions(self):
"""Check the dimensions of the source and target ports,
if they dont match, raises an exception.
"""
n_src = len(self.source)
for trg in self.targets:
if len(trg) != n_src:
raise ValueError(f"Source and target have different number of ports!")
def _validate_ports(self):
"""Check the existence of the input and output ports of
the defined source and target blocks.
Utilizes the `PortReference._validate_output_ports` and
`PortReference._validate_input_ports` methods.
"""
self.source._validate_output_ports()
for trg in self.targets:
trg._validate_input_ports()
def get_blocks(self):
"""Returns all the unique internal source and target blocks
of the connection instance
Returns
-------
list[Block]
internal unique blocks of the connection
"""
blocks = [self.source.block]
for trg in self.targets:
if trg.block not in blocks:
blocks.append(trg.block)
return blocks
def on(self):
self._active = True
def off(self):
self._active = False
def update(self):
"""Transfers data from the source block output port
to the target block input port.
"""
for trg in self.targets:
self.source.to(trg)
@deprecated(version="1.0.0")
class Duplex(Connection):
"""Extension of the 'Connection' class, that defines bidirectional
connections between two blocks by grouping together the inputs and
outputs of the blocks into an IO-pair.
"""
__slots__ = ["source", "target", "targets", "_active"]
def __init__(self, source, target):
self.source = source if isinstance(source, PortReference) else PortReference(source)
self.target = target if isinstance(target, PortReference) else PortReference(target)
#this is required for path length estimation
self.targets = [self.target, self.source]
#flag to set connection active
self._active = True
def update(self):
"""Transfers data between the two target blocks
and ports bidirectionally.
"""
#bidirectional data transfer
self.target.to(self.source)
self.source.to(self.target)