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

PrincetonUniversity / PsyNeuLink / 15917088825

05 Jun 2025 04:18AM UTC coverage: 84.482% (+0.5%) from 84.017%
15917088825

push

github

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

Devel

9909 of 12966 branches covered (76.42%)

Branch coverage included in aggregate %.

1708 of 1908 new or added lines in 54 files covered. (89.52%)

25 existing lines in 14 files now uncovered.

34484 of 39581 relevant lines covered (87.12%)

0.87 hits per line

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

84.76
/psyneulink/core/components/mechanisms/mechanism.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
# ****************************************  MECHANISM MODULE ***********************************************************
10

11
"""
12

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

16
  * `Mechanism_Overview`
17
  * `Mechanism_Creation`
18
  * `Mechanism_Structure`
19
      - `Mechanism_Function`
20
      - `Mechanism_Ports`
21
          • `Mechanism_InputPorts`
22
          • `Mechanism_ParameterPorts`
23
          • `Mechanism_OutputPorts`
24
      - `Mechanism_Additional_Attributes`
25
      - `Mechanism_in_Composition`
26
  * `Mechanism_Execution`
27
      - `Mechanism_Execution_Composition`
28
      - `Mechanism_Runtime_Params`
29
  * `Mechanism_Class_Reference`
30

31

32
.. _Mechanism_Overview:
33

34
Overview
35
--------
36

37
A Mechanism takes an input, transforms it in some way, and makes the result available as its output.  There are two
38
types of Mechanisms in PsyNeuLink:
39

40
    * `ProcessingMechanisms <ProcessingMechanism>` aggregate the input they receive from other Mechanisms, and/or the
41
      input to the `Composition` to which they belong, transform it in some way, and provide the result as input to
42
      other Mechanisms in the Composition, or as the output for the Composition itself.  There are a variety of
43
      different types of ProcessingMechanism, that accept various forms of input and transform them in different ways
44
      (see `ProcessingMechanisms <ProcessingMechanism>` for a list).
45

46
      to modulate the parameters of other Mechanisms or Projections.  There are three basic ModulatoryMechanisms:
47

48
      * `LearningMechanism <LearningMechanism>` - these receive training (target) values, and compare them with the
49
        output of a Mechanism to generate `LearningSignals <LearningSignal>` that are used to modify `MappingProjections
50
        <MappingProjection>` (see `learning <Process_Execution_Learning>`).
51

52
      * `ControlMechanism <ControlMechanism>` - these evaluate the output of a specified set of Mechanisms, and
53
        generate `ControlSignals <ControlSignal>` used to modify the parameters of those or other Mechanisms.
54

55
      * `GatingMechanism <GatingMechanism>` - these use their input(s) to determine whether and how to modify the
56
        `value <Port_Base.value>` of the `InputPort(s) <InputPort>` and/or `OutputPort(s) <OutputPort>` of other
57
        Mechanisms.
58

59
      Each type of ModulatoryMechanism is associated with a corresponding type of `ModulatorySignal <ModulatorySignal>`
60
      (a type of `OutputPort` specialized for use with the ModulatoryMechanism) and `ModulatoryProjection
61
      <ModulatoryProjection>`.
62

63
Every Mechanism is made up of four fundamental components:
64

65
    * `InputPort(s) <InputPort>` used to receive and represent its input(s);
66

67
    * `Function <Function>` used to transform its input(s) into its output(s);
68

69
    * `ParameterPort(s) <ParameterPort>` used to represent the parameters of its Function (and/or any
70
      parameters that are specific to the Mechanism itself);
71

72
    * `OutputPort(s) <OutputPort>` used to represent its output(s)
73

74
These are described in the sections on `Mechanism_Function` and `Mechanism_Ports` (`Mechanism_InputPorts`,
75
`Mechanism_ParameterPorts`, and `Mechanism_OutputPorts`), and shown graphically in a `figure <Mechanism_Figure>`,
76
under `Mechanism_Structure` below.
77

78
.. _Mechanism_Creation:
79

80
Creating a Mechanism
81
--------------------
82

83
Mechanisms are created by calling the constructor for a particular type.  PsyNeuLink also automatically
84
creates one or more Mechanisms under some circumstances. For example, a `ComparatorMechanism` and `LearningMechanism
85
<LearningMechanism>` are created automatically when `learning is specified <Composition_Learning>` for a `Composition`;
86
and an `ObjectiveMechanism` may be created when a `ControlMechanism <ControlMechanism>` is created.
87

88
COMMENT:
89
Mechanisms can be created in several ways.  The simplest is to call the constructor for the desired type of Mechanism.
90
Alternatively, the `mechanism` command can be used to create a specific type of Mechanism or an instance of
91
`default_mechanism <Mechanism_Base.default_mechanism>`. Mechanisms can also be specified "in context," for example in
92
the `pathway <Composition.pathway>` attribute of a `Process`; the Mechanism can be specified in either of the ways
93
mentioned above, or using one of the following:
94

95
  * the name of an **existing Mechanism**;
96

97
  * the name of a **Mechanism type** (subclass);
98

99
  * a **specification dictionary** -- this can contain an entry specifying the type of Mechanism,
100
    and/or entries specifying the value of parameters used to instantiate it.
101
    These should take the following form:
102

103
      * *MECHANISM_TYPE*: <name of a Mechanism type>
104
          if this entry is absent, a `default_mechanism <Mechanism_Base.default_mechanism>` will be created.
105

106
      * *NAME*: <str>
107
          the string will be used as the `name <Mechanism_Base.name>` of the Mechanism;  if this entry is absent,
108
          the name will be the name of the Mechanism's type, suffixed with an index if there are any others of the
109
          same type for which a default name has been assigned.
110

111
      * <name of parameter>:<value>
112
          this can contain any of the `standard parameters <Mechanism_Additional_Attributes>` for instantiating a
113
          Mechanism or ones specific to a particular type of Mechanism (see documentation for the type).  The key must
114
          be the name of the argument used to specify the parameter in the Mechanism's constructor, and the value must
115
          be a legal value for that parameter, using any of the ways allowed for `specifying a parameter
116
          <ParameterPort_Specification>`. The parameter values specified will be used to instantiate the Mechanism.
117
          These can be overridden during execution by specifying `Mechanism_Runtime_Params`, either when calling
118
          the Mechanism's `execute <Mechanism_Base.execute>` method, or in the `execution method
119
          <Composition_Execution_Methods>` of a Composition.
120

121
  * **automatically** -- PsyNeuLink automatically creates one or more Mechanisms under some circumstances. For example,
122
    a `ComparatorMechanism` and `LearningMechanism <LearningMechanism>` are created automatically when `learning is
123
    specified <Composition_Learning>` for a Composition; and an `ObjectiveMechanism` may be created when a
124
    `ControlMechanism <ControlMechanism>` is created.
125
COMMENT
126

127
.. _Mechanism_Port_Specification:
128

129
*Specifying Ports*
130
~~~~~~~~~~~~~~~~~~~
131

132
Every Mechanism has one or more `InputPorts <InputPort>`, `ParameterPorts <ParameterPort>`, and `OutputPorts
133
<OutputPort>` (described `below <Mechanism_Ports>`) that allow it to receive and send `Projections <Projection>`,
134
and to execute its `function <Mechanism_Base.function>`).  When a Mechanism is created, it automatically creates the
135
ParameterPorts it needs to represent its parameters, including those of its `function <Mechanism_Base.function>`.
136
It also creates any InputPorts and OutputPorts required for the Projections it has been assigned. InputPorts and
137
OutputPorts, and corresponding Projections (including those from `ModulatorySignals <ModulatorySignal>`) can also be
138
specified explicitly in the **input_ports** and **output_ports** arguments of the Mechanism's constructor (see
139
`Mechanism_InputPorts` and `Mechanism_OutputPorts`, respectively, as well as the `first example <Mechanism_Example_1>`
140
below, and `Port_Examples`).  They can also be specified in a `parameter specification dictionary
141
<ParameterPort_Specification>` assigned to the Mechanism's **params** argument, using entries with the keys
142
*INPUT_PORTS* and *OUTPUT_PORTS*, respectively (see `second example <Mechanism_Example_2>` below).  While
143
specifying the **input_ports** and **output_ports** arguments directly is simpler and more convenient,
144
the dictionary format allows parameter sets to be created elsewhere and/or re-used.  The value of each entry can be
145
any of the allowable forms for `specifying a port <Port_Specification>`. InputPorts and OutputPorts can also be
146
added to an existing Mechanism using its `add_ports <Mechanism_Base.add_ports>` method, although this is generally
147
not needed and can have consequences that must be considered (e.g., see `note <Mechanism_Add_InputPorts_Note>`),
148
and therefore is not recommended.
149

150
.. _Mechanism_Default_Port_Suppression_Note:
151

152
    .. note::
153
       When Ports are specified in the **input_ports** or **output_ports** arguments of a Mechanism's constructor,
154
       they replace any default Ports generated by the Mechanism when it is created (if no Ports were specified).
155
       This is particularly relevant for OutputPorts, as most Mechanisms create one or more `Standard OutputPorts
156
       <OutputPort_Standard>` by default, that have useful properties.  To retain those Ports if any are specified in
157
       the **output_ports** argument, they must be included along with those ports in the **output_ports** argument
158
       (see `examples <Port_Standard_OutputPorts_Example>`).  The same is true for default InputPorts and the
159
       **input_ports** argument.
160

161
       This behavior differs from adding a Port once the Mechanism is created.  Ports added to Mechanism using the
162
       Mechanism's `add_ports <Mechanism_Base.add_ports>` method, or by assigning the Mechanism in the **owner**
163
       argument of the Port's constructor, are added to the Mechanism without replacing any of its existing Ports,
164
       including any default Ports that may have been generated when the Mechanism was created (see `examples
165
       <Port_Create_Port_Examples>` in Port).
166

167

168
Examples
169
^^^^^^^^
170

171
.. _Mechanism_Example_1:
172

173
The following example creates an instance of a TransferMechanism that names the default InputPort ``MY_INPUT``,
174
and assigns three `Standard OutputPorts <OutputPort_Standard>`::
175

176
    >>> import psyneulink as pnl
177
    >>> my_mech = pnl.TransferMechanism(input_ports=['MY_INPUT'],
178
    ...                                 output_ports=[pnl.RESULT, pnl.MEAN, pnl.VARIANCE])
179

180

181
.. _Mechanism_Example_2:
182

183
This shows how the same Mechanism can be specified using a dictionary assigned to the **params** argument::
184

185
     >>> my_mech = pnl.TransferMechanism(params={pnl.INPUT_PORTS: ['MY_INPUT'],
186
     ...                                         pnl.OUTPUT_PORTS: [pnl.RESULT, pnl.MEAN, pnl.VARIANCE]})
187

188
See `Port <Port_Examples>` for additional examples of specifying the Ports of a Mechanism.
189

190
.. _Mechanism_Parameter_Specification:
191

192
*Specifying Parameters*
193
~~~~~~~~~~~~~~~~~~~~~~~
194

195
As described `below <Mechanism_ParameterPorts>`, Mechanisms have `ParameterPorts <ParameterPort>` that provide the
196
current value of a parameter used by the Mechanism and/or its `function <Mechanism_Base.function>` when it is `executed
197
<Mechanism_Execution>`. These can also be used by a `ControlMechanism <ControlMechanism>` to control the parameters of
198
the Mechanism and/or it `function <Mechanism_Base.function>`.  The value of any of these, and their control, can be
199
specified in the corresponding argument of the constructor for the Mechanism and/or its `function
200
<Mechanism_Base.function>`,  or in a parameter specification dictionary assigned to the **params** argument of its
201
constructor, as described under `ParameterPort_Specification`.
202

203

204
.. _Mechanism_Structure:
205

206
Structure
207
---------
208

209
.. _Mechanism_Function:
210

211
*Function*
212
~~~~~~~~~~
213

214
The core of every Mechanism is its function, which transforms its input to generate its output.  The function is
215
specified by the Mechanism's `function <Mechanism_Base.function>` attribute.  Every type of Mechanism has at least one
216
(primary) function, and some have additional (auxiliary) ones (for example, `TransferMechanism` and
217
`EVCControlMechanism`). Mechanism functions are generally from the PsyNeuLink `Function` class.  Most Mechanisms
218
allow their function to be specified, using the `function` argument of the Mechanism's constructor.  The function can
219
be specified using the name of `Function <Function>` class, or its constructor (including arguments that specify its
220
parameters).  For example, the `function <Mechanism_Base.function>` of a `TransferMechanism`, which is `Linear` by
221
default, can be specified to be the `Logistic` function as follows::
222

223
    >>> my_mechanism = pnl.TransferMechanism(function=pnl.Logistic(gain=1.0, bias=-4))
224

225
Notice that the parameters of the :keyword:`function` (in this case, `gain` and `bias`) can be specified by including
226
them in its constructor.  Some Mechanisms support only a single function.  In that case, the :keyword:`function`
227
argument is not available in the Mechanism's constructor, but it does include arguments for the function's
228
parameters.  For example, the :keyword:`function` of a `ComparatorMechanism` is always the `LinearCombination` function,
229
so the Mechanisms' constructor does not have a :keyword:`function` argument.  However, it does have a
230
**comparison_operation** argument, that is used to set the LinearCombination function's `operation` parameter.
231

232
The parameters for a Mechanism's primary function can also be specified as entries in a *FUNCTION_PARAMS* entry of a
233
`parameter specification dictionary <ParameterPort_Specification>` in the **params** argument of the Mechanism's
234
constructor.  For example, the parameters of the `Logistic` function in the example above can
235
also be assigned as follows::
236

237
    >>> my_mechanism = pnl.TransferMechanism(function=pnl.Logistic,
238
    ...                                      params={pnl.FUNCTION_PARAMS: {pnl.GAIN: 1.0, pnl.BIAS: -4.0}})
239

240
Again, while not as simple as specifying these as arguments in the function's constructor, this format is more flexible.
241
Any values specified in the parameter dictionary will **override** any specified within the constructor for the function
242
itself (see `DDM <DDM_Creation>` for an example).
243

244
COMMENT:
245
.. _Mechanism_Function_Attribute:
246

247
`function <Mechanism_Base.function>` *attribute*
248
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
249

250
The `Function <Function>` assigned as the primary function of a Mechanism is assigned to the Mechanism's
251
`function <Component.function>` attribute, and its `function <Function_Base.function>` is assigned
252
to the Mechanism's `function <Mechanism_Base.function>` attribute.
253
COMMENT
254

255
.. note::
256
   It is important to recognize the distinction between a `Function <Function>` and its `function
257
   <Function_Base.function>` attribute (note the difference in capitalization).  A *Function* is a PsyNeuLink `Component
258
   <Component>`, that can be created using a constructor; a *function* is an attribute that contains a callable method
259
   belonging to a Function, and that is executed when the Component to which the Function belongs is executed.
260
   Functions are used to assign, store, and apply parameter values associated with their function (see `Function
261
   <Function_Overview>` for a more detailed explanation).
262

263
The parameters of a Mechanism's `function <Mechanism_Base.function>` are attributes of its `function
264
<Component.function>`, and can be accessed using standard "dot" notation for that object.  For
265
example, the `gain <Logistic.gain>` and `bias <Logistic.bias>` parameters of the `Logistic` function in the example
266
above can be access as ``my_mechanism.function.gain`` and ``my_mechanism.function.bias``.  They are
267
also assigned to a dictionary in the Mechanism's `function_params <Mechanism_Base.function_params>` attribute,
268
and can be  accessed using the parameter's name as the key for its entry in the dictionary.  For example,
269
the parameters in the  example above could also be accessed as ``my_mechanism.function_params[GAIN]`` and
270
``my_mechanism.function_params[GAIN]``
271

272
Some Mechanisms have auxiliary functions that are inherent (i.e., not made available as arguments in the Mechanism's
273
constructor;  e.g., the `integrator_function <TransferMechanism.integrator_function>` of a `TransferMechanism`);
274
however, the Mechanism may include parameters for those functions in its constructor (e.g., the **noise** argument in
275
the constructor for a `TransferMechanism` is used as the `noise <AdaptiveIntegrator.noise>` parameter of the
276
`AdaptiveIntegrator` assigned to the TransferMechanism's `integrator_function <TransferMechanism.integrator_function>`).
277

278
COMMENT:
279
NOT CURRENTLY IMPLEMENTED
280
For Mechanisms that offer a selection of functions for the primary function (such as the `TransferMechanism`), if all
281
of the functions use the same parameters, then those parameters can also be specified as entries in a `parameter
282
specification dictionary <ParameterPort_Specification>` as described above;  however, any parameters that are unique
283
to a particular function must be specified in a constructor for that function.  For Mechanisms that have additional,
284
auxiliary functions, those must be specified in arguments for them in the Mechanism's constructor, and their parameters
285
must be specified in constructors for those functions unless documented otherwise.
286
COMMENT
287

288

289
COMMENT:
290
    FOR DEVELOPERS:
291
    + FUNCTION : function or method :  method used to transform Mechanism input to its output;
292
        This must be implemented by the subclass, or an exception will be raised;
293
        each item in the variable of this method must be compatible with a corresponding InputPort;
294
        each item in the output of this method must be compatible  with the corresponding OutputPort;
295
        for any parameter of the method that has been assigned a ParameterPort,
296
        the output of the ParameterPort's own execute method must be compatible with
297
        the value of the parameter with the same name in params[FUNCTION_PARAMS] (EMP)
298
    + FUNCTION_PARAMS (dict):
299
        NOTE: function parameters can be specified either as arguments in the Mechanism's __init__ method,
300
        or by assignment of the function_params attribute.
301
        Only one of these methods should be used, and should be chosen using the following principle:
302
        - if the Mechanism implements one function, then its parameters should be provided as arguments in the __init__
303
        - if the Mechanism implements several possible functions and they do not ALL share the SAME parameters,
304
            then the function should be provided as an argument but not they parameters; they should be specified
305
            as arguments in the specification of the function
306
        each parameter is instantiated as a ParameterPort
307
        that will be placed in <Mechanism_Base>._parameter_ports;  each parameter is also referenced in
308
        the <Mechanism>.function_params dict, and assigned its own attribute (<Mechanism>.<param>).
309
COMMENT
310

311

312
.. _Mechanism_Custom_Function:
313

314
Custom Functions
315
^^^^^^^^^^^^^^^^
316

317
A Mechanism's `function <Mechanism_Base.function>` can be customized by assigning a user-defined function (e.g.,
318
a lambda function), so long as it takes arguments and returns values that are compatible with those of the
319
Mechanism's defaults for that function.  This is also true for auxiliary functions that appear as arguments in a
320
Mechanism's constructor (e.g., the `EVCControlMechanism`).  A user-defined function can be assigned
321
directly to the corresponding attribute of the Mechanism
322
(for its primary function, its `function <Mechanism_Base.function>` attribute). When a user-defined function is
323
specified, it is automatically converted to a `UserDefinedFunction`.
324

325
.. note::
326
   It is *strongly advised* that auxiliary functions that are inherent to a Mechanism
327
   (i.e., ones that do *not* appear as an argument in the Mechanism's constructor,
328
   such as the `integrator_function <TransferMechanism.integrator_function>` of a
329
   `TransferMechanism`) *not* be assigned custom functions;  this is because their
330
   parameters are included as arguments in the constructor for the Mechanism,
331
   and thus changing the function could produce confusing and/or unpredictable effects.
332

333

334
COMMENT:
335
    When a custom function is specified,
336
    the function itself is assigned to the Mechanism's designated attribute.  At the same time, PsyNeuLink automatically
337
    creates a `UserDefinedFunction` object, and assigns the custom function to its
338
    `function <UserDefinedFunction.function>` attribute.
339
COMMENT
340

341
.. _Mechanism_Variable_and_Value:
342

343
`variable <Mechanism_Base.variable>` *and* `value <Mechanism_Base.value>` *attributes*
344
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
345

346
The input to a Mechanism's `function <Mechanism_Base.function>` is provided by the Mechanism's `variable
347
<Mechanism_Base.variable>` attribute.  This is an ndarray that is at least 2d, with one item of its outermost
348
dimension (axis 0) for each of the Mechanism's `input_ports <Mechanism_Base.input_ports>` (see
349
`below <Mechanism_InputPorts>`).  The result of the  `function <Mechanism_Base.function>` is placed in the
350
Mechanism's `value <Mechanism_Base.value>` attribute which is  also at least a 2d array.  The Mechanism's `value
351
<Mechanism_Base.value>` is referenced by its `OutputPorts <Mechanism_OutputPorts>` to generate their own `value
352
<OutputPort.value>` attributes, each of which is assigned as the value of an item of the list in the Mechanism's
353
`output_values <Mechanism_Base.output_values>` attribute (see `Mechanism_OutputPorts` below), which is the value
354
returned by a call to the Mechanism's `execute <Mechanism_Base.execute>` method.
355

356
.. note::
357
   The input to a Mechanism is not necessarily the same as the input to its `function <Mechanism_Base.function>`. The
358
   input to a Mechanism is first processed by its `InputPort(s) <Mechanism_InputPorts>`, and then assigned to the
359
   Mechanism's `variable <Mechanism_Base.variable>` attribute, which is used as the input to its `function
360
   <Mechanism_Base.function>`. Similarly, the result of a Mechanism's function is not necessarily the same as the
361
   Mechanism's output.  The result of the `function <Mechanism_Base.function>` is assigned to the Mechanism's  `value
362
   <Mechanism_Base.value>` attribute, which is then used by its `OutputPort(s) <Mechanism_OutputPorts>` to assign
363
   items to its `output_values <Mechanism_Base.output_values>` attribute.
364

365
.. _Mechanism_Ports:
366

367
*Ports*
368
~~~~~~~~
369

370
Every Mechanism has one or more of each of three types of Ports:  `InputPort(s) <InputPort>`,
371
`ParameterPort(s) <ParameterPort>`, `and OutputPort(s) <OutputPort>`.  Generally, these are created automatically
372
when the Mechanism is created.  InputPorts and OutputPorts (but not ParameterPorts) can also be specified explicitly
373
for a Mechanism, or added to an existing Mechanism using its `add_ports <Mechanism_Base.add_ports>` method, as
374
described `above <Mechanism_Port_Specification>`).
375

376
.. _Mechanism_Figure:
377

378
The three types of Ports are shown schematically in the figure below, and described briefly in the following sections.
379

380
.. figure:: _static/Mechanism_Ports_fig.svg
381
   :alt: Mechanism Ports
382
   :align: left
383

384
   **Schematic of a Mechanism showing its three types of Ports** (`InputPort`, `ParameterPort` and `OutputPort`).
385
   Every Mechanism has at least one (`primary <InputPort_Primary>`) InputPort and one (`primary
386
   <OutputPort_Primary>`) OutputPort, but can have additional ports of each type.  It also has one
387
   `ParameterPort` for each of its parameters and the parameters of its `function <Mechanism_Base.function>`.
388
   The `value <InputPort.value>` of each InputPort is assigned as an item of the Mechanism's `variable
389
   <Mechanism_Base.variable>`, and the result of its `function <Mechanism_Base.function>` is assigned as the Mechanism's
390
   `value <Mechanism_Base.value>`, the items of which are referenced by its OutputPorts to determine their own
391
   `value <OutputPort.value>`\\s (see `Mechanism_Variable_and_Value` above, and more detailed descriptions below).
392

393
.. _Mechanism_InputPorts:
394

395
InputPorts
396
^^^^^^^^^^^
397

398
These receive, potentially combine, and represent the input to a Mechanism, and provide this to the Mechanism's
399
`function <Mechanism_Base.function>`. Usually, a Mechanism has only one (`primary <InputPort_Primary>`) `InputPort`,
400
identified in its `input_port <Mechanism_Base.input_port>` attribute. However some Mechanisms have more than one
401
InputPort. For example, a `ComparatorMechanism` has one InputPort for its **SAMPLE** and another for its **TARGET**
402
input. All of the Mechanism's InputPorts (including its primary InputPort <InputPort_Primary>` are listed in its
403
`input_ports <Mechanism_Base.input_ports>` attribute (note the plural).
404

405
.. technical_note::
406
   The `input_ports <Mechanism_Base.input_ports>` attribute is a ContentAddressableList
407
   -- a PsyNeuLink-defined subclass of the Python class `UserList
408
   <https://docs.python.org/3.6/library/collections.html?highlight=userlist#collections.UserList>`_ --
409
   that allows a specific InputPort in the list to be accessed using its name as the index for the list
410
   (e.g., ``my_mechanism['InputPort name']``).
411

412
.. _Mechanism_Variable_and_InputPorts:
413

414
The `value <InputPort.value>` of each InputPort for a Mechanism is assigned to a different item of the Mechanism's
415
`variable <Mechanism_Base.variable>` attribute (a 2d np.array), as well as to a corresponding item of its `input_values
416
<Mechanism_Base.input_values>` attribute (a list).  The `variable <Mechanism_Base.variable>` provides the input to the
417
Mechanism's `function <Mechanism_Base.function>`, while its `input_values <Mechanism_Base.input_values>` provides a
418
convenient way of accessing the value of its individual items.  Because there is a one-to-one correspondence between
419
a Mechanism's InputPorts and the items of its `variable <Mechanism_Base.variable>`, their size along their outermost
420
dimension (axis 0) must be equal; that is, the number of items in the Mechanism's `variable <Mechanism_Base.variable>`
421
attribute must equal the number of InputPorts in its `input_ports <Mechanism_Base.input_ports>` attribute. A
422
Mechanism's constructor does its best to insure this:  if its **default_variable** and/or its **input_shapes** argument is
423
specified, it constructs a number of InputPorts (and each with a `value <InputPort.value>`) corresponding to the
424
items specified for the Mechanism's `variable <Mechanism_Base.variable>`, as in the examples below::
425

426
    my_mech_A = pnl.TransferMechanism(default_variable=[[0],[0,0]])
427
    print(my_mech_A.input_ports)
428
    > [(InputPort InputPort-0), (InputPort InputPort-1)]
429
    print(my_mech_A.input_ports[0].value)
430
    > [ 0.]
431
    print(my_mech_A.input_ports[1].value)
432
    > [ 0.  0.]
433

434
    my_mech_B = pnl.TransferMechanism(default_variable=[[0],[0],[0]])
435
    print(my_mech_B.input_ports)
436
    > [(InputPort InputPort-0), (InputPort InputPort-1), (InputPort InputPort-2)]
437

438
Conversely, if the **input_ports** argument is used to specify InputPorts for the Mechanism, they are used to format
439
the Mechanism's variable::
440

441
    my_mech_C = pnl.TransferMechanism(input_ports=[[0,0], 'Hello'])
442
    print(my_mech_C.input_ports)
443
    > [(InputPort InputPort-0), (InputPort Hello)]
444
    print(my_mech_C.variable)
445
    > [array([0, 0]) array([0])]
446

447
If both the **default_variable** (or **input_shapes**) and **input_ports** arguments are specified, then the number and format
448
of their respective items must be the same (see `Port <Port_Examples>` for additional examples of specifying Ports).
449

450
If InputPorts are added using the Mechanism's `add_ports <Mechanism_Base.add_ports>` method, then its
451
`variable <Mechanism_Base.variable>` is extended to accommodate the number of InputPorts added (note that this must
452
be coordinated with the Mechanism's `function <Mechanism_Base.function>`, which takes the Mechanism's `variable
453
<Mechanism_Base.variable>` as its input (see `note <Mechanism_Add_InputPorts_Note>`).
454

455
The order in which `InputPorts are specified <Mechanism_InputPort_Specification>` in the Mechanism's constructor,
456
and/or `added <Mechanism_Add_InputPorts>` using its `add_ports <Mechanism_Base.add_ports>` method,  determines the
457
order of the items to which they are assigned assigned in he Mechanism's `variable  <Mechanism_Base.variable>`,
458
and are listed in its `input_ports <Mechanism_Base.input_ports>` and `input_values <Mechanism_Base.input_values>`
459
attribute.  Note that a Mechanism's `input_values <Mechanism_Base.input_values>` attribute has the same information as
460
the Mechanism's `variable <Mechanism_Base.variable>`, but in the form of a list rather than an ndarray.
461

462
.. _Mechanism_InputPort_Specification:
463

464
**Specifying InputPorts and a Mechanism's** `variable <Mechanism_Base.variable>` **Attribute**
465

466
When a Mechanism is created, the number and format of the items in its `variable <Mechanism_Base.variable>`
467
attribute, as well as the number of InputPorts it has and their `variable <InputPort.variable>` and `value
468
<InputPort.value>` attributes, are determined by one of the following arguments in the Mechanism's constructor:
469

470
* **default_variable** (at least 2d ndarray) -- determines the number and format of the items of the Mechanism's
471
  `variable <Mechanism_Base.variable>` attribute.  The number of items in its outermost dimension (axis 0) determines
472
  the number of InputPorts created for the Mechanism, and the format of each item determines the format for the
473
  `variable <InputPort.variable>` and `value  <InputPort.value>` attributes of the corresponding InputPort.
474
  If any InputPorts are specified in the **input_ports** argument or an *INPUT_PORTS* entry of
475
  a specification dictionary assigned to the **params** argument of the Mechanism's constructor, then the number
476
  must match the number of items in **default_variable**, or an error is generated.  The format of the items in
477
  **default_variable** are used to specify the format of the `variable <InputPort.variable>` or `value
478
  <InputPort.value>` of the corresponding InputPorts for any that are not explicitly specified in the
479
  **input_ports** argument or *INPUT_PORTS* entry (see below).
480
..
481
* **input_shapes** (int, list or ndarray) -- specifies the number and length of items in the Mechanism's variable,
482
  if **default_variable** is not specified. For example, the following mechanisms are equivalent::
483
    T1 = TransferMechanism(input_shapes = [3, 2])
484
    T2 = TransferMechanism(default_variable = [[0, 0, 0], [0, 0]])
485
  The relationship to any specifications in the **input_ports** argument or
486
  *INPUT_PORTS* entry of a **params** dictionary is the same as for the **default_variable** argument,
487
  with the latter taking precedence (see above).
488
..
489
* **input_ports** (list) -- this can be used to explicitly `specify the InputPorts <InputPort_Specification>`
490
  created for the Mechanism. Each item must be an `InputPort specification <InputPort_Specification>`, and the number
491
  of items must match the number of items in the **default_variable** argument or **input_shapes** argument
492
  if either of those is specified.  If the `variable <InputPort.variable>` and/or `value <InputPort.value>`
493
  is `explicitly specified for an InputPort <InputPort_Variable_and_Value>` in the **input_ports** argument or
494
  *INPUT_PORTS* entry of a **params** dictionary, it must be compatible with the value of the corresponding
495
  item of **default_variable**; otherwise, the format of the item in **default_variable** corresponding to the
496
  InputPort is used to specify the format of the InputPort's `variable <InputPort.variable>` (e.g., the InputPort is
497
  `specified using an OutputPort <InputPort_Projection_Source_Specification>` to project to it;).  If
498
  **default_variable** is not specified, a default value is specified by the Mechanism.  InputPorts can also be
499
  specifed that `shadow the inputs <InputPort_Shadow_Inputs>` of other InputPorts and/or Mechanisms; that is, receive
500
  Projections from all of the same `senders <Projection_Base.sender>` as those specified.
501

502
COMMENT:
503
    *** ADD SOME EXAMPLES HERE (see `examples <XXX>`)
504
COMMENT
505

506
COMMENT:
507
    *** ADD THESE TO ABOVE WHEN IMPLEMENTED:
508
        If more InputPorts are specified than there are items in `variable <Mechanism_Base.variable>,
509
            the latter is extended to  match the former.
510
        If the Mechanism's `variable <Mechanism_Base.variable>` has more than one item, it may still be assigned
511
            a single InputPort;  in that case, the `value <InputPort.value>` of that InputPort must have the same
512
            number of items as the Mechanisms's `variable <Mechanism_Base.variable>`.
513
COMMENT
514
..
515
* *INPUT_PORTS* entry of a params dict (list) -- specifications are treated in the same manner as those in the
516
  **input_ports** argument, and take precedence over those.
517

518
.. _Mechanism_Add_InputPorts:
519

520
**Adding InputPorts**
521

522
InputPorts can be added to a Mechanism using its `add_ports <Mechanism_Base.add_ports>` method;  this extends its
523
`variable <Mechanism_Base.variable>` by a number of items equal to the number of InputPorts added, and each new item
524
is assigned a format compatible with the `value <InputPort.value>` of the corresponding InputPort added;  if the
525
InputPort's `variable <InputPort.variable>` is not specified, it is assigned the default format for an item of the
526
owner's `variable <Mechanism_Base.variable>` attribute. The InputPorts are appended to the end of the list in the
527
Mechanism's `input_ports <Mechanism_Base.input_ports>` attribute.  Adding in Ports in this manner does **not**
528
replace any existing Ports, including any default Ports generated when the Mechanism was constructed (this is
529
contrast to Ports specified in a Mechanism's constructor which **do** `replace any default Port(s) of the same type
530
<Mechanism_Default_Port_Suppression_Note>`).
531

532
.. _Mechanism_Add_InputPorts_Note:
533

534
.. note::
535
    Adding InputPorts to a Mechanism using its `add_ports <Mechanism_Base.add_ports>` method may introduce an
536
    incompatibility with the Mechanism's `function <Mechanism_Base.function>`, which takes the Mechanism's `variable
537
    <Mechanism_Base.variable>` as its input; such an incompatibility will generate an error.  It may also influence
538
    the number of OutputPorts created for the Mechanism. It is the user's responsibility to ensure that the
539
    assignment of InputPorts to a Mechanism using the `add_ports <Mechanism_Base.add_ports>` is coordinated with
540
    the specification of its `function <Mechanism_Base.function>`, so that the total number of InputPorts (listed
541
    in the Mechanism's `input_ports <Mechanism_Base.input_ports>` attribute matches the number of items expected
542
    for the input to the function specified in the Mechanism's `function <Mechanism_Base.function>` attribute
543
    (i.e., its length along axis 0).
544

545
.. _Mechanism_InputPort_Projections:
546

547
**Projections to InputPorts**
548

549
Each InputPort of a Mechanism can receive one or more `Projections <Projection>` from other Mechanisms.  When a
550
Mechanism is created, a `MappingProjection` is created automatically for any OutputPorts or Projections from them that
551
are in its `InputPort specification <InputPort_Specification>`, using `AUTO_ASSIGN_MATRIX` as the Projection's `matrix
552
specification <MappingProjection_Matrix_Specification>`.  However, if a specification in the **input_ports** argument
553
or an *INPUT_PORTS* entry of a **params** dictionary cannot be resolved to an instantiated OutputPort at the time the
554
Mechanism is created, no MappingProjection is assigned to the InputPort, and this must be done by some other means;
555
any specifications in the Mechanism's `input_ports <Mechanism_Base.input_ports>` attribute that are not
556
associated with an instantiated OutputPort at the time the Mechanism is executed are ignored.
557

558
The `PathwayProjections <PathwayProjection>` (e.g., `MappingProjections <MappingProjection>`) it receives are listed
559
in its `path_afferents <Port_Base.path_afferents>` attribute.  If the Mechanism is an `ORIGIN` Mechanism of a
560
`Composition`, this includes a Projection from the Composition's `input_CIM <Composition.input_CIM>`.  Any
561
`ControlProjections <ControlProjection>` or `GatingProjections <GatingProjection>` it receives are listed in its
562
`mod_afferents <Port.mod_afferents>` attribute.
563

564

565
.. _Mechanism_ParameterPorts:
566

567
*ParameterPorts and Parameters*
568
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
569

570
`ParameterPorts <ParameterPort>` provide the value for each parameter of a Mechanism and its `function
571
<Mechanism_Base.function>`.  One ParameterPort is assigned to each of the parameters of the Mechanism and/or its
572
`function <Mechanism_Base.function>` (corresponding to the arguments in their constructors). The ParameterPort takes
573
the value specified for a parameter (see `below <Mechanism_Parameter_Value_Specification>`) as its `variable
574
<ParameterPort.variable>`, and uses it as the input to the ParameterPort's `function <ParameterPort.function>`,
575
which `modulates <ModulatorySignal_Modulation>` it in response to any `ControlProjections <ControlProjection>` received
576
by the ParameterPort (specified in its `mod_afferents <ParameterPort.mod_afferents>` attribute), and assigns the
577
result to the ParameterPort's `value <ParameterPort.value>`.  This is the value used by the Mechanism or its
578
`function <Mechanism_Base.function>` when the Mechanism `executes <Mechanism_Execution>`.  Accordingly, when the value
579
of a parameter is accessed (e.g., using "dot" notation, such as ``my_mech.my_param``), it is actually the
580
*ParameterPort's* `value <ParameterPort.value>` that is returned (thereby accurately reflecting the value used
581
during the last execution of the Mechanism or its `function <Mechanism_Base.function>`).  The ParameterPorts for a
582
Mechanism are listed in its `parameter_ports <Mechanism_Base.parameter_ports>` attribute.
583

584
.. _Mechanism_Parameter_Value_Specification:
585

586
The "base" value of a parameter (i.e., the unmodulated value used as the ParameterPort's `variable
587
<ParameterPort.variable>` and the input to its `function <ParameterPort.function>`) can specified when a Mechanism
588
and/or its `function <Mechanism_Base.function>` are first created,  using the corresponding arguments of their
589
constructors (see `Mechanism_Function` above).  Parameter values can also be specified later, by direct assignment of a
590
value to the attribute for the parameter, or by using the Mechanism's `assign_param` method (the recommended means;
591
see `ParameterPort_Specification`).  Note that the attributes for the parameters of a Mechanism's `function
592
<Mechanism_Base.function>` usually belong to the `Function <Function_Overview>` referenced in its `function
593
<Component.function>` attribute, not the Mechanism itself, and therefore must be assigned to the Function
594
Component (see `Mechanism_Function` above).
595

596
.. _Mechanism_OutputPorts:
597

598
OutputPorts
599
^^^^^^^^^^^^
600
These represent the output(s) of a Mechanism. A Mechanism can have several `OutputPorts <OutputPort>`, and each can
601
send Projections that transmit its value to other Mechanisms and/or as the output of the `Composition` to which
602
the Mechanism belongs.  Every Mechanism has at least one OutputPort, referred to as its `primary OutputPort
603
<OutputPort_Primary>`.  If OutputPorts are not explicitly specified for a Mechanism, a primary OutputPort is
604
automatically created and assigned to its `output_port <Mechanism_Base.output_port>` attribute (note the singular),
605
and also to the first entry of the Mechanism's `output_ports <Mechanism_Base.output_ports>` attribute (note the
606
plural).  The `value <OutputPort.value>` of the primary OutputPort is assigned as the first (and often only) item
607
of the Mechanism's `value <Mechanism_Base.value>` attribute, which is the result of the Mechanism's `function
608
<Mechanism_Base.function>`.  Additional OutputPorts can be assigned to represent values corresponding to other items
609
of the Mechanism's `value <Mechanism_Base.value>` (if there are any) and/or values derived from any or all of those
610
items. `Standard OutputPorts <OutputPort_Standard>` are available for each type of Mechanism, and custom ones can
611
be configured (see `OutputPort Specification <OutputPort_Specification>`. These can be assigned in the
612
**output_ports** argument of the Mechanism's constructor.
613

614
All of a Mechanism's OutputPorts (including the primary one) are listed in its `output_ports
615
<Mechanism_Base.output_ports>` attribute (note the plural). The `output_ports <Mechanism_Base.output_ports>`
616
attribute is a ContentAddressableList -- a PsyNeuLink-defined subclass of the Python class
617
`UserList <https://docs.python.org/3.6/library/collections.html?highlight=userlist#collections.UserList>`_ -- that
618
allows a specific OutputPort in the list to be accessed using its name as the index for the list (e.g.,
619
``my_mechanism['OutputPort name']``).  This list can also be used to assign additional OutputPorts to the Mechanism
620
after it has been created.
621

622
The `value <OutputPort.value>` of each of the Mechanism's OutputPorts is assigned as an item in the Mechanism's
623
`output_values <Mechanism_Base.output_values>` attribute, in the same order in which they are listed in its
624
`output_ports <Mechanism_Base.output_ports>` attribute.  Note, that the `output_values <Mechanism_Base.output_values>`
625
attribute of a Mechanism is distinct from its `value <Mechanism_Base.value>` attribute, which contains the full and
626
unmodified results of its `function <Mechanism_Base.function>` (this is because OutputPorts can modify the item of
627
the Mechanism`s `value <Mechanism_Base.value>` to which they refer -- see `OutputPorts <OutputPort_Customization>`).
628

629
.. _Mechanism_Additional_Attributes:
630

631
*Additional Attributes*
632
~~~~~~~~~~~~~~~~~~~~~~~
633

634
.. _Mechanism_Constructor_Arguments:
635

636
*Additional Constructor Arguments*
637
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
638

639
In addition to the `standard attributes <Component_Structure>` of any `Component <Component>`, Mechanisms have a set of
640
Mechanism-specific attributes (listed below). These can be specified in arguments of the Mechanism's constructor,
641
in a `parameter specification dictionary <ParameterPort_Specification>` assigned to the **params** argument of the
642
Mechanism's constructor, by direct reference to the corresponding attribute of the Mechanisms after it has been
643
constructed (e.g., ``my_mechanism.param``). The Mechanism-specific
644
attributes are listed below by their argument names / keywords, along with a description of how they are specified:
645

646
    * **input_ports** / *INPUT_PORTS* - a list specifying the Mechanism's input_ports
647
      (see `InputPort_Specification` for details of specification).
648
    ..
649
    * **input_labels** / *INPUT_LABEL_DICTS* - a dict specifying labels that can be used as inputs
650
      (see `Mechanism_Labels_Dicts` for details of specification).
651
    ..
652
    * **output_ports** / *OUTPUT_PORTS* - specifies specialized OutputPorts required by a Mechanism subclass
653
      (see `OutputPort_Specification` for details of specification).
654
    ..
655
    * **output_labels** / *OUTPUT_LABEL_DICTS* - a dict specifying labels that can be for reporting outputs
656
      (see `Mechanism_Labels_Dicts` for details of specification).
657
    ..
658
    COMMENT:
659
    * **monitor_for_control** / *MONITOR_FOR_CONTROL* - specifies which of the Mechanism's OutputPorts is monitored by
660
      the `controller` for the Composition to which the Mechanism belongs (see `specifying monitored OutputPorts
661
      <ObjectiveMechanism_Monitor>` for details of specification).
662
    COMMENT
663
    ..
664
    * **monitor_for_learning** / *MONITOR_FOR_LEARNING* - specifies which of the Mechanism's OutputPorts is used for
665
      learning (see `Learning <LearningMechanism_Activation_Output>` for details of specification).
666

667
.. _Mechanism_Convenience_Properties:
668

669
*Projection Convenience Properties*
670
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
671

672
A Mechanism also has several convenience properties, listed below, that list its `Projections <Projection>` and the
673
Mechanisms that send/receive these:
674

675
    * `projections <Mechanism_Base.projections>` -- all of the Projections sent or received by the Mechanism;
676
    * `afferents <Mechanism_Base.afferents>` -- all of the Projections received by the Mechanism;
677
    * `path_afferents <Mechanism_Base.afferents>` -- all of the PathwayProjections received by the Mechanism;
678
    * `mod_afferents <Mechanism_Base.afferents>` -- all of the ModulatoryProjections received by the Mechanism;
679
    * `efferents <Mechanism_Base.efferents>` -- all of the Projections sent by the Mechanism;
680
    * `senders <Mechanism_Base.senders>` -- all of the Mechanisms that send a Projection to the Mechanism
681
    * `modulators <Mechanism_Base.modulators>` -- all of the ModulatoryMechanisms that send a ModulatoryProjection to
682
      the Mechanism
683
    * `receivers <Mechanism_Base.receivers>` -- all of the Mechanisms that receive a Projection from the Mechanism
684

685
Each of these is a `ContentAddressableList`, which means that the names of the Components in each list can be listed by
686
appending ``.names`` to the property.  For examples, the names of all of the Mechanisms that receive a Projection from
687
``my_mech`` can be accessed by ``my_mech.receivers.names``.
688

689

690
.. _Mechanism_Labels_Dicts:
691

692
*Value Label Dictionaries*
693
^^^^^^^^^^^^^^^^^^^^^^^^^^
694

695
*Overview*
696

697
Mechanisms have two attributes that can be used to specify labels for the values of its InputPort(s) and
698
OutputPort(s):
699

700
    * *INPUT_LABELS_DICT* -- used to specify labels for values of the InputPort(s) of the Mechanism;  if specified,
701
      the dictionary is contained in the Mechanism's `input_labels_dict <Mechanism_Base.input_labels_dict>` attribute.
702

703
    * *OUTPUT_LABELS_DICT* -- used to specify labels for values of the OutputPort(s) of the Mechanism;  if specified,
704
      the dictionary is contained in the Mechanism's `output_labels_dict <Mechanism_Base.output_labels_dict>` attribute.
705

706
The labels specified in these dictionaries can be used to:
707

708
    - specify items in the `inputs <Composition_Execution_Inputs>` argument of the `run <Composition.run>` method of a
709
      `Composition`, or the `targets <Composition_Targret_Inputs>` argument of its `learn <Composition.learn>` method.
710
    - report the values of the InputPort(s) and OutputPort(s) of a Mechanism
711
    - visualize the inputs and outputs of the Composition's Mechanisms
712

713
*Specifying Label Dictionaries*
714

715
Label dictionaries can only be specified in a parameters dictionary assigned to the **params** argument of the
716
Mechanism's constructor, using the keywords described above.  A standard label dictionary contains key:value pairs of
717
the following form:
718

719
    * *<port name or index>:<sub-dictionary>* -- this is used to specify labels that are specific to individual Ports
720
      of the type corresponding to the dictionary;
721
        - *key* - either the name of a Port of that type, or its index in the list of Ports of that type (i.e,
722
          `input_ports <Mechanism_Base.input_ports>` or `output_ports <Mechanism_Base.output_ports>`);
723
        - *value* - a dictionary containing *label:value* entries to be used for that Port, where the label is a string
724
          and the shape of the value matches the shape of the `InputPort value <InputPort.value>` or `OutputPort
725
          value <OutputPort.value>` for which it is providing a *label:value* mapping.
726

727
      For example, if a Mechanism has two InputPorts, named *SAMPLE* and *TARGET*, then *INPUT_LABELS_DICT* could be
728
      assigned two entries, *SAMPLE*:<dict> and *TARGET*:<dict> or, correspondingly, 0:<dict> and 1:<dict>, in which
729
      each dictionary contains separate *label:value* entries for the *SAMPLE* and *TARGET* InputPorts.
730

731
>>> input_labels_dictionary = {pnl.SAMPLE: {"red": [0],
732
...                                         "green": [1]},
733
...                            pnl.TARGET: {"red": [0],
734
...                                         "green": [1]}}
735

736
In the following two cases, a shorthand notation is allowed:
737

738
    - a Mechanism has only one port of a particular type (only one InputPort or only one OutputPort)
739
    - only the index zero InputPort or index zero OutputPort needs labels
740

741
In these cases, a label dictionary for that type of port may simply contain the *label:value* entries described above.
742
The *label:value* mapping will **only** apply to the index zero port of the port type for which this option is used.
743
Any additional ports of that type will not have value labels. For example, if the input_labels_dictionary below were
744
applied to a Mechanism with multiple InputPort, only the index zero InputPort would use the labels "red" and "green".
745

746
>>> input_labels_dictionary = {"red": [0],
747
...                            "green": [1]}
748

749
*Using Label Dictionaries*
750

751
When using labels to specify items in the `inputs <Composition_Execution_Inputs>` arguments of the `run
752
<Composition.run>` method, labels may directly replace any or all of the `InputPort values <InputPort.value>` in an
753
input specification dictionary. Keep in mind that each label must be specified in the `input_labels_dict
754
<Mechanism_Base.input_labels_dict>` of the `INPUT` Mechanism to which inputs are being specified, and must map to a
755
value that would have been valid in that position of the input dictionary.
756

757
        >>> import psyneulink as pnl
758
        >>> input_labels_dict = {"red": [[1, 0, 0]],
759
        ...                      "green": [[0, 1, 0]],
760
        ...                      "blue": [[0, 0, 1]]}
761
        >>> M = pnl.ProcessingMechanism(default_variable=[[0, 0, 0]],
762
        ...                             params={pnl.INPUT_LABELS_DICT: input_labels_dict})
763
        >>> C = pnl.Composition(pathways=[M])
764
        >>> input_dictionary = {M: ['red', 'green', 'blue', 'red']}
765
        >>> # (equivalent to {M: [[[1, 0, 0]], [[0, 1, 0]], [[0, 0, 1]], [[1, 0, 0]]]}, which is a valid input specification)
766
        >>> results = C.run(inputs=input_dictionary)
767

768
The same general rules apply when using labels to specify `target values <Run_Targets>` for a pathway with learning.
769
With target values, however, the labels must be included in the `output_labels_dict <Mechanism_Base.output_labels_dict>`
770
of the Mechanism that projects to the `TARGET` Mechanism (see `TARGET Mechanisms <LearningMechanism_Targets>`), or in
771
other words, the last Mechanism in a `learning pathway <LearningMechanism_Multilayer_Learning>`. This is the same
772
Mechanism used to specify target values for a particular learning pathway in the `targets dictionary <Run_Targets>`.
773

774
        >>> input_labels_dict_M1 = {"red": [[1]],
775
        ...                         "green": [[0]]}
776
        >>> output_labels_dict_M2 = {"red": [1],
777
        ...                         "green": [0]}
778
        >>> M1 = pnl.ProcessingMechanism(params={pnl.INPUT_LABELS_DICT: input_labels_dict_M1})
779
        >>> M2 = pnl.ProcessingMechanism(params={pnl.OUTPUT_LABELS_DICT: output_labels_dict_M2})
780
        >>> C = pnl.Composition()
781
        >>> learning_pathway = C.add_backpropagation_learning_pathway(pathway=[M1, M2], learning_rate=0.25)
782
        >>> input_dictionary = {M1: ['red', 'green', 'green', 'red']}
783
        >>> # (equivalent to {M1: [[[1]], [[0]], [[0]], [[1]]]}, which is a valid input specification)
784
        >>> target_dictionary = {M2: ['red', 'green', 'green', 'red']}
785
        >>> # (equivalent to {M2: [[1], [0], [0], [1]]}, which is a valid target specification)
786
        >>> results = C.learn(inputs=input_dictionary,
787
        ...                   targets=target_dictionary)
788

789
Several attributes are available for viewing the labels for the current value(s) of a Mechanism's InputPort(s) and
790
OutputPort(s).
791

792
    - The `label <InputPort.labeled_value>` attribute of an InputPort or OutputPort returns the current label of
793
      its value, if one exists, and its numeric value otherwise.
794

795
    - The `labeled_input_values <Mechanism_Base.labeled_input_values>` and `labeled_output_values
796
      <Mechanism_Base.labeled_output_values>` attributes of a Mechanism return lists containing the labels
797
      corresponding to the value(s) of the InputPort(s) or OutputPort(s) of the Mechanism, respectively. If the
798
      current value of a Port does not have a corresponding label, then its numeric value is reported instead.
799

800
        >>> output_labels_dict = {"red": [1, 0, 0],
801
        ...                      "green": [0, 1, 0],
802
        ...                      "blue": [0, 0, 1]}
803
        >>> M = pnl.ProcessingMechanism(default_variable=[[0, 0, 0]],
804
        ...                             params={pnl.OUTPUT_LABELS_DICT: output_labels_dict})
805
        >>> C = pnl.Composition(pathways=[M])
806
        >>> input_dictionary =  {M: [[1, 0, 0]]}
807
        >>> results = C.run(inputs=input_dictionary)
808
        >>> M.labeled_output_values(C)
809
        ['red']
810
        >>> M.output_ports[0].labeled_value(C)
811
        'red'
812

813
Labels may be used to visualize the input and outputs of Mechanisms in a Composition with the **show_structure** option
814
of the Composition's `show_graph`show_graph <ShowGraph.graph>` method with the keyword **LABELS**.
815

816
        >>> C.show_graph(show_mechanism_structure=pnl.LABELS)  #doctest: +SKIP
817

818
.. note::
819

820
    A given label dictionary only applies to the Mechanism to which it belongs, and a given label only applies to its
821
    corresponding InputPort. For example, the label 'red', may translate to different values on different InputPorts
822
    of the same Mechanism, and on different Mechanisms of a Composition.
823

824
.. _Mechanism_in_Composition:
825

826
*Mechanisms in Compositions*
827
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
828

829
Mechanisms are most commonly used as `Nodes <Composition_Nodes>` in a `Composition's graph <Composition_Graph>`,
830
where they are connected to other Nodes using `Projections <Projection>`.
831

832
.. _Mechanism_Role_In_Compositions:
833

834
*Role in Compositions*
835
^^^^^^^^^^^^^^^^^^^^^^
836

837
When a Mechanism is added to a `Composition_Addition_Methods>`, it is assigned as a `Node <Composition_Nodes>` in the
838
`graph <Composition_Graph>` of that Comopsition, and one or more `NodeRoles <NodeRole>` indicating the role(s) that
839
the Node play(s) in the Composition.  These can be listed by calling the `Comopsition's `get_roles_by_nodes
840
<Comopsition.get_roles_by_nodes>` with the Mechanism as its argument.  The NodeRoles assigned to a Mechanism can
841
be different for different Compositions.  If a Mechanism is designated as an `INPUT` Node, it receives a
842
`MappingProjection` to its `primary InputPort <InputPort_Primary>` from the Composition.  When the Composition is
843
`executed <Composition_Execution>`, that InputPort receives the input specified for the Mechanism in the `inputs
844
<Composition_Execution_Inputs>` argument of the Composition's `execution method <Composition_Execution_Methods>`; or,
845
if it is a `nested Composition <Composition_Nested>`, then the Mechanism gets its input from a Node that projects to
846
it from the outer Composition.  If a Mechanism is designated as an `OUTPUT` Node, its `output_values
847
<Mechanism_Base.output_values>` are included in the value returned by its `execution method
848
<Composition_Execution_Methods>` and the Composition's `results <Composition.results>` attribute.
849

850
.. _Mechanism_Execution:
851

852
Execution
853
---------
854

855
When a Mechanism executes, the following sequence of actions is carried out:
856

857
    - The Mechanism updates its `InputPort`\\(s) by executing the `function <InputPort.function>` of each.  The
858
      resulting `value <InputPort.value>`\\(s) are used to assemble the Mechanism's `variable<Mechanism_Base.variable>`.
859
      Each `value <InputPort.value>` is added to an outer array, such that each item of the Mechanism's `variable
860
      <Mechanism_Base.variable>` corresponds to an InputPort `value <InputPort.value>`.  The array is placed in
861
      the Mechanism's `input_values <Mechanism_Base.input_values>` attribute, and also passed as the input to the
862
      Mechanism's `function <Mechanism_Base.function>` after updating its `ParameterPorts <ParamterPorts>`.
863

864
    - The Mechanism updates its `ParameterPort`\\(s) by executing each of their `functions <ParameterPort.function>`,
865
      the results of which are assigned as the values used for the corresponding Parameters, which include those of the
866
      Mechanism's `function <Mechanism_Base.function>`.
867

868
    - The Mechanism's `variable <Mechanism_Base.variable>` is passed as the input to the its `function
869
      <Mechanism_Base.function>`, and the function is execute using the parameter values generating by the execution
870
      of its ParameterPorts. The result of the Mechanism's `function <Mechanism_Base.function>` is placed in the
871
      Mechanism's `value <Mechanism_Base.value>` attribute.
872

873
    - The Mechanism updates its `OutputPort`\\(s) are updated based on `value <Mechanism_Base.value>`, by executing the
874
      `function <OutputPort.function>` of each. The resulting `value <OutputPort.value>` for each Outport is placed
875
      in the Mechanism's `output_values <Mechanism_Base.output_values>` attribute.
876

877
A Mechanism may be executed by calling its `execute <Mechanism_Base.execute>` method directly:
878

879
    >>> my_simple_mechanism = pnl.ProcessingMechanism()      #doctest: +SKIP
880
    >>> my_simple_mechanism.execute(1.0)                     #doctest: +SKIP
881

882
This can be useful for testing a Mechanism and/or debugging.  However, more typically, Mechanisms are `executed as
883
part of a Composition <Composition_Execution>`.
884

885
.. _Mechanism_Execution_Composition:
886

887
*Execution in a Composition*
888
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
889

890
A Mechanism can be assigned to one or more Compositions;  the values of its `parameters <Component_Parameters>`,
891
including its `variable <Mechanism_Base.variable>` and `value <Mechanism_Base.value>` attributes, are maintained
892
separately for each `context in which it is executed <Composition_Execution_Context>` which, by default, is distinct
893
for each Composition in which it is executed;  these execution-specific values can be accessed using the parameter's
894
`get <Parameter.get>` method. A parameter's value can also be accessed using standard `dot <Parameter_Dot_Notation>`,
895
which returns its most recenty assigned value, irrespective of the context (including Composition) in which it was
896
assigned.
897

898
.. _Mechanism_Runtime_Params:
899

900
*Runtime Parameters*
901
~~~~~~~~~~~~~~~~~~~~
902

903
.. note::
904
   This is an advanced feature, but is generally not required for most applications. It is included for convenience;
905
   similar functionality can be achieved by setting the values of `parameters <Component_Parameters>` programmatically
906
   before the Mechanism is executed and then resetting them afterwards.
907

908
The runtime parameter values are those assigned to a Mechanism and its Components (i.e., its `function
909
<Mechanism_Base.function>` and `Ports <Mechanism_Ports`) when they execute.  These are generally the values specified
910
in the corresponding constructors, assigned explicitly after construction (see `User_Modifiable_Parameters`), or the
911
default values.  However, these values can be overidden for a particular execution, by specifying the desired values
912
in the **runtime_params** argument of the Mechanism's `execute <Mechanism_Base.execute>` method (see `below
913
<Mechanism_Runtime_Param_Specification>`) or the `execution method <Composition_Execution_Methods>` of a `Composition`
914
to which it belongs (see `Composition_Runtime_Params`).  When assigned in the context of a Composition, `Conditions
915
<Condition>` can be specified that determine when the values apply. Any values assigned using **runtime_params**
916
that apply will override the current value of the parameter for that (and *only* that) execution (if the Mechanism's
917
`execute <Mechanism_Base.execute>` is used) or as long as its `Condition` applies (if executed in a Composition),
918
after which the value will return to its previous value.  The value of a parameter can be modified on a permanent
919
basis, either for a given `execution context <Composition_Execution_Context>` using a its `set <Parameter.set>`
920
method; or for all execution contexts, by setting its default value using the Mechanism's `defaults
921
<Component.defaults>` attribute.
922

923
.. _Mechanism_Runtime_Param_Specification:
924

925
*Runtime specification ditionary: parameters of a Mechanism and its function*
926
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
927

928
Runtime parameter values are specified in the **runtime_params** argument of a Mechanism's `execute
929
<Mechanism_Base.execute>` method using a dictionary, in which each entry contains the name of the
930
of a parameter (as the key) and the value to assign to it, as in the following example::
931

932
        >>> T = pnl.TransferMechanism(function=Linear)
933
        >>> T.function.slope.base  #doctest: +SKIP
934
        1.0   # Default for slope
935
        >>> T.clip #doctest: +SKIP
936
        None  # Default for clip is None
937
        >>> T.execute(2.0,
938
        ...          runtime_params={"slope": 3.0,
939
        ...                           "clip": (0,5)}) #doctest: +SKIP
940
        array([[5.]])  # = 2 (input) * 3 (slope) = 6, but clipped at 5
941
        >>> T.function.slope.base #doctest: +SKIP
942
        1.0   # slope is restored 1.0
943
        >>> T.clip     #doctest: +SKIP
944
        None  # clip is restored to None
945

946
Note that even though ``slope`` is a parameter of the Mechanism's `function <Mechanism_Base.function>` (in this case,
947
`Linear`), the function itself does not have to be specified in the key of the runtime_params dictionary (although it
948
does have to be used when accessing or assigning the parameter's value using `dot notation <Parameter_Dot_Notation>`,
949
as shown above).
950

951
If a parameter is assigned a new value before the execution, that value is restored after the execution;  that is,
952
the parameter is assigned its previous value and *not* its default, as shown below::
953

954
        >>> T.function.slope.base = 10
955
        >>> T.clip = (0,3)
956
        >>> T.function.slope
957
        10
958
        >>> T.clip
959
        (0, 3)
960
        >>> T.execute(3.0,
961
        ...          runtime_params={"slope": 4.0,
962
        ...                           "clip": (0,4)}) #doctest: +SKIP
963
        array([[4.]])  # = 3 (input) * 4 (slope) = 12, but clipped at 4
964
        >>> T.function.slope.base #doctest: +SKIP
965
        10      # slope is restored 10.0, its previously assigned value
966
        >>> T.clip #doctest: +SKIP
967
        (0, 3)  # clip is restored to (0,3), its previously assigned value
968

969
.. _Mechanism_Runtime_Port_and_Projection_Param_Specification:
970

971
*Runtime specification ditionary: parameters of a Mechanism's Ports and Projections*
972
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
973

974
Runtime values can also be assigned to the parameters of a Mechanism's `Ports <Port>` and/or their `afferent
975
Projections <Mechanism_Base.afferents>` in entries of a **runtime_params** dict,
976

977
*Ports*.  Runtime values are assigned to the parameters of Ports (and/or their `function <Port_Base.function>`\\s)
978
in entries with a key that designates the type of Port (*INPUT_PORT_PARAMS*, *OUTPUT_PORT_PARAMS* or
979
*PARAMETER_PORT_PARAMS*), and a sub-dictionary containing the specifications for that type of Port as the value.
980
The sub-dictionary can contain entries with specification that apply to *all* Ports of that type and/or individual
981
Ports. If the key of an entry is the name of a parameter of the Port (or its `function <Port_Base.function>`), the
982
specified value applies to *all* Ports of that type.  Parameters for individual Ports are specified using the Port
983
or its `name <Port_Base.name>` as the key, and a dictionary containing parameter specifications as its value.
984

985
  .. note::
986

987
     - If the `variable <Port_base.variable>` of a Port is specified as a runtime parameter, then its afferent
988
       Projections will not be executed (see `Lazy Evaluation <Component_Lazy_Updating>`), but its `function
989
       <Port_Base.function>` will be.
990

991
     - If the `value <Port_Base.value>` of a Port is specified, *neither* its `afferent Projections <Port_Projections>`
992
       nor it `function <Port_Base.function>` will be executed.
993

994
     - If the `variable <Port_base.variable>` and/or `value <Port_Base.value>` is specified for *all* of the
995
       OutputPorts of a Mechanism, then it's function will not be executed, and the `value <Mechanism_Base.value>`
996
       will retain its previous value (again in accord with `Lazy Evaluation <Component_Lazy_Updating>`), though its
997
       OutputPorts *will* be executed using the assigned values, and it's `execution_count <Component_Execution_Count>`
998
       and `num_executions <Component_Num_Executions>` attributes will be incremented (since the OutputPorts --
999
       Components of the Mechanism -- executed).
1000

1001
     - As expected, specifying `value <Port_Base.value>` supersedes any specification of `variable
1002
       <Port_Base.variable>` or of the parameters of its `function <Port_Base.function>`.
1003

1004
COMMENT:
1005
   FIX 5/8/20 [JDC]: GET EXAMPLES FROM test_runtime_params
1006
COMMENT
1007

1008
*Projections*.  The sub-dictionary specifying the parameters of a Port can also contain specifications for parameters
1009
of its afferent `Projections <Port_Projections>` Projections.  These are placed in entries with a key that designates
1010
the type of Projection, and a sub-dictionary containing the specifications for that type of Port as the value.  The
1011
key for each type of projecction is its `componentType <Component_Type>` appended with ``_PARAMS`` (e.g.,
1012
*MAPPING_PROJECTION_PARAMS*, *CONTROL_PROJECTION_PARAMS*, etc.).  The sub-dictionary can contain specifications that
1013
apply to *all* Projections of that type and/or individual Projections. If the key of an entryis the name of a parameter
1014
of the Projection (or its `function <Port_Base.function>`), the specified value applies to *all* Projections of that
1015
type. Parameters for individual Projections are specified using the Projections or its `name <Projection_Base.name>`
1016
as the key, and a dictionary containing parameter specifications as its value.
1017

1018
   .. note::
1019
     If the `value <Projection_Base.value>` of a Projection is specified as a runtime parameter, then it will not be
1020
     executed (see `Lazy Evaluation <Component_Lazy_Updating>`); accordingly, specifying `value <Port_Base.value>`
1021
     supersedes any specification of `variable <Port_Base.variable>` or of the parameters of its `function
1022
     <Projection_Base.function>.`
1023

1024
COMMENT:
1025
   FIX 5/8/20 [JDC]: EXAMPLES HERE AND ADD CORRESPONDING TESTS
1026
.. note::
1027
    Runtime parameter values are subject to the same type, value, and shape requirements as the original parameter
1028
    value.
1029
COMMENT
1030

1031

1032
COMMENT:
1033
?? DO PROJECTION DICTIONARIES PERTAIN TO INCOMING OR OUTGOING PROJECTIONS OR BOTH??
1034
?? CAN THE KEY FOR A PORT DICTIONARY REFERENCE A SPECIFIC PORT BY NAME, OR ONLY PORT-TYPE??
1035

1036
Port keyword: dict for Port's params
1037
    Function or Projection keyword: dict for Funtion or Projection's params
1038
        parameter keyword: vaue of param
1039

1040
    dict: can be one (or more) of the following:
1041
        + INPUT_PORT_PARAMS:<dict>
1042
        + PARAMETER_PORT_PARAMS:<dict>
1043
   [TBI + OUTPUT_PORT_PARAMS:<dict>]
1044
        - each dict will be passed to the corresponding Port
1045
        - params can be any permissible executeParamSpecs for the corresponding Port
1046
        - dicts can contain the following embedded dicts:
1047
            + FUNCTION_PARAMS:<dict>:
1048
                 will be passed the Port's execute method,
1049
                     overriding its current values for that call
1050
            + PROJECTION_PARAMS:<dict>:
1051
                 entry will be passed to all of the Port's Projections, and used by
1052
                 by their execute methods, overriding their current values for that call
1053
            + MAPPING_PROJECTION_PARAMS:<dict>:
1054
                 entry will be passed to all of the Port's MappingProjections,
1055
                 along with any in a PROJECTION_PARAMS dict, and override current values
1056
            + LEARNING_PROJECTION_PARAMS:<dict>:
1057
                 entry will be passed to all of the Port's LearningProjections,
1058
                 along with any in a PROJECTION_PARAMS dict, and override current values
1059
            + CONTROL_PROJECTION_PARAMS:<dict>:
1060
                 entry will be passed to all of the Port's ControlProjections,
1061
                 along with any in a PROJECTION_PARAMS dict, and override current values
1062
            + GATING_PROJECTION_PARAMS:<dict>:
1063
                 entry will be passed to all of the Port's GatingProjections,
1064
                 along with any in a PROJECTION_PARAMS dict, and override current values
1065
            + <ProjectionName>:<dict>:
1066
                 entry will be passed to the Port's Projection with the key's name,
1067
                 along with any in the PROJECTION_PARAMS and MappingProjection or ControlProjection dicts
1068
COMMENT
1069

1070
.. _Mechanism_Class_Reference:
1071

1072
Class Reference
1073
---------------
1074

1075
"""
1076

1077
import abc
1✔
1078
import copy
1✔
1079
import inspect
1✔
1080
import itertools
1✔
1081
import logging
1✔
1082
import warnings
1✔
1083
from collections import defaultdict, OrderedDict, UserDict, UserList
1✔
1084
from inspect import isclass
1✔
1085
from numbers import Number
1✔
1086

1087
import numpy as np
1✔
1088
from beartype import beartype
1✔
1089

1090
from psyneulink._typing import Optional, Union, Literal, Type
1✔
1091

1092
from psyneulink.core import llvm as pnlvm
1✔
1093
from psyneulink.core.components.component import Component, ComponentError
1✔
1094
from psyneulink.core.components.functions.function import FunctionOutputType
1✔
1095
from psyneulink.core.components.functions.nonstateful.transferfunctions import Linear
1✔
1096
from psyneulink.core.components.ports.inputport import DEFER_VARIABLE_SPEC_TO_MECH_MSG, InputPort
1✔
1097
from psyneulink.core.components.ports.modulatorysignals.modulatorysignal import _is_modulatory_spec
1✔
1098
from psyneulink.core.components.ports.outputport import OutputPort
1✔
1099
from psyneulink.core.components.ports.parameterport import ParameterPort
1✔
1100
from psyneulink.core.components.ports.port import \
1✔
1101
    PORT_SPEC, _parse_port_spec, PORT_SPECIFIC_PARAMS, PROJECTION_SPECIFIC_PARAMS
1102
from psyneulink.core.components.shellclasses import Mechanism, Projection, Port
1✔
1103
from psyneulink.core.globals.context import Context, ContextFlags, handle_external_context
1✔
1104
# TODO: remove unused keywords
1105
from psyneulink.core.globals.keywords import \
1✔
1106
    ADDITIVE_PARAM, EXECUTION_PHASE, EXPONENT, FUNCTION_PARAMS, \
1107
    INITIALIZING, INIT_EXECUTE_METHOD_ONLY, INIT_FUNCTION_METHOD_ONLY, INPUT, \
1108
    INPUT_LABELS_DICT, INPUT_PORT, INPUT_PORT_PARAMS, INPUT_PORTS, MECHANISM, MECHANISM_VALUE, \
1109
    MECHANISM_COMPONENT_CATEGORY, \
1110
    MULTIPLICATIVE_PARAM, EXECUTION_COUNT, \
1111
    NAME, OUTPUT, OUTPUT_LABELS_DICT, OUTPUT_PORT, OUTPUT_PORT_PARAMS, OUTPUT_PORTS, OWNER_EXECUTION_COUNT, OWNER_VALUE, \
1112
    PARAMETER_PORT, PARAMETER_PORT_PARAMS, PARAMETER_PORTS, PROJECTIONS, REFERENCE_VALUE, RESULT, \
1113
    TARGET_LABELS_DICT, VALUE, VARIABLE, WEIGHT, MODEL_SPEC_ID_MDF_VARIABLE, MODEL_SPEC_ID_INPUT_PORT_COMBINATION_FUNCTION
1114
from psyneulink.core.globals.parameters import (
1✔
1115
    Parameter,
1116
    ParameterNoValueError,
1117
    check_user_specified,
1118
    copy_parameter_value,
1119
)
1120
from psyneulink.core.globals.preferences.preferenceset import PreferenceLevel
1✔
1121
from psyneulink.core.globals.registry import register_category, remove_instance_from_registry
1✔
1122
from psyneulink.core.globals.utilities import \
1✔
1123
    ContentAddressableList, append_type_to_name, convert_all_elements_to_np_array, convert_to_np_array, \
1124
    iscompatible, kwCompatibilityNumeric, convert_to_list, is_numeric, parse_valid_identifier, safe_len
1125
from psyneulink.core.scheduling.condition import Condition
1✔
1126
from psyneulink.core.scheduling.time import TimeScale
1✔
1127

1128
__all__ = [
1✔
1129
    'Mechanism_Base', 'MechanismError', 'MechanismRegistry'
1130
]
1131

1132
logger = logging.getLogger(__name__)
1✔
1133
MechanismRegistry = {}
1✔
1134

1135

1136
class MechanismError(ComponentError):
1✔
1137
    pass
1✔
1138

1139

1140
class MechParamsDict(UserDict):
1✔
1141
    """Subclass for validation of dicts used to pass Mechanism parameters to OutputPort for variable specification."""
1142
    pass
1✔
1143

1144

1145
def _input_port_variables_getter(owning_component=None, context=None):
1✔
1146
    try:
1✔
1147
        return convert_all_elements_to_np_array([input_port.parameters.variable._get(context) for input_port in owning_component.input_ports])
1✔
1148
    except (AttributeError, ParameterNoValueError, TypeError):
1✔
1149
        return None
1✔
1150

1151

1152
class Mechanism_Base(Mechanism):
1✔
1153
    """
1154
    Mechanism_Base(             \
1155
        default_variable=None,  \
1156
        input_shapes=None,      \
1157
        input_ports,            \
1158
        function,               \
1159
        output_ports,           \
1160
        )
1161

1162
    Abstract base class for Mechanism.
1163

1164
    The arguments below can be used in the constructor for any subclass of Mechanism.
1165
    See `Component <Component_Class_Reference>` and subclasses for additional arguments and attributes.
1166

1167
    .. note::
1168
       Mechanism is an abstract class and should *never* be instantiated by a direct call to its constructor.
1169
       Mechanisms should be instantiated by calling the constructor for the desired subclass, or using other methods
1170
       for specifying a Mechanism in context (see `Mechanism_Creation`)
1171

1172
    COMMENT:
1173
        Description
1174
        -----------
1175
            Mechanism is a Category of the Component class.
1176
            A Mechanism is associated with a name and:
1177
            - one or more input_ports:
1178
                two ways to get multiple input_ports, if supported by Mechanism subclass being instantiated:
1179
                    • specify 2d variable for Mechanism (i.e., without explicit InputPort specifications)
1180
                        once the variable of the Mechanism has been converted to a 2d array, an InputPort is assigned
1181
                        for each item of axis 0, and the corresponding item is assigned as the InputPort's variable
1182
                    • explicitly specify input_ports in params[*INPUT_PORTS*] (each with its own variable
1183
                        specification); those variables will be concantenated into a 2d array to create the Mechanism's
1184
                        variable
1185
                if both methods are used, they must generate the same sized variable for the mechanims
1186
                ?? WHERE IS THIS CHECKED?  WHICH TAKES PRECEDENCE: InputPort SPECIFICATION (IN _instantiate_port)??
1187
            - an execute method:
1188
                coordinates updating of input_ports, parameter_ports (and params), execution of the function method
1189
                implemented by the subclass, (by calling its _execute method), and updating of the OutputPorts
1190
            - one or more parameters, each of which must be (or resolve to) a reference to a ParameterPort
1191
                these determine the operation of the function of the Mechanism subclass being instantiated
1192
            - one or more OutputPorts:
1193
                the variable of each receives the corresponding item in the output of the Mechanism's function
1194
                the value of each is passed to corresponding MappingProjections for which the Mechanism is a sender
1195
                * Notes:
1196
                    by default, a Mechanism has only one OutputPort, assigned to <Mechanism>.outputPort;  however:
1197
                    if params[OUTPUT_PORTS] is a list (of names) or specification dict (of MechanismOuput Port
1198
                    specs), <Mechanism>.output_ports (note plural) is created and contains a list of OutputPorts,
1199
                    the first of which points to <Mechanism>.outputPort (note singular)
1200
                [TBI * each OutputPort maintains a list of Projections for which it serves as the sender]
1201

1202
        Constraints
1203
        -----------
1204
            - the number of input_ports must correspond to the length of the variable of the Mechanism's execute method
1205
            - the value of each InputPort must be compatible with the corresponding item in the
1206
                variable of the Mechanism's execute method
1207
            - the value of each ParameterPort must be compatible with the corresponding parameter of  the Mechanism's
1208
                 execute method
1209
            - the number of OutputPorts must correspond to the length of the output of the Mechanism's execute method,
1210
                (self.defaults.value)
1211
            - the value of each OutputPort must be compatible with the corresponding item of the self.value
1212
                 (the output of the Mechanism's execute method)
1213

1214
        MechanismRegistry
1215
        -----------------
1216
            All Mechanisms are registered in MechanismRegistry, which maintains a dict for each subclass,
1217
            a count for all instances of that type, and a dictionary of those instances
1218
    COMMENT
1219

1220
    Arguments
1221
    ---------
1222

1223
    default_variable : number, list or np.ndarray : default None
1224
        specifies the input to the Mechanism to use if none is provided in a call to its `execute
1225
        <Mechanism_Base.execute>` method; also serves as a template to specify the shape of the `variable
1226
        <InputPort.variable>` for its `InputPorts <Mechanism_InputPorts>` and the `variable <Mechanism_Base.variable>`
1227
        of its `function <Mechanism_Base.function>` if those are not specified.  If it is not specified, then a
1228
        subclass-specific default is assigned (usually [[0]]).
1229

1230
    input_shapes : int, or Iterable of tuples or ints : default None
1231
        specifies default_variable as array(s) of zeros if **default_variable** is not passed as an argument;
1232
        if **default_variable** is specified, it must be equivalent to
1233
        **input_shapes**.
1234
        For example, the following Mechanisms are equivalent::
1235
            my_mech = ProcessingMechanism(input_shapes = [3, 2])
1236
            my_mech = ProcessingMechanism(default_variable = [[0, 0, 0], [0, 0]])
1237
        When specified as an iterable, each element of **input_shapes** is used
1238
        as the size of the corresponding InputPort.
1239

1240
    input_ports : str, list, dict, or np.ndarray : default None
1241
        specifies the InputPorts for the Mechanism; if it is not specified, a single InputPort is created
1242
        using the value of default_variable as its `variable <InputPort.variable>`;  if more than one is specified,
1243
        the number and, if specified, their values must be compatible with any specifications made for
1244
        **default_variable** or **input_shapes** (see `Mechanism_InputPorts` for additional details).
1245

1246
    input_labels : dict
1247
        specifies labels (strings) that can be used to specify numeric values as input to the Mechanism;
1248
        entries must be either label:value pairs, or sub-dictionaries containing label:value pairs,
1249
        in which each label (key) specifies a string associated with a value for the corresponding InputPort(s)
1250
        of the Mechanism; see `Mechanism_Labels_Dicts` for additional details.
1251

1252
    function : Function : default Linear
1253
        specifies the function used to generate the Mechanism's `value <Mechanism_Base.value>`;
1254
        can be a PsyNeuLink `Function` or a `UserDefinedFunction`;  it `value <Function_Base.value>` is used to
1255
        determine the shape of the `primary outputPort <OutputPort_Primary>` of the Mechanism.
1256

1257
    output_ports : str, list or np.ndarray : default None
1258
        specifies the OutputPorts for the Mechanism; if it is not specified, a single OutputPort is created
1259
        the `value <OutputPort.value>` of which is assigned the first item in the outermost dimension (axis 0) of the
1260
        Mechanism's `value <Mechanism_Base.value>` (see `Mechanism_OutputPorts` for additional details).
1261

1262
    output_labels : dict
1263
        specifies labels (strings) that can be reported in place of numeric values as output(s) of the Mechanism;
1264
        entries must be either label:value pairs, or sub-dictionaries containing label:value pairs,
1265
        in which each label (key) specifies a string associated with a value for the OutputPort(s) of the
1266
        Mechanism; see `Mechanism_Labels_Dicts` for additional details.
1267

1268
    Attributes
1269
    ----------
1270

1271
    variable : at least 2d array
1272
        used as input to the Mechanism's `function <Mechanism_Base.function>`.  It is always at least a 2d np.array,
1273
        with each item of axis 0 corresponding to a `value <InputPort.value>` of one of the Mechanism's `InputPorts
1274
        <InputPort>` (in the order they are listed in its `input_ports <Mechanism_Base.input_ports>` attribute), and
1275
        the first item (i.e., item 0) corresponding to the `value <InputPort.value>` of the `primary InputPort
1276
        <InputPort_Primary>`.  When specified in the **variable** argument of the constructor for the Mechanism,
1277
        it is used as a template to define the format (shape and type of elements) of the input the Mechanism's
1278
        `function <Mechanism_Base.function>`.
1279

1280
    input_port : InputPort
1281
        `primary InputPort <InputPort_Primary>` for the Mechanism;  same as first entry of its `input_ports
1282
        <Mechanism_Base.input_ports>` attribute.  Its `value <InputPort.value>` is assigned as the first item of the
1283
        Mechanism's `variable <Mechanism_Base.variable>`.
1284

1285
    input_ports : ContentAddressableList[str, InputPort]
1286
        a list of the Mechanism's `InputPorts <Mechanism_InputPorts>`. The first (and possibly only) entry is always
1287
        the Mechanism's `primary InputPort <InputPort_Primary>` (i.e., the one in the its `input_port
1288
        <Mechanism_Base.input_port>` attribute).
1289

1290
    input_values : List[List or 1d np.array]
1291
        each item in the list corresponds to the `value <InputPort.value>` of one of the Mechanism's `InputPorts
1292
        <Mechanism_InputPorts>` listed in its `input_ports <Mechanism_Base.input_ports>` attribute.  The value of
1293
        each item is the same as the corresponding item in the Mechanism's `variable <Mechanism_Base.variable>`
1294
        attribute.  The latter is a 2d np.array; the `input_values <Mechanism_Base.input_values>` attribute provides
1295
        this information in a simpler list format.
1296

1297
    input_labels_dict : dict
1298
        contains entries that are either label:value pairs, or sub-dictionaries containing label:value pairs,
1299
        in which each label (key) specifies a string associated with a value for the corresponding InputPort(s)
1300
        of the Mechanism; see `Mechanism_Labels_Dicts` for additional details.
1301

1302
    labeled_input_values : list[str]
1303
        contains the labels corresponding to the current value(s) of the InputPort(s) of the Mechanism. If the
1304
        current value of an InputPort does not have a corresponding label, then its numeric value is used instead.
1305

1306
    external_input_ports : list[InputPort]
1307
        list of the `input_ports <Mechanism_Base.input_ports>` for the Mechanism that are not designated as
1308
        `internal_only <InputPort.internal_only>`;  these receive `inputs from a Composition
1309
        <Composition_Execution_Inputs>` if the Mechanism is one of its `INPUT` `Nodes <Composition_Nodes>`.
1310

1311
    external_input_shape : List[List or 1d np.array]
1312
        list of the `input_shape <InputPort.input_shape>`\\s of the Mechanism's external `input_ports
1313
        <Mechanism_Base.input_ports>` (i.e., excluding any `InputPorts <InputPort>` designated as `internal_only
1314
        <InputPort.internal_only>`), that shows the shape of the inputs expected for the Mechanism.  Each item
1315
        corresponds to an expected `path_afferent Projection <Port_Base.path_afferents>`, and its shape is
1316
        the expected `value <Projection_Base.value>` of that `Projection`.
1317

1318
    external_input_variables : List[List or 1d np.array]
1319
        list of the `variable <InputPort.variable>`\\s of the Mechanism's `external_input_ports
1320
        <Mechanism_Base.external_input_ports>`.
1321

1322
    external_input_values : List[List or 1d np.array]
1323
        list of the `value <InputPort.value>`\\s of the Mechanism's `external_input_ports
1324
        <Mechanism_Base.external_input_ports>`.
1325

1326
    default_external_inputs : List[1d np.array]
1327
        list of the `default_input <InputPort.default_input>`\\s of the Mechanism's `external_input_ports
1328
        <Mechanism_Base.external_input_ports>`.
1329

1330
    COMMENT:
1331
    target_labels_dict : dict
1332
        contains entries that are either label:value pairs, or sub-dictionaries containing label:value pairs,
1333
        in which each label (key) specifies a string associated with a value for the InputPort(s) of the
1334
        Mechanism if it is the `TARGET` Mechanism for a Composition; see `Mechanism_Labels_Dicts` and
1335
        `target mechanism <LearningMechanism_Targets>` for additional details.
1336
    COMMENT
1337

1338
    parameter_ports : ContentAddressableList[str, ParameterPort]
1339
        a read-only list of the Mechanism's `ParameterPorts <Mechanism_ParameterPorts>`, one for each of its
1340
        `modulable parameters <ParameterPort_Modulable_Parameters>`, including those of its `function
1341
        <Mechanism_Base.function>`.  The value of the parameters of the Mechanism and its `function
1342
        <Mechanism_Base.function>` are also accessible as (and can be modified using) attributes of the Mechanism
1343
        (see `Mechanism_ParameterPorts`).
1344

1345
    function : Function, function or method
1346
        the primary function for the Mechanism, called when it is `executed <Mechanism_Execution>`.  It takes the
1347
        Mechanism's `variable <Mechanism_Base.variable>` attribute as its input, and its result is assigned to the
1348
        Mechanism's `value <Mechanism_Base.value>` attribute (see `Component_Function` for additional details).
1349

1350
    function_params : Dict[str, value]
1351
        contains the parameters for the Mechanism's `function <Mechanism_Base.function>`.  The key of each entry is the
1352
        name of a parameter of the function, and its value is the parameter's value.
1353

1354
    value : 2d np.array [array(float64)]
1355
        result of the Mechanism's `execute` method, which is usually (but not always) the `value <Function_Base.value>`
1356
        of it `function <Mechanism_Base.function>` (it is not if the Mechanism implements any auxiliary function(s)
1357
        after calling its primary function). It is always at least a 2d np.array, with the items of axis 0 corresponding
1358
        to the values referenced by the corresponding `index <OutputPort.index>` attribute of the Mechanism's
1359
        `OutputPorts <OutputPort>`.  The first item is generally referenced by the Mechanism's `primary OutputPort
1360
        <OutputPort_Primary>` (i.e., the one in the its `output_port <Mechanism_Base.output_port>` attribute), as well
1361
        as the first item of `output_values <Mechanism_Base.output_values>`.  The `value <Mechanism_Base.value>` is
1362
        None until the Mechanism has been executed at least once.
1363

1364
        .. note::
1365
           the `value <Mechanism_Base.value>` of a Mechanism is not necessarily the same as its
1366
           `output_values <Mechanism_Base.output_values>` attribute, which lists the `values <OutputPort.value>`
1367
           of its `OutputPorts <Mechanism_Base.output_ports>`.
1368

1369
    output_port : OutputPort
1370
        `primary OutputPort <OutputPort_Primary>` for the Mechanism;  same as first entry of its `output_ports
1371
        <Mechanism_Base.output_ports>` attribute.
1372

1373
    output_ports : ContentAddressableList[str, OutputPort]
1374
        list of the Mechanism's `OutputPorts <Mechanism_OutputPorts>`. The first (and possibly only) entry is always
1375
        the Mechanism's `primary OutputPort <OutputPort_Primary>` (i.e., the one in the its `output_port
1376
        <Mechanism_Base.output_port>` attribute).
1377

1378
    output_values : List[array(float64)]
1379
        each item in the list corresponds to the `value <OutputPort.value>` of one of the Mechanism's `OutputPorts
1380
        <Mechanism_OutputPorts>` listed in its `output_ports <Mechanism_Base.output_ports>` attribute.
1381

1382
        .. note:: The `output_values <Mechanism_Base.output_values>` of a Mechanism is not necessarily the same as its
1383
                  `value <Mechanism_Base.value>` attribute, since an OutputPort's
1384
                  `function <OutputPort.OutputPort.function>` and/or its `assign <Mechanism_Base.assign>`
1385
                  attribute may use the Mechanism's `value <Mechanism_Base.value>` to generate a derived quantity for
1386
                  the `value <OutputPort.OutputPort.value>` of that OutputPort (and its corresponding item in the
1387
                  the Mechanism's `output_values <Mechanism_Base.output_values>` attribute).
1388

1389
    labeled_output_values : list
1390
        contains the labels corresponding to the current value(s) of the OutputPort(s) of the Mechanism. If the
1391
        current value of an OutputPort does not have a corresponding label, then its numeric value is used instead.
1392

1393
    output_labels_dict : dict
1394
        contains entries that are either label:value pairs, or sub-dictionaries containing label:value pairs,
1395
        in which each label (key) specifies a string associated with a value for the OutputPort(s) of the
1396
        Mechanism; see `Mechanism_Labels_Dicts` for additional details.
1397

1398
    standard_output_ports : list[dict]
1399
        list of the dictionary specifications for `StandardOutputPorts <OutputPort_Standard>` that can be assigned as
1400
        `OutputPorts <OutputPort>`; subclasses may extend this list to include additional ones.
1401

1402
        *RESULT* : 1d np.array
1403
          first item in the outermost dimension (axis 0) of the Mechanism's `value <Mechanism_Base.value>`.
1404

1405
        *OWNER_VALUE* : list
1406
          Full ndarray of Mechanism's `value <Mechanism_Base.value>`.
1407

1408
        *MECHANISM_VALUE* : list
1409
          Synonym for *OWNER_VALUE*.
1410

1411
    standard_output_port_names : list[str]
1412
        list of the names of the `standard_output_ports <Mechanism_Base.standard_output_ports>` that can be used to
1413
        specify a `StandardOutputPort <OutputPort_Standard>` in the **output_ports** argument of the Mechanism's
1414
        constructor, and to reference it in Mechanism's list of `output_ports <Mechanism_Base.output_ports>`.
1415

1416
    ports : ContentAddressableList
1417
        a list of all of the Mechanism's `Ports <Port>`, composed from its `input_ports
1418
        <Mechanism_Base.input_ports>`, `parameter_ports <Mechanism_Base.parameter_ports>`, and
1419
        `output_ports <Mechanism_Base.output_ports>` attributes.
1420

1421
    projections : ContentAddressableList
1422
        a list of all of the Mechanism's `Projections <Projection>`, composed from the
1423
        `path_afferents <Port_Base.path_afferents>` of all of its `input_ports <Mechanism_Base.input_ports>`,
1424
        the `mod_afferents` of all of its `input_ports <Mechanism_Base.input_ports>`,
1425
        `parameter_ports <Mechanism)Base.parameter_ports>`, and `output_ports <Mechanism_Base.output_ports>`,
1426
        and the `efferents <Port_Base.efferents>` of all of its `output_ports <Mechanism_Base.output_ports>`.
1427

1428
    afferents : ContentAddressableList
1429
        a list of all of the Mechanism's afferent `Projections <Projection>`, composed from the
1430
        `path_afferents <Port_Base.path_afferents>` of all of its `input_ports <Mechanism_Base.input_ports>`,
1431
        and the `mod_afferents` of all of its `input_ports <Mechanism_Base.input_ports>`,
1432
        `parameter_ports <Mechanism)Base.parameter_ports>`, and `output_ports <Mechanism_Base.output_ports>`.,
1433

1434
    path_afferents : ContentAddressableList
1435
        a list of all of the Mechanism's afferent `PathwayProjections <PathwayProjection>`, composed from the
1436
        `path_afferents <Port_Base.path_afferents>` attributes of all of its `input_ports
1437
        <Mechanism_Base.input_ports>`.
1438

1439
    mod_afferents : ContentAddressableList
1440
        a list of all of the Mechanism's afferent `ModulatoryProjections <ModulatoryProjection>`, composed from the
1441
        `mod_afferents` attributes of all of its `input_ports <Mechanism_Base.input_ports>`, `parameter_ports
1442
        <Mechanism)Base.parameter_ports>`, and `output_ports <Mechanism_Base.output_ports>`.
1443

1444
    efferents : ContentAddressableList
1445
        a list of all of the Mechanism's efferent `Projections <Projection>`, composed from the `efferents
1446
        <Port_Base.efferents>` attributes of all of its `output_ports <Mechanism_Base.output_ports>`.
1447

1448
    senders : ContentAddressableList
1449
        a list of all of the Mechanisms that send `Projections <Projection>` to the Mechanism (i.e., the senders of
1450
        its `afferents <Mechanism_Base.afferents>`; this includes both `ProcessingMechanisms <ProcessingMechanism>`
1451
        (that send `MappingProjections <MappingProjection>` and `ModulatoryMechanisms <ModulatoryMechanism>` (that send
1452
        `ModulatoryProjections <ModulatoryProjection>` (also see `modulators <Mechanism_Base.modulators>`).
1453

1454
    modulators : ContentAddressableList
1455
        a list of all of the `AdapativeMechanisms <ModulatoryMechanism>` that send `ModulatoryProjections
1456
        <ModulatoryProjection>` to the Mechanism (i.e., the senders of its `mod_afferents
1457
        <Mechanism_Base.mod_afferents>` (also see `senders <Mechanism_Base.senders>`).
1458

1459
    receivers : ContentAddressableList
1460
        a list of all of the Mechanisms that receive `Projections <Projection>` from the Mechanism (i.e.,
1461
        the receivers of its `efferents <Mechanism_Base.efferents>`.
1462

1463
    condition : Condition : None
1464
        condition to be associated with the Mechanism in the `Scheduler` responsible for executing it in each
1465
        `Composition` to which it is assigned;  if it is not specified (i.e., its value is `None`), the default
1466
        Condition for a `Component` is used.  It can be overridden in a given `Composition` by assigning a Condition
1467
        for the Mechanism directly to a Scheduler that is then assigned to the Composition.
1468

1469
    name : str
1470
        the name of the Mechanism; if it is not specified in the **name** argument of the constructor, a default is
1471
        assigned by MechanismRegistry (see `Registry_Naming` for conventions used for default and duplicate names).
1472

1473
    prefs : PreferenceSet or specification dict
1474
        the `PreferenceSet` for the Mechanism; if it is not specified in the **prefs** argument of the
1475
        constructor, a default is assigned using `classPreferences` defined in __init__.py (see `Preferences`
1476
        for details).
1477
        .. _portRegistry : Registry
1478
               registry containing dicts for each Port type (InputPort, OutputPort and ParameterPort) with instance
1479
               dicts for the instances of each type and an instance count for each Port type in the Mechanism.
1480
               Note: registering instances of Port types with the Mechanism (rather than in the PortRegistry)
1481
                     allows the same name to be used for instances of a Port type belonging to different Mechanisms
1482
                     without adding index suffixes for that name across Mechanisms
1483
                     while still indexing multiple uses of the same base name within a Mechanism.
1484
    """
1485

1486
    # CLASS ATTRIBUTES
1487
    componentCategory = MECHANISM_COMPONENT_CATEGORY
1✔
1488
    className = componentCategory
1✔
1489
    suffix = " " + className
1✔
1490

1491
    registry = MechanismRegistry
1✔
1492

1493
    classPreferenceLevel = PreferenceLevel.CATEGORY
1✔
1494
    # Any preferences specified below will override those specified in CategoryDefaultPreferences
1495
    # Note: only need to specify setting;  level will be assigned to CATEGORY automatically
1496
    # classPreferences = {
1497
    #     PREFERENCE_SET_NAME: 'MechanismCustomClassPreferences',
1498
    #     PREFERENCE_KEYWORD<pref>: <setting>...}
1499

1500
    # Class-specific loggable items
1501
    @property
1✔
1502
    def _loggable_items(self):
1✔
1503
        # Ports, afferent Projections are loggable for a Mechanism
1504
        #     - this allows the value of InputPorts and OutputPorts to be logged
1505
        #     - for MappingProjections, this logs the value of the Projection's matrix parameter
1506
        #     - for ModulatoryProjections, this logs the value of the Projection
1507
        # IMPLEMENTATION NOTE: this needs to be a property as Projections may be added after instantiation
1508
        try:
×
1509
            # return list(self.ports) + list(self.afferents)
1510
            return list(self.ports)
×
1511
        except:
×
1512
            return []
×
1513

1514
    #FIX:  WHEN CALLED BY COMPOSITION, SHOULD USE FULL Mechanism.execute
1515
    # By default, init only the _execute method of Mechanism subclass objects when their execute method is called;
1516
    #    that is, DO NOT run the full Mechanism execute Process, since some components may not yet be instantiated
1517
    #    (such as OutputPorts)
1518
    initMethod = INIT_EXECUTE_METHOD_ONLY
1✔
1519

1520
    # Note:  the following enforce encoding as 2D np.ndarrays,
1521
    #        to accomodate multiple Ports:  one 1D np.ndarray per port
1522
    variableEncodingDim = 2
1✔
1523
    valueEncodingDim = 2
1✔
1524

1525
    portListAttr = {InputPort:INPUT_PORTS,
1✔
1526
                     ParameterPort:PARAMETER_PORTS,
1527
                     OutputPort:OUTPUT_PORTS}
1528

1529
    # Category specific defaults:
1530

1531
    standard_output_ports = [{NAME: RESULT},
1✔
1532
                             {NAME: MECHANISM_VALUE,
1533
                              VARIABLE: OWNER_VALUE},
1534
                             {NAME: OWNER_VALUE,
1535
                              VARIABLE: OWNER_VALUE}]
1536
    standard_output_port_names = [i['name'] for i in standard_output_ports]
1✔
1537

1538
    class Parameters(Mechanism.Parameters):
1✔
1539
        """
1540
            Attributes
1541
            ----------
1542

1543
                variable
1544
                    see `variable <Mechanism_Base.variable>`
1545

1546
                    :default value: numpy.array([[0]])
1547
                    :type: ``numpy.ndarray``
1548
                    :read only: True
1549

1550
                value
1551
                    see `value <Mechanism_Base.value>`
1552

1553
                    :default value: numpy.array([[0]])
1554
                    :type: ``numpy.ndarray``
1555
                    :read only: True
1556

1557
                function
1558
                    see `function <Mechanism_Base.function>`
1559

1560
                    :default value: `Linear`
1561
                    :type: `Function`
1562

1563
                input_labels_dict
1564
                    see `input_labels_dict <Mechanism_Base.input_labels_dict>`
1565

1566
                    :default value: {}
1567
                    :type: <class 'dict'>
1568

1569
                input_ports
1570
                    see `input_ports <Mechanism_Base.input_ports>`
1571

1572
                    :default value: None
1573
                    :type:
1574
                    :read only: True
1575

1576
                output_labels_dict
1577
                    see `output_labels_dict <Mechanism_Base.output_labels_dict>`
1578

1579
                    :default value: {}
1580
                    :type: <class 'dict'>
1581

1582
                output_ports
1583
                    see `output_ports <Mechanism_Base.output_ports>`
1584

1585
                    :default value: None
1586
                    :type:
1587
                    :read only: True
1588
        """
1589
        variable = Parameter(np.array([[0]]),
1✔
1590
                             read_only=True, pnl_internal=True,
1591
                             constructor_argument='default_variable')
1592
        value = Parameter(np.array([[0]]), read_only=True, pnl_internal=True)
1✔
1593
        function = Parameter(Linear, stateful=False, loggable=False)
1✔
1594

1595
        input_port_variables = Parameter(None, read_only=True, user=False,
1✔
1596
                                         getter=_input_port_variables_getter,
1597
                                         pnl_internal=True)
1598
        input_labels_dict = Parameter(
1✔
1599
            {},
1600
            stateful=False,
1601
            loggable=False,
1602
            pnl_internal=True
1603
        )
1604
        output_labels_dict = Parameter(
1✔
1605
            {},
1606
            stateful=False,
1607
            loggable=False,
1608
            pnl_internal=True
1609
        )
1610
        input_ports = Parameter(
1✔
1611
            None,
1612
            stateful=False,
1613
            loggable=False,
1614
            read_only=True,
1615
            structural=True,
1616
        )
1617
        output_ports = Parameter(
1✔
1618
            None,
1619
            stateful=False,
1620
            loggable=False,
1621
            read_only=True,
1622
            structural=True,
1623
        )
1624

1625
        def _parse_variable(self, variable):
1✔
1626
            if variable is None:
1✔
1627
                return None
1✔
1628
            # 2d empty port causes input problems.
1629
            # occurs for CIMs of empty Compositions, or when remove_port
1630
            # called on only input port
1631
            if safe_len(variable) == 0:
1✔
1632
                return np.array([])
1✔
1633
            return convert_to_np_array(variable, dimension=2)
1✔
1634

1635
        def _parse_input_ports(self, input_ports):
1✔
1636
            if input_ports is None:
1✔
1637
                return input_ports
1✔
1638
            elif (
1✔
1639
                not isinstance(input_ports, list)
1640
                and not (isinstance(input_ports, np.ndarray) and input_ports.ndim > 0)
1641
            ):
1642
                input_ports = [input_ports]
1✔
1643

1644
            spec_list = []
1✔
1645

1646
            for port in input_ports:
1✔
1647
                # handle tuple specification only because it cannot
1648
                # be translated to and from JSON (converts to list, which is
1649
                # not accepted as a valid specification)
1650
                if isinstance(port, tuple):
1✔
1651
                    if len(port) == 2:
1✔
1652
                        # allows case [(transfer_mech, None)] in
1653
                        # TestInputPortSpec
1654
                        if not isinstance(port[0], str):
1✔
1655
                            # no parsing
1656
                            spec = port
1✔
1657
                        else:
1658
                            spec = {
1✔
1659
                                NAME: port[0],
1660
                                MECHANISM: port[1]
1661
                            }
1662
                    elif len(port) > 2:
1!
1663
                        # nonstandard 4 item tuple
1664
                        if isinstance(port[0], (list, tuple)):
1✔
1665
                            spec = port
1✔
1666
                        else:
1667
                            # if port is assigned to an object,
1668
                            # use a reference instead of name/value
1669
                            if isinstance(port[0], Component):
1!
1670
                                spec = {PORT_SPEC: port[0]}
1✔
1671

1672
                            else:
1673
                                spec = {
×
1674
                                    NAME: port[0].name,
1675
                                    VALUE: port[0].defaults.value,
1676
                                }
1677

1678
                            spec[WEIGHT] = port[1]
1✔
1679
                            spec[EXPONENT] = port[2]
1✔
1680

1681
                            try:
1✔
1682
                                spec[PROJECTIONS] = port[3]
1✔
1683
                            except IndexError:
1✔
1684
                                pass
1✔
1685

1686
                    spec_list.append(spec)
1✔
1687
                else:
1688
                    spec_list.append(port)
1✔
1689

1690
            if is_numeric(spec_list):
1✔
1691
                spec_list = convert_all_elements_to_np_array(spec_list)
1✔
1692

1693
            return spec_list
1✔
1694

1695
        def _parse_output_ports(self, output_ports):
1✔
1696
            if (
1✔
1697
                output_ports is not None
1698
                and not isinstance(output_ports, list)
1699
                and not (isinstance(output_ports, np.ndarray) and output_ports.ndim > 0)
1700
            ):
1701
                output_ports = [output_ports]
1✔
1702

1703
            if is_numeric(output_ports):
1!
1704
                output_ports = convert_all_elements_to_np_array(output_ports)
×
1705

1706
            return output_ports
1✔
1707

1708
    # def __new__(cls, *args, **kwargs):
1709
    # def __new__(cls, name=NotImplemented, params=NotImplemented, context=None):
1710

1711
    @check_user_specified
1✔
1712
    @beartype
1✔
1713
    @abc.abstractmethod
1✔
1714
    def __init__(self,
1✔
1715
                 default_variable=None,
1716
                 input_shapes=None,
1717
                 input_ports=None,
1718
                 input_labels=None,
1719
                 function=None,
1720
                 output_ports=None,
1721
                 output_labels=None,
1722
                 params=None,
1723
                 name=None,
1724
                 prefs=None,
1725
                 context=None,
1726
                 **kwargs
1727
                 ):
1728
        """Assign name, category-level preferences, and variable; register Mechanism; and enforce category methods
1729

1730
        This is an abstract class, and can only be called from a subclass;
1731
           it must be called by the subclass with a context value
1732

1733
        NOTES:
1734
        * Since Mechanism is a subclass of Component, it calls super.__init__
1735
            to validate input_shapes and default_variable and param_defaults;
1736
            it uses INPUT_PORT as the default_variable
1737
        * registers Mechanism with MechanismRegistry
1738

1739
        """
1740

1741
        # IMPLEMENT **kwargs (PER Port)
1742

1743
        self.aux_components = []
1✔
1744
        self.monitor_for_learning = None
1✔
1745
        # Register with MechanismRegistry or create one
1746
        register_category(entry=self,
1✔
1747
                          base_class=Mechanism_Base,
1748
                          name=name,
1749
                          registry=MechanismRegistry,
1750
                          )
1751

1752
        # Create Mechanism's _portRegistry and port type entries
1753
        from psyneulink.core.components.ports.port import Port_Base
1✔
1754
        self._portRegistry = {}
1✔
1755

1756
        # InputPort
1757
        from psyneulink.core.components.ports.inputport import InputPort
1✔
1758
        register_category(entry=InputPort,
1✔
1759
                          base_class=Port_Base,
1760
                          registry=self._portRegistry,
1761
                          )
1762

1763
        # ParameterPort
1764
        from psyneulink.core.components.ports.parameterport import ParameterPort
1✔
1765
        register_category(entry=ParameterPort,
1✔
1766
                          base_class=Port_Base,
1767
                          registry=self._portRegistry,
1768
                          )
1769

1770
        # OutputPort
1771
        from psyneulink.core.components.ports.outputport import OutputPort
1✔
1772
        register_category(entry=OutputPort,
1✔
1773
                          base_class=Port_Base,
1774
                          registry=self._portRegistry,
1775
                          )
1776

1777
        super(Mechanism_Base, self).__init__(
1✔
1778
            default_variable=default_variable,
1779
            input_shapes=input_shapes,
1780
            function=function,
1781
            param_defaults=params,
1782
            prefs=prefs,
1783
            name=name,
1784
            input_ports=input_ports,
1785
            output_ports=output_ports,
1786
            input_labels_dict=input_labels,
1787
            output_labels_dict=output_labels,
1788
            **kwargs
1789
        )
1790

1791
        # FIX: 10/3/17 - IS THIS CORRECT?  SHOULD IT BE INITIALIZED??
1792
        self._status = INITIALIZING
1✔
1793
        self._receivesProcessInput = False
1✔
1794

1795
    # ------------------------------------------------------------------------------------------------------------------
1796
    # Parsing methods
1797
    # ------------------------------------------------------------------------------------------------------------------
1798
    # ---------------------------------------------------------
1799
    # Argument parsers
1800
    # ---------------------------------------------------------
1801

1802
    def _parse_arg_variable(self, variable):
1✔
1803
        if variable is None:
1✔
1804
            return None
1✔
1805

1806
        return super()._parse_arg_variable(convert_to_np_array(variable, dimension=2))
1✔
1807

1808
    # ------------------------------------------------------------------------------------------------------------------
1809
    # Handlers
1810
    # ------------------------------------------------------------------------------------------------------------------
1811

1812
    def _handle_default_variable(self, default_variable=None, input_shapes=None, input_ports=None, function=None, params=None):
1✔
1813
        """
1814
            Finds whether default_variable can be determined using **default_variable** and **input_shapes**
1815
            arguments.
1816

1817
            Returns
1818
            -------
1819
                a default variable if possible
1820
                None otherwise
1821
        """
1822
        default_variable_from_input_ports = None
1✔
1823

1824
        # handle specifying through params dictionary
1825
        try:
1✔
1826
            default_variable_from_input_ports, input_ports_variable_was_specified = \
1✔
1827
                self._handle_arg_input_ports(params[INPUT_PORTS])
1828
        except (TypeError, KeyError):
1✔
1829
            pass
1✔
1830
        except AttributeError as e:
×
1831
            if DEFER_VARIABLE_SPEC_TO_MECH_MSG in e.args[0]:
×
1832
                pass
×
1833

1834
        if default_variable_from_input_ports is None:
1!
1835
            # fallback to standard arg specification
1836
            try:
1✔
1837
                default_variable_from_input_ports, input_ports_variable_was_specified = \
1✔
1838
                    self._handle_arg_input_ports(input_ports)
1839
            except AttributeError as e:
1✔
1840
                if DEFER_VARIABLE_SPEC_TO_MECH_MSG in e.args[0]:
×
1841
                    pass
×
1842

1843
        if default_variable_from_input_ports is not None:
1✔
1844
            if default_variable is None:
1✔
1845
                if input_shapes is None:
1✔
1846
                    default_variable = default_variable_from_input_ports
1✔
1847
                else:
1848
                    if input_ports_variable_was_specified:
1!
1849
                        input_shapes_variable = self._handle_input_shapes(input_shapes, None)
1✔
1850
                        if iscompatible(input_shapes_variable, default_variable_from_input_ports):
1✔
1851
                            default_variable = default_variable_from_input_ports
1✔
1852
                        else:
1853
                            raise MechanismError(
1854
                                f'Default variable for {self.name} determined from the specified input_ports spec '
1855
                                f'({default_variable_from_input_ports}) is not compatible with the default variable '
1856
                                f'determined from input_shapes parameter ({input_shapes_variable}).')
1857
                    else:
1858
                        # do not pass input_ports variable as default_variable, fall back to input_shapes specification
1859
                        pass
1✔
1860
            else:
1861
                if input_ports_variable_was_specified:
1✔
1862
                    if not iscompatible(self._parse_arg_variable(default_variable), default_variable_from_input_ports):
1✔
1863
                        raise MechanismError(
1864
                            f'Default variable for {self.name} determined from the specified input_ports spec '
1865
                            f'({default_variable_from_input_ports}) is not compatible with its specified '
1866
                            f'default variable ({default_variable}).')
1867
                else:
1868
                    # do not pass input_ports variable as default_variable, fall back to default_variable specification
1869
                    pass
1870

1871
        return super()._handle_default_variable(default_variable=default_variable, input_shapes=input_shapes)
1✔
1872

1873
    def _handle_arg_input_ports(self, input_ports):
1✔
1874
        """Take user-inputted argument **input_ports** and returns a defaults.variable-like object that it represents
1875

1876
        Returns
1877
        -------
1878
            A, B where
1879
            A is a defaults.variable-like object
1880
            B is True if **input_ports** contained an explicit variable specification, False otherwise
1881
        """
1882

1883
        if input_ports is None:
1✔
1884
            return None, False
1✔
1885

1886
        default_variable_from_input_ports = []
1✔
1887
        input_port_variable_was_specified = None
1✔
1888

1889
        if (
1✔
1890
            not isinstance(input_ports, (list, UserList))
1891
            and not (isinstance(input_ports, np.ndarray) and input_ports.ndim > 0)
1892
        ):
1893
            input_ports = [input_ports]
1✔
1894

1895
        for i, s in enumerate(input_ports):
1✔
1896

1897

1898
            try:
1✔
1899
                parsed_input_port_spec = _parse_port_spec(owner=self,
1✔
1900
                                                          port_type=InputPort,
1901
                                                          port_spec=s,
1902
                                                          context=Context(string='handle_arg_input_ports'))
1903
            except AttributeError as e:
1✔
1904
                if DEFER_VARIABLE_SPEC_TO_MECH_MSG in e.args[0]:
1✔
1905
                    default_variable_from_input_ports.append(InputPort.defaults.variable)
1✔
1906
                    continue
1✔
1907
                else:
1908
                    raise MechanismError(f"PROGRAM ERROR: Problem parsing {InputPort.__name__} specification ({s}) "
1909
                                         f"for {self.name}.")
1910

1911
            mech_variable_item = None
1✔
1912

1913
            if isinstance(parsed_input_port_spec, dict):
1✔
1914
                try:
1✔
1915
                    mech_variable_item = parsed_input_port_spec[VALUE]
1✔
1916
                    if parsed_input_port_spec[VARIABLE] is None:
1✔
1917
                        input_port_variable_was_specified = False
1✔
1918
                except KeyError:
×
1919
                    pass
×
1920
            elif isinstance(parsed_input_port_spec, (Projection, Mechanism, Port)):
1!
1921
                if parsed_input_port_spec.initialization_status == ContextFlags.DEFERRED_INIT:
1✔
1922
                    args = parsed_input_port_spec._init_args
1✔
1923
                    if REFERENCE_VALUE in args and args[REFERENCE_VALUE] is not None:
1✔
1924
                        mech_variable_item = args[REFERENCE_VALUE]
1✔
1925
                    elif VALUE in args and args[VALUE] is not None:
1!
1926
                        mech_variable_item = args[VALUE]
×
1927
                    elif VARIABLE in args and args[VARIABLE] is not None:
1✔
1928
                        mech_variable_item = args[VARIABLE]
1✔
1929
                else:
1930
                    try:
1✔
1931
                        mech_variable_item = parsed_input_port_spec.defaults.value
1✔
1932
                    except AttributeError:
×
1933
                        mech_variable_item = parsed_input_port_spec.defaults.mech_variable_item
×
1934
            else:
1935
                mech_variable_item = parsed_input_port_spec.defaults.mech_variable_item
×
1936

1937
            if mech_variable_item is None:
1✔
1938
                mech_variable_item = InputPort.defaults.variable
1✔
1939
            elif input_port_variable_was_specified is None and not InputPort._port_spec_allows_override_variable(s):
1✔
1940
                input_port_variable_was_specified = True
1✔
1941

1942
            default_variable_from_input_ports.append(mech_variable_item)
1✔
1943

1944
        return default_variable_from_input_ports, input_port_variable_was_specified
1✔
1945

1946
    # ------------------------------------------------------------------------------------------------------------------
1947
    # Validation methods
1948
    # ------------------------------------------------------------------------------------------------------------------
1949

1950
    def _validate_variable(self, variable, context=None):
1✔
1951
        """Convert variable to 2D np.array: one 1D value for each InputPort
1952

1953
        # VARIABLE SPECIFICATION:                                        ENCODING:
1954
        # Simple value variable:                                         0 -> [array([0])]
1955
        # Single port array (vector) variable:                         [0, 1] -> [array([0, 1])
1956
        # Multiple port variables, each with a single value variable:  [[0], [0]] -> [array[0], array[0]]
1957

1958
        :param variable:
1959
        :param context:
1960
        :return:
1961
        """
1962

1963
        variable = super(Mechanism_Base, self)._validate_variable(variable, context)
1✔
1964

1965
        # Force Mechanism variable specification to be a 2D array (to accomodate multiple InputPorts - see above):
1966
        variable = convert_to_np_array(variable, 2)
1✔
1967

1968
        return variable
1✔
1969

1970
    def _validate_params(self, request_set, target_set=None, context=None):
1✔
1971
        """validate TimeScale, INPUT_PORTS, FUNCTION_PARAMS, OUTPUT_PORTS and MONITOR_FOR_CONTROL
1972

1973
        Go through target_set params (populated by Component._validate_params) and validate values for:
1974
            + INPUT_PORTS:
1975
                <MechanismsInputPort or Projection object or class,
1976
                specification dict for one, 2-item tuple, or numeric value(s)>;
1977
                if it is missing or not one of the above types, it is set to self.defaults.variable
1978
            + FUNCTION_PARAMS:  <dict>, every entry of which must be one of the following:
1979
                ParameterPort or Projection object or class, specification dict for one, 2-item tuple, or numeric
1980
                value(s);
1981
                if invalid, default is assigned
1982
            + OUTPUT_PORTS:
1983
                <MechanismsOutputPort object or class, specification dict, or numeric value(s);
1984
                if it is missing or not one of the above types, it is set to None here;
1985
                    and then to default value of value (output of execute method) in instantiate_output_port
1986
                    (since execute method must be instantiated before self.defaults.value is known)
1987
                if OUTPUT_PORTS is a list or OrderedDict, it is passed along (to instantiate_output_ports)
1988
                if it is a OutputPort class ref, object or specification dict, it is placed in a list
1989
            + MONITORED_PORTS:
1990
                ** DOCUMENT
1991

1992
        Note: PARAMETER_PORTS are validated separately -- ** DOCUMENT WHY
1993

1994
        TBI - Generalize to go through all params, reading from each its type (from a registry),
1995
                                   and calling on corresponding subclass to get default values (if param not found)
1996
                                   (as PROJECTION_TYPE and PROJECTION_SENDER are currently handled)
1997
        """
1998

1999
        from psyneulink.core.components.ports.port import _parse_port_spec
1✔
2000
        from psyneulink.core.components.ports.inputport import InputPort
1✔
2001
        from psyneulink.core.components.ports.outputport import OutputPort
1✔
2002

2003
        # Perform first-pass validation in Function.__init__():
2004
        super(Mechanism, self)._validate_params(request_set,target_set,context)
1✔
2005

2006
        params = target_set
1✔
2007

2008
        # VALIDATE InputPort(S)
2009

2010
        # INPUT_PORTS is specified, so validate:
2011
        if INPUT_PORTS in params and params[INPUT_PORTS] is not None:
1✔
2012
            try:
1✔
2013
                try:
1✔
2014
                    for port_spec in params[INPUT_PORTS]:
1✔
2015
                        _parse_port_spec(owner=self, port_type=InputPort, port_spec=port_spec,
1✔
2016
                                         context=Context(string='mechanism.validate_params'))
2017
                except TypeError:
1✔
2018
                    _parse_port_spec(owner=self, port_type=InputPort, port_spec=params[INPUT_PORTS],
×
2019
                                     context=Context(string='mechanism.validate_params'))
2020
            except AttributeError as e:
1✔
2021
                if DEFER_VARIABLE_SPEC_TO_MECH_MSG in e.args[0]:
1!
2022
                    pass
1✔
2023

2024
        # VALIDATE FUNCTION_PARAMS
2025
        try:
1✔
2026
            function_param_specs = params[FUNCTION_PARAMS]
1✔
2027
        except KeyError:
1✔
2028
            if context.source is ContextFlags.COMMAND_LINE:
1!
2029
                pass
×
2030
            elif self.prefs.verbosePref:
1!
2031
                print("No params specified for {0}".format(self.__class__.__name__))
×
2032
        else:
2033
            if not (isinstance(function_param_specs, dict)):
×
2034
                raise MechanismError("{0} in {1} must be a dict of param specifications".
2035
                                     format(FUNCTION_PARAMS, self.__class__.__name__))
2036
            # Validate params
2037

2038
            from psyneulink.core.components.ports.parameterport import ParameterPort
×
2039
            for param_name, param_value in function_param_specs.items():
×
2040
                if not ((isclass(param_value) and
×
2041
                             (issubclass(param_value, ParameterPort) or
2042
                              issubclass(param_value, Projection))) or
2043
                        isinstance(param_value, ParameterPort) or
2044
                        isinstance(param_value, Projection) or
2045
                        isinstance(param_value, dict) or
2046
                        iscompatible(param_value, self.defaults.value)):
2047
                    params[FUNCTION_PARAMS][param_name] = self.defaults.value
×
2048
                    if self.prefs.verbosePref:
×
2049
                        print("{0} param ({1}) for execute method {2} of {3} is not a ParameterPort, "
×
2050
                              "projection, tuple, or value; default value ({4}) will be used".
2051
                              format(param_name,
2052
                                     param_value,
2053
                                     self.execute.__self__.componentName,
2054
                                     self.__class__.__name__,
2055
                                     self.defaults.value))
2056

2057
        # VALIDATE OUTPUTPORT(S)
2058

2059
        # OUTPUT_PORTS is specified, so validate:
2060
        if OUTPUT_PORTS in params and params[OUTPUT_PORTS] is not None:
1✔
2061

2062
            param_value = params[OUTPUT_PORTS]
1✔
2063

2064
            # If it is a single item or a non-OrderedDict, place in list (for use here and in instantiate_output_port)
2065
            if not isinstance(param_value, (ContentAddressableList, list, OrderedDict)):
1!
2066
                param_value = [param_value]
×
2067
            # Validate each item in the list or OrderedDict
2068
            i = 0
1✔
2069
            for key, item in param_value if isinstance(param_value, dict) else enumerate(param_value):
1✔
2070
                # If not valid...
2071
                if not ((isclass(item) and issubclass(item, OutputPort)) or  # OutputPort class ref
1✔
2072
                        isinstance(item, OutputPort) or  # OutputPort object
2073
                        isinstance(item, dict) or  # OutputPort specification dict
2074
                        isinstance(item, str) or  # Name (to be used as key in OutputPorts list)
2075
                        isinstance(item, tuple) or  # Projection specification tuple
2076
                        _is_modulatory_spec(item) or  # Modulatory specification for the OutputPort
2077
                        iscompatible(item, **{kwCompatibilityNumeric: True})):  # value
2078
                    # set to None, so it is set to default (self.value) in instantiate_output_port
2079
                    param_value[key] = None
1✔
2080
                    if self.prefs.verbosePref:
1!
2081
                        print("Item {0} of {1} param ({2}) in {3} is not a"
×
2082
                              " OutputPort, specification dict or value, nor a list of dict of them; "
2083
                              "output ({4}) of execute method for {5} will be used"
2084
                              " to create a default OutputPort for {3}".
2085
                              format(i,
2086
                                     OUTPUT_PORTS,
2087
                                     param_value,
2088
                                     self.__class__.__name__,
2089
                                     self.value,
2090
                                     self.execute.__self__.name))
2091
                i += 1
1✔
2092
            params[OUTPUT_PORTS] = param_value
1✔
2093

2094
        def validate_labels_dict(lablel_dict, type):
1✔
2095
            for label, value in labels_dict.items():
1✔
2096
                # KDM 11/26/19: allowed ints and dicts because they are
2097
                # expected in test_3_input_ports_2_label_dicts
2098
                if not isinstance(label, (str, int)):
1✔
2099
                    raise MechanismError("Key ({}) in the {} for {} must be a string or int ".
2100
                                         format(label, type, self.name))
2101
                if not isinstance(value, (list, np.ndarray, dict, int)):
1✔
2102
                    raise MechanismError("The value of {} ({}) in the {} for {} must be a list, dict, or array".
2103
                                         format(label, value, type, self.name))
2104
        def validate_subdict_key(port_type, key, dict_type):
1✔
2105
            # IMPLEMENTATION NOTE:
2106
            #    can't yet validate that string is a legit InputPort name or that index is within
2107
            #    bounds of the number of InputPorts;  that is done in _get_port_value_labels()
2108
            if not isinstance(key, (int, str)):
1✔
2109
                raise MechanismError("Key ({}) for {} of {} must the name of an {} or the index for one".
2110
                                     format(key, dict_type, self.name, port_type.__name__))
2111

2112
        if INPUT_LABELS_DICT in params and params[INPUT_LABELS_DICT]:
1✔
2113
            labels_dict = params[INPUT_LABELS_DICT]
1✔
2114
            if isinstance(list(labels_dict.values())[0], dict):
1✔
2115
                for subdict in labels_dict.values():
1✔
2116
                    for key, ld in subdict.items():
1✔
2117
                        validate_subdict_key(InputPort, key, INPUT_LABELS_DICT)
1✔
2118
                        validate_labels_dict(ld, INPUT_LABELS_DICT)
1✔
2119
            else:
2120
                validate_labels_dict(labels_dict, INPUT_LABELS_DICT)
1✔
2121

2122
        if OUTPUT_LABELS_DICT in params and params[OUTPUT_LABELS_DICT]:
1✔
2123
            labels_dict = params[OUTPUT_LABELS_DICT]
1✔
2124
            if isinstance(list(labels_dict.values())[0], dict):
1!
2125
                for key, ld in labels_dict.values():
1✔
2126
                    validate_subdict_key(OutputPort, key, OUTPUT_LABELS_DICT)
1✔
2127
                    validate_labels_dict(ld, OUTPUT_LABELS_DICT)
1✔
2128
            else:
2129
                validate_labels_dict(labels_dict, OUTPUT_LABELS_DICT)
×
2130

2131
        if TARGET_LABELS_DICT in params and params[TARGET_LABELS_DICT]:
1!
2132
            for label, value in params[TARGET_LABELS_DICT].items():
×
2133
                if not isinstance(label,str):
×
2134
                    raise MechanismError("Key ({}) in the {} for {} must be a string".
2135
                                         format(label, TARGET_LABELS_DICT, self.name))
2136
                if not isinstance(value,(list, np.ndarray)):
×
2137
                    raise MechanismError("The value of {} ({}) in the {} for {} must be a list or array".
2138
                                         format(label, value, TARGET_LABELS_DICT, self.name))
2139

2140
    def _validate_inputs(self, inputs=None):
1✔
2141
        # Only ProcessingMechanism supports run() method of Function;  ControlMechanism and LearningMechanism do not
2142
        raise MechanismError("{} does not support run() method".format(self.__class__.__name__))
2143

2144
    def _instantiate_attributes_before_function(self, function=None, context=None):
1✔
2145
        self._instantiate_input_ports(context=context)
1✔
2146
        self._instantiate_parameter_ports(function=function, context=context)
1✔
2147
        super()._instantiate_attributes_before_function(function=function, context=context)
1✔
2148

2149
    def _instantiate_function(self, function, function_params=None, context=None):
1✔
2150
        """Assign weights and exponents if specified in input_ports
2151
        """
2152

2153
        super()._instantiate_function(function=function, function_params=function_params, context=context)
1✔
2154

2155
        if (
1✔
2156
            self.input_ports
2157
            and any(
2158
                input_port.parameters.weight._get(context) is not None
2159
                for input_port in self.input_ports
2160
            )
2161
            and 'weights' in self.function.parameters
2162
        ):
2163

2164
            # Construct defaults:
2165
            #    from function.weights if specified else 1's
2166
            try:
1✔
2167
                default_weights = self.function.defaults.weights
1✔
2168
            except AttributeError:
×
2169
                default_weights = None
×
2170
            if default_weights is None:
1✔
2171
                default_weights = default_weights or np.ones(len(self.input_ports))
1✔
2172

2173
            # Assign any weights specified in input_port spec
2174
            weights = convert_to_np_array([
1✔
2175
                [input_port.defaults.weight if input_port.defaults.weight is not None else default_weight]
2176
                for input_port, default_weight in zip(self.input_ports, default_weights)
2177
            ])
2178
            self.function.parameters.weights._set(weights, context)
1✔
2179

2180
        if (
1✔
2181
            self.input_ports
2182
            and any(
2183
                input_port.parameters.exponent._get(context) is not None
2184
                for input_port in self.input_ports
2185
            )
2186
            and 'exponents' in self.function.parameters
2187
        ):
2188

2189
            # Construct defaults:
2190
            #    from function.weights if specified else 1's
2191
            try:
1✔
2192
                default_exponents = self.function.defaults.exponents
1✔
2193
            except AttributeError:
×
2194
                default_exponents = None
×
2195
            if default_exponents is None:
1!
2196
                default_exponents = default_exponents or np.ones(len(self.input_ports))
1✔
2197

2198
            # Assign any exponents specified in input_port spec
2199
            exponents = convert_to_np_array([
1✔
2200
                [
2201
                    input_port.parameters.exponent._get(context)
2202
                    if input_port.parameters.exponent._get(context) is not None
2203
                    else default_exponent
2204
                ]
2205
                for input_port, default_exponent in zip(self.input_ports, default_exponents)
2206
            ])
2207
            self.function.parameters.exponents._set(exponents, context)
1✔
2208

2209
        # this may be removed when the restriction making all Mechanism values 2D np arrays is lifted
2210
        # ignore warnings of certain Functions that disable conversion
2211
        with warnings.catch_warnings():
1✔
2212
            warnings.simplefilter(action='ignore', category=UserWarning)
1✔
2213
            self.function.output_type = FunctionOutputType.NP_2D_ARRAY
1✔
2214
            self.function.parameters.enable_output_type_conversion._set(True, context)
1✔
2215

2216
        self.function._instantiate_value(context)
1✔
2217

2218
    def _instantiate_attributes_after_function(self, context=None):
1✔
2219
        from psyneulink.core.components.ports.parameterport import _instantiate_parameter_port
1✔
2220

2221
        self._instantiate_output_ports(context=context)
1✔
2222
        # instantiate parameter ports from UDF custom parameters if necessary
2223
        try:
1✔
2224
            for param_name, param_value in self.function.cust_fct_params.items():
1✔
2225
                if param_name not in self.parameter_ports.names:
1✔
2226
                    source_param = getattr(self.function.parameters, param_name)
1✔
2227
                    _instantiate_parameter_port(
1✔
2228
                        self,
2229
                        param_name,
2230
                        param_value,
2231
                        context=context,
2232
                        function=self.function,
2233
                        source=source_param,
2234
                    )
2235
        except AttributeError:
1✔
2236
            pass
1✔
2237

2238
        super()._instantiate_attributes_after_function(context=context)
1✔
2239

2240
    def _instantiate_input_ports(self, input_ports=None, reference_value=None, context=None):
1✔
2241
        """Call Port._instantiate_input_ports to instantiate orderedDict of InputPort(s)
2242

2243
        This is a stub, implemented to allow Mechanism subclasses to override _instantiate_input_ports
2244
            or process InputPorts before and/or after call to _instantiate_input_ports
2245
        """
2246
        from psyneulink.core.components.ports.inputport import _instantiate_input_ports
1✔
2247
        return _instantiate_input_ports(owner=self,
1✔
2248
                                         input_ports=input_ports or self.input_ports,
2249
                                         reference_value=reference_value,
2250
                                         context=context)
2251

2252
    def _instantiate_parameter_ports(self, function=None, context=None):
1✔
2253
        """Call Port._instantiate_parameter_ports to instantiate a ParameterPort
2254
        for each parameter with modulable=True
2255

2256
        This is a stub, implemented to allow Mechanism subclasses to override _instantiate_parameter_ports
2257
            or process InputPorts before and/or after call to _instantiate_parameter_ports
2258
            :param function:
2259
        """
2260
        from psyneulink.core.components.ports.parameterport import _instantiate_parameter_ports
1✔
2261
        _instantiate_parameter_ports(owner=self, function=function, context=context)
1✔
2262

2263
    def _instantiate_output_ports(self, context=None):
1✔
2264
        """Call Port._instantiate_output_ports to instantiate orderedDict of OutputPort(s)
2265

2266
        This is a stub, implemented to allow Mechanism subclasses to override _instantiate_output_ports
2267
            or process InputPorts before and/or after call to _instantiate_output_ports
2268
        """
2269
        from psyneulink.core.components.ports.outputport import _instantiate_output_ports
1✔
2270
        _instantiate_output_ports(owner=self, output_ports=self.output_ports, context=context)
1✔
2271

2272
    def _add_projection_to_mechanism(self, port, projection, context=None):
1✔
2273
        from psyneulink.core.components.projections.projection import _add_projection_to
×
2274
        _add_projection_to(receiver=self, port=port, projection_spec=projection, context=context)
×
2275

2276
    def _add_projection_from_mechanism(self, receiver, port, projection, context=None):
1✔
2277
        """Add projection to specified port
2278
        """
2279
        from psyneulink.core.components.projections.projection import _add_projection_from
×
2280
        _add_projection_from(sender=self, port=port, projection_spec=projection, receiver=receiver, context=context)
×
2281

2282
    def _projection_added(self, projection, context=None):
1✔
2283
        """Stub that can be overidden by subclasses that need to know when a projection is added to the Mechanism"""
2284
        pass
1✔
2285

2286
    @handle_external_context(fallback_most_recent=True)
1✔
2287
    def reset(self, *args, force=False, context=None, **kwargs):
1✔
2288
        """Reset `value <Mechanism_Base.value>` if Mechanisms is stateful.
2289

2290
        If the mechanism's `function <Mechanism.function>` is an `IntegratorFunction`, or if the mechanism has and
2291
        `integrator_function <TransferMechanism.integrator_function>` (see `TransferMechanism`), this method
2292
        effectively begins the function's accumulation over again at the specified value, and updates related
2293
        attributes on the mechanism.  It also clears the `value <Mechanism_Base.value>` `history <Parameter.history>`,
2294
        thus effectively setting the previous value to ``None``.
2295

2296
        If the mechanism's `function <Mechanism_Base.function>` is an `IntegratorFunction`, its `reset
2297
        <Mechanism_Base.reset>` method:
2298

2299
            (1) Calls the function's own `reset <IntegratorFunction.reset>` method (see Note below for
2300
                details)
2301

2302
            (2) Sets the mechanism's `value <Mechanism_Base.value>` to the output of the function's
2303
                reset method
2304

2305
            (3) Updates its `output ports <Mechanism_Base.output_port>` based on its new `value
2306
                <Mechanism_Base.value>`
2307

2308
        If the mechanism has an `integrator_function <TransferMechanism.integrator_function>`, its `reset
2309
        <Mechanism_Base.reset>` method::
2310

2311
            (1) Calls the `integrator_function's <TransferMechanism.integrator_function>` own `reset
2312
                <IntegratorFunction.reset>` method (see Note below for details)
2313

2314
            (2) Executes its `function <Mechanism_Base.function>` using the output of the `integrator_function's
2315
                <TransferMechanism.integrator_function>` `reset <IntegratorFunction.reset>` method as
2316
                the function's variable
2317

2318
            (3) Sets the mechanism's `value <Mechanism_Base.value>` to the output of its function
2319

2320
            (4) Updates its `output ports <Mechanism_Base.output_port>` based on its new `value
2321
                <Mechanism_Base.value>`
2322

2323
        .. note::
2324
                The reset method of an IntegratorFunction Function typically resets the function's
2325
                `previous_value <IntegratorFunction.previous_value>` (and any other `stateful_attributes
2326
                <IntegratorFunction.stateful_attributes>`) and `value <IntegratorFunction.value>` to the quantity (or
2327
                quantities) specified. If `reset <Mechanism_Base.reset>` is called without arguments,
2328
                the `initializer <IntegratorFunction.initializer>` value (or the values of each of the attributes in
2329
                `initializers <IntegratorFunction.initializers>`) is used instead. The `reset
2330
                <IntegratorFunction.reset>` method may vary across different Integrators. See individual
2331
                functions for details on their `stateful_attributes <IntegratorFunction.stateful_attributes>`,
2332
                as well as other reinitialization steps that the reset method may carry out.
2333
        """
2334
        from psyneulink.core.components.functions.stateful.statefulfunction import StatefulFunction
1✔
2335
        from psyneulink.core.components.functions.stateful.integratorfunctions import IntegratorFunction
1✔
2336

2337
        # If the primary function of the mechanism is stateful:
2338
        # (1) reset it, (2) update value, (3) update output ports
2339
        if isinstance(self.function, StatefulFunction):
1✔
2340
            new_value = self.function.reset(*args, **kwargs, context=context)
1✔
2341
            self.parameters.value._set(convert_to_np_array(new_value, dimension=2), context=context)
1✔
2342
            self._update_output_ports(context=context)
1✔
2343

2344
        # FIX: SHOULD MOVE ALL OF THIS TO TransferMechanism SINCE NO OTHER MECH TYPES HAVE integrator_funtions/modes
2345
        # If the mechanism has an auxiliary integrator function:
2346
        # (1) reset it, (2) run the primary function with the new "previous_value" as input
2347
        # (3) update value, (4) update output ports
2348
        elif hasattr(self, "integrator_function"):
1✔
2349
            if not isinstance(self.integrator_function, IntegratorFunction):
1✔
2350
                raise MechanismError(
2351
                    f"Resetting '{self.name}' is not allowed because its integrator_function "
2352
                    f"is not an IntegratorFunction type function, therefore the Mechanism "
2353
                    f"does not have an integrator to reset."
2354
                )
2355

2356
            if self.parameters.integrator_mode._get(context) or force:
1✔
2357
                new_input = self.integrator_function.reset(*args, **kwargs, context=context)[0]
1✔
2358
                self.parameters.value._set(
1✔
2359
                    self.function.execute(variable=new_input, context=context),
2360
                    context=context,
2361
                    override=True
2362
                )
2363
                self._update_output_ports(context=context)
1✔
2364

2365
            elif hasattr(self, "integrator_mode"):
1✔
2366
                # raise MechanismError(f"Resetting '{self.name}' is not allowed because this Mechanism "
2367
                #                      f"is not stateful; it does not have an integrator to reset. "
2368
                #                      f"If it should be stateful, try setting the integrator_mode argument to True.")
2369
                raise MechanismError(f"Resetting '{self.name}' is not allowed because its `integrator_mode` parameter "
2370
                                    f"is currently set to 'False'; try setting it to 'True'.")
2371

2372
            else:
2373
                raise MechanismError(f"Resetting '{self.name}' is not allowed because this Mechanism "
2374
                                     f"is not stateful; it does not have an integrator to reset.")
2375
        else:
2376
            raise MechanismError(f"Resetting '{self.name}' is not allowed because this Mechanism is not stateful; "
2377
                                 f"it does not have an accumulator to reset.")
2378

2379
    # when called externally, ContextFlags.PROCESSING is not set. Maintain this behavior here
2380
    # even though it will not update input ports for example
2381
    @handle_external_context(execution_phase=ContextFlags.IDLE)
1✔
2382
    def execute(self,
1✔
2383
                input=None,
2384
                context=None,
2385
                runtime_params=None,
2386
                report_output=None,
2387
                report_params=None,
2388
                report_num=None
2389
                ):
2390
        """Carry out a single `execution <Mechanism_Execution>` of the Mechanism.
2391

2392
        .. technical_note::
2393
            Execution sequence:
2394

2395
            * Handle initialization if `initialization_status <Compoonent.initialization_status>` is
2396
              *ContextFlags.INITIALIZING*
2397
            * Assign any `Port-specific runtime params <_Mechanism_Runtime_Port_and_Projection_Param_Specification>`
2398
              to corresponding `runtime_params <Mechanism_Base.runtime_params>` dict.
2399
            * While `is_finished <Component_Is_Finished> is not True:
2400
              - validate `variable <Mechanism_Base.variable>` from `InputPorts <Mechanism_Base.input_ports>` and
2401
                `runtime_params <Mechanism_Base.runtime_params>`.
2402
              - update `input_ports <Mechanism_Base.input_ports>`
2403
              - update `parameter_ports <Mechanism_Base.parameter_ports>`
2404
              - execute Mechanism (calling _execute method) and set `value <Mechanism_Base.value>` parameter
2405
              - update `output_ports <Mechanism_Base.output_ports>`
2406
                Note:
2407
                  > if execution is occurring as part of initialization, each output_port is reset to 0
2408
                  > otherwise, their values are left as is until the next update
2409
              - update `num_executions <Component_Num_Executions>` and check `max_executions <Component_Max_Executions>`
2410
            * Report execution (if reportOutputPref is set)
2411

2412
        Arguments
2413
        ---------
2414

2415
        input : List[value] or ndarray : default self.defaults.variable
2416
            input to use for execution of the Mechanism.
2417
            This must be consistent with the format of the Mechanism's `InputPort(s) <Mechanism_InputPorts>`:
2418
            the number of items in the  outermost level of the list, or axis 0 of the ndarray, must equal the number
2419
            of the Mechanism's `input_ports  <Mechanism_Base.input_ports>`, and each item must be compatible with the
2420
            format (number and type of elements) of the `variable <InputPort.variable>` of the corresponding
2421
            InputPort (see `Run Inputs <Composition_Execution_Inputs>` for details of input specification formats).
2422

2423
        runtime_params : [Dict[str, Dict[str, Dict[str, value]]]] : None
2424
            a dictionary specifying values for `Parameters <Parameter>` of the Mechanism or those of any of its
2425
            `Components <Component>` (`function <Mechanism_Base.function>`, `Ports <Mechanism_Ports>` and/or
2426
            their `afferent Projections <Port_Projections>`), that temporarily override their values for the current
2427
            execution, and are then restored to their previous values following execution (see
2428
            `Mechanism_Runtime_Param_Specification` for details of specification).
2429

2430
        context : Context or str : None
2431
            the context in which the Mechanism is executed, usually specified by its `execution_id
2432
            <Context.execution_id>` (see `Composition_Execution_Context` for additional information about execution
2433
            contexts.
2434

2435
        Returns
2436
        -------
2437

2438
        Mechanism's output_values : List[value]
2439
            list with the `value <OutputPort.value>` of each of the Mechanism's `OutputPorts
2440
            <Mechanism_OutputPorts>` after either one `TIME_STEP` or a `TRIAL <TimeScale.TRIAL>`.
2441

2442
        """
2443

2444
        if self.initialization_status == ContextFlags.INITIALIZED:
1✔
2445
            context.string = f"{context.source.name} EXECUTING {self.name}: " \
1✔
2446
                             f"{ContextFlags._get_context_string(context.flags, EXECUTION_PHASE)}."
2447
        else:
2448
            context.string = f"{context.source.name} INITIALIZING {self.name}."
1✔
2449

2450
        if context.source is ContextFlags.COMMAND_LINE:
1✔
2451
            self._initialize_from_context(context, override=False)
1✔
2452

2453
        # IMPLEMENTATION NOTE: Re-write by calling execute methods according to their order in functionDict:
2454
        #         for func in self.functionDict:
2455
        #             self.functionsDict[func]()
2456

2457
        # INITIALIZE MECHANISM if needed
2458

2459
        # Limit init to scope specified by context
2460
        if self.initialization_status == ContextFlags.INITIALIZING:
1✔
2461
            if context.composition:
1!
2462
                # Run full execute method for init of Composition
2463
                pass
×
2464
            # Only call subclass' _execute method and then return (do not complete the rest of this method)
2465
            elif self.initMethod == INIT_EXECUTE_METHOD_ONLY:
1!
2466
                return_value = self._execute(variable=copy_parameter_value(self.defaults.variable),
1✔
2467
                                             context=context,
2468
                                             runtime_params=runtime_params)
2469
                if context.source is ContextFlags.COMMAND_LINE:
1!
2470
                    return_value = copy_parameter_value(return_value)
×
2471

2472
                # IMPLEMENTATION NOTE:  THIS IS HERE BECAUSE IF return_value IS A LIST, AND THE LENGTH OF ALL OF ITS
2473
                #                       ELEMENTS ALONG ALL DIMENSIONS ARE EQUAL (E.G., A 2X2 MATRIX PAIRED WITH AN
2474
                #                       ARRAY OF LENGTH 2), np.array (AS WELL AS np.atleast_2d) GENERATES A ValueError
2475
                if (isinstance(return_value, list) and
1!
2476
                    (all(isinstance(item, np.ndarray) for item in return_value) and
2477
                        all(
2478
                                all(item.shape[i]==return_value[0].shape[0]
2479
                                    for i in range(len(item.shape)))
2480
                                for item in return_value))):
2481

2482
                    return return_value
×
2483
                else:
2484
                    converted_to_2d = convert_to_np_array(return_value, dimension=2)
1✔
2485
                # If return_value is a list of heterogenous elements, return as is
2486
                #     (satisfies requirement that return_value be an array of possibly multidimensional values)
2487
                if converted_to_2d.dtype == object:
1✔
2488
                    return return_value
1✔
2489
                # Otherwise, return value converted to 2d np.array
2490
                else:
2491
                    return converted_to_2d
1✔
2492

2493
            # Call only subclass' function during initialization (not its full _execute method nor rest of this method)
2494
            elif self.initMethod == INIT_FUNCTION_METHOD_ONLY:
×
2495
                return_value = super()._execute(variable=copy_parameter_value(self.defaults.variable),
×
2496
                                                context=context,
2497
                                                runtime_params=runtime_params)
2498
                return convert_to_np_array(return_value, dimension=2)
×
2499

2500
        # SET UP RUNTIME PARAMS if any
2501

2502
        # Extract all param specifications not related to the Mechanism itself or its function and place in subdicts;
2503
        #    when Mechanism executes, _validate_and_assign_runtime_params will throw an error for any others found.
2504
        runtime_port_params = self._parse_runtime_params(runtime_params, context)
1✔
2505

2506
        # EXECUTE MECHANISM
2507

2508
        if self.parameters.is_finished_flag._get(context) is True:
1✔
2509
            self.parameters.num_executions_before_finished._set(np.array(0), override=True, context=context)
1✔
2510

2511
        while True:
2512

2513
            # Don't bother executing Mechanism if variable and/or value has been specified for all of its OutputPorts
2514
            # Mechanism value is set to None, so its previous value will be retained (in accord with Lazy Evaluation)
2515
            # However, num_executions and execution_count will be incremented (since the Mechanism was in fact executed)
2516
            if (any(var_or_val in runtime_port_params[OUTPUT_PORT_PARAMS] for var_or_val in {VARIABLE, VALUE})
1✔
2517
                    or
2518
                    (PORT_SPECIFIC_PARAMS in runtime_port_params[OUTPUT_PORT_PARAMS]
2519
                     and (all((var_or_val in p for var_or_val in {VARIABLE, VALUE})
2520
                              for p in runtime_port_params[OUTPUT_PORT_PARAMS][PORT_SPECIFIC_PARAMS]
2521
                              if p in {self, self.name})))
2522
            ):
2523
                self.parameters.is_finished_flag._set(True, context)
1✔
2524
                value = None
1✔
2525

2526
            else:
2527
                # VALIDATE InputPort(S) AND RUNTIME PARAMS
2528
                self._check_args(params=runtime_params,
1✔
2529
                                 target_set=runtime_params,
2530
                                 context=context)
2531

2532
                # UPDATE VARIABLE and InputPort(s)
2533
                # Executing or simulating Composition, so get input by updating input_ports
2534
                if (
1✔
2535
                    input is None
2536
                    and (
2537
                        (
2538
                            context.execution_phase is not ContextFlags.IDLE
2539
                            and any(p.path_afferents for p in self.input_ports)
2540
                        )
2541
                        or any(p.default_input is not None for p in self.input_ports)
2542
                    )
2543
                ):
2544
                    variable = self._update_input_ports(runtime_port_params[INPUT_PORT_PARAMS], context)
1✔
2545

2546
                else:
2547
                    # Direct call to execute Mechanism with specified input, so assign input to Mechanism's input_ports
2548
                    if context.source & ContextFlags.COMMAND_LINE:
1✔
2549
                        context.execution_phase = ContextFlags.PROCESSING
1✔
2550
                        if input is not None:
1✔
2551
                            input = convert_all_elements_to_np_array(input)
1✔
2552

2553
                    # No input was specified, so use Mechanism's default variable
2554
                    if input is None:
1✔
2555
                        input = copy_parameter_value(self.defaults.variable)
1✔
2556
                    #     FIX:  this input value is sent to input CIMs when compositions are nested
2557
                    #           variable should be based on afferent projections
2558
                    variable = self._get_variable_from_input(input, context)
1✔
2559

2560
                self.parameters.variable._set(variable, context=context)
1✔
2561

2562
                # UPDATE PARAMETERPORT(S)
2563
                self._update_parameter_ports(runtime_port_params[PARAMETER_PORT_PARAMS], context)
1✔
2564

2565
                # EXECUTE MECHANISM BY CALLING SUBCLASS _execute method AND ASSIGN RESULT TO self.value
2566

2567
                # IMPLEMENTATION NOTE: use value as buffer variable until it has been fully processed
2568
                #                      to avoid multiple calls to (and potential log entries for) self.value property
2569

2570
                value = self._execute(variable=variable,
1✔
2571
                                      runtime_params=runtime_params,
2572
                                      context=context)
2573

2574
                # IMPLEMENTATION NOTE:  THIS IS HERE BECAUSE IF return_value IS A LIST, AND THE LENGTH OF ALL OF ITS
2575
                #                       ELEMENTS ALONG ALL DIMENSIONS ARE EQUAL (E.G., A 2X2 MATRIX PAIRED WITH AN
2576
                #                       ARRAY OF LENGTH 2), np.array (AS WELL AS np.atleast_2d) GENERATES A ValueError
2577
                if (isinstance(value, list) and
1!
2578
                    (all(isinstance(item, np.ndarray) for item in value) and
2579
                        all(
2580
                                all(item.shape[i]==value[0].shape[0]
2581
                                    for i in range(len(item.shape)))
2582
                                for item in value))):
2583
                    pass
×
2584
                else:
2585
                    converted_to_2d = convert_to_np_array(value, dimension=2)
1✔
2586
                    # If return_value is a list of heterogenous elements, return as is
2587
                    #     (satisfies requirement that return_value be an array of possibly multidimensional values)
2588
                    if converted_to_2d.dtype == object:
1✔
2589
                        pass
1✔
2590
                    # Otherwise, return value converted to 2d np.array
2591
                    else:
2592
                        # return converted_to_2d
2593
                        value = converted_to_2d
1✔
2594

2595
                self.parameters.value._set(value, context=context)
1✔
2596

2597
            # UPDATE OUTPUTPORT(S)
2598
            self._update_output_ports(runtime_port_params[OUTPUT_PORT_PARAMS], context)
1✔
2599

2600
            # MANAGE MAX_EXECUTIONS_BEFORE_FINISHED AND DETERMINE WHETHER TO BREAK
2601
            max_executions = self.parameters.max_executions_before_finished._get(context)
1✔
2602
            num_executions = np.asarray(self.parameters.num_executions_before_finished._get(context) + 1)
1✔
2603

2604
            self.parameters.num_executions_before_finished._set(num_executions, override=True, context=context)
1✔
2605

2606
            if num_executions >= max_executions:
1✔
2607
                self.parameters.is_finished_flag._set(True, context)
1✔
2608
                warnings.warn(f"Maximum number of executions ({max_executions}) reached for {self.name}.")
1✔
2609
                break
1✔
2610

2611
            if self.is_finished(context):
1✔
2612
                self.parameters.is_finished_flag._set(True, context)
1✔
2613
                break
1✔
2614

2615
            self.parameters.is_finished_flag._set(False, context)
1✔
2616
            if not self.parameters.execute_until_finished._get(context):
1✔
2617
                break
1✔
2618

2619
        # REPORT EXECUTION
2620

2621
        # Generate report for Mechanism if it is:
2622
        #   - executed on its own (i.e., from the command line, not in a Composition)
2623
        #   - or in a Composition while that is executing and has passed it an report_num
2624
        #     (the latter excludes CIMs [not reported] and controllers [reporting handled directly]
2625
        if ((context.source == ContextFlags.COMMAND_LINE and not context.composition) or
1✔
2626
                (context.execution_phase & (ContextFlags.PROCESSING | ContextFlags.LEARNING)
2627
                 and report_num is not None)):
2628

2629
            from psyneulink.core.compositions.report import Report, ReportOutput, ReportParams, MECHANISM_REPORT
1✔
2630
            # Use any report_output and report_params options passed to execute from command line;
2631
            # otherwise try to get from Mechanism's reportOutputPref
2632
            report_output = report_output or next((pref for pref in convert_to_list(self.prefs.reportOutputPref)
1✔
2633
                                                   if isinstance(pref, ReportOutput)), ReportOutput.OFF)
2634
            report_params = report_params or next((pref for pref in convert_to_list(self.prefs.reportOutputPref)
1✔
2635
                                                   if isinstance(pref, ReportParams)), ReportParams.OFF)
2636
            with Report(self,
1✔
2637
                        report_output=report_output,
2638
                        report_params=report_params,
2639
                        context=context) as report:
2640
                report(self,
1✔
2641
                       MECHANISM_REPORT,
2642
                       report_num=report_num,
2643
                       scheduler=None,
2644
                       content='node',
2645
                       context=context,
2646
                       node=self)
2647

2648
        # return copy on external call so users can store it directly
2649
        # without it changing
2650
        if context.source is ContextFlags.COMMAND_LINE:
1✔
2651
            value = copy_parameter_value(value)
1✔
2652
        return value
1✔
2653

2654
    def _get_variable_from_input(self, input, context=None):
1✔
2655
        """Return array of results from each InputPort function executed with corresponding input item as its variable
2656
        This is called when Mechanism is executed on its own (e.g., during init or from the command line).
2657
        It:
2658
        - bypasses call to Port._update(), thus ignoring any afferent Projections assigned to the Mechanism;
2659
        - assigns each item of **input** to variable of corresponding InputPort;
2660
        - executes function of each InputPort using corresponding item of input as its variable;
2661
        - returns array of values generated by execution of each InputPort function.
2662
        """
2663

2664
        # The following is needed to accomodate lists, empty nd arrays, scalars, and string values
2665
        np_array = convert_to_np_array(input)
1✔
2666
        if (np_array.ndim and len(np_array)) or is_numeric(input):
1✔
2667
            input = convert_to_np_array(input, dimension=2)
1✔
2668
            num_inputs = np.size(input, 0)
1✔
2669
        else:
2670
            num_inputs = 0
1✔
2671
        num_input_ports = len(self.input_ports)
1✔
2672
        if num_inputs != num_input_ports:
1!
2673
            # Check if inputs are of different lengths (indicated by dtype == np.dtype('O'))
UNCOV
2674
            num_inputs = np.size(input)
×
UNCOV
2675
            if isinstance(input, np.ndarray) and input.dtype is np.dtype('O') and num_inputs == num_input_ports:
×
2676
                # Reduce input back down to sequence of arrays (to remove extra dim added by atleast_2d above)
2677
                input = np.squeeze(input)
×
2678
            else:
UNCOV
2679
                num_inputs = np.size(input, 0)  # revert num_inputs to its previous value, when printing the error
×
2680
                raise MechanismError(f"Number of inputs ({num_inputs}) to {self.name} does not match "
2681
                                     f"its number of input_ports ({num_input_ports}).")
2682
        for input_item, input_port in zip(input, self.input_ports):
1✔
2683
            if input_port.default_input_shape.size == np.array(input_item).size:
1✔
2684
                from psyneulink.core.compositions.composition import RunError
1✔
2685
                # Assign input_item as input_port.variable
2686
                input_port.parameters.variable._set(np.atleast_2d(input_item), context)
1✔
2687

2688
                # Call input_port._execute with newly assigned variable and assign result to input_port.value
2689
                base_error_msg = f"Input to '{self.name}' ({input_item}) is incompatible " \
1✔
2690
                                 f"with its corresponding {InputPort.__name__} ({input_port.full_name})"
2691
                variable = input_port.parameters.variable.get(context)
1✔
2692
                try:
1✔
2693
                    value = input_port._execute(variable, context)
1✔
2694
                except (RunError, TypeError) as error:
1✔
2695
                    raise MechanismError(f"{base_error_msg}: '{error.args[0]}.'")
2696
                except:
×
2697
                    raise MechanismError(f"{base_error_msg}.")
2698
                else:
2699
                    input_port.parameters.value._set(value, context)
1✔
2700
            else:
2701
                raise MechanismError(f"Shape ({input_item.shape}) of input ({input_item}) does not match "
2702
                                     f"required shape ({input_port.default_input_shape.shape}) for input "
2703
                                     f"to {InputPort.__name__} {repr(input_port.name)} of {self.name}.")
2704

2705
        # Return values of input_ports for use as variable of Mechanism
2706
        return convert_to_np_array(self.get_input_values(context))
1✔
2707

2708
    def _update_input_ports(self, runtime_input_port_params=None, context=None):
1✔
2709
        """Update value for each InputPort in self.input_ports:
2710

2711
        Call execute method for all (MappingProjection) Projections in Port.path_afferents
2712
        Aggregate results (using InputPort execute method)
2713
        Update InputPort.value
2714

2715
        """
2716

2717
        for i in range(len(self.input_ports)):
1✔
2718
            port= self.input_ports[i]
1✔
2719
            port._update(params=runtime_input_port_params,
1✔
2720
                         context=context)
2721
        return convert_to_np_array(self.get_input_values(context))
1✔
2722

2723
    def _update_parameter_ports(self, runtime_parameter_port_params=None, context=None):
1✔
2724

2725
        for port in self._parameter_ports:
1✔
2726
            port._update(params=runtime_parameter_port_params,
1✔
2727
                         context=context)
2728

2729
    def _get_parameter_port_deferred_init_control_specs(self):
1✔
2730
        # FIX: 9/14/19 - THIS ASSUMES THAT ONLY CONTROLPROJECTIONS RELEVANT TO COMPOSITION ARE in DEFERRED INIT;
2731
        #                BUT WHAT IF NODE SPECIFIED CONTROL BY AN EXISTING CONTROLMECHANISM NOT IN A COMPOSITION
2732
        #                THAT WAS THEN ADDED;  COMPOSITION WOULD STILL NEED TO KNOW ABOUT IT TO ACTIVATE THE CTLPROJ
2733
        ctl_specs = []
1✔
2734
        for parameter_port in self._parameter_ports:
1✔
2735
            for proj in parameter_port.mod_afferents:
1✔
2736
                if proj.initialization_status == ContextFlags.DEFERRED_INIT:
1✔
2737
                    try:
1✔
2738
                        proj_control_signal_specs = proj._init_args['control_signal_params'] or {}
1✔
2739
                    except (KeyError, TypeError):
×
2740
                        proj_control_signal_specs = {}
×
2741
                    proj_control_signal_specs.update({PROJECTIONS: [proj]})
1✔
2742
                    ctl_specs.append(proj_control_signal_specs)
1✔
2743
        return ctl_specs
1✔
2744

2745
    def _update_output_ports(self, runtime_output_port_params=None, context=None):
1✔
2746
        """Execute function for each OutputPort and assign result of each to corresponding item of self.output_values
2747

2748
        owner_value arg can be used to override existing (or absent) value of owner as variable for OutputPorts
2749
        and assign a specified (set of) value(s).
2750

2751
        """
2752
        for i in range(len(self.output_ports)):
1✔
2753
            port = self.output_ports[i]
1✔
2754
            port._update(params=runtime_output_port_params,
1✔
2755
                         context=context)
2756

2757
    def initialize(self, value, context=None):
1✔
2758
        """Assign an initial value to the Mechanism's `value <Mechanism_Base.value>` attribute and update its
2759
        `OutputPorts <Mechanism_OutputPorts>`.
2760

2761
        Arguments
2762
        ---------
2763

2764
        value : List[value] or 1d ndarray
2765
            value used to initialize the first item of the Mechanism's `value <Mechanism_Base.value>` attribute.
2766

2767
        """
2768
        if self.paramValidationPref:
1!
2769
            if not iscompatible(value, self.defaults.value):
1✔
2770
                raise MechanismError("Initialization value ({}) is not compatiable with value of {}".
2771
                                     format(value, append_type_to_name(self)))
2772
        self.parameters.value.set(np.atleast_1d(value), context, override=True)
1✔
2773
        self._update_output_ports(context=context)
1✔
2774

2775
    def _parse_runtime_params(self, runtime_params, context):
1✔
2776
        """Move Port param specifications and nested Project-specific specifications into sub-dicts.
2777

2778
        Move any specifications for Port types into type-specific sub-dicts
2779
        For each type-specific sub-dict,
2780
          - move any specifications for individual Ports into PORT_SPECIFIC sub-dict
2781
          - move any specifications for Projection types into type-specific subdicts
2782
          - move any specifications for individual Projections into PORT_SPECIFIC sub-dict
2783

2784
        Returns
2785
        -------
2786

2787
        dict : {port_param_keyword : Port type-specific dict}
2788
            dict containing three sub-dicts, one for each of the three Port types, of the following form:
2789

2790
            {'<Port type>_PARAMS': parameter of Port type or its function : value,
2791
                                   'PORT_SPECIFIC_PARAMS' : {<Port or Port name> : {parameter : value}}
2792
                                   '<Projection type>_PARAMS' : {parameter of Projection type or its function : value},
2793
                                   'PROJECTION_SPECIFIC_PARAMS' : {<Projection or name> : {parameter : value}}}
2794

2795
        """
2796

2797
        from psyneulink.core.components.projections.projection import projection_param_keywords
1✔
2798

2799
        def move_item_specific_params_to_specific_sub_dict(outer_dict,
1✔
2800
                                                           dest_dict,
2801
                                                           sub_dict_names,
2802
                                                           item_list,
2803
                                                           specific_dict_name):
2804
            """Move any specifications for individual Ports or Projections into a consolidated SPECIFIC sub-dict
2805

2806
            Arguments
2807
            ---------
2808
            outer_dict : dict
2809
                outer-most dict to be searched;
2810
                runtime_params for port params; a port_params_dict for projection params
2811
            dest_dict : dict
2812
                dict where <COMPONENT>_SPECIFIC_PARAMS will be created; (always a Port type-specific dict)
2813
            sub_dict_names : list(str)
2814
                port_param_keywords or projection_param_keywords()
2815
            item_list : ContentAddressableList
2816
                attribute with list of items to search for specific item being specified;
2817
                self.<port_type> for port param, self.afferents for for projection params
2818
            specific_dict_name : str
2819
                <COMPONENT>_SPECIFIC_PARAMS:  PORT_SPECIFIC_PARAMS or PROJECTION_SPECIFIC_PARAMS
2820

2821
            """
2822
            for key in outer_dict.copy():
1✔
2823
                # Recursively check Port or Projection type-specific sub-dicts for entries to be moved; even though
2824
                #    the search is recursive, the move is always to the <COMPONENT>_SPECIFIC_PARAMS dict in dest_dict
2825
                if key in sub_dict_names:
1✔
2826
                    move_item_specific_params_to_specific_sub_dict(outer_dict[key],
1✔
2827
                                                                   dest_dict,
2828
                                                                   sub_dict_names,
2829
                                                                   item_list,
2830
                                                                   specific_dict_name)
2831
                    continue
1✔
2832

2833
                # Skip if entry is not a paramater specification dict;
2834
                #  this is so that a key being used as the name of a parameter itself to be specified, is not treated
2835
                #  below as a specification for the corresponding ParameterPort (which has the key as its name)
2836
                if not isinstance(outer_dict[key], dict):
1✔
2837
                    continue
1✔
2838

2839
                # Reference can be the Port or Projection itself...
2840
                elif key in item_list:
1✔
2841
                    item = key
1✔
2842
                # or the Port or Projection's name
2843
                elif key in item_list.names:
1!
2844
                    item = item_list[item]
×
2845
                else:
2846
                    continue
×
2847
                # Move param specification dict for item to entry with same key in <COMPONENT>_SPECIFIC_PARAMS dict
2848
                item_specific_dict = {key : outer_dict.pop(key)}
1✔
2849
                item_specific_dict = {
1✔
2850
                    k: convert_all_elements_to_np_array(v) if is_numeric(v) else v
2851
                    for (k, v) in item_specific_dict.items()
2852
                }
2853

2854
                if specific_dict_name in dest_dict:
1✔
2855
                    dest_dict[specific_dict_name].update(item_specific_dict)
1✔
2856
                else:
2857
                    dest_dict[specific_dict_name] = defaultdict(lambda:{}, item_specific_dict)
1✔
2858

2859
        port_param_dicts = {INPUT_PORT_PARAMS: {},
1✔
2860
                            PARAMETER_PORT_PARAMS: {},
2861
                            OUTPUT_PORT_PARAMS: {}}
2862

2863
        if runtime_params:
1✔
2864

2865
            for port_type in self.portListAttr:
1✔
2866
                port_param_dict_name = port_type.paramsType
1✔
2867
                ports_attr = getattr(self, self.portListAttr[port_type])
1✔
2868

2869
                # Create port_param_dict if it doesn't yet exist
2870
                if port_param_dict_name not in runtime_params:
1✔
2871
                    runtime_params[port_param_dict_name] = defaultdict(lambda:{})
1✔
2872

2873
                # Move any specifications of individual Ports of this type to PORT_SPECIFIC_PARAMS dict
2874
                move_item_specific_params_to_specific_sub_dict(outer_dict = runtime_params,
1✔
2875
                                                               dest_dict = runtime_params[port_param_dict_name],
2876
                                                               sub_dict_names = [port_param_dict_name],
2877
                                                               item_list = ports_attr,
2878
                                                               specific_dict_name = PORT_SPECIFIC_PARAMS)
2879

2880
                # Move any specifications of individual Projections for this Port type to PROJECTION_SPECIFIC_PARAMS
2881
                move_item_specific_params_to_specific_sub_dict(outer_dict = runtime_params[port_param_dict_name],
1✔
2882
                                                               dest_dict = runtime_params[port_param_dict_name],
2883
                                                               sub_dict_names = projection_param_keywords(),
2884
                                                               item_list = self.afferents,
2885
                                                               specific_dict_name = PROJECTION_SPECIFIC_PARAMS)
2886

2887
                # Move the port_specific_param dicts to port_param_dicts for return
2888
                port_param_dicts[port_param_dict_name] = defaultdict(lambda:{},
1✔
2889
                                                                     runtime_params.pop(port_param_dict_name, {}))
2890

2891
        return port_param_dicts
1✔
2892

2893
    def _get_param_ids(self):
1✔
2894
        if len(self._parameter_ports) == 0:
1✔
2895
            return super()._get_param_ids()
1✔
2896

2897
        # FIXME: parameter ports should be part of generated params
2898
        return ["_parameter_ports"] + super()._get_param_ids()
1✔
2899

2900
    def _get_param_struct_type(self, ctx):
1✔
2901
        mech_param_struct = ctx.get_param_struct_type(super())
1✔
2902
        if len(self._parameter_ports) == 0:
1✔
2903
            return mech_param_struct
1✔
2904

2905
        ports_params = (ctx.get_param_struct_type(s) for s in self._parameter_ports)
1✔
2906
        ports_param_struct = pnlvm.ir.LiteralStructType(ports_params)
1✔
2907
        return pnlvm.ir.LiteralStructType((ports_param_struct, *mech_param_struct))
1✔
2908

2909
    def _get_param_initializer(self, context):
1✔
2910
        mech_param_init = super()._get_param_initializer(context)
1✔
2911
        if len(self._parameter_ports) == 0:
1✔
2912
            return mech_param_init
1✔
2913

2914
        port_param_init = tuple(s._get_param_initializer(context) for s in self._parameter_ports)
1✔
2915
        return (port_param_init, *mech_param_init)
1✔
2916

2917
    def _get_state_ids(self):
1✔
2918
        if len(self._parameter_ports) == 0:
1✔
2919
            return super()._get_state_ids()
1✔
2920

2921
        # FIXME: parameter ports should be part of generated state
2922
        return ["_parameter_ports"] + super()._get_state_ids()
1✔
2923

2924
    def _get_state_struct_type(self, ctx):
1✔
2925
        mech_state_struct = ctx.get_state_struct_type(super())
1✔
2926
        if len(self._parameter_ports) == 0:
1✔
2927
            return mech_state_struct
1✔
2928

2929
        ports_state = (ctx.get_state_struct_type(s) for s in self._parameter_ports)
1✔
2930
        ports_state_struct = pnlvm.ir.LiteralStructType(ports_state)
1✔
2931
        return pnlvm.ir.LiteralStructType((ports_state_struct, *mech_state_struct))
1✔
2932

2933
    def _get_state_initializer(self, context):
1✔
2934
        mech_state_init = super()._get_state_initializer(context)
1✔
2935
        if len(self._parameter_ports) == 0:
1✔
2936
            return mech_state_init
1✔
2937

2938
        port_state_init = tuple(s._get_state_initializer(context) for s in self._parameter_ports)
1✔
2939
        return (port_state_init, *mech_state_init)
1✔
2940

2941
    def _get_output_struct_type(self, ctx):
1✔
2942
        output_type_list = (ctx.get_output_struct_type(port) for port in self.output_ports)
1✔
2943
        return pnlvm.ir.LiteralStructType(output_type_list)
1✔
2944

2945
    def _get_input_struct_type(self, ctx):
1✔
2946
        # Extract the non-modulation portion of InputPort input struct
2947
        def _get_data_part_of_input_struct(p):
1✔
2948
            struct_ty = ctx.get_input_struct_type(p)
1✔
2949
            return struct_ty.elements[0] if len(p.mod_afferents) > 0 else struct_ty
1✔
2950

2951
        input_type_list = [_get_data_part_of_input_struct(port) for port in self.input_ports]
1✔
2952

2953

2954
        # Get modulatory inputs
2955
        if len(self.mod_afferents) > 0:
1✔
2956
            mod_input_type_list = (ctx.get_output_struct_type(proj) for proj in self.mod_afferents)
1✔
2957
            input_type_list.append(pnlvm.ir.LiteralStructType(mod_input_type_list))
1✔
2958
        # Prefer an array type if there is no modulation.
2959
        # This is used to keep ctypes inputs as arrays instead of structs.
2960
        elif all(t == input_type_list[0] for t in input_type_list):
1✔
2961
            return pnlvm.ir.ArrayType(input_type_list[0], len(input_type_list))
1✔
2962

2963
        return pnlvm.ir.LiteralStructType(input_type_list)
1✔
2964

2965
    def _gen_llvm_ports(self, ctx, builder, ports, group,
1✔
2966
                        get_output_ptr, get_input_data_ptr,
2967
                        mech_params, mech_state, mech_input):
2968
        group_ports = getattr(self, group)
1✔
2969
        ports_param, ports_state = ctx.get_param_or_state_ptr(builder, self, group, param_struct_ptr=mech_params, state_struct_ptr=mech_state, history=None)
1✔
2970

2971
        mod_afferents = self.mod_afferents
1✔
2972
        for i, port in enumerate(ports):
1✔
2973
            p_function = ctx.import_llvm_function(port)
1✔
2974

2975
            # Find input and output locations
2976
            builder, p_input_data = get_input_data_ptr(builder, i)
1✔
2977
            builder, p_output = get_output_ptr(builder, i)
1✔
2978

2979
            if len(port.mod_afferents) == 0:
1✔
2980
                # There's no modulation so the only input is data
2981
                if p_input_data.type == p_function.args[2].type:
1✔
2982
                    p_input = p_input_data
1✔
2983
                else:
2984
                    assert port in self.output_ports
1✔
2985
                    # Ports always take at least 2d input. However, parsing
2986
                    # the function result can result in 1d structure or scalar
2987
                    # Casting the pointer is LLVM way of adding dimensions
2988
                    array_1d = pnlvm.ir.ArrayType(p_input_data.type.pointee, 1)
1✔
2989
                    assert array_1d == p_function.args[2].type.pointee, \
1✔
2990
                        "{} vs. {}".format(p_function.args[2].type.pointee, p_input_data.type.pointee)
2991

2992
                    # restrict shape matching to casting 1d values to 2d arrays
2993
                    # for Control/Gating signals
2994
                    assert len(p_function.args[2].type.pointee) == 1
1✔
2995
                    assert str(port).startswith("(ControlSignal") or str(port).startswith("(GatingSignal")
1✔
2996
                    p_input = builder.bitcast(p_input_data, p_function.args[2].type)
1✔
2997

2998
            else:
2999
                # Port input structure is: (data, [modulations]),
3000
                p_input = builder.alloca(p_function.args[2].type.pointee,
1✔
3001
                                         name=group + "_port_" + str(i) + "_input")
3002
                # Fill in the data.
3003
                # FIXME: We can potentially hit the same dimensionality issue
3004
                #        as above, but it's more difficult to manifest and
3005
                #        not even new tests that modulate output ports hit it.
3006
                p_data = builder.gep(p_input, [ctx.int32_ty(0), ctx.int32_ty(0)])
1✔
3007
                builder.store(builder.load(p_input_data), p_data)
1✔
3008

3009
            # Copy mod_afferent inputs
3010
            for idx, p_mod in enumerate(port.mod_afferents):
1✔
3011
                mech_mod_afferent_idx = mod_afferents.index(p_mod)
1✔
3012
                mod_in_ptr = builder.gep(mech_input, [ctx.int32_ty(0),
1✔
3013
                                                      ctx.int32_ty(len(self.input_ports)),
3014
                                                      ctx.int32_ty(mech_mod_afferent_idx)])
3015
                mod_out_ptr = builder.gep(p_input, [ctx.int32_ty(0), ctx.int32_ty(1 + idx)])
1✔
3016
                afferent_val = builder.load(mod_in_ptr)
1✔
3017
                builder.store(afferent_val, mod_out_ptr)
1✔
3018

3019
            port_idx = group_ports.index(port)
1✔
3020
            p_params = builder.gep(ports_param, [ctx.int32_ty(0),
1✔
3021
                                                 ctx.int32_ty(port_idx)])
3022
            p_state = builder.gep(ports_state, [ctx.int32_ty(0),
1✔
3023
                                                ctx.int32_ty(port_idx)])
3024

3025
            builder.call(p_function, [p_params, p_state, p_input, p_output])
1✔
3026

3027
        return builder
1✔
3028

3029
    def _gen_llvm_input_ports(self, ctx, builder,
1✔
3030
                              mech_params, mech_state, mech_input):
3031
        # Allocate temporary storage. We rely on the fact that series
3032
        # of InputPort results should match the main function input.
3033
        ip_output_list = []
1✔
3034
        for port in self.input_ports:
1✔
3035
            ip_function = ctx.import_llvm_function(port)
1✔
3036
            ip_output_list.append(ip_function.args[3].type.pointee)
1✔
3037

3038
        # Check if all elements are the same. Function input will be array type if yes.
3039
        if len(set(ip_output_list)) == 1:
1✔
3040
            ip_output_type = pnlvm.ir.ArrayType(ip_output_list[0], len(ip_output_list))
1✔
3041
        else:
3042
            ip_output_type = pnlvm.ir.LiteralStructType(ip_output_list)
1✔
3043

3044
        ip_output = builder.alloca(ip_output_type, name="input_ports_out")
1✔
3045

3046
        def _get_output_ptr(b, i):
1✔
3047
            ptr = b.gep(ip_output, [ctx.int32_ty(0), ctx.int32_ty(i)])
1✔
3048
            return b, ptr
1✔
3049

3050
        def _get_input_data_ptr(b, i):
1✔
3051
            ptr = builder.gep(mech_input, [ctx.int32_ty(0), ctx.int32_ty(i)])
1✔
3052
            return b, ptr
1✔
3053

3054
        builder = self._gen_llvm_ports(ctx, builder, self.input_ports, "input_ports",
1✔
3055
                                       _get_output_ptr, _get_input_data_ptr,
3056
                                       mech_params, mech_state, mech_input)
3057

3058
        return ip_output, builder
1✔
3059

3060
    def _gen_llvm_param_ports_for_obj(self, obj, params_in, ctx, builder,
1✔
3061
                                      mech_params, mech_state, mech_input):
3062
        # This should be faster than 'obj._get_compilation_params'
3063
        compilation_params = (getattr(obj.parameters, p_id, None) for p_id in obj.llvm_param_ids)
1✔
3064
        # Filter out param ports without corresponding param for this function
3065
        param_ports = [self._parameter_ports[param] for param in compilation_params if param in self._parameter_ports]
1✔
3066

3067
        # Exit early if there's no modulation. It's difficult for compiler
3068
        # to replace pointer arguments to functions with the source location.
3069
        if len(param_ports) == 0:
1✔
3070
            return params_in, builder
1✔
3071

3072
        # Allocate a shadow structure to overload user supplied parameters
3073
        params_out = builder.alloca(params_in.type.pointee, name="modulated_parameters")
1✔
3074
        if len(param_ports) != len(obj.llvm_param_ids):
1✔
3075
            builder = pnlvm.helpers.memcpy(builder, params_out, params_in)
1✔
3076

3077
        def _get_output_ptr(b, i):
1✔
3078
            ptr = ctx.get_param_or_state_ptr(b, obj, param_ports[i].source, param_struct_ptr=params_out)
1✔
3079
            return b, ptr
1✔
3080

3081
        def _get_input_data_ptr(b, i):
1✔
3082
            ptr = ctx.get_param_or_state_ptr(b, obj, param_ports[i].source, param_struct_ptr=params_in)
1✔
3083
            return b, ptr
1✔
3084

3085
        builder = self._gen_llvm_ports(ctx, builder, param_ports, "_parameter_ports",
1✔
3086
                                       _get_output_ptr, _get_input_data_ptr,
3087
                                       mech_params, mech_state, mech_input)
3088
        return params_out, builder
1✔
3089

3090
    def _gen_llvm_output_port_parse_variable(self, ctx, builder,
1✔
3091
                                             mech_params, mech_state, value, port):
3092
        port_spec = port._variable_spec
1✔
3093
        if port_spec == OWNER_VALUE:
1✔
3094
            return value
1✔
3095
        elif port_spec == OWNER_EXECUTION_COUNT:
1✔
3096
            # Convert execution count to (num_executions, TimeScale.LIFE)
3097
            # The difference in Python PNL is that the former counts across
3098
            # all contexts. This is not possible in compiled code, thus
3099
            # the two are identical.
3100
            port_spec = ("num_executions", TimeScale.LIFE)
1✔
3101

3102
        try:
1✔
3103
            name = port_spec[0]
1✔
3104
            ids = (x() if callable(x) else getattr(x, 'value', x) for x in port_spec[1:])
1✔
3105
        except TypeError as e:
×
3106
            # TypeError means we can't index.
3107
            # Convert this to assertion failure below
3108
            data = None
×
3109
        else:
3110
            #TODO: support more spec options
3111
            if name == OWNER_VALUE:
1✔
3112
                data = value
1✔
3113
            elif name in self.llvm_state_ids:
1!
3114
                data = ctx.get_param_or_state_ptr(builder, self, name, state_struct_ptr=mech_state)
1✔
3115
            else:
3116
                data = None
×
3117

3118
        assert data is not None, "Unsupported OutputPort spec: {} ({})".format(port_spec, value.type)
1✔
3119

3120
        parsed = builder.gep(data, [ctx.int32_ty(0), *(ctx.int32_ty(i) for i in ids)])
1✔
3121
        # "num_executions" are kept as int64, we need to convert the value to float first
3122
        # port inputs are also expected to be 1d arrays
3123
        if name == "num_executions":
1✔
3124
            count = builder.load(parsed)
1✔
3125
            count_fp = builder.uitofp(count, ctx.float_ty)
1✔
3126
            parsed = builder.alloca(pnlvm.ir.ArrayType(count_fp.type, 1))
1✔
3127
            ptr = builder.gep(parsed, [ctx.int32_ty(0), ctx.int32_ty(0)])
1✔
3128
            builder.store(count_fp, ptr)
1✔
3129

3130
        return parsed
1✔
3131

3132
    def _gen_llvm_output_ports(self, ctx, builder, value,
1✔
3133
                               mech_params, mech_state, mech_in, mech_out):
3134
        def _get_output_ptr(b, i):
1✔
3135
            ptr = b.gep(mech_out, [ctx.int32_ty(0), ctx.int32_ty(i)])
1✔
3136
            return b, ptr
1✔
3137

3138
        def _get_input_data_ptr(b, i):
1✔
3139
            ptr = self._gen_llvm_output_port_parse_variable(ctx, b,
1✔
3140
               mech_params, mech_state, value, self.output_ports[i])
3141
            return b, ptr
1✔
3142

3143
        builder = self._gen_llvm_ports(ctx, builder, self.output_ports, "output_ports",
1✔
3144
                                       _get_output_ptr, _get_input_data_ptr,
3145
                                       mech_params, mech_state, mech_in)
3146
        return builder
1✔
3147

3148
    def _gen_llvm_invoke_function(self, ctx, builder, function, f_params, f_state,
1✔
3149
                                  variable, out, *, tags:frozenset):
3150

3151
        fun = ctx.import_llvm_function(function, tags=tags)
1✔
3152
        if out is None:
1✔
3153
            f_out = builder.alloca(fun.args[3].type.pointee, name=function.name + "_output")
1✔
3154
        else:
3155
            f_out = out
1✔
3156

3157
        builder.call(fun, [f_params, f_state, variable, f_out])
1✔
3158

3159
        return f_out, builder
1✔
3160

3161
    def _gen_llvm_is_finished_cond(self, ctx, builder, m_base_params, m_state, m_inputs):
1✔
3162
        return ctx.bool_ty(1)
1✔
3163

3164
    def _gen_llvm_mechanism_functions(self, ctx, builder, m_base_params, m_params, m_state, m_in,
1✔
3165
                                      m_val, ip_output, *, tags:frozenset):
3166

3167
        # Default mechanism runs only the main function
3168
        f_base_params, f_state = ctx.get_param_or_state_ptr(builder, self, "function", param_struct_ptr=m_base_params, state_struct_ptr=m_state)
1✔
3169
        f_params, builder = self._gen_llvm_param_ports_for_obj(
1✔
3170
                self.function, f_base_params, ctx, builder, m_base_params, m_state, m_in)
3171

3172
        return self._gen_llvm_invoke_function(ctx, builder, self.function,
1✔
3173
                                              f_params, f_state, ip_output,
3174
                                              m_val, tags=tags)
3175

3176
    def _gen_llvm_function_internal(self, ctx, builder, m_params, m_state, arg_in,
1✔
3177
                                    arg_out, m_base_params, *, tags:frozenset):
3178

3179
        ip_output, builder = self._gen_llvm_input_ports(ctx, builder,
1✔
3180
                                                        m_base_params, m_state, arg_in)
3181

3182
        # This will move history items around to make space for a new entry
3183
        mech_val_ptr = ctx.get_state_space(builder, self, m_state, VALUE)
1✔
3184

3185
        value, builder = self._gen_llvm_mechanism_functions(ctx, builder, m_base_params,
1✔
3186
                                                            m_params, m_state, arg_in,
3187
                                                            mech_val_ptr,
3188
                                                            ip_output, tags=tags)
3189

3190

3191
        if mech_val_ptr.type.pointee == value.type.pointee:
1✔
3192
            assert value is mech_val_ptr
1✔
3193
        else:
3194
            # FIXME: Does this need some sort of parsing?
3195
            warnings.warn("Shape mismatch: function result does not match mechanism value param: {} vs. {}".format(
1✔
3196
                          value.type.pointee, mech_val_ptr.type.pointee),
3197
                          pnlvm.PNLCompilerWarning)
3198

3199
        # Update  num_executions parameter
3200
        num_executions_ptr = ctx.get_param_or_state_ptr(builder, self, "num_executions", state_struct_ptr=m_state)
1✔
3201
        for scale in TimeScale:
1✔
3202
            assert scale.value < len(num_executions_ptr.type.pointee)
1✔
3203
            num_exec_time_ptr = builder.gep(num_executions_ptr,
1✔
3204
                                            [ctx.int32_ty(0), ctx.int32_ty(scale.value)],
3205
                                            name="num_executions_{}_ptr".format(scale))
3206
            new_val = builder.load(num_exec_time_ptr)
1✔
3207
            new_val = builder.add(new_val, new_val.type(1))
1✔
3208
            builder.store(new_val, num_exec_time_ptr)
1✔
3209

3210
        # Run output ports after updating the mech state (num_executions and value)
3211
        builder = self._gen_llvm_output_ports(ctx, builder, value, m_base_params, m_state, arg_in, arg_out)
1✔
3212

3213
        # is_finished should be checked after output ports ran
3214
        is_finished_f = ctx.import_llvm_function(self, tags=tags.union({"is_finished"}))
1✔
3215
        is_finished_cond = builder.call(is_finished_f, [m_base_params, m_state, arg_in, arg_out])
1✔
3216
        return builder, is_finished_cond
1✔
3217

3218
    def _gen_llvm_function_reset(self, ctx, builder, m_base_params, m_state, m_arg_in, m_arg_out, *, tags:frozenset):
1✔
3219
        assert "reset" in tags
1✔
3220

3221
        reinit_func = ctx.import_llvm_function(self.function, tags=tags)
1✔
3222
        reinit_in = builder.alloca(reinit_func.args[2].type.pointee, name="reinit_in")
1✔
3223
        reinit_out = builder.alloca(reinit_func.args[3].type.pointee, name="reinit_out")
1✔
3224

3225
        reinit_base_params, reinit_state = ctx.get_param_or_state_ptr(builder,
1✔
3226
                                                                      self,
3227
                                                                      "function",
3228
                                                                      param_struct_ptr=m_base_params,
3229
                                                                      state_struct_ptr=m_state)
3230
        reinit_params, builder = self._gen_llvm_param_ports_for_obj(self.function,
1✔
3231
                                                                    reinit_base_params,
3232
                                                                    ctx,
3233
                                                                    builder,
3234
                                                                    m_base_params,
3235
                                                                    m_state,
3236
                                                                    m_arg_in)
3237

3238
        builder.call(reinit_func, [reinit_params, reinit_state, reinit_in, reinit_out])
1✔
3239

3240
        if hasattr(self, "integrator_function") and getattr(self, "integrator_mode", False):
1✔
3241
            reinit_func = ctx.import_llvm_function(self.integrator_function, tags=tags)
1✔
3242
            reinit_in = builder.alloca(reinit_func.args[2].type.pointee, name="integrator_reinit_in")
1✔
3243
            reinit_out = builder.alloca(reinit_func.args[3].type.pointee, name="integrator_reinit_out")
1✔
3244

3245
            reinit_base_params, reinit_state = ctx.get_param_or_state_ptr(builder,
1✔
3246
                                                                          self,
3247
                                                                          "integrator_function",
3248
                                                                          param_struct_ptr=m_base_params,
3249
                                                                          state_struct_ptr=m_state)
3250
            reinit_params, builder = self._gen_llvm_param_ports_for_obj(self.integrator_function,
1✔
3251
                                                                        reinit_base_params,
3252
                                                                        ctx,
3253
                                                                        builder,
3254
                                                                        m_base_params,
3255
                                                                        m_state,
3256
                                                                        m_arg_in)
3257

3258
            builder.call(reinit_func, [reinit_params, reinit_state, reinit_in, reinit_out])
1✔
3259

3260
        # update output ports after getting the reinitialized value
3261
        builder = self._gen_llvm_output_ports(ctx, builder, reinit_out, m_base_params, m_state, m_arg_in, m_arg_out)
1✔
3262

3263
        return builder
1✔
3264

3265
    def _gen_llvm_function(self, *, extra_args=[], ctx:pnlvm.LLVMBuilderContext, tags:frozenset):
1✔
3266
        """
3267
        Overloaded main function LLVM generation method.
3268

3269
        Mechanisms need to support "is_finished" execution variant (used by scheduling conditions)
3270
        on top of the variants supported by Component.
3271
        """
3272

3273
        if "is_finished" in tags:
1✔
3274

3275
            # Keep all 4 standard arguments to ease invocation
3276
            args = [ctx.get_param_struct_type(self).as_pointer(),
1✔
3277
                    ctx.get_state_struct_type(self).as_pointer(),
3278
                    ctx.get_input_struct_type(self).as_pointer(),
3279
                    ctx.get_output_struct_type(self).as_pointer()]
3280

3281
            builder = ctx.create_llvm_function(args, self, return_type=ctx.bool_ty, tags=tags)
1✔
3282
            params, state, inputs = builder.function.args[:3]
1✔
3283
            finished = self._gen_llvm_is_finished_cond(ctx, builder, params, state, inputs)
1✔
3284
            builder.ret(finished)
1✔
3285
            return builder.function
1✔
3286

3287
        # Call parent "_gen_llvm_function". This handles standard variants like
3288
        # no tags, or the "reset" tag.
3289
        return super()._gen_llvm_function(extra_args=extra_args, ctx=ctx, tags=tags)
1✔
3290

3291
    def _gen_llvm_function_body(self, ctx, builder, base_params, state, arg_in, arg_out, *, tags:frozenset):
1✔
3292
        """
3293
        Overloaded LLVM code generation method.
3294

3295
        Implements main mechanisms loop (while not finished). Calls two other internal Mechanism functions;
3296
        'is_finished' to terminate the loop, and '_gen_llvm_function_internal' to generate body of the
3297
        loop (invocation of Ports and Functions).
3298
        """
3299

3300
        assert "reset" not in tags
1✔
3301
        assert "is_finished" not in tags
1✔
3302

3303
        params, builder = self._gen_llvm_param_ports_for_obj(
1✔
3304
                self, base_params, ctx, builder, base_params, state, arg_in)
3305

3306
        is_finished_flag_ptr = ctx.get_param_or_state_ptr(builder, self, "is_finished_flag", state_struct_ptr=state)
1✔
3307
        is_finished_count_ptr = ctx.get_param_or_state_ptr(builder, self, "num_executions_before_finished", state_struct_ptr=state)
1✔
3308
        is_finished_max_ptr = ctx.get_param_or_state_ptr(builder, self, "max_executions_before_finished", param_struct_ptr=params)
1✔
3309

3310
        # Reset the flag and counter
3311
        # FIXME: Use int for flag
3312
        current_flag = builder.load(is_finished_flag_ptr)
1✔
3313
        was_finished = builder.fcmp_ordered("==", current_flag, current_flag.type(1))
1✔
3314
        with builder.if_then(was_finished):
1✔
3315
            builder.store(is_finished_count_ptr.type.pointee(0),
1✔
3316
                          is_finished_count_ptr)
3317

3318
        # Enter the loop
3319
        loop_block = builder.append_basic_block(builder.basic_block.name + "_loop")
1✔
3320
        end_block = builder.append_basic_block(builder.basic_block.name + "_end")
1✔
3321
        builder.branch(loop_block)
1✔
3322
        builder.position_at_end(loop_block)
1✔
3323

3324
        # Get internal function. Use function call to get proper stack manipulation
3325
        # inside the body of the execution loop. We could use 'stacksave' and
3326
        # 'stackrestore', but not all LLVM targets support those ops.
3327
        args_t = [a.type for a in builder.function.args]
1✔
3328
        args_t[4:4] = [base_params.type]
1✔
3329
        internal_builder = ctx.create_llvm_function(args_t, self,
1✔
3330
                                                    name=builder.function.name + "_internal",
3331
                                                    return_type=ctx.bool_ty)
3332
        iparams, istate, iin, iout, ibase_params = internal_builder.function.args[:5]
1✔
3333
        internal_builder, is_finished = self._gen_llvm_function_internal(ctx, internal_builder,
1✔
3334
                                                                         iparams, istate, iin, iout,
3335
                                                                         ibase_params, tags=tags)
3336
        internal_builder.ret(is_finished)
1✔
3337

3338
        # Call Internal Function
3339
        internal_f = internal_builder.function
1✔
3340
        is_finished_cond = builder.call(internal_f, [params, state, arg_in, arg_out, base_params, *builder.function.args[4:]])
1✔
3341

3342
        #FIXME: Flag and count should be int instead of float
3343
        # Check if we reached maximum iteration count
3344
        is_finished_count = builder.load(is_finished_count_ptr)
1✔
3345
        is_finished_count = builder.fadd(is_finished_count,
1✔
3346
                                         is_finished_count.type(1))
3347
        builder.store(is_finished_count, is_finished_count_ptr)
1✔
3348
        is_finished_max = builder.load(is_finished_max_ptr)
1✔
3349
        max_reached = builder.fcmp_ordered(">=", is_finished_count,
1✔
3350
                                           is_finished_max)
3351

3352
        # Check if execute until finished mode is enabled
3353
        exec_until_fin_ptr = ctx.get_param_or_state_ptr(builder, self, "execute_until_finished", param_struct_ptr=params)
1✔
3354
        exec_until_fin = builder.load(exec_until_fin_ptr)
1✔
3355
        exec_until_off = builder.fcmp_ordered("==", exec_until_fin, exec_until_fin.type(0))
1✔
3356

3357
        # Combine conditions
3358
        is_finished = builder.or_(is_finished_cond, max_reached)
1✔
3359
        iter_end = builder.or_(is_finished, exec_until_off)
1✔
3360

3361
        # Check if in integrator mode
3362
        if hasattr(self, "integrator_mode"):
1✔
3363
            int_mode_ptr = ctx.get_param_or_state_ptr(builder, self, "integrator_mode", param_struct_ptr=params)
1✔
3364
            int_mode = builder.load(int_mode_ptr)
1✔
3365
            int_mode_off = builder.fcmp_ordered("==", int_mode, int_mode.type(0))
1✔
3366
            iter_end = builder.or_(iter_end, int_mode_off)
1✔
3367

3368
        with builder.if_then(iter_end):
1✔
3369
            new_flag = builder.uitofp(is_finished, current_flag.type)
1✔
3370
            builder.store(new_flag, is_finished_flag_ptr)
1✔
3371
            builder.branch(end_block)
1✔
3372

3373
        builder.branch(loop_block)
1✔
3374
        builder.position_at_end(end_block)
1✔
3375

3376
        return builder
1✔
3377

3378
    @beartype
1✔
3379
    def _show_structure(self,
1✔
3380
                        show_functions: bool = False,
3381
                        show_mech_function_params: bool = False,
3382
                        show_port_function_params: bool = False,
3383
                        show_values: bool = False,
3384
                        use_labels: bool = False,
3385
                        show_headers: bool = False,
3386
                        show_roles: bool = False,
3387
                        show_conditions: bool = False,
3388
                        composition=None,
3389
                        compact_cim: bool = False,
3390
                        condition: Optional[Condition] = None,
3391
                        node_border: str = "1",
3392
                        output_fmt: Literal['pdf', 'struct'] = 'pdf',
3393
                        context=None
3394
                        ):
3395
        """Generate a detailed display of a the structure of a Mechanism.
3396

3397
        .. note::
3398
           This method relies on `graphviz <http://www.graphviz.org>`_
3399
           python and system packages, which must be installed. The
3400
           python package comes standard with PsyNeuLink pip install,
3401
           but the system package must be installed separately. It can
3402
           be downloaded at https://www.graphviz.org/download/.
3403

3404
        Displays the structure of a Mechanism using html table format and shape='plaintext'.
3405
        This method is called by `Composition.show_graph` if its **show_mechanism_structure** argument is specified as
3406
        `True` when it is called.
3407

3408
        Arguments
3409
        ---------
3410

3411
        show_functions : bool : default False
3412
            show the `function <Component.function>` of the Mechanism and each of its Ports.
3413

3414
        show_mech_function_params : bool : default False
3415
            show the parameters of the Mechanism's `function <Component.function>` if **show_functions** is True.
3416

3417
        show_port_function_params : bool : default False
3418
            show parameters for the `function <Component.function>` of the Mechanism's Ports if **show_functions** is
3419
            True).
3420

3421
        show_values : bool : default False
3422
            show the `value <Component.value>` of the Mechanism and each of its Ports (prefixed by "=").
3423

3424
        use_labels : bool : default False
3425
            use labels for values if **show_values** is `True`; labels must be specified in the `input_labels_dict
3426
            <Mechanism.input_labels_dict>` (for InputPort values) and `output_labels_dict
3427
            <Mechanism.output_labels_dict>` (for OutputPort values), otherwise the value is used.
3428

3429
        show_headers : bool : default False
3430
            show the Mechanism, InputPort, ParameterPort and OutputPort headers.
3431

3432
            **composition** argument (if **composition** is not specified, show_roles is ignored).
3433

3434
        show_conditions : bool : default False
3435
            show the `conditions <Condition>` used by `Composition` to determine whether/when to execute each Mechanism
3436
            (if **composition** is not specified, show_conditions is ignored).
3437

3438
        composition : Composition : default None
3439
            specifies the `Composition` (to which the Mechanism must belong) for which to show its role (see **roles**);
3440
            if this is not specified, the **show_roles** argument is ignored.
3441

3442
        compact_cim : bool : default False
3443
            specifies whether to suppress InputPort fields for input_CIM and OutputPort fields for output_CIM
3444

3445
        output_fmt : keyword : default 'pdf'
3446
            'pdf': generate and open a pdf with the visualization;\n
3447
            'jupyter': return the object (ideal for working in jupyter/ipython notebooks)\n
3448
            'struct': return a string that specifies the structure of the Mechanism using html table format
3449
            for use in a GraphViz node specification.
3450

3451
        Example HTML for structure:
3452
            .. parsed-literal::
3453

3454
                <<table border="1" cellborder="0" cellspacing="0" bgcolor="tan">          <- MAIN TABLE
3455

3456
                <tr>                                                                      <- BEGIN OutputPorts
3457
                    <td colspan="2"><table border="0" cellborder="0" BGCOLOR="bisque">    <- OutputPorts OUTER TABLE
3458
                        <tr>
3459
                            <td colspan="1"><b>OutputPorts</b></td>                      <- OutputPorts HEADER
3460
                        </tr>
3461
                        <tr>
3462
                            <td><table border="0" cellborder="1">                         <- OutputPort CELLS TABLE
3463
                                <tr>
3464
                                    <td port="OutputPortPort1">OutputPort 1<br/><i>function 1</i><br/><i>=value</i></td>
3465
                                    <td port="OutputPortPort2">OutputPort 2<br/><i>function 2</i><br/><i>=value</i></td>
3466
                                </tr>
3467
                            </table></td>
3468
                        </tr>
3469
                    </table></td>
3470
                </tr>
3471

3472
                <tr>                                                                      <- BEGIN MECHANISM & ParameterPorts
3473
                    <td port="Mech name"><b>Mech name</b><br/><i>Roles</i></td>           <- MECHANISM CELL (OUTERMOST TABLE)
3474
                    <td><table border="0" cellborder="0" BGCOLOR="bisque">                <- ParameterPorts OUTER TABLE
3475
                        <tr>
3476
                            <td><b>ParameterPorts</b></td>                               <- ParameterPorts HEADER
3477
                        </tr>
3478
                        <tr>
3479
                            <td><table border="0" cellborder="1">                         <- ParameterPort CELLS TABLE
3480
                                <tr><td port="ParamPort1">Param 1<br/><i>function 1</i><br/><i>= value</i></td></tr>
3481
                                <tr><td port="ParamPort1">Param 2<br/><i>function 2</i><br/><i>= value</i></td></tr>
3482
                            </table></td>
3483
                        </tr>
3484
                    </table></td>
3485
                </tr>
3486

3487
                <tr>                                                                      <- BEGIN InputPorts
3488
                    <td colspan="2"><table border="0" cellborder="0" BGCOLOR="bisque">    <- InputPortS OUTER TABLE
3489
                        <tr>
3490
                            <td colspan="1"><b>InputPorts</b></td>                       <- InputPorts HEADER
3491
                        </tr>
3492
                        <tr>
3493
                            <td><table border="0" cellborder="1">                         <- InputPort CELLS TABLE
3494
                                <tr>
3495
                                    <td port="InputPortPort1">InputPort 1<br/><i>function 1</i><br/><i>= value</i></td>
3496
                                    <td port="InputPortPort2">InputPort 2<br/><i>function 2</i><br/><i>= value</i></td>
3497
                                </tr>
3498
                            </table></td>
3499
                        </tr>
3500
                    </table></td>
3501
                </tr>
3502

3503
                </table>>
3504

3505
        """
3506

3507
        # Table / cell specifications:
3508

3509
        # Overall node table:                                               NEAR LIGHTYELLOW
3510
        node_table_spec = f'<table border={repr(node_border)} cellborder="0" cellspacing="1" bgcolor="#FFFFF0">'
1✔
3511

3512
        # Header of Mechanism cell:
3513
        mech_header = f'<b><i>{Mechanism.__name__}</i></b>:<br/>'
1✔
3514

3515
        # Outer Port table:
3516
        outer_table_spec = '<table border="0" cellborder="0" bgcolor="#FAFAD0">' # NEAR LIGHTGOLDENRODYELLOW
1✔
3517

3518
        # Header cell of outer Port table:
3519
        input_ports_header     = f'<tr><td colspan="1" valign="middle"><b><i>{InputPort.__name__}s</i></b></td></tr>'
1✔
3520
        parameter_ports_header = f'<tr><td rowspan="1" valign="middle"><b><i>{ParameterPort.__name__}s</i></b></td>'
1✔
3521
        output_ports_header    = f'<tr><td colspan="1" valign="middle"><b><i>{OutputPort.__name__}s</i></b></td></tr>'
1✔
3522

3523
        # Inner Port table (i.e., that contains individual ports in each cell):
3524
        inner_table_spec = \
1✔
3525
            '<table border="0" cellborder="2" cellspacing="0" color="LIGHTGOLDENRODYELLOW" bgcolor="PALEGOLDENROD">'
3526

3527
        def mech_cell():
1✔
3528
            """Return html with name of Mechanism, possibly with function and/or value
3529
            Inclusion of roles, function and/or value is determined by arguments of call to _show_structure()
3530
            """
3531
            header = ''
1✔
3532
            if show_headers:
1!
3533
                header = mech_header
1✔
3534
            mech_name = f'<b>{header}<font point-size="16" >{self.name}</font></b>'
1✔
3535

3536
            mech_roles = ''
1✔
3537
            if composition and show_roles:
1✔
3538
                from psyneulink.core.compositions.composition import CompositionInterfaceMechanism, NodeRole
1✔
3539
                if self is composition.controller:
1!
3540
                    mech_roles = f'<br/><i>CONTROLLER</i>'
×
3541
                elif not isinstance(self, CompositionInterfaceMechanism):
1✔
3542
                    roles = [role.name for role in list(composition.nodes_to_roles[self])]
1✔
3543
                    mech_roles = f'<br/><i>{",".join(roles)}</i>'
1✔
3544

3545
            mech_condition = ''
1✔
3546
            if composition and show_conditions and condition:
1✔
3547
                mech_condition = f'<br/><i>{str(condition)}</i>'
1✔
3548

3549
            mech_function = ''
1✔
3550
            fct_params = ''
1✔
3551
            if show_functions:
1✔
3552
                if show_mech_function_params:
1!
3553
                    fct_params = []
1✔
3554
                    for param in [param for param in self.function_parameters
1✔
3555
                                  if param.modulable and param.name not in {ADDITIVE_PARAM, MULTIPLICATIVE_PARAM}]:
3556
                        fct_params.append(f'{param.name}={param._get(context)}')
1✔
3557
                    fct_params = ", ".join(fct_params)
1✔
3558
                mech_function = f'<br/><i>{self.function.__class__.__name__}({fct_params})</i>'
1✔
3559
            mech_value = ''
1✔
3560
            if show_values:
1✔
3561
                mech_value = f'<br/>={self.value}'
1✔
3562
            # Mech cell should span full width if there are no ParameterPorts
3563
            cols = 1
1✔
3564
            if not len(self.parameter_ports):
1!
3565
                cols = 2
×
3566
            return f'<td port="{self.name}" colspan="{cols}">' + \
1✔
3567
                   mech_name + mech_roles + mech_condition + mech_function + mech_value + '</td>'
3568

3569
        def port_table(port_list: ContentAddressableList,
1✔
3570
                       port_type: Union[Type[InputPort], Type[ParameterPort], Type[OutputPort]]):
3571
            """Return html with table for each port in port_list, including functions and/or values as specified
3572

3573
            Each table has a header cell and and inner table with cells for each port in the list
3574
            InputPort and OutputPort cells are aligned horizontally;  ParameterPort cells are aligned vertically.
3575
            Use show_functions, show_values and include_labels arguments from call to _show_structure()
3576
            See _show_structure docstring for full template.
3577
            """
3578

3579
            def port_cell(port, include_function:bool=False, include_value:bool=False, use_label:bool=False):
1✔
3580
                """Return html for cell in port inner table
3581
                Format:  <td port="PortPort">PortName<br/><i>function 1</i><br/><i>=value</i></td>
3582
                """
3583

3584
                function = ''
1✔
3585
                fct_params = ''
1✔
3586
                if include_function:
1✔
3587
                    if show_port_function_params:
1!
3588
                        fct_params = []
1✔
3589
                        for param in [param for param in port.function_parameters
1✔
3590
                                      if param.modulable and param.name not in {ADDITIVE_PARAM, MULTIPLICATIVE_PARAM}]:
3591
                            fct_params.append(f'{param.name}={param._get(context)}')
1✔
3592
                        fct_params = ", ".join(fct_params)
1✔
3593
                    function = f'<br/><i>{port.function.__class__.__name__}({fct_params})</i>'
1✔
3594
                value=''
1✔
3595
                if include_value:
1✔
3596
                    if use_label and not isinstance(port, ParameterPort):
1✔
3597
                        value = f'<br/>={port.labeled_value}'
1✔
3598
                    else:
3599
                        value = f'<br/>={port.value}'
1✔
3600
                return f'<td port="{self._get_port_name(port)}"><b>{port.name}</b>{function}{value}</td>'
1✔
3601

3602

3603
            # InputPorts
3604
            if port_type is InputPort:
1✔
3605
                if show_headers:
1!
3606
                    ports_header = input_ports_header
1✔
3607
                else:
3608
                    ports_header = ''
×
3609
                table = f'<td colspan="2"> {outer_table_spec} {ports_header}<tr><td>{inner_table_spec}<tr>'
1✔
3610
                for port in port_list:
1✔
3611
                    table += port_cell(port, show_functions, show_values, use_labels)
1✔
3612
                table += '</tr></table></td></tr></table></td>'
1✔
3613

3614
            # ParameterPorts
3615
            elif port_type is ParameterPort:
1✔
3616
                if show_headers:
1!
3617
                    ports_header = parameter_ports_header
1✔
3618
                else:
3619
                    ports_header = '<tr>'
×
3620
                table = f'<td> {outer_table_spec} {ports_header} <td> {inner_table_spec}'
1✔
3621
                for port in port_list:
1✔
3622
                    table += '<tr>' + port_cell(port, show_functions, show_values, use_labels) + '</tr>'
1✔
3623
                table += '</table></td></tr></table></td>'
1✔
3624

3625
            # OutputPorts
3626
            elif port_type is OutputPort:
1!
3627
                if show_headers:
1!
3628
                    ports_header = output_ports_header
1✔
3629
                else:
3630
                    ports_header = ''
×
3631
                table = f'<td colspan="2"> {outer_table_spec} <tr><td>{inner_table_spec}<tr>'
1✔
3632
                for port in port_list:
1✔
3633
                    table += port_cell(port, show_functions, show_values, use_labels)
1✔
3634
                table += f'</tr></table></td></tr> {ports_header} </table></td>'
1✔
3635

3636
            return table
1✔
3637

3638

3639
        # Construct InputPorts table
3640
        if (len(self.input_ports)
1✔
3641
                and (not compact_cim or (self is not composition.input_CIM and self is not composition.parameter_CIM))):
3642
            input_ports_table = f'<tr>{port_table(self.input_ports, InputPort)}</tr>'
1✔
3643
        else:
3644
            input_ports_table = ''
1✔
3645

3646
        # Construct ParameterPorts table
3647
        if len(self.parameter_ports):
1!
3648
        # if len(self.parameter_ports) and (not compact_cim or self is not composition.parameter_CIM):
3649
            parameter_ports_table = port_table(self.parameter_ports, ParameterPort)
1✔
3650
        else:
3651
            parameter_ports_table = ''
×
3652

3653
        # Construct OutputPorts table
3654
        if len(self.output_ports) and (not compact_cim or self is not composition.output_CIM):
1✔
3655
            output_ports_table = f'<tr>{port_table(self.output_ports, OutputPort)}</tr>'
1✔
3656

3657
        else:
3658
            output_ports_table = ''
1✔
3659

3660
        # Construct full table
3661
        m_node_struct = '<' + node_table_spec + \
1✔
3662
                        output_ports_table + \
3663
                        '<tr>' + mech_cell() + parameter_ports_table + '</tr>' + \
3664
                        input_ports_table + \
3665
                        '</table>>'
3666

3667
        if output_fmt == 'struct':
1!
3668
            # return m.node
3669
            return m_node_struct
1✔
3670

3671
        # Make node
3672
        import graphviz as gv
×
3673
        struct_shape = 'plaintext' # assumes html is used to specify structure in m_node_struct
×
3674

3675
        m = gv.Digraph(#'mechanisms',
×
3676
                       #filename='mechanisms_revisited.gv',
3677
                       node_attr={'shape': struct_shape},
3678
                       )
3679
        m.node(self.name, m_node_struct, shape=struct_shape)
×
3680

3681
        if output_fmt == 'pdf':
×
3682
            m.view(self.name.replace(" ", "-"), cleanup=True)
×
3683

3684
        elif output_fmt == 'jupyter':
×
3685
            return m
×
3686

3687
    def plot(self, x_range=None):
1✔
3688
        """Generate a plot of the Mechanism's `function <Mechanism_Base.function>` using the specified parameter values
3689
        (see `DDM.plot <DDM.plot>` for details of the animated DDM plot).
3690

3691
        Arguments
3692
        ---------
3693

3694
        x_range : List
3695
            specify the range over which the `function <Mechanism_Base.function>` should be plotted. x_range must be
3696
            provided as a list containing two floats: lowest value of x and highest value of x.  Default values
3697
            depend on the Mechanism's `function <Mechanism_Base.function>`.
3698

3699
            - Logistic Function: default x_range = [-5.0, 5.0]
3700
            - Exponential Function: default x_range = [0.1, 5.0]
3701
            - All Other Functions: default x_range = [-10.0, 10.0]
3702

3703
        Returns
3704
        -------
3705
        Plot of Mechanism's `function <Mechanism_Base.function>` : Matplotlib window
3706
            Matplotlib window of the Mechanism's `function <Mechanism_Base.function>` plotted with specified parameters
3707
            over the specified x_range
3708

3709
        """
3710

3711
        import matplotlib.pyplot as plt
1✔
3712

3713
        if not x_range:
1!
3714
            if "Logistic" in str(self.function):
1!
3715
                x_range= [-5.0, 5.0]
1✔
3716
            elif "Exponential" in str(self.function):
×
3717
                x_range = [0.1, 5.0]
×
3718
            else:
3719
                x_range = [-10.0, 10.0]
×
3720
        x_space = np.linspace(x_range[0],x_range[1])
1✔
3721
        plt.plot(x_space, self.function(x_space)[0], lw=3.0, c='r')
1✔
3722
        plt.show()
1✔
3723

3724
    # def remove_projection(self, projection):
3725
    #     pass
3726
    @beartype
1✔
3727
    def _get_port_name(self, port:Port):
1✔
3728
        if isinstance(port, InputPort):
1✔
3729
            port_type = InputPort.__name__
1✔
3730
        elif isinstance(port, ParameterPort):
1✔
3731
            port_type = ParameterPort.__name__
1✔
3732
        elif isinstance(port, OutputPort):
1✔
3733
            port_type = OutputPort.__name__
1✔
3734
        else:
3735
            assert False, f'Mechanism._get_port_name() must be called with an ' \
3736
                f'{InputPort.__name__}, {ParameterPort.__name__} or {OutputPort.__name__}'
3737
        return port_type + '-' + port.name
1✔
3738

3739
    @beartype
1✔
3740
    @handle_external_context()
1✔
3741
    def add_ports(self, ports, update_variable=True, context=None):
1✔
3742
        """
3743
        add_ports(ports)
3744

3745
        Add one or more `Ports <Port>` to the Mechanism.  Only `InputPorts <InputPort>` and `OutputPorts
3746
        <OutputPort>` can be added; `ParameterPorts <ParameterPort>` cannot be added to a Mechanism after it has
3747
        been constructed.
3748

3749
        If the `owner <Port_Base.owner>` of a Port specified in the **ports** argument is not the same as the
3750
        Mechanism to which it is being added an error is generated.    If the name of a specified Port is the same
3751
        as an existing one with the same name, an index is appended to its name, and incremented for each Port
3752
        subsequently added with the same name (see `naming conventions <Registry_Naming>`).  If a specified Port
3753
        already belongs to the Mechanism, the request is ignored.
3754

3755
        .. note::
3756
            Adding InputPorts to a Mechanism changes the size of its `variable <Mechanism_Base.variable>` attribute,
3757
            which may produce an incompatibility with its `function <Mechanism_Base.function>` (see
3758
            `Mechanism InputPorts <Mechanism_InputPorts>` for a more detailed explanation).
3759

3760
        Arguments
3761
        ---------
3762

3763
        ports : Port or List[Port]
3764
            one more `InputPorts <InputPort>` or `OutputPorts <OutputPort>` to be added to the Mechanism.
3765
            Port specification(s) can be an InputPort or OutputPort object, class reference, class keyword, or
3766
            `Port specification dictionary <Port_Specification>` (the latter must have a *PORT_TYPE* entry
3767
            specifying the class or keyword for InputPort or OutputPort).
3768

3769
        Returns a dictionary with two entries, containing the list of InputPorts and OutputPorts added.
3770
        -------
3771

3772
        Dictionary with entries containing InputPorts and/or OutputPorts added
3773

3774
        """
3775
        from psyneulink.core.components.ports.port import _parse_port_type
1✔
3776
        from psyneulink.core.components.ports.inputport import InputPort, _instantiate_input_ports
1✔
3777
        from psyneulink.core.components.ports.outputport import OutputPort, _instantiate_output_ports
1✔
3778

3779
        # not transferring execution_id here because later function calls
3780
        # need execution_id=None to succeed.
3781
        # TODO: remove context passing for init methods if they don't need it
3782
        context = Context(source=ContextFlags.METHOD, execution_id=None)
1✔
3783

3784
        # Put in list to standardize treatment below
3785
        if not isinstance(ports, list):
1✔
3786
            ports = [ports]
1✔
3787

3788
        input_ports = []
1✔
3789
        output_ports = []
1✔
3790
        instantiated_input_ports = None
1✔
3791
        instantiated_output_ports = None
1✔
3792

3793
        for port in ports:
1✔
3794
            # FIX: 11/9/17: REFACTOR USING _parse_port_spec
3795
            port_type = _parse_port_type(self, port)
1✔
3796
            if (isinstance(port_type, InputPort) or
1✔
3797
                    (inspect.isclass(port_type) and issubclass(port_type, InputPort))):
3798
                input_ports.append(port)
1✔
3799

3800
            elif (isinstance(port_type, OutputPort) or
1!
3801
                  (inspect.isclass(port_type) and issubclass(port_type, OutputPort))):
3802
                output_ports.append(port)
1✔
3803

3804
        if input_ports:
1✔
3805
            added_variable, added_input_port = self._handle_arg_input_ports(input_ports)
1✔
3806
            if added_input_port:
1!
3807
                if not isinstance(self.defaults.variable, list):
1!
3808
                    old_variable = self.defaults.variable.tolist()
1✔
3809
                else:
3810
                    old_variable = self.defaults.variable
×
3811
                old_variable.extend(added_variable)
1✔
3812
                self.defaults.variable = convert_to_np_array(old_variable)
1✔
3813
            instantiated_input_ports = _instantiate_input_ports(self,
1✔
3814
                                                                  input_ports,
3815
                                                                  added_variable,
3816
                                                                  context=context)
3817
            for port in instantiated_input_ports:
1✔
3818
                if port.name is port.componentName or port.componentName + '-' in port.name:
1✔
3819
                    port._assign_default_port_Name()
1✔
3820
            # self._instantiate_function(function=self.function)
3821
        if output_ports:
1✔
3822
            instantiated_output_ports = _instantiate_output_ports(self, output_ports, context=context)
1✔
3823

3824
        if update_variable:
1✔
3825
            self._update_default_variable(
1✔
3826
                [copy.deepcopy(port.defaults.value) for port in self.input_ports],
3827
                context
3828
            )
3829

3830
        return {INPUT_PORTS: instantiated_input_ports,
1✔
3831
                OUTPUT_PORTS: instantiated_output_ports}
3832

3833
    @beartype
1✔
3834
    def remove_ports(self, ports):
1✔
3835
        """
3836
        remove_ports(ports)
3837

3838
        Remove one or more `Ports <Port>` from the Mechanism.  Only `InputPorts <InputPort> and `OutputPorts
3839
        <OutputPort>` can be removed; `ParameterPorts <ParameterPort>` cannot be removed from a Mechanism.
3840

3841
        Each Specified port must be owned by the Mechanism, otherwise the request is ignored.
3842

3843
        .. note::
3844
            Removing InputPorts from a Mechanism changes the size of its `variable <Mechanism_Base.variable>`
3845
            attribute, which may produce an incompatibility with its `function <Mechanism_Base.function>` (see
3846
            `Mechanism InputPorts <Mechanism_InputPorts>` for more detailed information).
3847

3848
        Arguments
3849
        ---------
3850

3851
        ports : Port or List[Port]
3852
            one more ports to be removed from the Mechanism.
3853
            Port specification(s) can be an Port object or the name of one.
3854

3855
        """
3856
        # from psyneulink.core.components.ports.inputPort import INPUT_PORT
3857

3858
        # Put in list to standardize treatment below
3859
        if not isinstance(ports, (list, ContentAddressableList)):
1✔
3860
            ports = [ports]
1✔
3861

3862
        def delete_port_Projections(proj_list, port):
1✔
3863
            for proj in proj_list:
1✔
3864
                try:
1✔
3865
                    type(proj)._delete_projection(proj)
1✔
3866
                except:
×
3867
                    raise MechanismError(f"PROGRAM ERROR: {proj} not found when removing {port} from {self.name}.")
3868

3869
        for port in ports:
1✔
3870

3871
            delete_port_Projections(port.mod_afferents.copy(), port)
1✔
3872

3873
            if port in self.input_ports:
1✔
3874
                if isinstance(port, str):
1!
3875
                    port = self.input_ports[port]
×
3876
                index = self.input_ports.index(port)
1✔
3877
                delete_port_Projections(port.path_afferents.copy(), port)
1✔
3878
                del self.input_ports[index]
1✔
3879
                # If port is subclass of OutputPort:
3880
                #    check if regsistry has category for that class, and if so, use that
3881
                category = INPUT_PORT
1✔
3882
                class_name = port.__class__.__name__
1✔
3883
                if class_name != INPUT_PORT and class_name in self._portRegistry:
1!
3884
                    category = class_name
×
3885
                remove_instance_from_registry(registry=self._portRegistry,
1✔
3886
                                              category=category,
3887
                                              component=port)
3888
                old_variable = self.defaults.variable
1✔
3889
                old_variable = np.delete(old_variable,index,0)
1✔
3890
                self.defaults.variable = old_variable
1✔
3891

3892
            elif port in self.parameter_ports:
1✔
3893
                if isinstance(port, ParameterPort):
1!
3894
                    index = self.parameter_ports.index(port)
1✔
3895
                else:
3896
                    index = self.parameter_ports.index(self.parameter_ports[port])
×
3897
                del self.parameter_ports[index]
1✔
3898
                remove_instance_from_registry(registry=self._portRegistry,
1✔
3899
                                              category=PARAMETER_PORT,
3900
                                              component=port)
3901

3902
            elif port in self.output_ports:
1✔
3903
                delete_port_Projections(port.efferents.copy(), port)
1✔
3904
                # NOTE: removed below del because output_values is
3905
                # generated on the fly. These comments can be removed
3906
                # del self.output_values[index]
3907
                del self.output_ports[port]
1✔
3908
                # If port is subclass of OutputPort:
3909
                #    check if regsistry has category for that class, and if so, use that
3910
                category = OUTPUT_PORT
1✔
3911
                class_name = port.__class__.__name__
1✔
3912
                if class_name != OUTPUT_PORT and class_name in self._portRegistry:
1✔
3913
                    category = class_name
1✔
3914
                remove_instance_from_registry(registry=self._portRegistry,
1✔
3915
                                              category=category,
3916
                                              component=port)
3917

3918
        self.defaults.variable = [copy_parameter_value(ip.defaults.value) for ip in self.input_ports]
1✔
3919

3920
    def _delete_mechanism(mechanism):
1✔
3921
        mechanism.remove_ports(mechanism.input_ports)
1✔
3922
        mechanism.remove_ports(mechanism.parameter_ports)
1✔
3923
        mechanism.remove_ports(mechanism.output_ports)
1✔
3924
        # del mechanism.function
3925
        remove_instance_from_registry(MechanismRegistry, mechanism.__class__.__name__,
1✔
3926
                                      component=mechanism)
3927

3928
    def _get_standardized_label_dicts(self):
1✔
3929
        """
3930
        Gets dict of Mechanism's input and output port labels in a standardized form
3931

3932
        Returns
3933
        -------
3934
            dict
3935
                .. parsed-literal::
3936
                    {
3937
                        INPUT_PORTS:
3938
                                {(int) port_index:
3939
                                        {{label_1: value_1},
3940
                                         {label_2: value_2}}
3941
                                },
3942
                        OUTPUT_PORTS:
3943
                                {(int) port_index:
3944
                                        {{label_1: value_1},
3945
                                         {label_2: value_2}}
3946
                                }
3947
                    }
3948

3949
        """
3950
        input_labels = self._get_standardized_label_dict(INPUT)
×
3951
        output_labels = self._get_standardized_label_dict(INPUT)
×
3952
        port_labels = {
×
3953
            INPUT_PORTS: input_labels,
3954
            OUTPUT_PORTS: output_labels
3955
        }
3956
        return port_labels
×
3957

3958
    def _get_standardized_label_dict(self, label_type):
1✔
3959
        """
3960
        Parses input or output label dicts into a standardized form
3961

3962
        Parameters
3963
        ----------
3964
        (str) port_type: INPUT or OUTPUT keyword, specifying the type of labels to parse and return
3965

3966
        port_type
3967

3968
        Returns
3969
        -------
3970
        dict
3971
            .. parsed-literal::
3972
                {INPUT_PORTS/OUTPUT_PORTS:
3973
                        {(int) port_index:
3974
                                {{label_1: value_1},
3975
                                 {label_2: value_2}}
3976
                        }
3977
                }
3978
        """
3979
        if label_type == INPUT:
1!
3980
            label_dict = self.input_labels_dict
1✔
3981
            ports = self.input_ports
1✔
3982
        elif label_type == OUTPUT:
×
3983
            label_dict = self.output_labels_dict
×
3984
            ports = self.output_ports
×
3985
        _label_dict = {}
1✔
3986
        if label_dict:
1✔
3987
            for k, v in label_dict.items():
1✔
3988
                if isinstance(k, Number):
1✔
3989
                    _label_dict[k] = v
1✔
3990
                elif type(v) == dict:
1✔
3991
                    i = ports[k].position_in_mechanism
1✔
3992
                    _label_dict[i] = v
1✔
3993
                else:
3994
                    if 0 not in _label_dict:
1✔
3995
                        _label_dict[0] = {}
1✔
3996
                    _label_dict[0].update({k:v})
1✔
3997
        return _label_dict
1✔
3998

3999
    def get_input_port_position(self, port):
1✔
4000
        if port in self.input_ports:
1✔
4001
            return self.input_ports.index(port)
1✔
4002
        raise MechanismError("{} is not an InputPort of {}.".format(port.name, self.name))
4003

4004
    @beartype
1✔
4005
    def _get_port_value_labels(self, port_type: Union[Type[InputPort], Type[OutputPort]], context=None):
1✔
4006
        """Return list of labels for the value of each Port of specified port_type.
4007
        If the labels_dict has subdicts (one for each Port), get label for the value of each Port from its subdict.
4008
        If the labels dict does not have subdicts, then use the same dict for the only (or all) Port(s)
4009
        """
4010

4011
        if port_type is InputPort:
1!
4012
            ports = self.input_ports
×
4013

4014
        elif port_type is OutputPort:
1!
4015
            ports = self.output_ports
1✔
4016

4017
        labels = []
1✔
4018
        for port in ports:
1✔
4019
            labels.append(port.get_label(context))
1✔
4020
        return labels
1✔
4021

4022
    @property
1✔
4023
    def input_port(self):
1✔
4024
        return self.input_ports[0]
1✔
4025

4026
    def get_input_variables(self, context=None):
1✔
4027
        # FIX: 2/4/22 THIS WOULD PARALLEL get_input_values BUT MAY NOT BE NEEDED:
4028
        # input_variables = []
4029
        # for input_port in self.input_ports:
4030
        #     if "LearningSignal" in input_port.name:
4031
        #         input_variables.append(input_port.parameters.variable.get(context).flatten())
4032
        #     else:
4033
        #         input_variables.append(input_port.parameters.variable.get(context))
4034
        # return input_variables
4035
        return [input_port.parameters.variable.get(context) for input_port in self.input_ports]
×
4036

4037
    @property
1✔
4038
    def input_values(self):
1✔
4039
        try:
1✔
4040
            return self.input_ports.values
1✔
4041
        except (TypeError, AttributeError):
×
4042
            return None
×
4043

4044
    def get_input_values(self, context=None):
1✔
4045
        input_values = []
1✔
4046
        for input_port in self.input_ports:
1✔
4047
            if "LearningSignal" in input_port.name:
1!
4048
                input_values.append(input_port.parameters.value.get(context).flatten())
×
4049
            else:
4050
                input_values.append(input_port.parameters.value.get(context))
1✔
4051
        return input_values
1✔
4052

4053
    @property
1✔
4054
    def external_input_ports(self):
1✔
4055
        try:
1✔
4056
            return [input_port for input_port in self.input_ports if not input_port.internal_only]
1✔
4057
        except (TypeError, AttributeError):
×
4058
            return None
×
4059

4060
    @property
1✔
4061
    def external_input_shape(self):
1✔
4062
        """Alias for _default_external_input_shape"""
4063
        return self._default_external_input_shape
1✔
4064

4065
    @property
1✔
4066
    def _default_external_input_shape(self):
1✔
4067
        try:
1✔
4068
            shape = []
1✔
4069
            for input_port in self.input_ports:
1✔
4070
                if input_port.internal_only or input_port.default_input:
1!
4071
                    continue
×
4072
                if input_port._input_shape_template == VARIABLE:
1✔
4073
                    shape.append(input_port.defaults.variable)
1✔
4074
                elif input_port._input_shape_template == VALUE:
1✔
4075
                    shape.append(input_port.defaults.value)
1✔
4076
                else:
4077
                    assert False, f"PROGRAM ERROR: bad changes_shape in attempt to assign " \
4078
                                  f"default_external_input_shape for '{input_port.name}' of '{self.name}."
4079
            return shape
1✔
4080
        except (TypeError, AttributeError):
×
4081
            return None
×
4082

4083
    @property
1✔
4084
    def external_input_variables(self):
1✔
4085
        """Returns variables of all external InputPorts that belong to the Mechanism"""
4086
        try:
×
4087
            return [input_port.variable for input_port in self.input_ports if not input_port.internal_only]
×
4088
        except (TypeError, AttributeError):
×
4089
            return None
×
4090

4091
    @property
1✔
4092
    def default_external_inputs(self):
1✔
4093
        try:
×
4094
            return [input_port.default_input for input_port in self.input_ports if not input_port.internal_only]
×
4095
        except (TypeError, AttributeError):
×
4096
            return None
×
4097

4098
    @property
1✔
4099
    def default_external_input_variables(self):
1✔
4100
        try:
×
4101
            return [input_port.defaults.variable for input_port in self.input_ports if not input_port.internal_only]
×
4102
        except (TypeError, AttributeError):
×
4103
            return None
×
4104

4105
    @property
1✔
4106
    def external_input_values(self):
1✔
4107
        try:
1✔
4108
            return [input_port.value for input_port in self.input_ports if not input_port.internal_only]
1✔
4109
        except (TypeError, AttributeError):
×
4110
            return None
×
4111

4112
    @property
1✔
4113
    def default_external_input_values(self):
1✔
4114
        try:
×
4115
            return [input_port.defaults.value for input_port in self.input_ports if not input_port.internal_only]
×
4116
        except (TypeError, AttributeError):
×
4117
            return None
×
4118

4119
    @property
1✔
4120
    def labeled_input_values(self):
1✔
4121
        """
4122
        Returns a list with as many items as there are InputPorts of the Mechanism. Each list item represents the value
4123
        of the corresponding InputPort, and is populated by a string label (from the input_labels_dict) when one
4124
        exists, and the numeric value otherwise.
4125
        """
4126
        return self.get_input_labels()
×
4127

4128
    def get_input_labels(self, context=None):
1✔
4129
        if self.input_labels_dict:
×
4130
            return self._get_port_value_labels(InputPort, context)
×
4131
        else:
4132
            return self.get_input_values(context)
×
4133

4134
    @property
1✔
4135
    def parameter_ports(self):
1✔
4136
        return self._parameter_ports
1✔
4137

4138
    @property
1✔
4139
    def output_port(self):
1✔
4140
        return self.output_ports[0]
1✔
4141

4142
    @property
1✔
4143
    def output_values(self):
1✔
4144
        return self.get_output_values()
1✔
4145

4146
    def get_output_values(self, context=None):
1✔
4147
        return [output_port.parameters.value.get(context) for output_port in self.output_ports]
1✔
4148

4149
    @property
1✔
4150
    def labeled_output_values(self):
1✔
4151
        """
4152
        Returns a list with as many items as there are OutputPorts of the Mechanism. Each list item represents the
4153
        value of the corresponding OutputPort, and is populated by a string label (from the output_labels_dict) when
4154
        one exists, and the numeric value otherwise.
4155
        """
4156
        return self.get_output_labels()
1✔
4157

4158
    def get_output_labels(self, context=None):
1✔
4159
        if self.output_labels_dict:
1✔
4160
            return self._get_port_value_labels(OutputPort, context)
1✔
4161
        elif context:
1!
4162
            return self.get_output_values(context)
×
4163
        else:
4164
            # Use this to report most recent value if no context is available
4165
            return self.output_ports.values
1✔
4166

4167
    @property
1✔
4168
    def ports(self):
1✔
4169
        """Return list of all of the Mechanism's Ports"""
4170
        return ContentAddressableList(
1✔
4171
                component_type=Port,
4172
                list=list(self.input_ports) +
4173
                     list(self.parameter_ports) +
4174
                     list(self.output_ports))
4175

4176
    @property
1✔
4177
    def path_afferents(self):
1✔
4178
        """Return list of the `path_afferents <Port_Base.path_afferents>` for all of the Mechanism's input_ports"""
4179
        projs = []
1✔
4180
        for input_port in self.input_ports:
1✔
4181
            projs.extend(input_port.path_afferents)
1✔
4182
        return ContentAddressableList(component_type=Projection, list=projs)
1✔
4183

4184
    @property
1✔
4185
    def mod_afferents(self):
1✔
4186
        """Return all of the Mechanism's afferent modulatory Projections"""
4187
        projs = []
1✔
4188
        for input_port in self.input_ports:
1✔
4189
            projs.extend(input_port.mod_afferents)
1✔
4190
        for parameter_port in self.parameter_ports:
1✔
4191
            projs.extend(parameter_port.mod_afferents)
1✔
4192
        for output_port in self.output_ports:
1✔
4193
            projs.extend(output_port.mod_afferents)
1✔
4194
        return ContentAddressableList(component_type=Projection, list=projs)
1✔
4195

4196
    @property
1✔
4197
    def afferents(self):
1✔
4198
        """Return list of all of the Mechanism's afferent Projections"""
4199
        return ContentAddressableList(component_type=Projection,
1✔
4200
                                      list= list(self.path_afferents) + list(self.mod_afferents))
4201

4202
    @property
1✔
4203
    def efferents(self):
1✔
4204
        """Return list of all of the Mechanism's efferent Projections"""
4205
        projs = []
1✔
4206
        try:
1✔
4207
            for output_port in self.output_ports:
1✔
4208
                projs.extend(output_port.efferents)
1✔
4209
        except TypeError:
×
4210
            # self.output_ports might be None
4211
            pass
×
4212
        return ContentAddressableList(component_type=Projection, list=projs)
1✔
4213

4214
    @property
1✔
4215
    def projections(self):
1✔
4216
        """Return all Projections"""
4217
        return ContentAddressableList(component_type=Projection,
1✔
4218
                                      list=list(self.path_afferents) +
4219
                                           list(self.mod_afferents) +
4220
                                           list(self.efferents))
4221

4222
    @property
1✔
4223
    def senders(self):
1✔
4224
        """Return all Mechanisms that send Projections to self"""
4225
        return ContentAddressableList(component_type=Mechanism,
×
4226
                                      list=[p.sender.owner for p in self.afferents
4227
                                            if isinstance(p.sender.owner, Mechanism_Base)])
4228

4229
    @property
1✔
4230
    def receivers(self):
1✔
4231
        """Return all Mechanisms that send Projections to self"""
4232
        return ContentAddressableList(component_type=Mechanism,
×
4233
                                      list=[p.receiver.owner for p in self.efferents
4234
                                            if isinstance(p.sender.owner, Mechanism_Base)])
4235

4236
    @property
1✔
4237
    def modulators(self):
1✔
4238
        """Return all Mechanisms that send Projections to self"""
4239
        return ContentAddressableList(component_type=Mechanism,
×
4240
                                      list=[p.sender.owner for p in self.mod_afferents
4241
                                            if isinstance(p.sender.owner, Mechanism_Base)])
4242

4243
    @property
1✔
4244
    def _dependent_components(self):
1✔
4245
        return list(itertools.chain(
1✔
4246
            super()._dependent_components,
4247
            self.input_ports,
4248
            self.output_ports,
4249
            self.parameter_ports,
4250
        ))
4251

4252
    def as_mdf_model(self):
1✔
4253
        import modeci_mdf.mdf as mdf
1✔
4254
        from psyneulink.core.globals.mdf import _get_id_for_mdf_port
1✔
4255

4256
        model = mdf.Node(
1✔
4257
            id=parse_valid_identifier(self.name),
4258
            **self._mdf_metadata,
4259
        )
4260

4261
        for name, val in self._mdf_model_parameters[self._model_spec_id_parameters].items():
1✔
4262
            model.parameters.append(mdf.Parameter(id=name, value=val))
1✔
4263

4264
        input_ports = self.parameters.input_ports.get(fallback_value=None)
1✔
4265
        if (
1✔
4266
            input_ports is None
4267
            and self.initialization_status is ContextFlags.DEFERRED_INIT
4268
        ):
4269
            input_ports = []
1✔
4270
            primary_input_port = None
1✔
4271
        else:
4272
            primary_input_port = self.input_ports[0]
1✔
4273

4274
        primary_function_input_ids = []
1✔
4275
        for ip in input_ports:
1✔
4276
            ip_id = _get_id_for_mdf_port(ip)
1✔
4277
            if len(ip.path_afferents) > 1:
1✔
4278
                ip_afferent_mdf_ips = []
1✔
4279

4280
                for aff in ip.path_afferents:
1✔
4281
                    ip_model = mdf.InputPort(
1✔
4282
                        id=_get_id_for_mdf_port(ip, afferent=aff),
4283
                        shape=str(aff.defaults.value.shape),
4284
                        type=str(aff.defaults.value.dtype)
4285
                    )
4286
                    ip_afferent_mdf_ips.append(ip_model.id)
1✔
4287
                    model.input_ports.append(ip_model)
1✔
4288

4289
                # create combination function
4290
                combination_function_input_data_id = f'{ip_id}_combination_function_input_data'
1✔
4291
                model.parameters.append(
1✔
4292
                    mdf.Parameter(
4293
                        id=combination_function_input_data_id,
4294
                        value=f"[{', '.join(f'{mip}' for mip in ip_afferent_mdf_ips)}]"
4295
                    )
4296
                )
4297
                combination_function_id = f'{ip_id}_{MODEL_SPEC_ID_INPUT_PORT_COMBINATION_FUNCTION}'
1✔
4298
                combination_function_args = {
1✔
4299
                    'data': combination_function_input_data_id,
4300
                    'axes': 0
4301
                }
4302
                model.functions.append(
1✔
4303
                    mdf.Function(
4304
                        id=combination_function_id,
4305
                        function='onnx::ReduceSum',
4306
                        args=combination_function_args
4307
                    )
4308
                )
4309
                combination_function_dimreduce_id = f'{combination_function_id}_dimreduce'
1✔
4310
                primary_function_input_ids.append(combination_function_dimreduce_id)
1✔
4311
                model.functions.append(
1✔
4312
                    mdf.Function(
4313
                        id=combination_function_dimreduce_id,
4314
                        value=f'{MODEL_SPEC_ID_MDF_VARIABLE}[0]',
4315
                        args={
4316
                            MODEL_SPEC_ID_MDF_VARIABLE: combination_function_id,
4317
                        }
4318
                    )
4319
                )
4320
            else:
4321
                ip_model = ip.as_mdf_model()
1✔
4322
                ip_model.id = f'{parse_valid_identifier(self.name)}_{ip_model.id}'
1✔
4323

4324
                model.input_ports.append(ip_model)
1✔
4325
                primary_function_input_ids.append(ip_model.id)
1✔
4326

4327
        output_ports = self.parameters.output_ports.get(fallback_value=None)
1✔
4328
        if (
1✔
4329
            output_ports is None
4330
            and self.initialization_status is ContextFlags.DEFERRED_INIT
4331
        ):
4332
            output_ports = []
1✔
4333

4334
        for op in output_ports:
1✔
4335
            op_model = op.as_mdf_model()
1✔
4336
            op_model.id = f'{parse_valid_identifier(self.name)}_{op_model.id}'
1✔
4337

4338
            model.output_ports.append(op_model)
1✔
4339

4340
        # from deferred init above
4341
        if primary_input_port is None:
1✔
4342
            primary_function_input_id = ''
1✔
4343
        elif len(primary_function_input_ids) == 1:
1✔
4344
            primary_function_input_id = primary_function_input_ids[0]
1✔
4345
        else:
4346
            primary_function_input_id = f"numpy.array([{', '.join(primary_function_input_ids)}])"
1✔
4347

4348
        function = self.parameters.function.get(fallback_value=None)
1✔
4349
        if (
1✔
4350
            function is not None
4351
            and self.initialization_status is not ContextFlags.DEFERRED_INIT
4352
        ):
4353
            function._assign_to_mdf_model(model, primary_function_input_id)
1✔
4354

4355
        return model
1✔
4356

4357

4358
def _is_mechanism_spec(spec):
1✔
4359
    """Evaluate whether spec is a valid Mechanism specification
4360

4361
    Return true if spec is any of the following:
4362
    + Mechanism class
4363
    + Mechanism object:
4364
    Otherwise, return :keyword:`False`
4365

4366
    Returns: (bool)
4367
    """
4368
    if inspect.isclass(spec) and issubclass(spec, Mechanism):
×
4369
        return True
×
4370
    if isinstance(spec, Mechanism):
×
4371
        return True
×
4372
    return False
×
4373

4374
class MechanismList(UserList):
1✔
4375
    """Provides access to Mechanisms and their attributes in a list Mechanisms of an owner.
4376

4377
    Properties return dicts with item : attribute pairs.
4378
    Recursively process any item that itself is a MechanismList (e.g., a `Nested Composition <Composition_Nested>`.
4379

4380
    Attributes
4381
    ----------
4382
    mechanisms : List[item]
4383

4384
    names : List[str | Dict[str:List[str]]
4385
        each item is an item name or a dict with one item as its key and a list of subitem names as its value.
4386

4387
    values : Dict[str:value]
4388
        each entry is an item name : value pair.
4389

4390
    input_port_names : Dict[str:List[str]]
4391
        each entry is either an item name with a list of its `InputPort` `names <InputPort.name>` or, if the item is
4392
        a nested MechanismList, then a dict with the name of the nested item and a dict with item names and a list of
4393
        their InputPort names.
4394

4395
    input_port_values : Dict[str:Dict[str:value]]
4396
        each entry is either an item name with a dict of `InputPort` `name <InputPort.name>`:`value <InputPort.value>`
4397
        pairs or, if the item is a nested MechanismList, then a dict with the name of the nested item and a dict
4398
        with its InputPort name:value pairs.
4399

4400
    parameter_port_names : Dict[str:List[str]]
4401
        each entry is either an item name with a list of its `ParameterPort` `names <ParameterPort.name>` or, if the
4402
        item is a nested MechanismList, then a dict with the name of the nested item and a dict with item names and a
4403
        list of their ParameterPort names.
4404

4405
    parameter_port_values : Dict[str:Dict[str:value]]
4406
        each entry is either an item name with a dict of `ParameterPort` `name <ParameterPort.name>`:`value
4407
        <ParameterPort.value>` pairs or, if the item is a nested MechanismList, then a dict with the name of the
4408
        nested item and a dict with its ParameterPort name:value pairs.
4409

4410
    output_port_names : Dict[str:List[str]]
4411
        each entry is either an item name with a list of its `OutputPort` `names <OutputPort.name>` or, if the item is
4412
        a nested MechanismList, then a dict with the name of the nested item and a dict with item names and a list of
4413
        their OutputPort names.
4414

4415
    output_port_values : Dict[str:Dict[str:value]]
4416
        each entry is either an item name with a dict of `OutputPort` `name <OutputPort.name>`:`value
4417
        <OutputPort.value>` pairs or, if the item is a nested MechanismList, then a dict with the name of the nested
4418
        item and a dict with its OutputPort name:value pairs.
4419

4420
    """
4421

4422
    def __init__(self, owner, components_list:list):
1✔
4423
        super().__init__()
1✔
4424
        self.mechs = components_list
1✔
4425
        self.data = self.mechs
1✔
4426
        self.owner = owner
1✔
4427

4428
    def __getitem__(self, item):
1✔
4429
        """Return specified Mechanism in MechanismList"""
4430
        return self.mechs[item]
1✔
4431

4432
    def __setitem__(self, key, value):
1✔
4433
        raise KeyError("MechanismList is read only ")
4434

4435
    def __len__(self):
1✔
4436
        return (len(self.mechs))
×
4437

4438
    def __call__(self):
1✔
4439
        return self.data
×
4440

4441
    def _get_attributes_dict(self, mech_list_attr_name, item_attr_name, sub_attr_name=None, values_only=False):
1✔
4442
        """Generate dict of {item.name:item attribute value} pairs in "human readable" form.
4443
        Call recursively if item is itself a MechanismList.
4444
        """
4445
        ret_dict = {}
×
4446
        for item in self.mechanisms:
×
4447
            if isinstance(item, Mechanism):
×
4448
                attr_val = getattr(item, item_attr_name)
×
4449
                if isinstance(attr_val, (list, ContentAddressableList)):
×
4450
                    assert sub_attr_name, f"Need to specify sub_attr for attributs that are a list"
×
4451
                    if sub_attr_name == 'name':
×
4452
                        sub_items = []
×
4453
                        for sub_item in attr_val:
×
4454
                            sub_items.append(getattr(sub_item, sub_attr_name))
×
4455
                    else:
4456
                        sub_items = {}
×
4457
                        for sub_item in attr_val:
×
4458
                            sub_items[sub_item.name] = getattr(sub_item, sub_attr_name)
×
4459
                    ret_dict[item.name] = sub_items
×
4460
                elif not sub_attr_name:
×
4461
                    ret_dict[item.name] = attr_val
×
4462
                else:
4463
                    ret_dict[item.name] = getattr(attr_val, sub_attr_name)
×
4464
            else:
4465
                ret_dict[item.owner.name] = getattr(item, mech_list_attr_name)
×
4466
        if values_only:
×
4467
            # return list(ret_dict.values())
4468
            return [k if isinstance(v, str) else {k:v} for k,v in ret_dict.items()]
×
4469
        else:
4470
            return ret_dict
×
4471

4472
    @property
1✔
4473
    def mechs_sorted(self):
1✔
4474
        """Return list of Mechanisms sorted by Mechanisms' names"""
4475
        return sorted(self.mechs, key=lambda object_item: object_item.name)
×
4476

4477
    @property
1✔
4478
    def mechanisms(self):
1✔
4479
        """Return list of all Mechanisms in MechanismList"""
4480
        return list(self)
×
4481

4482
    @property
1✔
4483
    def names(self):
1✔
4484
        """Return dictwith names of all Mechanisms in MechanismList"""
4485
        return self._get_attributes_dict('names', 'name', values_only=True)
×
4486

4487
    @property
1✔
4488
    def values(self):
1✔
4489
        """Return dict with values of all Mechanisms in MechanismList"""
4490
        return self._get_attributes_dict('values', 'value')
×
4491

4492
    @property
1✔
4493
    def input_port_names(self):
1✔
4494
        """Return dict with names of all OutputPorts for all Mechanisms in MechanismList"""
4495
        return self._get_attributes_dict('input_port_names', 'input_ports', 'name')
×
4496

4497
    @property
1✔
4498
    def input_port_values(self):
1✔
4499
        """Return dict with values of OutputPorts for all Mechanisms in MechanismList"""
4500
        return self._get_attributes_dict('input_port_values', 'input_ports', 'value')
×
4501

4502
    @property
1✔
4503
    def input_values(self):
1✔
4504
        """Return dict with input_values for all Mechanisms in MechanismList"""
4505
        return self._get_attributes_dict('values', 'value')
×
4506

4507
    @property
1✔
4508
    def parameter_port_names(self):
1✔
4509
        """Return dict with names of all OutputPorts for all Mechanisms in MechanismList"""
4510
        return self._get_attributes_dict('parameter_port_names', 'parameter_ports', 'name')
×
4511

4512
    @property
1✔
4513
    def parameter_port_values(self):
1✔
4514
        """Return dict with values of OutputPorts for all Mechanisms in MechanismList"""
4515
        return self._get_attributes_dict('parameter_port_values', 'parameter_ports', 'value')
×
4516

4517
    @property
1✔
4518
    def output_port_names(self):
1✔
4519
        """Return dict with names of all OutputPorts for all Mechanisms in MechanismList"""
4520
        return self._get_attributes_dict('output_port_names', 'output_ports', 'name')
×
4521

4522
    @property
1✔
4523
    def output_port_values(self):
1✔
4524
        """Return dict with values of OutputPorts for all Mechanisms in MechanismList"""
4525
        return self._get_attributes_dict('output_port_values', 'output_ports', 'value')
×
4526

4527
    @property
1✔
4528
    def output_values(self):
1✔
4529
        """Return dict with output_values for all Mechanisms in MechanismList"""
4530
        return self._get_attributes_dict('values', 'value')
×
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

© 2025 Coveralls, Inc