• 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

75.0
/psyneulink/core/components/projections/pathway/mappingprojection.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
# **********************************************  MappingProjection ****************************************************
10

11
"""
12

13
Contents
14
--------
15
  * `MappingProjection_Overview`
16
  * `MappingProjection_Creation`
17
      - `MappingProjection_Matrix_Specification`
18
      - `MappingProjection_Learning_Specification`
19
      - `MappingProjection_Deferred_Initialization`
20
  * `MappingProjection_Structure`
21
      - `MappingProjection_Matrix`
22
      - `MappingProjection_Matrix_ParameterPort`
23
  * `MappingProjection_Execution`
24
      - `MappingProjection_Learning`
25
  * `MappingProjection_Class_Reference`
26

27

28
.. _MappingProjection_Overview:
29

30
Overview
31
--------
32

33
A MappingProjection transmits the `value <OutputPort.value>` of an `OutputPort` of one `ProcessingMechanism
34
<ProcessingMechanism>` (its `sender <MappingProjection.sender>`) to the `InputPort` of another (its `receiver
35
<MappingProjection.receiver>`). The default `function <Projection_Base.function>` for a MappingProjection is
36
`MatrixTransform`, which uses the MappingProjection's `matrix <MappingProjection.matrix>` attribute to transform the
37
value received from its `sender <MappingProjection.sender>` and provide the result to its `receiver
38
<MappingProjection.receiver>`.
39

40
.. _MappingProjection_Creation:
41

42
Creating a MappingProjection
43
-----------------------------
44

45
A MappingProjection can be created in any of the ways that can be used to create a `Projection <Projection_Creation>`
46
(see `Projection_Sender` and `Projection_Receiver` for specifying its `sender <MappingProjection.sender>` and
47
`receiver <MappingProjection.receiver>` attributes, respectively), or simply by `specifying it by its matrix parameter
48
<MappingProjection_Matrix_Specification>` wherever a `Projection can be specified <Projection_Specification>`.
49

50
MappingProjections are also generated automatically in the following circumstances, using a value for its `matrix
51
<MappingProjection.matrix>` parameter appropriate to the circumstance:
52

53
  * by a `Composition`, when two adjacent `Mechanisms <Mechanism>` in its `pathway <Process.pathway>` do not already
54
    have a Projection assigned between them (`AUTO_ASSIGN_MATRIX` is used as the `matrix <MappingProjection.matrix>`
55
    specification, which determines the appropriate matrix by context);
56
  ..
57
  * by an `ObjectiveMechanism`, from each `OutputPort` listed in its `monitored_output_ports
58
    <ObjectiveMechanism.monitored_output_ports>` attribute to the corresponding `InputPort` of the ObjectiveMechanism
59
    (`AUTO_ASSIGN_MATRIX` is used as the `matrix <MappingProjection.matrix>` specification, which determines the
60
    appropriate matrix by context);
61
  ..
62
  * by a `LearningMechanism`, between it and the other components required to implement learning
63
    (see `LearningMechanism_Learning_Configurations` for details);
64
  ..
65
  * by a `ControlMechanism <ControlMechanism>`, from the *OUTCOME* `OutputPort` of the `ObjectiveMechanism` that `it
66
    creates <ControlMechanism_ObjectiveMechanism>` to its *OUTCOME* `InputPort`, and from the `OutputPorts <OutputPort>`
67
    listed in the ObjectiveMechanism's `monitored_output_ports <ObjectiveMechanism.monitored_output_ports>` attribute
68
    to the ObjectiveMechanism's `primary InputPort <InputPort_Primary>` (as described above; an `IDENTITY_MATRIX` is
69
    used for all of these).
70

71
.. _MappingProjection_Matrix_Specification:
72

73
*Specifying the Matrix Parameter*
74
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75

76
When a MappingProjection is created automatically, its `matrix <MappingProjection.matrix>` attribute is generally
77
assigned using `AUTO_ASSIGN_MATRIX`, which determines its size by context: an `IDENTITY_MATRIX` is used if the
78
`sender <MappingProjection.sender>` and `receiver <MappingProjection.receiver>` are of equal length; otherwise a
79
`FULL_CONNECTIVITY_MATRIX` (all 1's) is used.
80

81
When a MappingProjection is created explicitly, the **matrix** argument of its constructor can be used to specify
82
its `matrix <MappingProjection.matrix>` parameter.  This is used by the MappingProjection's `function
83
<Projection_Base.function>` to transform the input from its `sender <MappingProjection.sender>` into the `value
84
<Projection_Base.value>` provided to its `receiver <MappingProjection.receiver>`. It can be specified in any of the
85
following ways:
86

87
  * **List or array**  -- if it is a list, each item must be a list or 1d np.array of numbers;  otherwise,
88
    it must be a 2d np.array.  In each case, the outer dimension (outer list items, array axis 0,
89
    or matrix rows) corresponds to the elements of the `sender <MappingProjection.sender>`, and the inner dimension
90
    (inner list items, array axis 1, or matrix columns) corresponds to the weighting of the contribution that a
91
    given `sender <MappingProjection.sender>` makes to the `receiver <MappingProjection.receiver>` (the number of which
92
    must match the length of the receiver's `variable <InputPort.variable>`).
93

94
  .. _Matrix_Keywords:
95

96
  * **Matrix keyword** -- used to specify a standard type of matrix without having to specify its individual
97
    values, or to allow the type of matrix to be determined by context;  any of the `matrix keywords
98
    <Keywords.MatrixKeywords>` can be used.
99

100
  ..
101
  * `RandomMatrix` -- assigns a matrix sized appropriately for the **sender** and **receiver**, with random values
102
    drawn from a uniform distribution with a specified **center** and **range**.
103

104
  .. _MappingProjection_Tuple_Specification:
105

106
  * **Tuple** -- used to specify the `matrix <MappingProjection.matrix>` along with a specification for learning;
107
    The tuple must have two items: the first can be any of the specifications described above; the second must be
108
    a `learning specification <MappingProjection_Learning_Tuple_Specification>`.
109

110
.. _MappingProjection_Learning_Specification:
111

112
*Specifying Learning*
113
~~~~~~~~~~~~~~~~~~~~~
114

115
A MappingProjection is specified for learning in any of the following ways:
116

117
    * in the **matrix** argument of the MappingProjection's constructor, using the `tuple format
118
      <MappingProjection_Tuple_Specification>` described above;
119
    ..
120
    * specifying a MappingProjection in the `learning method <Composition_Learning_Methods>` of a `Composition`,
121
      using the `tuple format <MappingProjection_Learning_Tuple_Specification>` to include a learning specification;
122

123
    * specifying the MappingProjection (or its *MATRIX* `ParameterPort`) as the `receiver
124
      <LearningProjection.receiver>` of a `LearningProjection`;
125
    ..
126
    * specifying the MappingProjection (or its *MATRIX* `ParameterPort`) in the **projections** argument of
127
      the constructor for a `LearningSignal <LearningSignal_Specification>`
128
    ..
129
    * specifying the MappingProjection (or its *MATRIX* `ParameterPort`) in the **learning_signals** argument of
130
      the constructor for a `LearningMechanism <LearningSignal_Specification>`
131

132
Learning can be disabled for a MappingProjection by specifying its **learnable** argument as `False` in its
133
constructor.  This can be useful for disabling specific MappingProjections in the `learning pathway
134
`learning pathway <Composition_Learning_Pathway>` of a `Composition`.
135

136
See `LearningMechanism` for an overview of `learning components <LearningMechanism_Overview>` and a detailed
137
description of `LearningMechanism_Learning_Configurations`; `Composition_Learning` for a description of how learning
138
is implemented within a `Composition`;  `MappingProjection_Learning` below for a description of how learning
139
modifies a MappingProjection.
140

141
.. _MappingProjection_Learning_Tuple_Specification:
142

143
Specifying Learning in a Tuple
144
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
145

146
A tuple can be used to specify learning for a MappingProjection in the **matrix** `argument of its constructor
147
<MappingProjection_Matrix_Specification>` or in the `pathway of a Process <Process_Projections>`.  In both cases,
148
the second item of the tuple must be a learning specification, which can be any of the following:
149

150
  * an existing `LearningProjection`, `LearningSignal`, or a constructor for one -- the specified Component is used, and
151
    defaults are automatically created for the other `learning Components <LearningMechanism_Learning_Configurations>`;
152
  ..
153
  * a reference to the LearningProjection or LearningSignal class, or the keyword *LEARNING* or *LEARNING_PROJECTION* --
154
    a default set of `learning Components <LearningMechanism_Learning_Configurations>` is automatically created.
155

156
Specifying Learning for AutodiffCompositions
157
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
158

159
By default, all MappingProjections in an `AutodiffComposition` are treated as trainable PyTorch parameters that
160
are updated during backwards passes through the network, and then used to update the MappingProjection's
161
`matrix <MappingProjection.matrix>` after each batch of train. However, this can be disabled for an individual
162
MappingProjection by specifying the **learnable** argument as False in its constructor.
163

164
.. _MappingProjection_Deferred_Initialization:
165

166
*Deferred Initialization*
167
~~~~~~~~~~~~~~~~~~~~~~~~~
168

169
When a MappingProjection is created, its full initialization is `deferred <Component_Deferred_Init>` until its
170
`sender <MappingProjection.sender>` and `receiver <MappingProjection.receiver>` have been fully specified.  This
171
allows a MappingProjection to be created before its `sender <MappingProjection.sender>` and/or `receiver
172
<MappingProjection.receiver>` have been created (e.g., before them in a script), by calling its constructor without
173
specifying its **sender** or **receiver** arguments. However, for the MappingProjection to be operational,
174
initialization must be completed by calling its `deferred_init` method.  This is not necessary if the MappingProjection
175
is specified in the **pathway** argument of a Composition's `add_linear_processing_pathway` or
176
`learning methods <Composition_Learning_Methods>`, or anywhere else that its `sender <MappingProjection.sender>` and
177
`receiver <MappingProjection.receiver>` can be determined by context.
178

179
.. _MappingProjection_Structure:
180

181
Structure
182
---------
183

184
In addition to its `sender <MappingProjection.sender>`, `receiver <MappingProjection.receiver>`, and `function
185
<Projection_Base.function>`, a MappingProjection has the following characteristic attributes:
186

187
.. _MappingProjection_Matrix:
188

189
* `matrix <MappingProjection.matrix>` parameter - used by the MappingProjection's `function <Projection_Base.function>`
190
  to carry out a matrix transformation of its input, that is then provided to its `receiver
191
  <MappingProjection.receiver>`. It can be specified in a variety of ways, as described `above
192
  <MappingProjection_Matrix_Specification>`.
193

194
  .. _MappingProjection_Matrix_Dimensionality
195

196
  * **Matrix Dimensionality** -- this must match the dimensionality of the MappingProjection's `sender
197
    <MappingProjection.sender>` and `receiver <MappingProjection.receiver>`.  For a standard 2d "weight" matrix (i.e.,
198
    one that maps a 1d array from its `sender <MappingProjection.sender>` to a 1d array of its `receiver
199
    <MappingProjection.receiver>`), the dimensionality of the sender is the number of rows and of the receiver
200
    the number of columns.  More generally, the sender dimensionality is the number of outer dimensions (i.e.,
201
    starting with axis 0 of numpy array) equal to the number of dimensions of its `sender <MappingProjection.sender>`'s
202
    `value <Port_Base.value>`, and the receiver dimensionality is the number of inner dimensions equal to its
203
    `receiver <MappingProjection.receiver>`'s `variable <Projection_Base.variable>` (equal to the dimensionality of the
204
    matrix minus its sender dimensionality).
205

206
.. _MappingProjection_Matrix_ParameterPort:
207

208
* *MATRIX* `ParameterPort` - this receives any `LearningProjections <LearningProjection>` that are assigned to the
209
  MappingProjection (see `MappingProjection_Learning_Specification` above), and updates the current value of the
210
  MappingProjection's `matrix <MappingProjection.matrix>` parameter in response to `learning
211
  <LearningMechanism>`.  The `function <ParameterPort.function>` of a *MATRIX* ParameterPort is an
212
  `AccumulatorIntegrator`, which accumulates the weight changes received from the LearningProjections
213
  that project to it (see `MappingProjection_Learning` below).  This can be replaced by any function that defines an
214
  *ADDITIVE_PARAM* `modulatory parameter <ModulatorySignal_Modulation>`), and that takes as its input an array or
215
  matrix and returns one of the same size.
216

217
.. _Mapping_Weight_Exponent:
218

219
*  `weight <Projection_Base.weight>` and `exponent <Projection_Base.exponent>` - applied to the `value
220
   <Projection_Base.value>` of the MappingProjection before it is combined with other MappingProjections to the same
221
   `InputPort` to determine its `value <InputPort.value>` (see description under `Projection
222
   <Projection_Weight_Exponent>` for additional details).
223

224
   .. note::
225
      The `weight <Projection_Base.weight>` and `exponent <Projection_Base.exponent>` attributes of a MappingProjection
226
      are distinct from those of the `InputPort` to which it projects.  It is also important to recognize that,
227
      as noted under `Projection <Projection_Weight_Exponent>`, they are not normalized, and thus contribute to the
228
      magnitude of the InputPort's `variable <InputPort.variable>` and therefore its relationship to that of other
229
      InputPorts that may belong to the same Mechanism.
230

231
.. _MappingProjection_Execution:
232

233
Execution
234
---------
235

236
A MappingProjection uses its `function <Projection_Base.function>` and `matrix <MappingProjection.matrix>` parameter to
237
transform its `sender <MappingProjection.sender>` into a form suitable for the `variable <InputPort.variable>` of its
238
`receiver <MappingProjection.receiver>`.  A MappingProjection cannot be executed directly. It is executed when the
239
`InputPort` to which it projects (i.e., its `receiver <MappingProjection.receiver>`) is updated;  that occurs when the
240
InputPort's owner `Mechanism <Mechanism>` is executed. When executed, the MappingProjection's *MATRIX* `ParameterPort`
241
updates its `matrix <MappingProjection.matrix>` parameter based on any `LearningProjection(s)` it receives (listed in
242
the ParameterPort's `mod_afferents <ParameterPort.mod_afferents>` attribute). This brings into effect any changes that
243
occurred due to `learning <MappingProjection_Learning>`.  Since this does not occur until the Mechanism that receives
244
the MappingProjection is executed (in accord with `Lazy Evaluation <Component_Lazy_Updating>`), any changes due to
245
learning do not take effect, and are not observable (e.g., through inspection of the `matrix <MappingProjection.matrix>`
246
attribute or the `value <ParameterPort.value>` of its ParameterPort) until the next `TRIAL <TimeScale.TRIAL>` of
247
execution (see `Lazy Evaluation <Component_Lazy_Updating>` for an explanation of "lazy" updating).
248

249
.. _MappingProjection_Learning:
250

251
*Learning*
252
~~~~~~~~~~
253

254
Learning modifies the `matrix <MappingProjection.matrix>` parameter of a MappingProjection, under the influence
255
of one or more `LearningProjections <LearningProjection>` that project to its *MATRIX* `ParameterPort`.
256
This conforms to the general procedures for modulation used by `ModulatoryProjections <ModulatoryProjection>`
257
A LearningProjection `modulates <LearningSignal_Modulation>` the `function <ParameterPort.function>` of the
258
*MATRIX* ParameterPort, which is responsible for keeping a record of the value of the MappingProjection's matrix,
259
and providing it to the MappingProjection's `function <Projection_Base.function>` (usually `MatrixTransform`).  By
260
default, the function for the *MATRIX* ParameterPort is an `AccumulatorIntegrator`.  A LearningProjection
261
modulates it by assigning the value of its `additive_param <AccumulatorIntegrator.additive_param>` (`increment
262
<AccumulatorIntegrator.increment>`), which is added to its `previous_value <AccumulatorIntegrator.previous_value>`
263
attribute each time it is executed. The result is that each time the MappingProjection is executed, and in turn
264
executes its *MATRIX* ParameterPort, the `weight changes <LearningProjection_Structure>` conveyed to the
265
MappingProjection from any LearningProjection(s) are added to the record of the matrix kept by the *MATRIX*
266
ParameterPort's `AccumulatorIntegrator` function in its `previous_value <AccumulatorIntegrator.previous_value>`
267
attribute. This is then the value of the matrix used  by the MappingProjection's `MatrixTransform` function when it is
268
executed.  It is important to note that the accumulated weight changes received by a MappingProjection from its
269
LearningProjection(s) are stored by the *MATRIX* ParameterPort's function, and not the MappingProjection's `matrix
270
<MappingProjection.matrix>` parameter itself; the latter stores the original value of the matrix before learning (that
271
is, its unmodulated value, conforming to the general protocol for `modulation <ModulatorySignal_Modulation>` in
272
PsyNeuLink).  The most recent value of the matrix used by the MappingProjection is stored in the `value
273
<ParameterPort.value>` of its *MATRIX* ParameterPort. As noted `above <MappingProjection_Execution>`, however, this does
274
not reflect any changes due to learning on the current `TRIAL <TimeScale.TRIAL>` of execution; those are assigned to the
275
ParameterPort's `value <ParameterPort.value>` when it executes, which does not occur until the `Mechanism
276
<Mechanism>` that receives the MappingProjection is executed in the next `TRIAL <TimeScale.TRIAL>` of execution
277
(see `Lazy Evaluation <Component_Lazy_Updating>` for an explanation of "lazy" updating).
278

279
.. _MappingProjection_Class_Reference:
280

281
Class Reference
282
---------------
283

284
"""
285
import copy
1✔
286

287
import numpy as np
1✔
288
from typing import Union
1✔
289

290
from psyneulink._typing import Optional
1✔
291

292
from psyneulink.core.components.component import parameter_keywords
1✔
293
from psyneulink.core.components.functions.stateful.integratorfunctions import AccumulatorIntegrator
1✔
294
from psyneulink.core.components.functions.nonstateful.transformfunctions import MatrixTransform
1✔
295
from psyneulink.core.components.functions.function import get_matrix
1✔
296
from psyneulink.core.components.projections.pathway.pathwayprojection import PathwayProjection_Base
1✔
297
from psyneulink.core.components.projections.projection import ProjectionError, projection_keywords
1✔
298
from psyneulink.core.components.ports.outputport import OutputPort
1✔
299
from psyneulink.core.globals.keywords import \
1✔
300
    AUTO_ASSIGN_MATRIX, DEFAULT_MATRIX, FULL_CONNECTIVITY_MATRIX, HOLLOW_MATRIX, IDENTITY_MATRIX, INPUT_PORT, \
301
    MAPPING_PROJECTION, MATRIX, \
302
    OUTPUT_PORT, VALUE
303
from psyneulink.core.globals.log import ContextFlags
1✔
304
from psyneulink.core.globals.parameters import FunctionParameter, Parameter, check_user_specified, copy_parameter_value
1✔
305
from psyneulink.core.globals.preferences.basepreferenceset import ValidPrefSet
1✔
306
from psyneulink.core.globals.preferences.preferenceset import PreferenceLevel
1✔
307

308
__all__ = [
1✔
309
    'MappingError', 'MappingProjection'
310
]
311

312
parameter_keywords.update({MAPPING_PROJECTION})
1✔
313
projection_keywords.update({MAPPING_PROJECTION})
1✔
314

315

316
class MappingError(ProjectionError):
1✔
317
    pass
1✔
318

319

320
def _mapping_projection_matrix_getter(owning_component=None, context=None):
1✔
321
    return owning_component.function.parameters.matrix.get(context)
×
322

323

324
def _mapping_projection_matrix_setter(value, owning_component=None, context=None):
1✔
325
    owning_component.function.parameters.matrix.set(value, context)
1✔
326
    # KDM 11/13/18: not sure that below is correct to do here, probably is better to do this in a "reset" type
327
    # method but this is needed for Kalanthroff model to work correctly (though untested, it is in Scripts/Models)
328
    owning_component.parameter_ports["matrix"].function.parameters.previous_value.set(value, context)
1✔
329

330
    return value
1✔
331

332

333
class MappingProjection(PathwayProjection_Base):
1✔
334
    """
335
    MappingProjection(         \
336
        sender=None,           \
337
        receiver=None,         \
338
        matrix=DEFAULT_MATRIX, \
339
        learnable)             \
340

341
    Subclass of `Projection` that transmits the `value <OutputPort.value>` of the `OutputPort` of one `Mechanism
342
    <Mechanism>` to the `InputPort` of another (or possibly itself).  See `Projection <Projection_Class_Reference>`
343
    for additional arguments and attributes.
344

345
    Arguments
346
    ---------
347

348
    sender : OutputPort or Mechanism : default None
349
        specifies the source of the Projection's input. If a `Mechanism <Mechanism>` is specified, its
350
        `primary OutputPort <OutputPort_Primary>` is used. If it is not specified, it is assigned in
351
        the context in which the MappingProjection is used, or its initialization will be `deferred
352
        <MappingProjection_Deferred_Initialization>`.
353

354
    receiver: InputPort or Mechanism : default None
355
        specifies the destination of the Projection's output.  If a `Mechanism <Mechanism>` is specified, its
356
        `primary InputPort <InputPort_Primary>` will be used. If it is not specified, it will be assigned in
357
        the context in which the Projection is used, or its initialization will be `deferred
358
        <MappingProjection_Deferred_Initialization>`.
359

360
    matrix : list, np.ndarray, function, `RandomMatrix` or keyword : default DEFAULT_MATRIX
361
        specifies the matrix used by `function <Projection_Base.function>` (default: `LinearCombination`) to
362
        transform the `value <Projection_Base.value>` of the `sender <MappingProjection.sender>` into a form suitable
363
        for the `variable <InputPort.variable>` of its `receiver <MappingProjection.receiver>` `InputPort`
364
        (see `MappingProjection_Matrix_Specification` for additional details).
365

366
    learnable : bool : default True
367
        specifies whether the MappingProjection's `matrix <MappingProjection.matrix>` parameter can be modified by
368
        `learning <LearningMechanism>` (see `MappingProjection_Learning` for additional details).
369

370

371
    Attributes
372
    ----------
373

374
    sender : OutputPort
375
        the `OutputPort` of the `Mechanism <Mechanism>` that is the source of the Projection's input.
376

377
    receiver: InputPort
378
        the `InputPort` of the `Mechanism <Mechanism>` that is the destination of the Projection's output.
379

380
    matrix : 2d np.array
381
        the matrix used by `function <Projection_Base.function>` to transform the input from the MappingProjection's
382
        `sender <MappingProjection.sender>` into the value provided to its `receiver <MappingProjection.receiver>`.
383

384
    has_learning_projection : bool : None
385
        identifies the `LearningProjection` assigned to the MappingProjection's `MATRIX` `ParameterPort
386
        <ParameterPort>`.
387

388
    learning_mechanism : LearningMechanism
389
        source of the `learning signal <LearningSignal>` that determines the changes to the `matrix
390
        <MappingProjection.matrix>` when `learning <LearningMechanism>` is used.
391

392
    name : str
393
        the name of the MappingProjection. If the specified name is the name of an existing MappingProjection,
394
        it is appended with an indexed suffix, incremented for each MappingProjection with the same base name (see
395
        `Registry_Naming`). If the name is not specified in the **name** argument of its constructor, a default name is
396
        assigned using the following format:
397
        'MappingProjection from <sender Mechanism>[<OutputPort>] to <receiver Mechanism>[InputPort]'
398
        (for example, ``'MappingProjection from my_mech_1[OutputPort-0] to my_mech2[InputPort-0]'``).
399
        If either the `sender <MappingProjection.sender>` or `receiver <MappingProjection.receiver>` has not yet been
400
        assigned (the MappingProjection is in `deferred initialization <MappingProjection_Deferred_Initialization>`),
401
        then the parenthesized name of class is used in place of the unassigned attribute
402
        (for example, if the `sender <MappingProjection.sender>` has not yet been specified:
403
        ``'MappingProjection from (OutputPort-0) to my_mech2[InputPort-0]'``).
404

405
    """
406

407
    componentType = MAPPING_PROJECTION
1✔
408
    className = componentType
1✔
409
    suffix = " " + className
1✔
410

411
    class Parameters(PathwayProjection_Base.Parameters):
1✔
412
        """
413
            Attributes
414
            ----------
415

416
                function
417
                    see `function <MappingProjection.function>`
418

419
                    :default value: `MatrixTransform`
420
                    :type: `Function`
421

422
                matrix
423
                    see `matrix <MappingProjection.matrix>`
424

425
                    :default value: `AUTO_ASSIGN_MATRIX`
426
                    :type: ``str``
427
        """
428
        function = Parameter(MatrixTransform, stateful=False, loggable=False)
1✔
429
        matrix = FunctionParameter(
1✔
430
            DEFAULT_MATRIX,
431
            setter=_mapping_projection_matrix_setter
432
        )
433

434
    classPreferenceLevel = PreferenceLevel.TYPE
1✔
435

436
    @property
1✔
437
    def _loggable_items(self):
1✔
438
        # Ports and afferent Projections are loggable for a Mechanism
439
        #     - this allows the value of InputPorts and OutputPorts to be logged
440
        #     - for MappingProjections, this logs the value of the Projection's matrix parameter
441
        #     - for ModulatoryProjections, this logs the value of the Projection
442
        # IMPLEMENTATION NOTE: this needs to be a property as that is expected by Log.loggable_items
443
        return list(self.parameter_ports)
×
444

445

446
    class sockets:
1✔
447
        sender=[OUTPUT_PORT]
1✔
448
        receiver=[INPUT_PORT]
1✔
449

450

451
    projection_sender = OutputPort
1✔
452

453
    @check_user_specified
1✔
454
    def __init__(self,
1✔
455
                 sender=None,
456
                 receiver=None,
457
                 weight=None,
458
                 exponent=None,
459
                 matrix=None,
460
                 function=None,
461
                 learnable=True,
462
                 params=None,
463
                 name=None,
464
                 prefs: Optional[ValidPrefSet] = None,
465
                 context=None,
466
                 **kwargs):
467

468
        # Assign matrix to function_params for use as matrix param of MappingProjection.function
469
        # (7/12/17 CW) this is a PATCH to allow the user to set matrix as an np.matrix... I still don't know why
470
        # it wasn't working.
471
        if isinstance(matrix, list):
1✔
472
            matrix = np.array(matrix)
1✔
473

474
        self.learning_mechanism = None
1✔
475
        self.has_learning_projection = None
1✔
476
        self.learnable = bool(learnable)
1✔
477

478
        # If sender or receiver has not been assigned, defer init to Port.instantiate_projection_to_state()
479
        if sender is None or receiver is None:
1✔
480
            self.initialization_status = ContextFlags.DEFERRED_INIT
1✔
481

482
        # Validate sender (as variable) and params
483
        super().__init__(sender=sender,
1✔
484
                         receiver=receiver,
485
                         weight=weight,
486
                         exponent=exponent,
487
                         matrix=matrix,
488
                         function=function,
489
                         params=params,
490
                         name=name,
491
                         prefs=prefs,
492
                         **kwargs)
493

494
        try:
1✔
495
            self._parameter_ports[MATRIX].function.reset(context=context)
1✔
496
        except AttributeError:
1✔
497
            pass
1✔
498

499
    def _instantiate_parameter_ports(self, function=None, context=None):
1✔
500

501
        super()._instantiate_parameter_ports(function=function, context=context)
1✔
502

503
        # FIX: UPDATE FOR LEARNING
504
        # FIX: UPDATE WITH MODULATION_MODS
505
        # FIX: MOVE THIS TO MappingProjection.__init__;
506
        # FIX: AS IT IS, OVER-WRITES USER ASSIGNMENT OF FUNCTION IN params dict FOR MappingProjection
507
        # TODO: why is this using the value not the variable? if there isn't a
508
        # specific reason, it should be variable, but this affects the values
509
        # tests/mechanisms/test_gating_mechanism.py::test_gating_with_composition
510
        new_variable = copy.deepcopy(self._parameter_ports[MATRIX].defaults.value)
1✔
511
        initial_rate = new_variable * 0.0
1✔
512

513
        # KDM 7/11/19: instead of simply setting the function, we need to reinstantiate to ensure
514
        # new defaults get set properly
515
        self._parameter_ports[MATRIX]._instantiate_function(
1✔
516
            function=AccumulatorIntegrator(
517
                owner=self._parameter_ports[MATRIX],
518
                default_variable=new_variable,
519
                initializer=new_variable,
520
                # rate=initial_rate
521
            ),
522
            context=context
523
        )
524
        self._parameter_ports[MATRIX]._instantiate_value(context)
1✔
525
        self._parameter_ports[MATRIX]._update_parameter_components(context)
1✔
526

527
        # # Assign ParameterPort the same Log as the MappingProjection, so that its entries are accessible to Mechanisms
528
        # self._parameter_ports[MATRIX].log = self.log
529

530
    def _instantiate_receiver(self, context=None):
1✔
531
        """Determine matrix needed to map from sender to receiver
532

533
        Assign specification to self.matrix_spec attribute
534
        Assign matrix to self.matrix attribute
535

536
        """
537
        self.reshapedWeightMatrix = False
1✔
538

539
        # Get sender and receiver lengths
540
        # Note: if either is a scalar, manually set length to 1 to avoid TypeError in call to len()
541
        try:
1✔
542
            mapping_input_len = len(self.defaults.variable)
1✔
543
        except TypeError:
×
544
            mapping_input_len = 1
×
545
        try:
1✔
546
            receiver_len = self.receiver.socket_width
1✔
547
        except TypeError:
×
548
            receiver_len = 1
×
549

550
        # Compare length of MappingProjection output and receiver's variable to be sure matrix has proper dimensions
551
        try:
1✔
552
            mapping_output_len = len(self.defaults.value)
1✔
553
        except TypeError:
×
554
            mapping_output_len = 1
×
555

556
        matrix_spec = copy_parameter_value(self.defaults.matrix)
1✔
557

558
        if (type(matrix_spec) == str and
1✔
559
                matrix_spec == AUTO_ASSIGN_MATRIX):
560
            if mapping_input_len == receiver_len:
1✔
561
                matrix_spec = IDENTITY_MATRIX
1✔
562
            else:
563
                matrix_spec = FULL_CONNECTIVITY_MATRIX
1✔
564

565
        # Length of the output of the Projection doesn't match the length of the receiving InputPort
566
        #    so consider reshaping the matrix
567
        if mapping_output_len != receiver_len:
1!
568

569
            if 'projection' in self.name or 'Projection' in self.name:
×
570
                projection_string = ''
×
571
            else:
572
                projection_string = 'projection'
×
573

574
            if all(string in self.name for string in {'from', 'to'}):
×
575
                states_string = ''
×
576
            else:
NEW
577
                states_string = (f" from '{self.sender.name}' OuputPort of '{self.sender.owner.name}' "
×
578
                                 f"to '{self.receiver.owner.name}'")
579

UNCOV
580
            if not isinstance(matrix_spec, str):
×
581
                # if all(string in self.name for string in {'from', 'to'}):
582

583
                raise ProjectionError(f"Width ({mapping_output_len}) of the {VALUE} of '{self.name}' "
584
                                      f"{projection_string}{states_string} does not match the length of its "
585
                                      f"'{self.receiver.name}' InputPort ({receiver_len}).")
586

587
            elif matrix_spec == IDENTITY_MATRIX or matrix_spec == HOLLOW_MATRIX:
×
588
                # Identity matrix is not reshapable
589
                raise ProjectionError(f"Output length ({mapping_output_len}) of '{self.name}' {projection_string} "
590
                                      f"from {self.sender.name} to Mechanism '{self.receiver.owner.name}' "
591
                                      f"must equal length of it InputPort ({receiver_len}) to use {matrix_spec}.")
592
            else:
593
                # Flag that matrix is being reshaped
594
                self.reshapedWeightMatrix = True
×
595
                if self.prefs.verbosePref:
×
NEW
596
                    print(f"Length ({mapping_output_len}) of the output of '{self.name}' {projection_string} "
×
597
                          f"does not match the length ({receiver_len}) of the InputPort for the receiver "
598
                          f"'{self.receiver.owner.name}'; the width of the matrix (number of columns) will be "
599
                          f"adjusted to accomodate the receiver.")
600

601
                self.parameters.matrix._set(
×
602
                    get_matrix(matrix_spec, mapping_input_len, receiver_len, context=context),
603
                    context
604
                )
605

606
                # Since matrix shape has changed, output of self.function may have changed, so update value
607
                self._instantiate_value(context=context)
×
608

609
        super()._instantiate_receiver(context=context)
1✔
610

611
    def _execute(self, variable=None, context=None, runtime_params=None):
1✔
612

613
        self._update_parameter_ports(runtime_params=runtime_params, context=context)
1✔
614

615
        value = super()._execute(
1✔
616
                variable=variable,
617
                context=context,
618
                runtime_params=runtime_params,
619
                )
620

621
        return value
1✔
622

623
    @property
1✔
624
    def logPref(self):
1✔
625
        return self.prefs.logPref
×
626

627
    # Always assign matrix ParameterPort the same logPref as the MappingProjection
628
    @logPref.setter
1✔
629
    def logPref(self, setting):
1✔
630
        self.prefs.logPref = setting
×
631
        self.parameter_ports[MATRIX].logPref = setting
×
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