• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PrincetonUniversity / PsyNeuLink / 7242882613

15 Dec 2023 11:58PM UTC coverage: 84.858% (+0.3%) from 84.589%
7242882613

push

github

web-flow
Merge pull request #2852 from PrincetonUniversity/devel

Devel

13478 of 16616 branches covered (0.0%)

Branch coverage included in aggregate %.

2168 of 2345 new or added lines in 47 files covered. (92.45%)

58 existing lines in 11 files now uncovered.

31552 of 36449 relevant lines covered (86.56%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.27
/psyneulink/core/components/mechanisms/processing/compositioninterfacemechanism.py
1
# Princeton University licenses this file to You under the Apache License, Version 2.0 (the "License");
2
# you may not use this file except in compliance with the License.  You may obtain a copy of the License at:
3
#     http://www.apache.org/licenses/LICENSE-2.0
4
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
5
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6
# See the License for the specific language governing permissions and limitations under the License.
7

8

9
# **************************************  CompositionInterfaceMechanism *************************************************
10

11
"""
1✔
12

13
Contents
14
--------
15

16
  * `CompositionInterfaceMechanism_Overview`
17
  * `CompositionInterfaceMechanism_Creation`
18
  * `CompositionInterfaceMechanism_Structure`
19
  * `CompositionInterfaceMechanism_Execution`
20
  * `CompositionInterfaceMechanism_Class_Reference`
21

22

23
.. _CompositionInterfaceMechanism_Overview:
24

25
Overview
26
--------
27

28
CompositionInterfaceMechanisms act as interfaces between a `Composition` and its inputs from and outputs to the
29
environment, or the Components of another Composition within which it is `nested <Composition_Nested>`.
30

31
.. technical_note::
32

33
    The CompositionInterfaceMechanism provides both a standard interface through which other Components can interact
34
    with the environment and/or Compositions, as well as a means of preserving the modularity of Compositions for
35
    `compilation <Composition_Compilation>`. By providing the standard Components for communication among `Mechanisms
36
    <Mechanism>` (`InputPorts <InputPort>` and `OutputPorts <OutputPort>`), Mechanisms (and/or other Compositions) that
37
    are `INPUT <NodeRole.INPUT>` `Nodes <Composition_Nodes>` of a Composition can receive inputs from the environment
38
    in the same way that any other Node receives inputs, from `afferent Projections <Mechanism_Base.afferents>` (in
39
    this case, the `input_CIM  <Composition.input_CIM>` of the Composition to which they belong);  and, similarly,
40
    Components that are `OUTPUT <NodeRole.OUTPUT>` `Nodes <Composition_Nodes>` of a Composition can either report their
41
    outputs to the Composition or, if they are in a `nested Composition <Composition_Nested>`, send their outputs to
42
    Nodes in an enclosing Composition just like any others, using `efferent Projections <Mechanism_Base.efferents>`.
43
    Similarly, for Compilation, they provide a standard interface through which to provide inputs to a Composition and
44
    for aggregating outputs that, again, maintain a standard interface to other Components (which may not be compiled).
45

46
.. _CompositionInterfaceMechanism_Creation:
47

48
Creation
49
--------
50

51
The following three CompositionInterfaceMechanisms are created and assigned automatically to a Composition when it is
52
constructed (and should never be constructed manually):  `input_CIM <Composition.input_CIM>`, `parameter_CIM
53
<Composition.parameter_CIM>` and `output_CIM <Composition.output_CIM>` (see `Composition_CIMs` for additional details).
54

55
.. _CompositionInterfaceMechanism_Structure:
56

57
Structure
58
---------
59

60
A CompositionInterfaceMechanisms has a set of `InputPort` / `OutputPort` pairs that its `function
61
<Mechanism_Base.function>` -- the `Identity` `Function` -- uses to transmit inputs to CompositionInterfaceMechanism
62
to its outputs.  These are listed in its `port_map  <CompositionInterfaceMechanism.port_map>` attribute, each entry
63
of which is a key designating the `Port` of the Component with which the CompositionInterfaceMechanism communicates
64
outside the Composition (i.e., from an `input_CIM <Composition.input_CIM>` receives an `afferent Projection
65
<Mechanism_Base.afferents>`, a `parameter_CIM <Composition.parameter_CIM>` receives a `modulatory projection
66
<ModulatoryProjections>`, or an `output_CIM <Composition.output_CIM>` sends an `efferent Projection
67
<Mechanism_Base.efferents>`), and the value of which is a tuple containing the corresponding (`InputPort`,
68
`OutputPort`) pair used to transmit the information to or from the CompositionInterfaceMechanism.
69
CompositionIntefaceMechanisms can be seen graphically using the `show_cim <ShowGraph.show_cim>` option of the
70
Composition's `show_graph <ShowGraph.show_graph>` method (see figure below).
71

72
.. figure:: _static/CIM_figure.svg
73

74
   **Examples of Projections to nested Compositions routed through CompositionInterfaceMechanisms.**  *Panel A:*
75
   Simple example showing a basic configuration.  *Panel B:*  More complex configuration, generated from script below,
76
   showing Projections automatically created from the Node of an outer Composition (*X*) to two `INPUT
77
   <NodeRole.INPUT>` `Nodes <Composition_Nodes>` of a `nested Composition <Composition_Nested>`, a `ControlProjection`
78
   from a `ControlMechanism` in the outer Composition to a Node it modulates in the nested one, and from a `PROBE
79
   <NodeRole.PROBE>` Node (*B*) in the nested Composition to the `ControlMechanism` that monitors it. ::
80

81
    A = ProcessingMechanism(name='A')
82
    B = ProcessingMechanism(name='B')
83
    C = ProcessingMechanism(name='C')
84
    D = ProcessingMechanism(name='D')
85
    E = ProcessingMechanism(name='E')
86
    F = ProcessingMechanism(name='F')
87
    nested_comp = Composition(pathways=[[A,B,C], [D,E,F]], name='NESTED COMPOSITION')
88
    X = ProcessingMechanism(name='INPUT NODE')
89
    Y = ProcessingMechanism(name='OUTPUT NODE')
90
    C = ControlMechanism(name='CONTROL MECHANISM',
91
                         monitor_for_control=B,
92
                         control=("slope", E))
93
    outer_comp = Composition(name='OUTER COMPOSITION', pathways=[X, nested_comp, Y, C])
94
    outer_comp.show_graph(show_cim=NESTED, show_node_structure=True)
95

96
.. _CompositionInterfaceMechanism_Execution:
97

98
Execution
99
---------
100

101
A CompositionInterface Mechanism is executed when the Composition to which it belongs is executed, and shown never
102
be executed manually.
103

104
.. _CompositionInterfaceMechanism_Class_Reference:
105

106
Class Reference
107
---------------
108

109
"""
110

111
import warnings
1✔
112
from collections.abc import Iterable
1✔
113

114
from beartype import beartype
1✔
115

116
from psyneulink._typing import Optional, Union
1✔
117

118
from psyneulink.core.components.functions.nonstateful.transferfunctions import Identity
1✔
119
from psyneulink.core.components.mechanisms.mechanism import Mechanism
1✔
120
from psyneulink.core.components.mechanisms.processing.processingmechanism import ProcessingMechanism_Base
1✔
121
from psyneulink.core.components.ports.inputport import InputPort
1✔
122
from psyneulink.core.components.ports.modulatorysignals.controlsignal import ControlSignal
1✔
123
from psyneulink.core.components.ports.outputport import OutputPort
1✔
124
from psyneulink.core.globals.context import ContextFlags, handle_external_context
1✔
125
from psyneulink.core.globals.keywords import COMPOSITION_INTERFACE_MECHANISM, INPUT_PORTS, OUTPUT_PORTS, \
1✔
126
    PREFERENCE_SET_NAME
127
from psyneulink.core.globals.parameters import Parameter, check_user_specified
1✔
128
from psyneulink.core.globals.preferences.basepreferenceset import ValidPrefSet, REPORT_OUTPUT_PREF
1✔
129
from psyneulink.core.globals.preferences.preferenceset import PreferenceEntry, PreferenceLevel
1✔
130

131
__all__ = ['CompositionInterfaceMechanism']
1✔
132

133

134
class CompositionInterfaceMechanism(ProcessingMechanism_Base):
1✔
135
    """
136
    CompositionInterfaceMechanism(  \
137
        function=Identity())
138

139
    Subclass of `ProcessingMechanism <ProcessingMechanism>` that acts as interface between a Composition and its inputs
140
    from and outputs to the environment or other Components (if it is a `nested Composition <Composition_Nested>`).
141

142
    See `Mechanism <Mechanism_Class_Reference>` for arguments and additional attributes.
143

144
    Attributes
145
    ----------
146

147
    function : InterfaceFunction : default Identity
148
        the function used to transform the variable before assigning it to the Mechanism's OutputPort(s)
149

150
    port_map : dict[Port:(InputPort,OutputPort)]
151
        entries are comprised of keys designating a Component outside the Composition with which it communicates,
152
        and values tuples that designate the corresponding `InputPort` - `OutputPort` pairs used to transmit that
153
        information into or out of the Composition (see `CompositionInterfaceMechanism_Structure`, and
154
        `Composition_CIMs` under Composition for additional details).
155
    """
156

157
    componentType = COMPOSITION_INTERFACE_MECHANISM
1✔
158
    outputPortTypes = [OutputPort, ControlSignal]
1✔
159

160
    classPreferenceLevel = PreferenceLevel.TYPE
1✔
161
    # These will override those specified in TYPE_DEFAULT_PREFERENCES
162
    classPreferences = {
1✔
163
        PREFERENCE_SET_NAME: 'CompositionInterfaceMechanismCustomClassPreferences',
164
        REPORT_OUTPUT_PREF: PreferenceEntry(False, PreferenceLevel.INSTANCE)}
165

166
    class Parameters(ProcessingMechanism_Base.Parameters):
1✔
167
        """
168
            Attributes
169
            ----------
170

171
                function
172
                    see `function <CompositionInterfaceMechanism.function>`
173

174
                    :default value: `Identity`
175
                    :type: `Function`
176
        """
177
        function = Parameter(Identity, stateful=False, loggable=False)
1✔
178

179
    @check_user_specified
1✔
180
    @beartype
1✔
181
    def __init__(self,
1✔
182
                 default_variable=None,
183
                 size=None,
184
                 input_ports: Optional[Union[Iterable, Mechanism, OutputPort, InputPort]] = None,
185
                 function=None,
186
                 composition=None,
187
                 port_map=None,
188
                 params=None,
189
                 name=None,
190
                 prefs:   Optional[ValidPrefSet] = None):
191

192
        if default_variable is None and size is None:
1✔
193
            default_variable = self.class_defaults.variable
1✔
194
        self.composition = composition
1✔
195
        self.port_map = port_map
1✔
196
        self.connected_to_composition = False
1✔
197
        self.user_added_ports = {
1✔
198
            INPUT_PORTS: set(),
199
            OUTPUT_PORTS: set()
200
        }
201
        super(CompositionInterfaceMechanism, self).__init__(default_variable=default_variable,
1✔
202
                                                            size=size,
203
                                                            input_ports=input_ports,
204
                                                            function=function,
205
                                                            params=params,
206
                                                            name=name,
207
                                                            prefs=prefs,
208
                                                            )
209

210
    @handle_external_context()
1✔
211
    def add_ports(self, ports, context=None):
1✔
212
        ports = super(CompositionInterfaceMechanism, self).add_ports(ports, context=context)
1✔
213
        if context.source == ContextFlags.COMMAND_LINE:
1✔
214
            # warnings.warn(
215
            #     'You are attempting to add custom ports to a CIM, which can result in unpredictable behavior and '
216
            #     'is therefore recommended against. If suitable, you should instead add ports to the mechanism(s) '
217
            #     'that project to or are projected to from the CIM.')
218
            # if ports[INPUT_PORTS]:
219
            #     self.user_added_ports[INPUT_PORTS].update([port for port in ports[INPUT_PORTS].data])
220
            # if ports[OUTPUT_PORTS]:
221
            #     self.user_added_ports[OUTPUT_PORTS].update([port for port in ports[OUTPUT_PORTS].data])
222
            from psyneulink.core.compositions.composition import CompositionError
1✔
223
            raise CompositionError(f"Adding ports to a {self.__class__.__name__} is not supported at this time; "
224
                                   f"these are handled automatically when a Composition is created.")
225
        return ports
1✔
226

227
    @handle_external_context()
1✔
228
    def remove_ports(self, ports, context=None):
1✔
229
        super(CompositionInterfaceMechanism, self).remove_ports(ports, context)
1✔
230
        input_ports_marked_for_deletion = set()
1✔
231
        for port in self.user_added_ports[INPUT_PORTS]:
1!
UNCOV
232
            if port not in self.input_ports:
×
UNCOV
233
                input_ports_marked_for_deletion.add(port)
×
234
        self.user_added_ports[INPUT_PORTS] = self.user_added_ports[INPUT_PORTS] - input_ports_marked_for_deletion
1✔
235
        output_ports_marked_for_deletion = set()
1✔
236
        for port in self.user_added_ports[OUTPUT_PORTS]:
1!
UNCOV
237
            if port not in self.output_ports:
×
UNCOV
238
                output_ports_marked_for_deletion.add(port)
×
239
        self.user_added_ports[OUTPUT_PORTS] = self.user_added_ports[OUTPUT_PORTS] - output_ports_marked_for_deletion
1✔
240

241
    # def _get_source_node_for_input_CIM(self, port, start_comp=None, end_comp=None):
242
    def _get_source_node_for_input_CIM(self, port, start_comp=None)->tuple or None:
1✔
243
        """Return Port, Node and Composition  for source of projection to input_CIM from (possibly nested) outer comp
244
        Return None if there is no source Node (i.e., port receives input direcdtly from input to outermost Composition)
245
        **port** InputPort or OutputPort of the input_CIM from which the local projection projects;
246
        **comp** Composition at which to begin the search (or continue it when called recursively;
247
                 assumes the current CompositionInterfaceMechanism's Composition by default
248
        """
249
        # Ensure method is being called on an output_CIM
250
        assert self == self.composition.input_CIM
1✔
251
        #  CIM MAP ENTRIES:  [RECEIVER InputPort,  [input_CIM InputPort,  input_CIM OutputPort]]
252
        # Get sender to input_port of input_CIM
253
        comp = start_comp or self.composition
1✔
254
        port_map = port.owner.port_map
1✔
255
        idx = 0 if isinstance(port, InputPort) else 1
1✔
256
        input_port = [port_map[k][0] for k in port_map if port_map[k][idx] is port]
1✔
257
        assert len(input_port)==1, f"PROGRAM ERROR: Expected exactly 1 input_port for {port.name} " \
1✔
258
                                   f"in port_map for {port.owner}; found {len(input_port)}."
259
        if not len(input_port[0].path_afferents):
1✔
260
            return None
1✔
261
        sender = input_port[0].path_afferents[0].sender
1✔
262
        if not isinstance(sender.owner, CompositionInterfaceMechanism):
1✔
263
            return sender, sender.owner, comp
1✔
264
        return self._get_source_node_for_input_CIM(sender, sender.owner.composition)
1✔
265

266
    def _get_destination_info_from_input_CIM(self, port, comp=None):
1✔
267
        """Return Port, Node and Composition for "ultimate" destination of projection to **port**.
268
        **port**: InputPort or OutputPort of the input_CIM to which the projection of interest projects;
269
                  used to find destination (key) in output_CIM's port_map.
270
        **comp**: Composition at which to begin the search (or continue it when called recursively);
271
                 assumes the Composition for the input_CIM to which **port** belongs by default
272
        """
273
        # Ensure method is being called on an input_CIM
274
        assert self == self.composition.input_CIM
1✔
275
        #  CIM MAP ENTRIES:  [RECEIVER PORT,  [input_CIM InputPort,  input_CIM OutputPort]]
276
        # Get receiver of output_port of input_CIM
277
        comp = comp or self.composition
1✔
278
        port_map = port.owner.port_map
1✔
279
        idx = 0 if isinstance(port, InputPort) else 1
1✔
280
        output_ports = [port_map[k][1] for k in port_map if port_map[k][idx] is port]
1✔
281
        assert len(output_ports)==1, f"PROGRAM ERROR: Expected exactly 1 output_port for {port.name} " \
1✔
282
                                   f"in port_map for {port.owner}; found {len(output_ports)}."
283
        output_port = output_ports[0]
1✔
284
        assert len(output_port.efferents)==1, f"PROGRAM ERROR: Port ({output_port.name}) expected to have " \
1✔
285
                                              f"just one efferent; has {len(output_port.efferents)}."
286
        receiver = output_port.efferents[0].receiver
1✔
287
        if not isinstance(receiver.owner, CompositionInterfaceMechanism):
1✔
288
            return receiver, receiver.owner, comp
1✔
289
        return self._get_destination_info_from_input_CIM(receiver, receiver.owner.composition)
1✔
290

291
    def _get_modulated_info_from_parameter_CIM(self, port, comp=None):
1✔
292
        """Return Port, Node and Composition for parameter modulated by ControlSignal that projects to parameter_CIM.
293
        **port**: InputPort or OutputPort of the parameter_CIM to which the ControlSignal projects;
294
                  used to find destination (key) in parameter_CIM's port_map.
295
        **comp**: Composition at which to begin the search (or continue it when called recursively);
296
                 assumes the Composition for the parameter_CIM to which **port** belongs by default.
297
        """
298
        # Ensure method is being called on a parameter_CIM
299
        assert self == self.composition.parameter_CIM
1✔
300
        #  CIM MAP ENTRIES:  [RECEIVER PORT,  [input_CIM InputPort,  input_CIM OutputPort]]
301
        # Get receiver of output_port of input_CIM
302
        comp = comp or self.composition
1✔
303
        port_map = port.owner.port_map
1✔
304
        idx = 0 if isinstance(port, InputPort) else 1
1✔
305
        output_port = [port_map[k][1] for k in port_map if port_map[k][idx] is port]
1✔
306
        assert len(output_port)==1, f"PROGRAM ERROR: Expected exactly 1 output_port for {port.name} " \
1✔
307
                                   f"in port_map for {port.owner}; found {len(output_port)}."
308
        assert len(output_port[0].efferents)==1, f"PROGRAM ERROR: Port ({output_port.name}) expected to have " \
1✔
309
                                                 f"just one efferent; has {len(output_port.efferents)}."
310
        receiver = output_port[0].efferents[0].receiver
1✔
311
        if not isinstance(receiver.owner, CompositionInterfaceMechanism):
1!
312
            return receiver, receiver.owner, comp
1✔
313
        return self._get_modulated_info_from_parameter_CIM(receiver, receiver.owner.composition)
×
314

315
    def _get_source_of_modulation_for_parameter_CIM(self, port, comp=None):
1✔
316
        """Return ControlSignal, Node and Composition that projects to a ParameterPort from (possibly nested) outer comp
317
        **port**: InputPort or OutputPort of the parameter_CIM from which the local ControlSignal projects;
318
                  used to find source (key) in parameter_CIM's port_map.
319
        **comp**: Composition at which to begin the search (or continue it when called recursively);
320
                 assumes the Composition for the parameter_CIM to which **port** belongs by default.
321
        """
322
        # Ensure method is being called on a parameter_CIM
323
        assert self == self.composition.parameter_CIM
1✔
324
        #  CIM MAP ENTRIES:  [RECEIVER ParameterPort : (parameter_CIM InputPort,  parameter_CIM ControlSignal)
325
        # Get sender to input_port of parameter_CIM
326
        comp = comp or self.composition
1✔
327
        port_map = port.owner.port_map
1✔
328
        idx = 0 if isinstance(port, InputPort) else 1
1✔
329
        input_port = [port_map[k][0] for k in port_map if port_map[k][idx] is port]
1✔
330
        assert len(input_port)==1, f"PROGRAM ERROR: Expected exactly 1 input_port for {port.name} " \
1✔
331
                                   f"in port_map for {port.owner}; found {len(input_port)}."
332
        assert len(input_port[0].path_afferents)==1, f"PROGRAM ERROR: Port ({input_port.name}) expected to have " \
1✔
333
                                                     f"just one path_afferent; has {len(input_port.path_afferents)}."
334
        sender = input_port[0].path_afferents[0].sender
1✔
335
        if not isinstance(sender.owner, CompositionInterfaceMechanism):
1!
336
            return sender, sender.owner, comp
1✔
337
        return self._get_source_of_modulation_for_parameter_CIM(sender, sender.owner.composition)
×
338

339
    def _get_source_info_from_output_CIM(self, port, comp=None)->tuple:
1✔
340
        """Return Port, Node and Composition for "original" source of projection from **port**.
341
        **port** InputPort or OutputPort of the output_CIM from which the projection of interest projects;
342
                 used to find source (key) in output_CIM's port_map.
343
        **comp** Composition at which to begin the search (or continue it when called recursively);
344
                 assumes the current CompositionInterfaceMechanism's Composition by default.
345
        """
346
        # Ensure method is being called on an output_CIM
347
        assert self == self.composition.output_CIM, f"_get_source_info_from_output_CIM called on {self.name} " \
1✔
348
                                                    f"which is not an output_CIM"
349
        #  CIM MAP ENTRIES:  [SENDER PORT,  [output_CIM InputPort,  output_CIM OutputPort]]
350
        # Get sender to input_port of output_CIM
351
        comp = comp or self.composition
1✔
352
        port_map = port.owner.port_map
1✔
353
        idx = 0 if isinstance(port, InputPort) else 1
1✔
354
        input_ports = [port_map[k][0] for k in port_map if port_map[k][idx] is port]
1✔
355
        assert len(input_ports)==1, f"PROGRAM ERROR: Expected exactly 1 input_port for {port.name} " \
1✔
356
                                   f"in port_map for {port.owner}; found {len(input_ports)}."
357
        assert len(input_ports[0].path_afferents)==1, f"PROGRAM ERROR: Port ({input_ports[0].name}) expected to have " \
1✔
358
                                                     f"just one path_afferent; has {len(input_ports.path_afferents)}."
359
        sender = input_ports[0].path_afferents[0].sender
1✔
360
        if not isinstance(sender.owner, CompositionInterfaceMechanism):
1✔
361
            return sender, sender.owner, comp
1✔
362
        return self._get_source_info_from_output_CIM(sender, sender.owner.composition)
1✔
363

364
    def _get_destination_info_for_output_CIM(self, port, comp=None)-> list or None:
1✔
365
        """Return Port, Node and Composition for "ultimate" destination(s) of projection to **port**.
366
        **port**: InputPort or OutputPort of the output_CIM to which the projection of interest projects;
367
                  used to find source (key=SENDER PORT) of the projection to the output_CIM.
368
        **comp**: Composition at which to begin the search (or continue it when called recursively);
369
                 assumes the Composition for the output_CIM to which **port** belongs by default
370
        Return list of tuples, one for each destination, if there is more than one destination;
371
          this occurs if the source of the projection to the output_CIM (SENDER PORT) is a Node in a nested Composition
372
          that is specified to project to more than one Node in the outer Composition
373
        Return None if there are no destination Nodes (i.e., source is output of outermost Composition
374
        """
375
        from psyneulink.core.compositions.composition import get_composition_for_node
1✔
376

377
        # Ensure method is being called on an output_CIM
378
        assert self == self.composition.output_CIM
1✔
379
        #  CIM MAP ENTRIES:  [SENDER PORT,  [output_CIM InputPort,  output_CIM OutputPort]]
380
        # Get receiver of output_port of output_CIM
381
        comp = comp or self.composition
1✔
382
        port_map = port.owner.port_map
1✔
383
        idx = 0 if isinstance(port, InputPort) else 1
1✔
384
        output_ports = [port_map[k][1] for k in port_map if port_map[k][idx] is port]
1✔
385
        assert len(output_ports)==1, f"PROGRAM ERROR: Expected exactly 1 output_port for {port.name} " \
1✔
386
                                   f"in port_map for {port.owner}; found {len(output_ports)}."
387
        output_port = output_ports[0]
1✔
388
        receivers_info = []
1✔
389
        if not output_port.efferents:
1✔
390
            return None
1✔
391
        for efferent in output_port.efferents:
1✔
392
            receiver = efferent.receiver
1✔
393
            if not isinstance(efferent.receiver.owner, CompositionInterfaceMechanism):
1✔
394
                assert comp.is_nested
1✔
395
                receiver_comp = get_composition_for_node(receiver.owner)
1✔
396
                receivers_info.append((efferent.receiver, efferent.receiver.owner, receiver_comp))
1✔
397
            else:
398
                receivers_info.append(self._get_destination_info_for_output_CIM(efferent.receiver,
1✔
399
                                                                                efferent.receiver.owner.composition))
400
        return receivers_info if any(receivers_info) else None
1✔
401

402
    def _sender_is_probe(self, output_port):
1✔
403
        """Return True if source of output_port is a PROBE Node of the Composition to which it belongs"""
404
        from psyneulink.core.compositions.composition import NodeRole
1✔
405
        port, node, comp = self._get_source_info_from_output_CIM(output_port, self.composition)
1✔
406
        return NodeRole.PROBE in comp.get_roles_by_node(node)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc